Remove the Claude Design round-trip
Deletes script/build_design_bundle.rb, the rake design task, the Claude
Design README section, and the now-unused design-bundle/ gitignore entry.
Not needed — the site itself is the deliverable now.
diff --git a/.gitignore b/.gitignore
index fc7f745..291ccd1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,3 @@
_site/
-design-bundle/
vendor/bundle/
.bundle/
diff --git a/README.md b/README.md
index 53d06d5..628c138 100644
--- a/README.md
+++ b/README.md
@@ -83,38 +83,6 @@ The site's `BASEURL` is a constant in `lib/build.rb` rather than coming from
`actions/configure-pages`, which keeps the build independent of the Pages
API. Change it there if the site ever moves off a project page.
-## Claude Design
-
-`rake design` renders the bundle that Claude Design's Design System pane
-wants, into `design-bundle/`:
-
-```console
-$ bundle exec rake design
-```
-
-One card per documentation page — five foundations, sixteen components,
-two patterns — each a standalone HTML file with the compiled tokens and
-component CSS inlined and a first-line `<!-- @dsCard group="…" -->` marker
-for the pane's card index. `design-bundle/css/` carries the two compiled
-stylesheets as well, so the project holds the artifact a consumer would link
-and not only the previews.
-
-The cards are generated from `_site` by `script/build_design_bundle.rb`
-rather than written by hand, for the same reason the docs are: a second copy
-of the system maintained separately starts disagreeing with the first one
-immediately. Rebuild them whenever a token changes.
-
-Pushing the bundle needs `DesignSync`, which requires design-system
-authorization via `/design-login` — an interactive terminal, so it cannot run
-in Claude Code on the web. From a local Claude Code session:
-
-```console
-$ bundle exec rake design
-$ claude # then: /design-login, and ask it to sync design-bundle/
-```
-
-`design-bundle/` is gitignored; it is a build artifact.
-
## Using the CSS elsewhere
Two files, in this order, and almost no JavaScript:
diff --git a/Rakefile b/Rakefile
index b292c78..0dbdb97 100644
--- a/Rakefile
+++ b/Rakefile
@@ -27,9 +27,4 @@ task :serve do
server.start
end
-desc "Build the Claude Design upload bundle into design-bundle/"
-task design: :build do
- sh "ruby script/build_design_bundle.rb"
-end
-
task default: :build
diff --git a/design-bundle/components/badge.html b/design-bundle/components/badge.html
new file mode 100644
index 0000000..39a6c93
--- /dev/null
+++ b/design-bundle/components/badge.html
@@ -0,0 +1,1815 @@
+<!-- @dsCard group="Core" -->
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Badge</title>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&display=swap">
+<style>
+/* Arbitrary Definitions — design tokens. Hand-maintained; edit values directly. */
+
+:root {
+ /* Palette ramps — reference only. Components use the aliases below. */
+ --ad-neutral-0: #faf9f7;
+ --ad-neutral-50: #fdfdfb;
+ --ad-neutral-100: #f1f0ed;
+ --ad-neutral-150: #e8e6e2;
+ --ad-neutral-200: #dcdad7;
+ --ad-neutral-300: #c0bdb9;
+ --ad-neutral-500: #888681;
+ --ad-neutral-600: #5f5d59;
+ --ad-neutral-800: #2a2926;
+ --ad-neutral-900: #100f0d;
+ --ad-neutral-dark-0: #0e0d0b;
+ --ad-neutral-dark-50: #151412;
+ --ad-neutral-dark-100: #1e1d1a;
+ --ad-neutral-dark-150: #252421;
+ --ad-neutral-dark-200: #2c2b29;
+ --ad-neutral-dark-300: #44423f;
+ --ad-neutral-dark-500: #7c7a76;
+ --ad-neutral-dark-600: #a09e9a;
+ --ad-neutral-dark-800: #d9d7d4;
+ --ad-neutral-dark-900: #e9e8e6;
+ --ad-accent-teal: #3a6a63;
+ --ad-accent-clay: #9a5a3c;
+ --ad-accent-ink-blue: #2b4a78;
+ --ad-accent-ochre: #93701f;
+ --ad-accent-plum: #6a3a5c;
+
+ /* Surfaces */
+ --ad-color-surface-page: #faf9f7;
+ --ad-color-surface-card: #fdfdfb;
+ --ad-color-surface-fill: #f1f0ed;
+ --ad-color-surface-fill-2: #e8e6e2;
+
+ /* Borders */
+ --ad-color-border-hairline: #dcdad7;
+ --ad-color-border-default: #c0bdb9;
+
+ /* Text */
+ --ad-color-text-primary: #100f0d;
+ --ad-color-text-secondary: #5f5d59;
+ --ad-color-text-tertiary: #888681;
+
+ /* Accent */
+ --ad-color-accent: #3a6a63;
+ --ad-color-accent-fg: #ffffff;
+ --ad-color-accent-ink: color-mix(in oklch, var(--ad-color-accent) 78%, black);
+ --ad-color-accent-soft: color-mix(in oklch, var(--ad-color-accent) 12%, white);
+
+ /* Status (fixed — never swapped like the accent) */
+ --ad-color-success: #3f7a4a;
+ --ad-color-success-bg: color-mix(in oklch, var(--ad-color-success) 12%, white);
+ --ad-color-danger: #ad3a2c;
+ --ad-color-danger-bg: color-mix(in oklch, var(--ad-color-danger) 10%, white);
+
+ /* Type families */
+ --ad-font-sans: -apple-system, "Helvetica Neue", Helvetica, Arial, system-ui, sans-serif;
+ --ad-font-mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, Consolas, "Courier New", monospace;
+ --ad-font-serif: Georgia, "Times New Roman", "Iowan Old Style", serif;
+
+ /* Type steps — fluid (Utopia scale), the design system's own body-text size */
+ --ad-step--2: clamp(0.7813rem, 0.7736rem + 0.0341vw, 0.8rem);
+ --ad-step--1: clamp(0.9375rem, 0.9119rem + 0.1136vw, 1rem);
+ --ad-step-0: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-step-1: clamp(1.35rem, 1.2631rem + 0.3864vw, 1.5625rem);
+ --ad-step-2: clamp(1.62rem, 1.4837rem + 0.6057vw, 1.9531rem);
+ --ad-step-3: clamp(1.944rem, 1.7405rem + 0.9044vw, 2.4414rem);
+ --ad-step-4: clamp(2.3328rem, 2.0387rem + 1.3072vw, 3.0518rem);
+ --ad-step-5: clamp(2.7994rem, 2.384rem + 1.8461vw, 3.8147rem);
+
+ /* Space */
+ --ad-space-3xs: clamp(0.3125rem, 0.3125rem + 0vw, 0.3125rem);
+ --ad-space-2xs: clamp(0.5625rem, 0.5369rem + 0.1136vw, 0.625rem);
+ --ad-space-xs: clamp(0.875rem, 0.8494rem + 0.1136vw, 0.9375rem);
+ --ad-space-s: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-space-m: clamp(1.6875rem, 1.6108rem + 0.3409vw, 1.875rem);
+ --ad-space-l: clamp(2.25rem, 2.1477rem + 0.4545vw, 2.5rem);
+ --ad-space-xl: clamp(3.375rem, 3.2216rem + 0.6818vw, 3.75rem);
+ --ad-space-2xl: clamp(4.5rem, 4.2955rem + 0.9091vw, 5rem);
+ --ad-space-3xl: clamp(6.75rem, 6.4432rem + 1.3636vw, 7.5rem);
+
+ /* Radius */
+ --ad-radius-sm: 4px;
+ --ad-radius-md: 6px;
+ --ad-radius-lg: 10px;
+ --ad-radius-full: 999px;
+ --ad-border-w: 1px;
+
+ /* Elevation */
+ --ad-shadow-sm: 0 1px 2px rgba(20, 18, 14, 0.05);
+ --ad-shadow-float: 0 2px 6px rgba(20, 18, 14, 0.06), 0 10px 24px -12px rgba(20, 18, 14, 0.18);
+
+ /* Motion */
+ --ad-dur-fast: 0.12s;
+ --ad-dur-base: 0.16s;
+ --ad-ease: cubic-bezier(0.2, 0, 0, 1);
+}
+
+@media (prefers-color-scheme: dark) {
+ :root:not([data-theme="light"]) {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+ }
+}
+
+:root[data-theme="dark"] {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ :root {
+ --ad-dur-fast: 0ms;
+ --ad-dur-base: 0ms;
+ }
+}
+
+/* A short reset. Everything it sets is something the system would otherwise
+ have to override in every component.
+
+ The rem values used for this documentation site's own chrome (measure,
+ content width, sidebar width, header height) belong to this site, not to
+ the design system it documents — the source design system defines no
+ container-width tokens at all, so there is nothing under `--ad-*` to reach
+ for here. See the header comment in site/shell.css. */
+
+*,
+*::before,
+*::after { box-sizing: border-box; }
+
+html {
+ -webkit-text-size-adjust: 100%;
+ scroll-behavior: smooth;
+ scroll-padding-top: calc(3.5rem + var(--ad-space-s));
+}
+
+@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;
+ }
+}
+
+body {
+ margin: 0;
+ background: var(--ad-color-surface-page);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ line-height: 1.55;
+ font-synthesis-weight: none;
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+}
+
+::selection {
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+}
+
+h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd { margin: 0; }
+ul[class], ol[class] { list-style: none; margin: 0; padding: 0; }
+
+img, svg, video { display: block; max-width: 100%; height: auto; }
+
+button, input, select, textarea {
+ font: inherit;
+ color: inherit;
+}
+
+hr {
+ height: 1px;
+ margin: var(--ad-space-l) 0;
+ border: 0;
+ background: var(--ad-color-border-hairline);
+}
+
+/* One focus ring, defined once. No component is allowed its own. */
+:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+ border-radius: var(--ad-radius-sm);
+}
+:focus:not(:focus-visible) { outline: none; }
+
+[hidden] { display: none !important; }
+
+.ad-visually-hidden {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip-path: inset(50%);
+ white-space: nowrap;
+ border: 0;
+}
+
+/* Every rule here reads a type token for family and size. Weight, line-height,
+ and letter-spacing are hardcoded to the exact numbers the source ships —
+ there is no `--ad-weight-*` / `--ad-leading-*` / `--ad-tracking-*` scale in
+ the real system, only eight fixed compound styles (see the Typography
+ foundation page, which this file implements verbatim). */
+
+.type-display { font: 600 var(--ad-step-4)/1.08 var(--ad-font-sans); letter-spacing: -0.015em; }
+.type-h1, h1 { font: 600 var(--ad-step-3)/1.12 var(--ad-font-sans); letter-spacing: -0.012em; }
+.type-h2, h2 { font: 600 var(--ad-step-2)/1.16 var(--ad-font-sans); letter-spacing: -0.01em; }
+.type-h3, h3 { font: 600 var(--ad-step-1)/1.25 var(--ad-font-sans); }
+.type-body { font: 400 var(--ad-step-0)/1.55 var(--ad-font-sans); }
+.type-body-sm { font: 400 var(--ad-step--1)/1.5 var(--ad-font-sans); }
+.type-label {
+ font: 500 var(--ad-step--2)/1 var(--ad-font-mono);
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: var(--ad-color-text-secondary);
+}
+.type-mono { font: 400 var(--ad-step--1)/1.5 var(--ad-font-mono); }
+
+/* Prose defaults for the documentation body copy. These read the same
+ tokens the compound classes above do; they are not a second scale. */
+h1, h2, h3, h4 { font-weight: 600; text-wrap: balance; }
+
+h4 { font: 600 var(--ad-step-0)/1.3 var(--ad-font-sans); }
+
+p, ul, ol { margin: 0 0 var(--ad-space-s); }
+li + li { margin-top: var(--ad-space-3xs); }
+
+a {
+ color: var(--ad-color-accent);
+ text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
+ text-underline-offset: 0.18em;
+ text-decoration-thickness: 1px;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ text-decoration-color var(--ad-dur-fast) var(--ad-ease);
+}
+a:hover {
+ color: var(--ad-color-accent-ink);
+ text-decoration-color: currentColor;
+}
+
+strong, b { font-weight: 600; }
+
+small { font-size: var(--ad-step--1); }
+
+.ad-lead {
+ font-size: var(--ad-step-1);
+ line-height: 1.5;
+ color: var(--ad-color-text-secondary);
+ max-width: 68ch;
+ text-wrap: pretty;
+}
+
+.ad-muted { color: var(--ad-color-text-secondary); }
+.ad-subtle { color: var(--ad-color-text-tertiary); }
+
+code, kbd, samp, pre {
+ font-family: var(--ad-font-mono);
+ font-feature-settings: "liga" 0;
+}
+
+:not(pre) > code {
+ padding: 0.15em 0.4em;
+ font-size: 0.875em;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-sm);
+ white-space: nowrap;
+}
+
+kbd {
+ display: inline-block;
+ padding: 0.1em 0.45em;
+ font-size: var(--ad-step--2);
+ line-height: 1.6;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-bottom-width: 2px;
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-secondary);
+}
+
+blockquote {
+ margin-block: var(--ad-space-s);
+ padding-left: var(--ad-space-s);
+ border-left: 2px solid var(--ad-color-accent);
+ color: var(--ad-color-text-secondary);
+}
+
+/* Button
+ A flat, quiet button that recedes rather than performs — no gradients, no
+ shine. Variants: default, primary, dark, ghost, danger. Sizes: sm, md
+ (default), lg — padding and font step only, weight and radius stay put.
+
+ The contract is the element itself: a bare <button> is styled directly, no
+ class required. An <a> that acts like a button (navigates rather than
+ changes something — see the Button page's "Links that act like buttons")
+ opts in with a single `.btn` class, since a bare `a` selector would catch
+ every ordinary prose link on the site. `:not(.icon-button)` keeps this
+ block from leaking onto IconButton, which shares the <button> tag but is
+ an entirely different shape. */
+
+button:not(.icon-button),
+a.btn {
+ --_bg: var(--ad-color-surface-card);
+ --_fg: var(--ad-color-text-primary);
+ --_border: var(--ad-color-border-default);
+ --_pad: var(--ad-space-xs) var(--ad-space-s);
+ --_font: var(--ad-step--1);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: var(--ad-space-2xs);
+ padding: var(--_pad);
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-sans);
+ font-size: var(--_font);
+ font-weight: 550;
+ letter-spacing: -0.01em;
+ line-height: 1.2;
+ text-align: center;
+ text-decoration: none;
+ white-space: nowrap;
+ cursor: pointer;
+ transition: filter var(--ad-dur-fast) var(--ad-ease),
+ border-color var(--ad-dur-fast) var(--ad-ease);
+}
+
+:is(button:not(.icon-button), a.btn):hover:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.96); }
+:is(button:not(.icon-button), a.btn):active:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.92); }
+
+button:not(.icon-button):disabled,
+:is(button:not(.icon-button), a.btn)[aria-disabled="true"] {
+ opacity: 0.5;
+ cursor: default;
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="primary"] {
+ --_bg: var(--ad-color-accent);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-accent);
+}
+
+/* Reserved for the single most-committing action in a view — a final Save,
+ never a whole row of them. */
+:is(button:not(.icon-button), a.btn)[data-variant="dark"] {
+ --_bg: var(--ad-color-text-primary);
+ --_fg: var(--ad-color-surface-page);
+ --_border: var(--ad-color-text-primary);
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="ghost"] {
+ --_bg: transparent;
+ --_fg: var(--ad-color-text-primary);
+ --_border: transparent;
+}
+
+/* The source hardcodes this label white rather than deriving it — danger
+ is not "on a filled accent surface", so --ad-color-accent-fg is reused
+ here only because its resolved value is the same literal white, not
+ because the two roles are the same thing. */
+:is(button:not(.icon-button), a.btn)[data-variant="danger"] {
+ --_bg: var(--ad-color-danger);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-danger);
+}
+
+:is(button:not(.icon-button), a.btn)[data-size="sm"] { --_pad: 0.375rem 0.75rem; --_font: 0.8125rem; }
+:is(button:not(.icon-button), a.btn)[data-size="lg"] { --_pad: 0.75rem 1.375rem; --_font: var(--ad-step-0); }
+
+:is(button:not(.icon-button), a.btn)[data-block] { width: 100%; }
+
+/* Button group — [role="group"] is already the correct ARIA role for a
+ toolbar of related buttons, so it doubles as the styling hook for free. */
+[role="group"] {
+ display: inline-flex;
+ isolation: isolate;
+}
+[role="group"] > button { border-radius: 0; }
+[role="group"] > button:first-child { border-start-start-radius: var(--ad-radius-md); border-end-start-radius: var(--ad-radius-md); }
+[role="group"] > button:last-child { border-start-end-radius: var(--ad-radius-md); border-end-end-radius: var(--ad-radius-md); }
+[role="group"] > button + button { margin-inline-start: -1px; }
+[role="group"] > button:hover,
+[role="group"] > button:focus-visible { z-index: 1; }
+
+/* IconButton — a square icon-only button. Always needs an aria-label; it
+ becomes both the accessible name and the native tooltip. There is no
+ attribute that distinguishes "a button that is only an icon" from a plain
+ Button, so this is the one sanctioned class in this file. */
+.icon-button {
+ --_size: 2.125rem;
+ --_bg: transparent;
+ --_border: transparent;
+ --_fg: var(--ad-color-text-secondary);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ padding: 0;
+ flex: none;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease),
+ color var(--ad-dur-fast) var(--ad-ease);
+}
+.icon-button:hover:not(:disabled) { background: var(--ad-color-surface-fill-2); }
+.icon-button:disabled { opacity: 0.5; cursor: default; }
+
+.icon-button[data-size="sm"] { --_size: 1.75rem; }
+.icon-button[data-size="lg"] { --_size: 2.5rem; }
+
+.icon-button[data-variant="outline"] { --_bg: var(--ad-color-surface-card); --_border: var(--ad-color-border-default); --_fg: var(--ad-color-text-primary); }
+.icon-button[data-variant="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); }
+
+/* Field
+ The labeled field wrapper — a <label> that wraps its own caption text
+ together with the control, so the association is native and needs no
+ `for`/`id` pair. The caption is mono, uppercase, tracked — the same
+ catalog-label convention as Badge and type-label.
+
+ A <label> is reused for three different shapes across this system (this
+ wrapper, Checkbox/Radio's row, Switch's row), so it cannot be styled bare
+ without one shape leaking into another. Field is the one of the three with
+ no distinguishing attribute of its own, so it gets the sanctioned class;
+ Checkbox/Radio tell themselves apart structurally instead (see below). */
+.field {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ min-width: 0;
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Hint and error read the native fine-print element. `role="alert"` is a
+ real live-region role for validation text, so it doubles as the selector
+ that turns a hint red. */
+.field > small {
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+.field > small[role="alert"] { color: var(--ad-color-danger); }
+
+/* Input / Select — bordered, matched to each other exactly. Both tags ARE
+ the component, so they are styled bare; nothing else on this site uses a
+ text input or a select. */
+input:not([type="checkbox"]):not([type="radio"]),
+select {
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ outline: none;
+ transition: border-color var(--ad-dur-fast) var(--ad-ease),
+ box-shadow var(--ad-dur-fast) var(--ad-ease);
+}
+input::placeholder { color: var(--ad-color-text-tertiary); }
+
+input:not([type="checkbox"]):not([type="radio"]):focus,
+select:focus {
+ border-color: var(--ad-color-accent);
+ box-shadow: 0 0 0 2px var(--ad-color-accent-soft);
+}
+
+input:not([type="checkbox"]):not([type="radio"]):disabled,
+select:disabled {
+ opacity: 0.5;
+ cursor: default;
+}
+
+/* The chevron is the one flagged color exception in this component set:
+ CSS cannot point a data-URI background-image at a custom property, so its
+ stroke is a literal hex rather than a var(--ad-*) reference. It is picked
+ to match --ad-color-text-secondary and commented here so it is not
+ mistaken for a stray color. */
+select {
+ appearance: none;
+ padding-inline-end: var(--ad-space-l);
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%235f5d59' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right var(--ad-space-xs) center;
+}
+
+input[aria-invalid="true"],
+select[aria-invalid="true"] {
+ border-color: var(--ad-color-danger);
+ background-color: var(--ad-color-danger-bg);
+}
+
+/* Checkbox / Radio — the real native controls, tinted with accent-color
+ instead of a hand-drawn box. This is not just a rename: it deletes the
+ custom SVG-checkmark markup entirely and lets the browser draw its own
+ (platform-appropriate) check and dot. `role="switch"` is excluded here so
+ Switch — a checkbox playing a different role — can style itself below
+ without this rule fighting it. A <label> wrapping a checkbox/radio is told
+ apart from Field's <label> structurally: it :has() one as a direct child,
+ no class required. */
+input[type="checkbox"]:not([role="switch"]),
+input[type="radio"] {
+ width: 1.125rem;
+ height: 1.125rem;
+ margin: 0;
+ accent-color: var(--ad-color-accent);
+ cursor: pointer;
+}
+input[type="checkbox"]:not([role="switch"]):focus-visible,
+input[type="radio"]:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+}
+
+label:has(> input[type="checkbox"]:not([role="switch"])),
+label:has(> input[type="radio"]) {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 400;
+ text-transform: none;
+ letter-spacing: normal;
+ color: var(--ad-color-text-primary);
+}
+label:has(> input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Switch — for on/off settings only, never a substitute for Checkbox.
+ `input[type="checkbox"][role="switch"]` is real ARIA (the correct role for
+ a toggle), not an invented hook, but the visual track+thumb needs a second
+ element for the thumb's independent position and transition — more than
+ `appearance: none` plus pseudo-elements on the input itself can cleanly
+ carry — so `.switch` is a deliberate, documented fallback class rather
+ than a first choice. */
+.switch {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ color: var(--ad-color-text-primary);
+}
+.switch input[role="switch"] { position: absolute; opacity: 0; width: 2.25rem; height: 1.25rem; margin: 0; }
+
+.switch span {
+ position: relative;
+ flex: none;
+ width: 2.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-border-default);
+ transition: background-color var(--ad-dur-base) var(--ad-ease);
+}
+.switch span::after {
+ content: "";
+ position: absolute;
+ inset-block-start: 2px;
+ inset-inline-start: 2px;
+ width: 1rem;
+ height: 1rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-fg);
+ box-shadow: var(--ad-shadow-sm);
+ transition: translate var(--ad-dur-base) var(--ad-ease);
+}
+.switch input[role="switch"]:checked + span { background: var(--ad-color-accent); }
+.switch input[role="switch"]:checked + span::after { translate: 1rem 0; }
+.switch input[role="switch"]:focus-visible + span { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+.switch:has(input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Rows of fields — used by the settings-form pattern. `<form>`/`<fieldset>`
+ are the elements that already mean this, so they are styled bare. A row
+ grouping two fields side by side has no element of its own, so it keeps
+ one plain class. */
+form {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-s);
+ max-width: 28rem;
+}
+.form-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
+ gap: var(--ad-space-xs);
+}
+fieldset {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ margin: 0;
+ padding: 0;
+ border: 0;
+}
+fieldset > legend {
+ padding: 0 0 var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Badge
+ Mono, uppercase, tracked — the catalog-label convention for status and
+ meta values (SAVED, OVERDUE, RECOMMENDED). A label, never a button.
+
+ A <span> is too generic to style globally — it is used all over running
+ prose for unrelated reasons — so Badge gets the one sanctioned class this
+ shape needs. Tone is an attribute, not a modifier class. */
+
+.badge {
+ --_bg: var(--ad-color-surface-fill);
+ --_fg: var(--ad-color-text-secondary);
+ --_border: var(--ad-color-border-hairline);
+
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.1875rem 0.5rem;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-sm);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ line-height: 1;
+ white-space: nowrap;
+}
+
+.badge[data-tone="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); --_border: transparent; }
+.badge[data-tone="success"] { --_bg: var(--ad-color-success-bg); --_fg: var(--ad-color-success); --_border: transparent; }
+.badge[data-tone="danger"] { --_bg: var(--ad-color-danger-bg); --_fg: var(--ad-color-danger); --_border: transparent; }
+
+.badge-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-2xs);
+}
+
+/* Tag
+ A removable mono chip for freeform tags — locations, categories.
+ `[data-dashed]` renders an "add tag" affordance instead of a value. Same
+ reasoning as Badge: <span> is too generic to select bare, so Tag gets one
+ class. The remove control is a real <button>, styled through the
+ descendant selector rather than a class of its own. */
+
+.tag {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.375rem;
+ padding: 0.25rem 0.625rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-default);
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ line-height: 1;
+}
+
+.tag[data-dashed] {
+ background: transparent;
+ border-style: dashed;
+ color: var(--ad-color-text-tertiary);
+ cursor: pointer;
+}
+
+.tag button {
+ all: unset;
+ cursor: pointer;
+ color: var(--ad-color-text-tertiary);
+ line-height: 1;
+ font-size: 0.75rem;
+}
+.tag button:hover { color: var(--ad-color-text-primary); }
+
+.tag-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.375rem;
+}
+
+/* Card
+ Flat surface, hairline border, radius-lg. `[data-elevated]` is opt-in and
+ reserved for the one focused/primary card in a view — a dialog, the
+ primary capture card — flat is the default everywhere else.
+
+ A <div>/<article> is too generic to select bare, so Card keeps one
+ sanctioned class. When Card's job is holding a list of rows — the common
+ case, entry lists, task lists, settings rows — it is a real <ul class="
+ card">, and CardRow is a bare <li> underneath it: no second class needed,
+ the row look comes from the `.card > li` descendant selector alone. The
+ first row's top border is removed automatically so callers never have to
+ override it by hand. */
+
+.card {
+ display: flex;
+ flex-direction: column;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: none;
+ overflow: hidden;
+}
+
+.card[data-elevated] { box-shadow: var(--ad-shadow-float); }
+
+.card > li {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+}
+.card > li:first-child { border-top: none; }
+
+/* A row whose ENTIRE content is wrapped in a real <button> or <a> — the
+ accessible way to make a row clickable — fills the row edge-to-edge and
+ picks up a hover fill and pointer cursor. `:only-child` is what tells this
+ apart from a row that merely contains a small button among other content
+ (a leading checkbox next to a title and a trailing value, say): only a
+ button/anchor that IS the row's one and only child means "the whole row
+ is the control." */
+.card > li:has(> button:only-child),
+.card > li:has(> a:only-child) {
+ padding: 0;
+}
+.card > li > button:only-child,
+.card > li > a:only-child {
+ all: unset;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ box-sizing: border-box;
+ color: inherit;
+ font: inherit;
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.card > li > button:only-child:hover,
+.card > li > a:only-child:hover { background: var(--ad-color-surface-fill); }
+
+/* Documentation-only extras: a body/footer pair and a responsive grid, used
+ by the prose pages (rationale.md, index.md) to show a Card as a plain
+ content tile rather than a data row. Neither is part of Card's props —
+ they exist because a content tile's inner layout has no element of its
+ own to hang a selector on. */
+.card-body {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-m);
+}
+.card-footer {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ margin-top: auto;
+ padding: var(--ad-space-xs) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+
+.card[data-link] { transition: border-color var(--ad-dur-fast) var(--ad-ease), box-shadow var(--ad-dur-fast) var(--ad-ease); }
+.card[data-link]:hover { border-color: var(--ad-color-border-default); box-shadow: var(--ad-shadow-sm); }
+.card[data-link]:focus-within { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+
+.card-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
+ gap: var(--ad-space-s);
+}
+
+/* Avatar — lives here because it almost never appears outside a card or a
+ row (the Loop dashboard's initials chip, Archive's file-type chip). A
+ <span> is too generic for a bare selector, so it keeps one class. */
+.avatar {
+ --_size: 1.875rem;
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ flex: none;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 600;
+ overflow: hidden;
+}
+.avatar[data-square] { border-radius: var(--ad-radius-md); background: var(--ad-color-surface-fill); color: var(--ad-color-text-secondary); border: 1px solid var(--ad-color-border-hairline); }
+.avatar[data-size="sm"] { --_size: 1.5rem; }
+.avatar[data-size="lg"] { --_size: 2.25rem; }
+
+/* Tooltip — a dark mono label on hover, positioned above the child. Pure CSS:
+ the trigger and bubble are siblings inside one relatively-positioned span,
+ shown on :hover/:focus-within so it works from the keyboard too.
+
+ `[role="tooltip"]` is the correct ARIA role for the bubble itself, so it
+ doubles as the styling hook — zero classes there. The wrapping span has no
+ attribute of its own, but it does not need one: `:has()` picks out "a span
+ that contains a [role=tooltip] bubble" precisely, without reaching for a
+ class on an otherwise-generic element. */
+span:has(> [role="tooltip"]) {
+ position: relative;
+ display: inline-flex;
+}
+
+[role="tooltip"] {
+ position: absolute;
+ bottom: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ margin-bottom: 0.375rem;
+ padding: 0.25rem 0.5rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-text-primary);
+ color: var(--ad-color-surface-page);
+ font-family: var(--ad-font-mono);
+ /* 11px is the source's own literal choice — smaller than --ad-step--2, the
+ smallest step on the scale, because a tooltip is meant to read as a
+ glance-only aside rather than body text. */
+ font-size: 11px;
+ white-space: nowrap;
+ line-height: 1.4;
+ z-index: 20;
+ opacity: 0;
+ visibility: hidden;
+ pointer-events: none;
+ transition: opacity var(--ad-dur-fast) var(--ad-ease);
+}
+span:has(> [role="tooltip"]):hover > [role="tooltip"],
+span:has(> [role="tooltip"]):focus-within > [role="tooltip"] {
+ opacity: 1;
+ visibility: visible;
+}
+
+/* Toast — the real pattern used for delete confirmations: a flat bar with an
+ optional destructive action and a dismiss. `[data-fixed]` pins it
+ bottom-right for real use; without it the toast stays in document flow for
+ documentation. `role="status"` is the correct live-region role for a
+ transient status message, so it is both the accessibility hook and the
+ style selector — no base class needed. The action and dismiss buttons are
+ plain <button>s told apart by `aria-label="Dismiss"`, an attribute they
+ need anyway for the icon-only close control. */
+[role="status"] {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ min-width: 20rem;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+}
+[role="status"][data-fixed] {
+ position: fixed;
+ inset-block-end: var(--ad-space-s);
+ inset-inline-end: var(--ad-space-s);
+ z-index: 60;
+}
+[role="status"] > span { flex: 1; }
+[role="status"] button {
+ all: unset;
+ cursor: pointer;
+ font-weight: 600;
+ color: var(--ad-color-danger);
+}
+[role="status"] button[aria-label="Dismiss"] {
+ color: var(--ad-color-text-tertiary);
+ font-size: 1rem;
+ font-weight: 400;
+ line-height: 1;
+}
+[role="status"] button[aria-label="Dismiss"]:hover { color: var(--ad-color-text-primary); }
+
+/* Dialog — the native <dialog> element: a centered modal, flat surface, no
+ colored header, hairline dividers between title/body/footer. A <header>
+ holds the title and a <footer> holds the actions, matching the elements
+ the tag already wants; everything else in between is the body, styled by
+ excluding those two rather than by a class of its own.
+
+ `.dialog-demo` scopes a static, always-open example to its own preview
+ container instead of the viewport, so it can sit safely inside a
+ documentation page — this is a doc-only affordance, not part of Dialog's
+ contract. It relies on the browser's own dialog centering: the UA
+ stylesheet already gives an open <dialog> `position: absolute; inset: 0;
+ margin: auto`, which centers it inside the nearest positioned ancestor —
+ here, `.dialog-demo` itself. A real `.showModal()` call gets the genuine
+ viewport-fixed overlay and `::backdrop` for free; a plain `open` attribute
+ (used here so the demo renders without JavaScript) does not paint a
+ backdrop at all, since `::backdrop` only exists for a dialog shown
+ modally — wire the real open/close state and call `.showModal()` in the
+ consuming app. */
+.dialog-demo {
+ position: relative;
+ min-height: 20rem;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-lg);
+}
+
+dialog {
+ width: 26rem;
+ max-width: calc(100% - (2 * var(--ad-space-s)));
+ height: fit-content;
+ padding: 0;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ overflow: hidden;
+}
+dialog::backdrop {
+ background: rgba(20, 18, 14, 0.35);
+}
+dialog > header {
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-sans);
+ font-weight: 600;
+ font-size: var(--ad-step-0);
+ color: var(--ad-color-text-primary);
+}
+dialog > footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+}
+dialog > *:not(header):not(footer) {
+ padding: var(--ad-space-m);
+ margin: 0;
+ color: var(--ad-color-text-primary);
+ font-size: var(--ad-step--1);
+}
+
+/* Tabs
+ Follows the real WAI-ARIA tabs pattern: a `[role="tablist"]` of
+ `[role="tab"]` buttons controlling `[role="tabpanel"]`s. Those roles are
+ required for the pattern to be accessible in the first place, so they
+ double as the entire styling contract — no classes at all for the base
+ shape. The keyboard wiring lives in assets/js/system.js and is
+ variant-agnostic — it only cares about `[data-tabs]` / `[role=tab]`.
+
+ Two real variants: `underline` (mono, accent underline — primary section
+ nav, the default) and `segmented` (pill track, active segment lifts to the
+ surface — filters like All · Overdue · Upcoming, never primary
+ navigation), chosen with `data-variant="segmented"` on the tablist since
+ neither has a natural attribute of its own. */
+
+/* Every rule below the list container pairs [role="tablist"] with
+ [role="tab"] rather than styling [role="tab"] alone — Tab buttons are
+ still literal <button> elements, so the compound selector's extra
+ specificity is what keeps Button's base styling (button.css) from
+ winning the cascade on shared properties like padding and background. */
+
+[role="tablist"] {
+ display: flex;
+ gap: var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+[role="tablist"] [role="tab"] {
+ padding: 0.5rem 0;
+ margin-bottom: -1px;
+ background: none;
+ border: 0;
+ border-bottom: 2px solid transparent;
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--1);
+ letter-spacing: 0.02em;
+ cursor: pointer;
+ transition: color var(--ad-dur-fast) var(--ad-ease), border-color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"] [role="tab"]:hover { color: var(--ad-color-text-primary); }
+[role="tablist"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ border-bottom-color: var(--ad-color-accent);
+}
+
+[role="tablist"][data-variant="segmented"] {
+ display: inline-flex;
+ gap: 0.125rem;
+ padding: 0.1875rem;
+ border-bottom: 0;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-md);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"] {
+ all: unset;
+ cursor: pointer;
+ padding: 0.375rem 0.875rem;
+ border-radius: var(--ad-radius-sm);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 550;
+ color: var(--ad-color-text-secondary);
+ transition: background-color var(--ad-dur-fast) var(--ad-ease), color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ background: var(--ad-color-surface-card);
+ box-shadow: var(--ad-shadow-sm);
+}
+
+[role="tabpanel"] { padding-top: var(--ad-space-s); }
+[role="tabpanel"][hidden] { display: none; }
+[role="tabpanel"]:focus-visible { outline-offset: 4px; }
+
+/* Documentation site chrome. Everything below the `.ad-doc-` prefix belongs
+ to this site rather than to the design system it documents — including the
+ width/height constants used directly below. The source design system
+ defines no container-width tokens at all, so there is nothing under
+ `--ad-*` for a doc shell to reach for; the Space foundation page's own
+ notes say to omit that group entirely rather than invent one. */
+
+.ad-doc-skip {
+ position: absolute;
+ inset-inline-start: var(--ad-space-xs);
+ inset-block-start: -4rem;
+ z-index: 40;
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ box-shadow: var(--ad-shadow-float);
+ transition: inset-block-start var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-skip:focus { inset-block-start: var(--ad-space-2xs); }
+
+.ad-doc-header {
+ position: sticky;
+ inset-block-start: 0;
+ z-index: 30;
+ height: 3.5rem;
+ background: color-mix(in srgb, var(--ad-color-surface-page) 92%, transparent);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-header__inner {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ height: 100%;
+ max-width: 72rem;
+ margin-inline: auto;
+ padding-inline: var(--ad-space-xs);
+}
+
+.ad-doc-wordmark {
+ display: flex;
+ align-items: baseline;
+ gap: var(--ad-space-2xs);
+ color: var(--ad-color-text-primary);
+ text-decoration: none;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ font-weight: 600;
+ letter-spacing: -0.01em;
+ white-space: nowrap;
+}
+.ad-doc-wordmark:hover { color: var(--ad-color-text-primary); }
+.ad-doc-wordmark__mark {
+ width: 1.375rem;
+ height: 1.375rem;
+ flex: none;
+ align-self: center;
+ color: var(--ad-color-accent);
+}
+.ad-doc-wordmark__version {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+
+.ad-doc-header__actions { margin-inline-start: auto; display: flex; align-items: center; gap: var(--ad-space-2xs); }
+
+.ad-doc-layout {
+ display: grid;
+ grid-template-columns: 16rem minmax(0, 1fr);
+ gap: var(--ad-space-l);
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-m) var(--ad-space-xs) var(--ad-space-2xl);
+}
+
+.ad-doc-sidebar {
+ position: sticky;
+ inset-block-start: calc(3.5rem + var(--ad-space-m));
+ align-self: start;
+ max-height: calc(100vh - 3.5rem - var(--ad-space-l));
+ overflow-y: auto;
+ padding-inline-end: var(--ad-space-2xs);
+}
+.ad-doc-nav__group + .ad-doc-nav__group { margin-top: var(--ad-space-s); }
+.ad-doc-nav__heading {
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-doc-nav__link {
+ display: block;
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ margin-inline-start: calc(var(--ad-space-xs) * -1);
+ border-inline-start: 2px solid transparent;
+ border-radius: 0 var(--ad-radius-sm) var(--ad-radius-sm) 0;
+ color: var(--ad-color-text-secondary);
+ font-size: var(--ad-step--1);
+ text-decoration: none;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-nav__link:hover { color: var(--ad-color-text-primary); background: var(--ad-color-surface-fill); }
+.ad-doc-nav__link[aria-current="page"] {
+ color: var(--ad-color-accent-ink);
+ border-inline-start-color: var(--ad-color-accent);
+ background: var(--ad-color-accent-soft);
+ font-weight: 500;
+}
+
+.ad-doc-main {
+ min-width: 0;
+ /* The design system's own body-text token (--ad-step-0, 18–20px) is right
+ for a product screen but too large for long-form documentation prose.
+ This site's own copy gets a plain, fixed 16px instead — the token above
+ is unchanged, and specimen tables/demos below still show its real
+ value. */
+ font-size: 1rem;
+}
+.ad-doc-main > * { max-width: 50rem; }
+
+.ad-doc-title { margin-bottom: var(--ad-space-xs); }
+.ad-doc-title__section {
+ display: block;
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-accent);
+}
+
+.ad-doc-main h2 {
+ margin-block: var(--ad-space-2xl) var(--ad-space-xs);
+ padding-bottom: var(--ad-space-2xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-main h3 { margin-block: var(--ad-space-l) var(--ad-space-2xs); }
+.ad-doc-main h4 { margin-block: var(--ad-space-s) var(--ad-space-2xs); }
+.ad-doc-main p,
+.ad-doc-main ul,
+.ad-doc-main ol { max-width: 68ch; }
+.ad-doc-main ul:not([class]),
+.ad-doc-main ol:not([class]) { padding-inline-start: var(--ad-space-s); }
+
+.ad-doc-footer {
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-s) var(--ad-space-xs) var(--ad-space-l);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-tertiary);
+ font-size: var(--ad-step--1);
+}
+.ad-doc-footer__inner { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); justify-content: space-between; }
+
+/* `button.ad-doc-nav-toggle` (element + class) rather than a bare class:
+ button.css's own base rule is `button:not(.icon-button)`, which is also
+ one element + one class in specificity and sets `display: inline-flex` —
+ a bare `.ad-doc-nav-toggle { display: none }` loses that fight and the
+ toggle stays visible at every width. Matching its specificity here (and
+ coming later in the concatenated stylesheet) settles it in source order. */
+button.ad-doc-nav-toggle { display: none; }
+
+@media (max-width: 60rem) {
+ .ad-doc-layout { grid-template-columns: minmax(0, 1fr); gap: var(--ad-space-s); }
+ .ad-doc-sidebar {
+ position: static;
+ max-height: none;
+ }
+ /* The toggle is a normal Button; the panel it opens gets its own card
+ treatment. Putting the same card styling on the <aside> wrapper too
+ nested a button-shaped box inside an outer box for no reason. */
+ .ad-doc-nav {
+ margin-top: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ }
+ .ad-doc-sidebar[data-collapsed="true"] > .ad-doc-nav { display: none; }
+ button.ad-doc-nav-toggle {
+ display: flex;
+ width: 100%;
+ justify-content: space-between;
+ }
+}
+
+@media (max-width: 34rem) {
+ .ad-doc-wordmark__version { display: none; }
+ .ad-doc-layout { padding-inline: var(--ad-space-2xs); }
+}
+
+/* The live-example figure used throughout the docs — see the "How the
+ example demos work" section on the Using the CSS page. One class on the
+ outer <figure>; everything inside is styled by plain element selectors,
+ since figcaption/div/details/summary/pre already say what they are.
+ `data-layout` and `data-surface` on the preview <div> distinguish the
+ handful of demos that need a non-default arrangement. */
+
+.example {
+ margin: var(--ad-space-s) 0 var(--ad-space-l);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+ background: var(--ad-color-surface-card);
+ overflow: hidden;
+}
+
+.example figcaption {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-secondary);
+}
+
+.example > div {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-s);
+}
+/* nowrap is load-bearing: a column-direction flex container with wrap enabled
+ sizes its single line against the container rather than the content, which
+ left every stacked example with a few hundred pixels of dead space. */
+.example > div[data-layout="stack"] { flex-direction: column; flex-wrap: nowrap; align-items: stretch; }
+.example > div[data-layout="grid"] {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
+ align-items: start;
+}
+.example > div[data-surface="sunken"] { background: var(--ad-color-surface-fill); }
+.example > div > * { min-width: 0; }
+
+.example details {
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+.example summary {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ color: var(--ad-color-text-secondary);
+ cursor: pointer;
+ list-style: none;
+ user-select: none;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+}
+.example summary::-webkit-details-marker { display: none; }
+.example summary::before {
+ content: "";
+ width: 0.5rem;
+ height: 0.5rem;
+ border-inline-end: 1.5px solid currentColor;
+ border-block-end: 1.5px solid currentColor;
+ rotate: -45deg;
+ transition: rotate var(--ad-dur-fast) var(--ad-ease);
+}
+.example details[open] > summary::before { rotate: 45deg; }
+.example summary:hover { color: var(--ad-color-text-primary); }
+
+.example pre {
+ margin: 0;
+ padding: var(--ad-space-xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+ tab-size: 2;
+}
+.example pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Standalone fenced code blocks in the prose (unhighlighted — there is no
+ syntax highlighter in this build any more, just Kramdown's plain
+ <pre><code class="language-*">). */
+.ad-doc-main > pre {
+ margin-block: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+}
+.ad-doc-main > pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Presentation for the token reference tables and swatch grids, plus a
+ generic data-table utility. All of this is documentation chrome — the
+ design system itself has no Table component (see the note on the removed
+ `table`/`progress`/`callout` pages in git history). */
+
+.ad-swatch-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs) var(--ad-space-s);
+ padding: 0;
+ list-style: none;
+}
+.ad-swatch {
+ display: flex;
+ flex-direction: column;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ overflow: hidden;
+ background: var(--ad-color-surface-card);
+}
+.ad-swatch__chip {
+ display: flex;
+ align-items: flex-end;
+ justify-content: flex-end;
+ height: 4rem;
+ padding: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+}
+.ad-swatch__meta {
+ display: flex;
+ flex-direction: column;
+ gap: 0.1rem;
+ padding: var(--ad-space-2xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+}
+.ad-swatch__hex { color: var(--ad-color-text-primary); text-transform: uppercase; }
+.ad-swatch__ratio { color: var(--ad-color-text-tertiary); }
+
+.ad-alias-row { display: flex; align-items: center; gap: var(--ad-space-2xs); }
+.ad-alias-chip {
+ width: 1.5rem;
+ height: 1.5rem;
+ flex: none;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+}
+.ad-alias-chip--pair { display: flex; }
+.ad-alias-chip--pair > span { flex: 1; }
+
+/* Type specimen rows. */
+.ad-specimen {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ gap: var(--ad-space-3xs);
+ padding-block: var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-specimen__meta {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-specimen__sample {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: var(--ad-color-text-primary);
+}
+
+/* Space and radius rulers. */
+.ad-ruler { display: flex; flex-direction: column; gap: var(--ad-space-2xs); }
+.ad-ruler__row { display: grid; grid-template-columns: 6.5rem 4.5rem 1fr; align-items: center; gap: var(--ad-space-xs); font-family: var(--ad-font-mono); font-size: 11px; }
+.ad-ruler__bar { height: 0.75rem; background: var(--ad-color-accent); border-radius: var(--ad-radius-sm); }
+.ad-ruler__num { color: var(--ad-color-text-tertiary); text-align: right; }
+
+.ad-radius-grid, .ad-elevation-grid, .ad-motion-grid, .ad-accent-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs);
+ padding: 0;
+ list-style: none;
+}
+.ad-radius-demo {
+ height: 4.5rem;
+ background: var(--ad-color-accent-soft);
+ border: 1px solid var(--ad-color-accent);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-elevation-demo {
+ height: 4.5rem;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-motion-demo {
+ height: 4.5rem;
+ display: grid;
+ place-items: center;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-md);
+ margin-bottom: var(--ad-space-2xs);
+ cursor: pointer;
+ overflow: hidden;
+}
+.ad-motion-demo__dot {
+ width: 1.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent);
+ translate: -3rem 0;
+ transition-property: translate;
+ transition-duration: var(--ad-dur-base);
+ transition-timing-function: var(--ad-ease);
+}
+.ad-motion-demo:hover .ad-motion-demo__dot,
+.ad-motion-demo:focus-visible .ad-motion-demo__dot { translate: 3rem 0; }
+
+.ad-accent-demo {
+ height: 4.5rem;
+ display: flex;
+ align-items: flex-end;
+ padding: var(--ad-space-2xs);
+ border-radius: var(--ad-radius-md);
+ color: #fff;
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ margin-bottom: var(--ad-space-2xs);
+}
+
+/* Token names read `<code>` sitewide, which already carries a mono chip
+ look for running prose (see :not(pre) > code in base/typography.css).
+ Inside these specimen/swatch/ruler rows the old `.ad-token-name` class was
+ plain mono text with no chip, so `<code>` is flattened back to that look
+ here the same way table cells are. */
+.ad-swatch code, .ad-specimen code, .ad-ruler code, .ad-alias-row code {
+ background: none;
+ border: 0;
+ padding: 0;
+ font-size: var(--ad-step--2);
+ color: var(--ad-color-text-primary);
+ white-space: nowrap;
+}
+
+/* A documentation aside. Like `<table>`, this is site chrome for the prose
+ itself, not a system component — the real system has no Callout. */
+.ad-callout {
+ margin-block: var(--ad-space-s);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-left: 3px solid var(--ad-color-accent);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+}
+/* Generic reference tables (component APIs, alias tables, the contrast
+ contract). `<table>` IS a table — no class needed to say so. Every table
+ on this site uses the one compact density, so there is no separate
+ modifier for it. `display: block` plus `overflow-x: auto` gives the table
+ its own horizontal scrollbar on narrow viewports without a wrapper div;
+ the anonymous table box the browser generates around `<thead>`/`<tbody>`
+ still does the row/column layout. `<code>` replaces the old
+ `.ad-token-name` / `.ad-table__code` cells — it is already styled
+ sitewide, just re-flattened here to a plain mono run instead of the
+ chip-with-border look prose code gets, to match this table's original
+ look. `.num` is the one sanctioned class left, for right-aligned numeric
+ columns — there is no attribute that means "this column is a number". */
+table { width: 100%; border-collapse: collapse; font-size: var(--ad-step--1); display: block; overflow-x: auto; }
+table th, table td {
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ text-align: left;
+ vertical-align: top;
+}
+table th { font-family: var(--ad-font-mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ad-color-text-secondary); font-weight: 500; }
+table code { background: none; border: 0; padding: 0; font-size: var(--ad-step--2); white-space: nowrap; }
+.num { text-align: right; font-variant-numeric: tabular-nums; }
+
+/* Home page. */
+.ad-hero { padding-block: var(--ad-space-m) var(--ad-space-l); }
+.ad-hero__title {
+ font-size: var(--ad-step-4);
+ margin-bottom: var(--ad-space-xs);
+}
+.ad-hero__actions { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); margin-top: var(--ad-space-s); }
+.ad-stat-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-l);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+}
+.ad-stat__value {
+ display: block;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-2);
+ font-weight: 600;
+ line-height: 1.16;
+ font-variant-numeric: tabular-nums;
+}
+.ad-stat__label {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+
+/* Card chrome. The pane supplies the frame; this just insets the page. */
+body { padding: var(--ad-space-l) var(--ad-space-m); }
+.ad-doc-main { max-width: 50rem; margin-inline: auto; }
+.ad-doc-main > * { max-width: none; }
+</style>
+</head>
+<body>
+<main class="ad-doc-main">
+<header class="ad-doc-title"><span class="ad-doc-title__section">Components</span>
+ <h1>Badge</h1>
+</header>
+<p class="ad-lead">Mono, uppercase, tracked — the catalog-label convention for status and meta values. An archivist's tag, not a badge asking for attention.</p>
+<p>Labels are catalog-style: short, mono, uppercase — <code>SAVED</code>, <code>RECOMMENDED</code>,
+<code>OVERDUE</code>. Never a click handler on one; a Badge is a label, not a button.</p>
+
+<p>A <code><span></code> is too generic to style bare — it shows up all over running prose
+for unrelated reasons — so Badge keeps one class. Tone is an attribute, not a
+modifier class.</p>
+
+<h2 id="tones">Tones</h2>
+
+<figure class="example">
+<figcaption>Tones</figcaption>
+<div>
+<span class="badge">Unassigned</span>
+<span class="badge" data-tone="accent">Draft</span>
+<span class="badge" data-tone="success">Saved</span>
+<span class="badge" data-tone="danger">Overdue</span>
+</div>
+<details open=""><summary>Markup</summary>
+<pre><code><span class="badge">Unassigned</span>
+<span class="badge" data-tone="accent">Draft</span>
+<span class="badge" data-tone="success">Saved</span>
+<span class="badge" data-tone="danger">Overdue</span></code></pre>
+</details>
+</figure>
+
+<h2 id="in-a-set">In a set</h2>
+
+<figure class="example">
+<figcaption>Badge set</figcaption>
+<div>
+<div class="badge-set">
+ <span class="badge" data-tone="accent">Recommended</span>
+ <span class="badge">Every 2 weeks</span>
+ <span class="badge" data-tone="danger">3d overdue</span>
+</div>
+</div>
+<details><summary>Markup</summary>
+<pre><code><div class="badge-set">
+ <span class="badge" data-tone="accent">Recommended</span>
+ <span class="badge">Every 2 weeks</span>
+ <span class="badge" data-tone="danger">3d overdue</span>
+</div></code></pre>
+</details>
+</figure>
+
+<h2 id="rules">Rules</h2>
+
+<ul>
+ <li><strong>A badge is never interactive.</strong> No click handlers, no <code><button></code>, no cursor
+change. If it does something, it is a button that happens to be small.</li>
+ <li><strong>Two or three words at most.</strong> A badge that wraps is a sentence in the wrong clothes.</li>
+ <li><strong><code>danger</code> means something needs attention, not that something failed.</strong> The
+source uses it for overdue, not for a system error — that is what prose is for.</li>
+</ul>
+
+<h2 id="api">API</h2>
+
+<table>
+ <thead><tr><th scope="col">Element / attribute</th><th scope="col">Values</th><th scope="col">Notes</th></tr></thead>
+ <tbody>
+ <tr><td><code>span.badge</code></td><td></td><td>Required base.</td></tr>
+ <tr><td><code>data-tone</code></td><td><code>accent | success | danger</code></td><td>Omit for the default neutral tone.</td></tr>
+ </tbody>
+</table>
+</main>
+<script>
+// Behavior for the pieces of the system that need it. Everything else is
+// CSS and semantic HTML on purpose — a design system that only works with
+// JavaScript running is a component library with extra steps.
+(function () {
+ "use strict";
+
+ var STORAGE_KEY = "ad-theme";
+
+ function readStored() {
+ try {
+ return localStorage.getItem(STORAGE_KEY);
+ } catch (error) {
+ return null;
+ }
+ }
+
+ function writeStored(value) {
+ try {
+ localStorage.setItem(STORAGE_KEY, value);
+ } catch (error) {
+ /* Nothing to do — the media query still gives a sensible default. */
+ }
+ }
+
+ function systemPrefersDark() {
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
+ }
+
+ function currentTheme() {
+ var explicit = document.documentElement.getAttribute("data-theme");
+ if (explicit) return explicit;
+ return systemPrefersDark() ? "dark" : "light";
+ }
+
+ function paintToggle(theme) {
+ document.querySelectorAll("[data-theme-toggle]").forEach(function (toggle) {
+ var next = theme === "dark" ? "light" : "dark";
+ toggle.querySelectorAll("[data-theme-icon]").forEach(function (icon) {
+ icon.hidden = icon.dataset.themeIcon !== theme;
+ });
+ toggle.setAttribute("aria-label", "Switch to the " + next + " theme");
+ toggle.setAttribute("title", "Switch to the " + next + " theme");
+ var label = toggle.querySelector("[data-theme-label]");
+ if (label) label.textContent = "Switch to the " + next + " theme";
+ });
+ }
+
+ function setUpTheme() {
+ paintToggle(currentTheme());
+
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-theme-toggle]");
+ if (!toggle) return;
+
+ var next = currentTheme() === "dark" ? "light" : "dark";
+ document.documentElement.setAttribute("data-theme", next);
+ writeStored(next);
+ paintToggle(next);
+ });
+
+ // Follow the OS while the reader has not made an explicit choice.
+ window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", function () {
+ if (!readStored()) paintToggle(currentTheme());
+ });
+ }
+
+ // Tabs, wired to the WAI-ARIA authoring practices: arrows move, Home/End
+ // jump, and only the selected tab stays in the tab order.
+ function setUpTabs() {
+ document.querySelectorAll("[data-tabs]").forEach(function (root) {
+ var tabs = Array.prototype.slice.call(root.querySelectorAll('[role="tab"]'));
+ if (!tabs.length) return;
+
+ function select(tab, focus) {
+ tabs.forEach(function (candidate) {
+ var selected = candidate === tab;
+ candidate.setAttribute("aria-selected", String(selected));
+ candidate.tabIndex = selected ? 0 : -1;
+ var panel = document.getElementById(candidate.getAttribute("aria-controls"));
+ if (panel) panel.hidden = !selected;
+ });
+ if (focus) tab.focus();
+ }
+
+ root.addEventListener("click", function (event) {
+ var tab = event.target.closest('[role="tab"]');
+ if (tab) select(tab, false);
+ });
+
+ root.addEventListener("keydown", function (event) {
+ var index = tabs.indexOf(document.activeElement);
+ if (index === -1) return;
+
+ var next = null;
+ if (event.key === "ArrowRight") next = tabs[(index + 1) % tabs.length];
+ else if (event.key === "ArrowLeft") next = tabs[(index - 1 + tabs.length) % tabs.length];
+ else if (event.key === "Home") next = tabs[0];
+ else if (event.key === "End") next = tabs[tabs.length - 1];
+
+ if (next) {
+ event.preventDefault();
+ select(next, true);
+ }
+ });
+
+ select(tabs.filter(function (tab) {
+ return tab.getAttribute("aria-selected") === "true";
+ })[0] || tabs[0], false);
+ });
+ }
+
+ function setUpNavToggle() {
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-nav-toggle]");
+ if (!toggle) return;
+
+ var sidebar = toggle.closest("[data-sidebar]");
+ var collapsed = sidebar.dataset.collapsed !== "false";
+ sidebar.dataset.collapsed = String(!collapsed);
+ toggle.setAttribute("aria-expanded", String(collapsed));
+ });
+ }
+
+ setUpTheme();
+ setUpTabs();
+ setUpNavToggle();
+})();
+
+</script>
+</body>
+</html>
diff --git a/design-bundle/components/button.html b/design-bundle/components/button.html
new file mode 100644
index 0000000..015e413
--- /dev/null
+++ b/design-bundle/components/button.html
@@ -0,0 +1,1913 @@
+<!-- @dsCard group="Core" -->
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Button</title>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&display=swap">
+<style>
+/* Arbitrary Definitions — design tokens. Hand-maintained; edit values directly. */
+
+:root {
+ /* Palette ramps — reference only. Components use the aliases below. */
+ --ad-neutral-0: #faf9f7;
+ --ad-neutral-50: #fdfdfb;
+ --ad-neutral-100: #f1f0ed;
+ --ad-neutral-150: #e8e6e2;
+ --ad-neutral-200: #dcdad7;
+ --ad-neutral-300: #c0bdb9;
+ --ad-neutral-500: #888681;
+ --ad-neutral-600: #5f5d59;
+ --ad-neutral-800: #2a2926;
+ --ad-neutral-900: #100f0d;
+ --ad-neutral-dark-0: #0e0d0b;
+ --ad-neutral-dark-50: #151412;
+ --ad-neutral-dark-100: #1e1d1a;
+ --ad-neutral-dark-150: #252421;
+ --ad-neutral-dark-200: #2c2b29;
+ --ad-neutral-dark-300: #44423f;
+ --ad-neutral-dark-500: #7c7a76;
+ --ad-neutral-dark-600: #a09e9a;
+ --ad-neutral-dark-800: #d9d7d4;
+ --ad-neutral-dark-900: #e9e8e6;
+ --ad-accent-teal: #3a6a63;
+ --ad-accent-clay: #9a5a3c;
+ --ad-accent-ink-blue: #2b4a78;
+ --ad-accent-ochre: #93701f;
+ --ad-accent-plum: #6a3a5c;
+
+ /* Surfaces */
+ --ad-color-surface-page: #faf9f7;
+ --ad-color-surface-card: #fdfdfb;
+ --ad-color-surface-fill: #f1f0ed;
+ --ad-color-surface-fill-2: #e8e6e2;
+
+ /* Borders */
+ --ad-color-border-hairline: #dcdad7;
+ --ad-color-border-default: #c0bdb9;
+
+ /* Text */
+ --ad-color-text-primary: #100f0d;
+ --ad-color-text-secondary: #5f5d59;
+ --ad-color-text-tertiary: #888681;
+
+ /* Accent */
+ --ad-color-accent: #3a6a63;
+ --ad-color-accent-fg: #ffffff;
+ --ad-color-accent-ink: color-mix(in oklch, var(--ad-color-accent) 78%, black);
+ --ad-color-accent-soft: color-mix(in oklch, var(--ad-color-accent) 12%, white);
+
+ /* Status (fixed — never swapped like the accent) */
+ --ad-color-success: #3f7a4a;
+ --ad-color-success-bg: color-mix(in oklch, var(--ad-color-success) 12%, white);
+ --ad-color-danger: #ad3a2c;
+ --ad-color-danger-bg: color-mix(in oklch, var(--ad-color-danger) 10%, white);
+
+ /* Type families */
+ --ad-font-sans: -apple-system, "Helvetica Neue", Helvetica, Arial, system-ui, sans-serif;
+ --ad-font-mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, Consolas, "Courier New", monospace;
+ --ad-font-serif: Georgia, "Times New Roman", "Iowan Old Style", serif;
+
+ /* Type steps — fluid (Utopia scale), the design system's own body-text size */
+ --ad-step--2: clamp(0.7813rem, 0.7736rem + 0.0341vw, 0.8rem);
+ --ad-step--1: clamp(0.9375rem, 0.9119rem + 0.1136vw, 1rem);
+ --ad-step-0: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-step-1: clamp(1.35rem, 1.2631rem + 0.3864vw, 1.5625rem);
+ --ad-step-2: clamp(1.62rem, 1.4837rem + 0.6057vw, 1.9531rem);
+ --ad-step-3: clamp(1.944rem, 1.7405rem + 0.9044vw, 2.4414rem);
+ --ad-step-4: clamp(2.3328rem, 2.0387rem + 1.3072vw, 3.0518rem);
+ --ad-step-5: clamp(2.7994rem, 2.384rem + 1.8461vw, 3.8147rem);
+
+ /* Space */
+ --ad-space-3xs: clamp(0.3125rem, 0.3125rem + 0vw, 0.3125rem);
+ --ad-space-2xs: clamp(0.5625rem, 0.5369rem + 0.1136vw, 0.625rem);
+ --ad-space-xs: clamp(0.875rem, 0.8494rem + 0.1136vw, 0.9375rem);
+ --ad-space-s: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-space-m: clamp(1.6875rem, 1.6108rem + 0.3409vw, 1.875rem);
+ --ad-space-l: clamp(2.25rem, 2.1477rem + 0.4545vw, 2.5rem);
+ --ad-space-xl: clamp(3.375rem, 3.2216rem + 0.6818vw, 3.75rem);
+ --ad-space-2xl: clamp(4.5rem, 4.2955rem + 0.9091vw, 5rem);
+ --ad-space-3xl: clamp(6.75rem, 6.4432rem + 1.3636vw, 7.5rem);
+
+ /* Radius */
+ --ad-radius-sm: 4px;
+ --ad-radius-md: 6px;
+ --ad-radius-lg: 10px;
+ --ad-radius-full: 999px;
+ --ad-border-w: 1px;
+
+ /* Elevation */
+ --ad-shadow-sm: 0 1px 2px rgba(20, 18, 14, 0.05);
+ --ad-shadow-float: 0 2px 6px rgba(20, 18, 14, 0.06), 0 10px 24px -12px rgba(20, 18, 14, 0.18);
+
+ /* Motion */
+ --ad-dur-fast: 0.12s;
+ --ad-dur-base: 0.16s;
+ --ad-ease: cubic-bezier(0.2, 0, 0, 1);
+}
+
+@media (prefers-color-scheme: dark) {
+ :root:not([data-theme="light"]) {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+ }
+}
+
+:root[data-theme="dark"] {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ :root {
+ --ad-dur-fast: 0ms;
+ --ad-dur-base: 0ms;
+ }
+}
+
+/* A short reset. Everything it sets is something the system would otherwise
+ have to override in every component.
+
+ The rem values used for this documentation site's own chrome (measure,
+ content width, sidebar width, header height) belong to this site, not to
+ the design system it documents — the source design system defines no
+ container-width tokens at all, so there is nothing under `--ad-*` to reach
+ for here. See the header comment in site/shell.css. */
+
+*,
+*::before,
+*::after { box-sizing: border-box; }
+
+html {
+ -webkit-text-size-adjust: 100%;
+ scroll-behavior: smooth;
+ scroll-padding-top: calc(3.5rem + var(--ad-space-s));
+}
+
+@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;
+ }
+}
+
+body {
+ margin: 0;
+ background: var(--ad-color-surface-page);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ line-height: 1.55;
+ font-synthesis-weight: none;
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+}
+
+::selection {
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+}
+
+h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd { margin: 0; }
+ul[class], ol[class] { list-style: none; margin: 0; padding: 0; }
+
+img, svg, video { display: block; max-width: 100%; height: auto; }
+
+button, input, select, textarea {
+ font: inherit;
+ color: inherit;
+}
+
+hr {
+ height: 1px;
+ margin: var(--ad-space-l) 0;
+ border: 0;
+ background: var(--ad-color-border-hairline);
+}
+
+/* One focus ring, defined once. No component is allowed its own. */
+:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+ border-radius: var(--ad-radius-sm);
+}
+:focus:not(:focus-visible) { outline: none; }
+
+[hidden] { display: none !important; }
+
+.ad-visually-hidden {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip-path: inset(50%);
+ white-space: nowrap;
+ border: 0;
+}
+
+/* Every rule here reads a type token for family and size. Weight, line-height,
+ and letter-spacing are hardcoded to the exact numbers the source ships —
+ there is no `--ad-weight-*` / `--ad-leading-*` / `--ad-tracking-*` scale in
+ the real system, only eight fixed compound styles (see the Typography
+ foundation page, which this file implements verbatim). */
+
+.type-display { font: 600 var(--ad-step-4)/1.08 var(--ad-font-sans); letter-spacing: -0.015em; }
+.type-h1, h1 { font: 600 var(--ad-step-3)/1.12 var(--ad-font-sans); letter-spacing: -0.012em; }
+.type-h2, h2 { font: 600 var(--ad-step-2)/1.16 var(--ad-font-sans); letter-spacing: -0.01em; }
+.type-h3, h3 { font: 600 var(--ad-step-1)/1.25 var(--ad-font-sans); }
+.type-body { font: 400 var(--ad-step-0)/1.55 var(--ad-font-sans); }
+.type-body-sm { font: 400 var(--ad-step--1)/1.5 var(--ad-font-sans); }
+.type-label {
+ font: 500 var(--ad-step--2)/1 var(--ad-font-mono);
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: var(--ad-color-text-secondary);
+}
+.type-mono { font: 400 var(--ad-step--1)/1.5 var(--ad-font-mono); }
+
+/* Prose defaults for the documentation body copy. These read the same
+ tokens the compound classes above do; they are not a second scale. */
+h1, h2, h3, h4 { font-weight: 600; text-wrap: balance; }
+
+h4 { font: 600 var(--ad-step-0)/1.3 var(--ad-font-sans); }
+
+p, ul, ol { margin: 0 0 var(--ad-space-s); }
+li + li { margin-top: var(--ad-space-3xs); }
+
+a {
+ color: var(--ad-color-accent);
+ text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
+ text-underline-offset: 0.18em;
+ text-decoration-thickness: 1px;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ text-decoration-color var(--ad-dur-fast) var(--ad-ease);
+}
+a:hover {
+ color: var(--ad-color-accent-ink);
+ text-decoration-color: currentColor;
+}
+
+strong, b { font-weight: 600; }
+
+small { font-size: var(--ad-step--1); }
+
+.ad-lead {
+ font-size: var(--ad-step-1);
+ line-height: 1.5;
+ color: var(--ad-color-text-secondary);
+ max-width: 68ch;
+ text-wrap: pretty;
+}
+
+.ad-muted { color: var(--ad-color-text-secondary); }
+.ad-subtle { color: var(--ad-color-text-tertiary); }
+
+code, kbd, samp, pre {
+ font-family: var(--ad-font-mono);
+ font-feature-settings: "liga" 0;
+}
+
+:not(pre) > code {
+ padding: 0.15em 0.4em;
+ font-size: 0.875em;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-sm);
+ white-space: nowrap;
+}
+
+kbd {
+ display: inline-block;
+ padding: 0.1em 0.45em;
+ font-size: var(--ad-step--2);
+ line-height: 1.6;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-bottom-width: 2px;
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-secondary);
+}
+
+blockquote {
+ margin-block: var(--ad-space-s);
+ padding-left: var(--ad-space-s);
+ border-left: 2px solid var(--ad-color-accent);
+ color: var(--ad-color-text-secondary);
+}
+
+/* Button
+ A flat, quiet button that recedes rather than performs — no gradients, no
+ shine. Variants: default, primary, dark, ghost, danger. Sizes: sm, md
+ (default), lg — padding and font step only, weight and radius stay put.
+
+ The contract is the element itself: a bare <button> is styled directly, no
+ class required. An <a> that acts like a button (navigates rather than
+ changes something — see the Button page's "Links that act like buttons")
+ opts in with a single `.btn` class, since a bare `a` selector would catch
+ every ordinary prose link on the site. `:not(.icon-button)` keeps this
+ block from leaking onto IconButton, which shares the <button> tag but is
+ an entirely different shape. */
+
+button:not(.icon-button),
+a.btn {
+ --_bg: var(--ad-color-surface-card);
+ --_fg: var(--ad-color-text-primary);
+ --_border: var(--ad-color-border-default);
+ --_pad: var(--ad-space-xs) var(--ad-space-s);
+ --_font: var(--ad-step--1);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: var(--ad-space-2xs);
+ padding: var(--_pad);
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-sans);
+ font-size: var(--_font);
+ font-weight: 550;
+ letter-spacing: -0.01em;
+ line-height: 1.2;
+ text-align: center;
+ text-decoration: none;
+ white-space: nowrap;
+ cursor: pointer;
+ transition: filter var(--ad-dur-fast) var(--ad-ease),
+ border-color var(--ad-dur-fast) var(--ad-ease);
+}
+
+:is(button:not(.icon-button), a.btn):hover:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.96); }
+:is(button:not(.icon-button), a.btn):active:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.92); }
+
+button:not(.icon-button):disabled,
+:is(button:not(.icon-button), a.btn)[aria-disabled="true"] {
+ opacity: 0.5;
+ cursor: default;
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="primary"] {
+ --_bg: var(--ad-color-accent);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-accent);
+}
+
+/* Reserved for the single most-committing action in a view — a final Save,
+ never a whole row of them. */
+:is(button:not(.icon-button), a.btn)[data-variant="dark"] {
+ --_bg: var(--ad-color-text-primary);
+ --_fg: var(--ad-color-surface-page);
+ --_border: var(--ad-color-text-primary);
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="ghost"] {
+ --_bg: transparent;
+ --_fg: var(--ad-color-text-primary);
+ --_border: transparent;
+}
+
+/* The source hardcodes this label white rather than deriving it — danger
+ is not "on a filled accent surface", so --ad-color-accent-fg is reused
+ here only because its resolved value is the same literal white, not
+ because the two roles are the same thing. */
+:is(button:not(.icon-button), a.btn)[data-variant="danger"] {
+ --_bg: var(--ad-color-danger);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-danger);
+}
+
+:is(button:not(.icon-button), a.btn)[data-size="sm"] { --_pad: 0.375rem 0.75rem; --_font: 0.8125rem; }
+:is(button:not(.icon-button), a.btn)[data-size="lg"] { --_pad: 0.75rem 1.375rem; --_font: var(--ad-step-0); }
+
+:is(button:not(.icon-button), a.btn)[data-block] { width: 100%; }
+
+/* Button group — [role="group"] is already the correct ARIA role for a
+ toolbar of related buttons, so it doubles as the styling hook for free. */
+[role="group"] {
+ display: inline-flex;
+ isolation: isolate;
+}
+[role="group"] > button { border-radius: 0; }
+[role="group"] > button:first-child { border-start-start-radius: var(--ad-radius-md); border-end-start-radius: var(--ad-radius-md); }
+[role="group"] > button:last-child { border-start-end-radius: var(--ad-radius-md); border-end-end-radius: var(--ad-radius-md); }
+[role="group"] > button + button { margin-inline-start: -1px; }
+[role="group"] > button:hover,
+[role="group"] > button:focus-visible { z-index: 1; }
+
+/* IconButton — a square icon-only button. Always needs an aria-label; it
+ becomes both the accessible name and the native tooltip. There is no
+ attribute that distinguishes "a button that is only an icon" from a plain
+ Button, so this is the one sanctioned class in this file. */
+.icon-button {
+ --_size: 2.125rem;
+ --_bg: transparent;
+ --_border: transparent;
+ --_fg: var(--ad-color-text-secondary);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ padding: 0;
+ flex: none;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease),
+ color var(--ad-dur-fast) var(--ad-ease);
+}
+.icon-button:hover:not(:disabled) { background: var(--ad-color-surface-fill-2); }
+.icon-button:disabled { opacity: 0.5; cursor: default; }
+
+.icon-button[data-size="sm"] { --_size: 1.75rem; }
+.icon-button[data-size="lg"] { --_size: 2.5rem; }
+
+.icon-button[data-variant="outline"] { --_bg: var(--ad-color-surface-card); --_border: var(--ad-color-border-default); --_fg: var(--ad-color-text-primary); }
+.icon-button[data-variant="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); }
+
+/* Field
+ The labeled field wrapper — a <label> that wraps its own caption text
+ together with the control, so the association is native and needs no
+ `for`/`id` pair. The caption is mono, uppercase, tracked — the same
+ catalog-label convention as Badge and type-label.
+
+ A <label> is reused for three different shapes across this system (this
+ wrapper, Checkbox/Radio's row, Switch's row), so it cannot be styled bare
+ without one shape leaking into another. Field is the one of the three with
+ no distinguishing attribute of its own, so it gets the sanctioned class;
+ Checkbox/Radio tell themselves apart structurally instead (see below). */
+.field {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ min-width: 0;
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Hint and error read the native fine-print element. `role="alert"` is a
+ real live-region role for validation text, so it doubles as the selector
+ that turns a hint red. */
+.field > small {
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+.field > small[role="alert"] { color: var(--ad-color-danger); }
+
+/* Input / Select — bordered, matched to each other exactly. Both tags ARE
+ the component, so they are styled bare; nothing else on this site uses a
+ text input or a select. */
+input:not([type="checkbox"]):not([type="radio"]),
+select {
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ outline: none;
+ transition: border-color var(--ad-dur-fast) var(--ad-ease),
+ box-shadow var(--ad-dur-fast) var(--ad-ease);
+}
+input::placeholder { color: var(--ad-color-text-tertiary); }
+
+input:not([type="checkbox"]):not([type="radio"]):focus,
+select:focus {
+ border-color: var(--ad-color-accent);
+ box-shadow: 0 0 0 2px var(--ad-color-accent-soft);
+}
+
+input:not([type="checkbox"]):not([type="radio"]):disabled,
+select:disabled {
+ opacity: 0.5;
+ cursor: default;
+}
+
+/* The chevron is the one flagged color exception in this component set:
+ CSS cannot point a data-URI background-image at a custom property, so its
+ stroke is a literal hex rather than a var(--ad-*) reference. It is picked
+ to match --ad-color-text-secondary and commented here so it is not
+ mistaken for a stray color. */
+select {
+ appearance: none;
+ padding-inline-end: var(--ad-space-l);
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%235f5d59' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right var(--ad-space-xs) center;
+}
+
+input[aria-invalid="true"],
+select[aria-invalid="true"] {
+ border-color: var(--ad-color-danger);
+ background-color: var(--ad-color-danger-bg);
+}
+
+/* Checkbox / Radio — the real native controls, tinted with accent-color
+ instead of a hand-drawn box. This is not just a rename: it deletes the
+ custom SVG-checkmark markup entirely and lets the browser draw its own
+ (platform-appropriate) check and dot. `role="switch"` is excluded here so
+ Switch — a checkbox playing a different role — can style itself below
+ without this rule fighting it. A <label> wrapping a checkbox/radio is told
+ apart from Field's <label> structurally: it :has() one as a direct child,
+ no class required. */
+input[type="checkbox"]:not([role="switch"]),
+input[type="radio"] {
+ width: 1.125rem;
+ height: 1.125rem;
+ margin: 0;
+ accent-color: var(--ad-color-accent);
+ cursor: pointer;
+}
+input[type="checkbox"]:not([role="switch"]):focus-visible,
+input[type="radio"]:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+}
+
+label:has(> input[type="checkbox"]:not([role="switch"])),
+label:has(> input[type="radio"]) {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 400;
+ text-transform: none;
+ letter-spacing: normal;
+ color: var(--ad-color-text-primary);
+}
+label:has(> input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Switch — for on/off settings only, never a substitute for Checkbox.
+ `input[type="checkbox"][role="switch"]` is real ARIA (the correct role for
+ a toggle), not an invented hook, but the visual track+thumb needs a second
+ element for the thumb's independent position and transition — more than
+ `appearance: none` plus pseudo-elements on the input itself can cleanly
+ carry — so `.switch` is a deliberate, documented fallback class rather
+ than a first choice. */
+.switch {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ color: var(--ad-color-text-primary);
+}
+.switch input[role="switch"] { position: absolute; opacity: 0; width: 2.25rem; height: 1.25rem; margin: 0; }
+
+.switch span {
+ position: relative;
+ flex: none;
+ width: 2.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-border-default);
+ transition: background-color var(--ad-dur-base) var(--ad-ease);
+}
+.switch span::after {
+ content: "";
+ position: absolute;
+ inset-block-start: 2px;
+ inset-inline-start: 2px;
+ width: 1rem;
+ height: 1rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-fg);
+ box-shadow: var(--ad-shadow-sm);
+ transition: translate var(--ad-dur-base) var(--ad-ease);
+}
+.switch input[role="switch"]:checked + span { background: var(--ad-color-accent); }
+.switch input[role="switch"]:checked + span::after { translate: 1rem 0; }
+.switch input[role="switch"]:focus-visible + span { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+.switch:has(input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Rows of fields — used by the settings-form pattern. `<form>`/`<fieldset>`
+ are the elements that already mean this, so they are styled bare. A row
+ grouping two fields side by side has no element of its own, so it keeps
+ one plain class. */
+form {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-s);
+ max-width: 28rem;
+}
+.form-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
+ gap: var(--ad-space-xs);
+}
+fieldset {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ margin: 0;
+ padding: 0;
+ border: 0;
+}
+fieldset > legend {
+ padding: 0 0 var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Badge
+ Mono, uppercase, tracked — the catalog-label convention for status and
+ meta values (SAVED, OVERDUE, RECOMMENDED). A label, never a button.
+
+ A <span> is too generic to style globally — it is used all over running
+ prose for unrelated reasons — so Badge gets the one sanctioned class this
+ shape needs. Tone is an attribute, not a modifier class. */
+
+.badge {
+ --_bg: var(--ad-color-surface-fill);
+ --_fg: var(--ad-color-text-secondary);
+ --_border: var(--ad-color-border-hairline);
+
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.1875rem 0.5rem;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-sm);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ line-height: 1;
+ white-space: nowrap;
+}
+
+.badge[data-tone="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); --_border: transparent; }
+.badge[data-tone="success"] { --_bg: var(--ad-color-success-bg); --_fg: var(--ad-color-success); --_border: transparent; }
+.badge[data-tone="danger"] { --_bg: var(--ad-color-danger-bg); --_fg: var(--ad-color-danger); --_border: transparent; }
+
+.badge-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-2xs);
+}
+
+/* Tag
+ A removable mono chip for freeform tags — locations, categories.
+ `[data-dashed]` renders an "add tag" affordance instead of a value. Same
+ reasoning as Badge: <span> is too generic to select bare, so Tag gets one
+ class. The remove control is a real <button>, styled through the
+ descendant selector rather than a class of its own. */
+
+.tag {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.375rem;
+ padding: 0.25rem 0.625rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-default);
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ line-height: 1;
+}
+
+.tag[data-dashed] {
+ background: transparent;
+ border-style: dashed;
+ color: var(--ad-color-text-tertiary);
+ cursor: pointer;
+}
+
+.tag button {
+ all: unset;
+ cursor: pointer;
+ color: var(--ad-color-text-tertiary);
+ line-height: 1;
+ font-size: 0.75rem;
+}
+.tag button:hover { color: var(--ad-color-text-primary); }
+
+.tag-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.375rem;
+}
+
+/* Card
+ Flat surface, hairline border, radius-lg. `[data-elevated]` is opt-in and
+ reserved for the one focused/primary card in a view — a dialog, the
+ primary capture card — flat is the default everywhere else.
+
+ A <div>/<article> is too generic to select bare, so Card keeps one
+ sanctioned class. When Card's job is holding a list of rows — the common
+ case, entry lists, task lists, settings rows — it is a real <ul class="
+ card">, and CardRow is a bare <li> underneath it: no second class needed,
+ the row look comes from the `.card > li` descendant selector alone. The
+ first row's top border is removed automatically so callers never have to
+ override it by hand. */
+
+.card {
+ display: flex;
+ flex-direction: column;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: none;
+ overflow: hidden;
+}
+
+.card[data-elevated] { box-shadow: var(--ad-shadow-float); }
+
+.card > li {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+}
+.card > li:first-child { border-top: none; }
+
+/* A row whose ENTIRE content is wrapped in a real <button> or <a> — the
+ accessible way to make a row clickable — fills the row edge-to-edge and
+ picks up a hover fill and pointer cursor. `:only-child` is what tells this
+ apart from a row that merely contains a small button among other content
+ (a leading checkbox next to a title and a trailing value, say): only a
+ button/anchor that IS the row's one and only child means "the whole row
+ is the control." */
+.card > li:has(> button:only-child),
+.card > li:has(> a:only-child) {
+ padding: 0;
+}
+.card > li > button:only-child,
+.card > li > a:only-child {
+ all: unset;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ box-sizing: border-box;
+ color: inherit;
+ font: inherit;
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.card > li > button:only-child:hover,
+.card > li > a:only-child:hover { background: var(--ad-color-surface-fill); }
+
+/* Documentation-only extras: a body/footer pair and a responsive grid, used
+ by the prose pages (rationale.md, index.md) to show a Card as a plain
+ content tile rather than a data row. Neither is part of Card's props —
+ they exist because a content tile's inner layout has no element of its
+ own to hang a selector on. */
+.card-body {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-m);
+}
+.card-footer {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ margin-top: auto;
+ padding: var(--ad-space-xs) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+
+.card[data-link] { transition: border-color var(--ad-dur-fast) var(--ad-ease), box-shadow var(--ad-dur-fast) var(--ad-ease); }
+.card[data-link]:hover { border-color: var(--ad-color-border-default); box-shadow: var(--ad-shadow-sm); }
+.card[data-link]:focus-within { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+
+.card-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
+ gap: var(--ad-space-s);
+}
+
+/* Avatar — lives here because it almost never appears outside a card or a
+ row (the Loop dashboard's initials chip, Archive's file-type chip). A
+ <span> is too generic for a bare selector, so it keeps one class. */
+.avatar {
+ --_size: 1.875rem;
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ flex: none;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 600;
+ overflow: hidden;
+}
+.avatar[data-square] { border-radius: var(--ad-radius-md); background: var(--ad-color-surface-fill); color: var(--ad-color-text-secondary); border: 1px solid var(--ad-color-border-hairline); }
+.avatar[data-size="sm"] { --_size: 1.5rem; }
+.avatar[data-size="lg"] { --_size: 2.25rem; }
+
+/* Tooltip — a dark mono label on hover, positioned above the child. Pure CSS:
+ the trigger and bubble are siblings inside one relatively-positioned span,
+ shown on :hover/:focus-within so it works from the keyboard too.
+
+ `[role="tooltip"]` is the correct ARIA role for the bubble itself, so it
+ doubles as the styling hook — zero classes there. The wrapping span has no
+ attribute of its own, but it does not need one: `:has()` picks out "a span
+ that contains a [role=tooltip] bubble" precisely, without reaching for a
+ class on an otherwise-generic element. */
+span:has(> [role="tooltip"]) {
+ position: relative;
+ display: inline-flex;
+}
+
+[role="tooltip"] {
+ position: absolute;
+ bottom: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ margin-bottom: 0.375rem;
+ padding: 0.25rem 0.5rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-text-primary);
+ color: var(--ad-color-surface-page);
+ font-family: var(--ad-font-mono);
+ /* 11px is the source's own literal choice — smaller than --ad-step--2, the
+ smallest step on the scale, because a tooltip is meant to read as a
+ glance-only aside rather than body text. */
+ font-size: 11px;
+ white-space: nowrap;
+ line-height: 1.4;
+ z-index: 20;
+ opacity: 0;
+ visibility: hidden;
+ pointer-events: none;
+ transition: opacity var(--ad-dur-fast) var(--ad-ease);
+}
+span:has(> [role="tooltip"]):hover > [role="tooltip"],
+span:has(> [role="tooltip"]):focus-within > [role="tooltip"] {
+ opacity: 1;
+ visibility: visible;
+}
+
+/* Toast — the real pattern used for delete confirmations: a flat bar with an
+ optional destructive action and a dismiss. `[data-fixed]` pins it
+ bottom-right for real use; without it the toast stays in document flow for
+ documentation. `role="status"` is the correct live-region role for a
+ transient status message, so it is both the accessibility hook and the
+ style selector — no base class needed. The action and dismiss buttons are
+ plain <button>s told apart by `aria-label="Dismiss"`, an attribute they
+ need anyway for the icon-only close control. */
+[role="status"] {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ min-width: 20rem;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+}
+[role="status"][data-fixed] {
+ position: fixed;
+ inset-block-end: var(--ad-space-s);
+ inset-inline-end: var(--ad-space-s);
+ z-index: 60;
+}
+[role="status"] > span { flex: 1; }
+[role="status"] button {
+ all: unset;
+ cursor: pointer;
+ font-weight: 600;
+ color: var(--ad-color-danger);
+}
+[role="status"] button[aria-label="Dismiss"] {
+ color: var(--ad-color-text-tertiary);
+ font-size: 1rem;
+ font-weight: 400;
+ line-height: 1;
+}
+[role="status"] button[aria-label="Dismiss"]:hover { color: var(--ad-color-text-primary); }
+
+/* Dialog — the native <dialog> element: a centered modal, flat surface, no
+ colored header, hairline dividers between title/body/footer. A <header>
+ holds the title and a <footer> holds the actions, matching the elements
+ the tag already wants; everything else in between is the body, styled by
+ excluding those two rather than by a class of its own.
+
+ `.dialog-demo` scopes a static, always-open example to its own preview
+ container instead of the viewport, so it can sit safely inside a
+ documentation page — this is a doc-only affordance, not part of Dialog's
+ contract. It relies on the browser's own dialog centering: the UA
+ stylesheet already gives an open <dialog> `position: absolute; inset: 0;
+ margin: auto`, which centers it inside the nearest positioned ancestor —
+ here, `.dialog-demo` itself. A real `.showModal()` call gets the genuine
+ viewport-fixed overlay and `::backdrop` for free; a plain `open` attribute
+ (used here so the demo renders without JavaScript) does not paint a
+ backdrop at all, since `::backdrop` only exists for a dialog shown
+ modally — wire the real open/close state and call `.showModal()` in the
+ consuming app. */
+.dialog-demo {
+ position: relative;
+ min-height: 20rem;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-lg);
+}
+
+dialog {
+ width: 26rem;
+ max-width: calc(100% - (2 * var(--ad-space-s)));
+ height: fit-content;
+ padding: 0;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ overflow: hidden;
+}
+dialog::backdrop {
+ background: rgba(20, 18, 14, 0.35);
+}
+dialog > header {
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-sans);
+ font-weight: 600;
+ font-size: var(--ad-step-0);
+ color: var(--ad-color-text-primary);
+}
+dialog > footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+}
+dialog > *:not(header):not(footer) {
+ padding: var(--ad-space-m);
+ margin: 0;
+ color: var(--ad-color-text-primary);
+ font-size: var(--ad-step--1);
+}
+
+/* Tabs
+ Follows the real WAI-ARIA tabs pattern: a `[role="tablist"]` of
+ `[role="tab"]` buttons controlling `[role="tabpanel"]`s. Those roles are
+ required for the pattern to be accessible in the first place, so they
+ double as the entire styling contract — no classes at all for the base
+ shape. The keyboard wiring lives in assets/js/system.js and is
+ variant-agnostic — it only cares about `[data-tabs]` / `[role=tab]`.
+
+ Two real variants: `underline` (mono, accent underline — primary section
+ nav, the default) and `segmented` (pill track, active segment lifts to the
+ surface — filters like All · Overdue · Upcoming, never primary
+ navigation), chosen with `data-variant="segmented"` on the tablist since
+ neither has a natural attribute of its own. */
+
+/* Every rule below the list container pairs [role="tablist"] with
+ [role="tab"] rather than styling [role="tab"] alone — Tab buttons are
+ still literal <button> elements, so the compound selector's extra
+ specificity is what keeps Button's base styling (button.css) from
+ winning the cascade on shared properties like padding and background. */
+
+[role="tablist"] {
+ display: flex;
+ gap: var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+[role="tablist"] [role="tab"] {
+ padding: 0.5rem 0;
+ margin-bottom: -1px;
+ background: none;
+ border: 0;
+ border-bottom: 2px solid transparent;
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--1);
+ letter-spacing: 0.02em;
+ cursor: pointer;
+ transition: color var(--ad-dur-fast) var(--ad-ease), border-color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"] [role="tab"]:hover { color: var(--ad-color-text-primary); }
+[role="tablist"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ border-bottom-color: var(--ad-color-accent);
+}
+
+[role="tablist"][data-variant="segmented"] {
+ display: inline-flex;
+ gap: 0.125rem;
+ padding: 0.1875rem;
+ border-bottom: 0;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-md);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"] {
+ all: unset;
+ cursor: pointer;
+ padding: 0.375rem 0.875rem;
+ border-radius: var(--ad-radius-sm);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 550;
+ color: var(--ad-color-text-secondary);
+ transition: background-color var(--ad-dur-fast) var(--ad-ease), color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ background: var(--ad-color-surface-card);
+ box-shadow: var(--ad-shadow-sm);
+}
+
+[role="tabpanel"] { padding-top: var(--ad-space-s); }
+[role="tabpanel"][hidden] { display: none; }
+[role="tabpanel"]:focus-visible { outline-offset: 4px; }
+
+/* Documentation site chrome. Everything below the `.ad-doc-` prefix belongs
+ to this site rather than to the design system it documents — including the
+ width/height constants used directly below. The source design system
+ defines no container-width tokens at all, so there is nothing under
+ `--ad-*` for a doc shell to reach for; the Space foundation page's own
+ notes say to omit that group entirely rather than invent one. */
+
+.ad-doc-skip {
+ position: absolute;
+ inset-inline-start: var(--ad-space-xs);
+ inset-block-start: -4rem;
+ z-index: 40;
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ box-shadow: var(--ad-shadow-float);
+ transition: inset-block-start var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-skip:focus { inset-block-start: var(--ad-space-2xs); }
+
+.ad-doc-header {
+ position: sticky;
+ inset-block-start: 0;
+ z-index: 30;
+ height: 3.5rem;
+ background: color-mix(in srgb, var(--ad-color-surface-page) 92%, transparent);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-header__inner {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ height: 100%;
+ max-width: 72rem;
+ margin-inline: auto;
+ padding-inline: var(--ad-space-xs);
+}
+
+.ad-doc-wordmark {
+ display: flex;
+ align-items: baseline;
+ gap: var(--ad-space-2xs);
+ color: var(--ad-color-text-primary);
+ text-decoration: none;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ font-weight: 600;
+ letter-spacing: -0.01em;
+ white-space: nowrap;
+}
+.ad-doc-wordmark:hover { color: var(--ad-color-text-primary); }
+.ad-doc-wordmark__mark {
+ width: 1.375rem;
+ height: 1.375rem;
+ flex: none;
+ align-self: center;
+ color: var(--ad-color-accent);
+}
+.ad-doc-wordmark__version {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+
+.ad-doc-header__actions { margin-inline-start: auto; display: flex; align-items: center; gap: var(--ad-space-2xs); }
+
+.ad-doc-layout {
+ display: grid;
+ grid-template-columns: 16rem minmax(0, 1fr);
+ gap: var(--ad-space-l);
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-m) var(--ad-space-xs) var(--ad-space-2xl);
+}
+
+.ad-doc-sidebar {
+ position: sticky;
+ inset-block-start: calc(3.5rem + var(--ad-space-m));
+ align-self: start;
+ max-height: calc(100vh - 3.5rem - var(--ad-space-l));
+ overflow-y: auto;
+ padding-inline-end: var(--ad-space-2xs);
+}
+.ad-doc-nav__group + .ad-doc-nav__group { margin-top: var(--ad-space-s); }
+.ad-doc-nav__heading {
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-doc-nav__link {
+ display: block;
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ margin-inline-start: calc(var(--ad-space-xs) * -1);
+ border-inline-start: 2px solid transparent;
+ border-radius: 0 var(--ad-radius-sm) var(--ad-radius-sm) 0;
+ color: var(--ad-color-text-secondary);
+ font-size: var(--ad-step--1);
+ text-decoration: none;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-nav__link:hover { color: var(--ad-color-text-primary); background: var(--ad-color-surface-fill); }
+.ad-doc-nav__link[aria-current="page"] {
+ color: var(--ad-color-accent-ink);
+ border-inline-start-color: var(--ad-color-accent);
+ background: var(--ad-color-accent-soft);
+ font-weight: 500;
+}
+
+.ad-doc-main {
+ min-width: 0;
+ /* The design system's own body-text token (--ad-step-0, 18–20px) is right
+ for a product screen but too large for long-form documentation prose.
+ This site's own copy gets a plain, fixed 16px instead — the token above
+ is unchanged, and specimen tables/demos below still show its real
+ value. */
+ font-size: 1rem;
+}
+.ad-doc-main > * { max-width: 50rem; }
+
+.ad-doc-title { margin-bottom: var(--ad-space-xs); }
+.ad-doc-title__section {
+ display: block;
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-accent);
+}
+
+.ad-doc-main h2 {
+ margin-block: var(--ad-space-2xl) var(--ad-space-xs);
+ padding-bottom: var(--ad-space-2xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-main h3 { margin-block: var(--ad-space-l) var(--ad-space-2xs); }
+.ad-doc-main h4 { margin-block: var(--ad-space-s) var(--ad-space-2xs); }
+.ad-doc-main p,
+.ad-doc-main ul,
+.ad-doc-main ol { max-width: 68ch; }
+.ad-doc-main ul:not([class]),
+.ad-doc-main ol:not([class]) { padding-inline-start: var(--ad-space-s); }
+
+.ad-doc-footer {
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-s) var(--ad-space-xs) var(--ad-space-l);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-tertiary);
+ font-size: var(--ad-step--1);
+}
+.ad-doc-footer__inner { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); justify-content: space-between; }
+
+/* `button.ad-doc-nav-toggle` (element + class) rather than a bare class:
+ button.css's own base rule is `button:not(.icon-button)`, which is also
+ one element + one class in specificity and sets `display: inline-flex` —
+ a bare `.ad-doc-nav-toggle { display: none }` loses that fight and the
+ toggle stays visible at every width. Matching its specificity here (and
+ coming later in the concatenated stylesheet) settles it in source order. */
+button.ad-doc-nav-toggle { display: none; }
+
+@media (max-width: 60rem) {
+ .ad-doc-layout { grid-template-columns: minmax(0, 1fr); gap: var(--ad-space-s); }
+ .ad-doc-sidebar {
+ position: static;
+ max-height: none;
+ }
+ /* The toggle is a normal Button; the panel it opens gets its own card
+ treatment. Putting the same card styling on the <aside> wrapper too
+ nested a button-shaped box inside an outer box for no reason. */
+ .ad-doc-nav {
+ margin-top: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ }
+ .ad-doc-sidebar[data-collapsed="true"] > .ad-doc-nav { display: none; }
+ button.ad-doc-nav-toggle {
+ display: flex;
+ width: 100%;
+ justify-content: space-between;
+ }
+}
+
+@media (max-width: 34rem) {
+ .ad-doc-wordmark__version { display: none; }
+ .ad-doc-layout { padding-inline: var(--ad-space-2xs); }
+}
+
+/* The live-example figure used throughout the docs — see the "How the
+ example demos work" section on the Using the CSS page. One class on the
+ outer <figure>; everything inside is styled by plain element selectors,
+ since figcaption/div/details/summary/pre already say what they are.
+ `data-layout` and `data-surface` on the preview <div> distinguish the
+ handful of demos that need a non-default arrangement. */
+
+.example {
+ margin: var(--ad-space-s) 0 var(--ad-space-l);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+ background: var(--ad-color-surface-card);
+ overflow: hidden;
+}
+
+.example figcaption {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-secondary);
+}
+
+.example > div {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-s);
+}
+/* nowrap is load-bearing: a column-direction flex container with wrap enabled
+ sizes its single line against the container rather than the content, which
+ left every stacked example with a few hundred pixels of dead space. */
+.example > div[data-layout="stack"] { flex-direction: column; flex-wrap: nowrap; align-items: stretch; }
+.example > div[data-layout="grid"] {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
+ align-items: start;
+}
+.example > div[data-surface="sunken"] { background: var(--ad-color-surface-fill); }
+.example > div > * { min-width: 0; }
+
+.example details {
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+.example summary {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ color: var(--ad-color-text-secondary);
+ cursor: pointer;
+ list-style: none;
+ user-select: none;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+}
+.example summary::-webkit-details-marker { display: none; }
+.example summary::before {
+ content: "";
+ width: 0.5rem;
+ height: 0.5rem;
+ border-inline-end: 1.5px solid currentColor;
+ border-block-end: 1.5px solid currentColor;
+ rotate: -45deg;
+ transition: rotate var(--ad-dur-fast) var(--ad-ease);
+}
+.example details[open] > summary::before { rotate: 45deg; }
+.example summary:hover { color: var(--ad-color-text-primary); }
+
+.example pre {
+ margin: 0;
+ padding: var(--ad-space-xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+ tab-size: 2;
+}
+.example pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Standalone fenced code blocks in the prose (unhighlighted — there is no
+ syntax highlighter in this build any more, just Kramdown's plain
+ <pre><code class="language-*">). */
+.ad-doc-main > pre {
+ margin-block: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+}
+.ad-doc-main > pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Presentation for the token reference tables and swatch grids, plus a
+ generic data-table utility. All of this is documentation chrome — the
+ design system itself has no Table component (see the note on the removed
+ `table`/`progress`/`callout` pages in git history). */
+
+.ad-swatch-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs) var(--ad-space-s);
+ padding: 0;
+ list-style: none;
+}
+.ad-swatch {
+ display: flex;
+ flex-direction: column;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ overflow: hidden;
+ background: var(--ad-color-surface-card);
+}
+.ad-swatch__chip {
+ display: flex;
+ align-items: flex-end;
+ justify-content: flex-end;
+ height: 4rem;
+ padding: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+}
+.ad-swatch__meta {
+ display: flex;
+ flex-direction: column;
+ gap: 0.1rem;
+ padding: var(--ad-space-2xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+}
+.ad-swatch__hex { color: var(--ad-color-text-primary); text-transform: uppercase; }
+.ad-swatch__ratio { color: var(--ad-color-text-tertiary); }
+
+.ad-alias-row { display: flex; align-items: center; gap: var(--ad-space-2xs); }
+.ad-alias-chip {
+ width: 1.5rem;
+ height: 1.5rem;
+ flex: none;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+}
+.ad-alias-chip--pair { display: flex; }
+.ad-alias-chip--pair > span { flex: 1; }
+
+/* Type specimen rows. */
+.ad-specimen {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ gap: var(--ad-space-3xs);
+ padding-block: var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-specimen__meta {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-specimen__sample {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: var(--ad-color-text-primary);
+}
+
+/* Space and radius rulers. */
+.ad-ruler { display: flex; flex-direction: column; gap: var(--ad-space-2xs); }
+.ad-ruler__row { display: grid; grid-template-columns: 6.5rem 4.5rem 1fr; align-items: center; gap: var(--ad-space-xs); font-family: var(--ad-font-mono); font-size: 11px; }
+.ad-ruler__bar { height: 0.75rem; background: var(--ad-color-accent); border-radius: var(--ad-radius-sm); }
+.ad-ruler__num { color: var(--ad-color-text-tertiary); text-align: right; }
+
+.ad-radius-grid, .ad-elevation-grid, .ad-motion-grid, .ad-accent-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs);
+ padding: 0;
+ list-style: none;
+}
+.ad-radius-demo {
+ height: 4.5rem;
+ background: var(--ad-color-accent-soft);
+ border: 1px solid var(--ad-color-accent);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-elevation-demo {
+ height: 4.5rem;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-motion-demo {
+ height: 4.5rem;
+ display: grid;
+ place-items: center;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-md);
+ margin-bottom: var(--ad-space-2xs);
+ cursor: pointer;
+ overflow: hidden;
+}
+.ad-motion-demo__dot {
+ width: 1.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent);
+ translate: -3rem 0;
+ transition-property: translate;
+ transition-duration: var(--ad-dur-base);
+ transition-timing-function: var(--ad-ease);
+}
+.ad-motion-demo:hover .ad-motion-demo__dot,
+.ad-motion-demo:focus-visible .ad-motion-demo__dot { translate: 3rem 0; }
+
+.ad-accent-demo {
+ height: 4.5rem;
+ display: flex;
+ align-items: flex-end;
+ padding: var(--ad-space-2xs);
+ border-radius: var(--ad-radius-md);
+ color: #fff;
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ margin-bottom: var(--ad-space-2xs);
+}
+
+/* Token names read `<code>` sitewide, which already carries a mono chip
+ look for running prose (see :not(pre) > code in base/typography.css).
+ Inside these specimen/swatch/ruler rows the old `.ad-token-name` class was
+ plain mono text with no chip, so `<code>` is flattened back to that look
+ here the same way table cells are. */
+.ad-swatch code, .ad-specimen code, .ad-ruler code, .ad-alias-row code {
+ background: none;
+ border: 0;
+ padding: 0;
+ font-size: var(--ad-step--2);
+ color: var(--ad-color-text-primary);
+ white-space: nowrap;
+}
+
+/* A documentation aside. Like `<table>`, this is site chrome for the prose
+ itself, not a system component — the real system has no Callout. */
+.ad-callout {
+ margin-block: var(--ad-space-s);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-left: 3px solid var(--ad-color-accent);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+}
+/* Generic reference tables (component APIs, alias tables, the contrast
+ contract). `<table>` IS a table — no class needed to say so. Every table
+ on this site uses the one compact density, so there is no separate
+ modifier for it. `display: block` plus `overflow-x: auto` gives the table
+ its own horizontal scrollbar on narrow viewports without a wrapper div;
+ the anonymous table box the browser generates around `<thead>`/`<tbody>`
+ still does the row/column layout. `<code>` replaces the old
+ `.ad-token-name` / `.ad-table__code` cells — it is already styled
+ sitewide, just re-flattened here to a plain mono run instead of the
+ chip-with-border look prose code gets, to match this table's original
+ look. `.num` is the one sanctioned class left, for right-aligned numeric
+ columns — there is no attribute that means "this column is a number". */
+table { width: 100%; border-collapse: collapse; font-size: var(--ad-step--1); display: block; overflow-x: auto; }
+table th, table td {
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ text-align: left;
+ vertical-align: top;
+}
+table th { font-family: var(--ad-font-mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ad-color-text-secondary); font-weight: 500; }
+table code { background: none; border: 0; padding: 0; font-size: var(--ad-step--2); white-space: nowrap; }
+.num { text-align: right; font-variant-numeric: tabular-nums; }
+
+/* Home page. */
+.ad-hero { padding-block: var(--ad-space-m) var(--ad-space-l); }
+.ad-hero__title {
+ font-size: var(--ad-step-4);
+ margin-bottom: var(--ad-space-xs);
+}
+.ad-hero__actions { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); margin-top: var(--ad-space-s); }
+.ad-stat-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-l);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+}
+.ad-stat__value {
+ display: block;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-2);
+ font-weight: 600;
+ line-height: 1.16;
+ font-variant-numeric: tabular-nums;
+}
+.ad-stat__label {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+
+/* Card chrome. The pane supplies the frame; this just insets the page. */
+body { padding: var(--ad-space-l) var(--ad-space-m); }
+.ad-doc-main { max-width: 50rem; margin-inline: auto; }
+.ad-doc-main > * { max-width: none; }
+</style>
+</head>
+<body>
+<main class="ad-doc-main">
+<header class="ad-doc-title"><span class="ad-doc-title__section">Components</span>
+ <h1>Button</h1>
+</header>
+<p class="ad-lead">A flat, quiet button that recedes rather than performs — no gradients, no shine. Five variants, three sizes, one geometry.</p>
+<h2 id="variants">Variants</h2>
+
+<p><code>default</code> is bordered and neutral. <code>primary</code> is the accent fill — one
+committing action per view, at most. <code>dark</code> is reserved for the single most
+committing action in a screen, typically a final Save; it should be rare.
+<code>ghost</code> has no border or fill, for dismissals and tertiary actions. <code>danger</code>
+is destructive and irreversible only.</p>
+
+<figure class="example">
+<figcaption>Variants</figcaption>
+<div>
+<button>Cancel</button>
+<button data-variant="primary">Save</button>
+<button data-variant="dark">Finish setup</button>
+<button data-variant="ghost">Dismiss</button>
+<button data-variant="danger">Delete</button>
+</div>
+<details open=""><summary>Markup</summary>
+<pre><code><button>Cancel</button>
+<button data-variant="primary">Save</button>
+<button data-variant="dark">Finish setup</button>
+<button data-variant="ghost">Dismiss</button>
+<button data-variant="danger">Delete</button></code></pre>
+</details>
+</figure>
+
+<h2 id="sizes">Sizes</h2>
+
+<p>Sizes adjust padding only — the font stays on the type scale, it does not
+grow a step of its own.</p>
+
+<figure class="example">
+<figcaption>Sizes</figcaption>
+<div>
+<button data-variant="primary" data-size="sm">Small</button>
+<button data-variant="primary">Medium</button>
+<button data-variant="primary" data-size="lg">Large</button>
+</div>
+<details><summary>Markup</summary>
+<pre><code><button data-variant="primary" data-size="sm">Small</button>
+<button data-variant="primary">Medium</button>
+<button data-variant="primary" data-size="lg">Large</button></code></pre>
+</details>
+</figure>
+
+<h2 id="states">States</h2>
+
+<p>Disabled buttons drop to 50% opacity and keep their variant colors, so a
+disabled primary still reads as the primary.</p>
+
+<figure class="example">
+<figcaption>States</figcaption>
+<div>
+<button data-variant="primary" disabled="">Disabled</button>
+<button aria-disabled="true">Not yet available</button>
+</div>
+<details><summary>Markup</summary>
+<pre><code><button data-variant="primary" disabled="">Disabled</button>
+<button aria-disabled="true">Not yet available</button></code></pre>
+</details>
+</figure>
+
+<div class="ad-callout">
+ <p class="type-label" style="margin-bottom: var(--ad-space-2xs); display:block;">disabled vs. aria-disabled</p>
+ <p style="margin:0; font-size: var(--ad-step--1);">
+ A <code>disabled</code> button is removed from the tab order, so a reader
+ navigating by keyboard never learns it is there. When a button is
+ temporarily unavailable and the reason matters, use
+ <code>aria-disabled="true"</code> instead — it stays focusable and can
+ carry an explanation.
+ </p>
+</div>
+
+<h2 id="with-an-icon">With an icon</h2>
+
+<p>Pass an icon as the first child. The gap is <code>--ad-space-2xs</code>, set by the
+button itself.</p>
+
+<figure class="example">
+<figcaption>Icon</figcaption>
+<div>
+<button data-variant="primary">
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 5v14M5 12h14" /></svg>
+ New
+</button>
+</div>
+<details><summary>Markup</summary>
+<pre><code><button data-variant="primary">
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 5v14M5 12h14" /></svg>
+ New
+</button></code></pre>
+</details>
+</figure>
+
+<h2 id="groups">Groups</h2>
+
+<p><code>[role="group"]</code> is already the correct ARIA role for a toolbar of related
+buttons, so it doubles as the styling hook — no wrapper class.</p>
+
+<figure class="example">
+<figcaption>Button group</figcaption>
+<div>
+<div role="group" aria-label="View">
+ <button data-size="sm">List</button>
+ <button data-size="sm">Board</button>
+ <button data-size="sm">Calendar</button>
+</div>
+</div>
+<details><summary>Markup</summary>
+<pre><code><div role="group" aria-label="View">
+ <button data-size="sm">List</button>
+ <button data-size="sm">Board</button>
+ <button data-size="sm">Calendar</button>
+</div></code></pre>
+</details>
+</figure>
+
+<h2 id="full-width">Full width</h2>
+
+<figure class="example">
+<figcaption>Block</figcaption>
+<div data-layout="stack">
+<button data-variant="primary" data-block="">Take a photo</button>
+</div>
+<details><summary>Markup</summary>
+<pre><code><button data-variant="primary" data-block>Take a photo</button></code></pre>
+</details>
+</figure>
+
+<h2 id="links-that-act-like-buttons">Links that act like buttons</h2>
+
+<p>An <code><a></code> with <code>.btn</code> is fine when the thing it does is navigate. A <code><button></code>
+is required when it changes something. <code>.btn</code> exists only because a bare <code>a</code>
+selector would catch every ordinary prose link on the site — it is the one
+class Button needs, and only anchors reach for it.</p>
+
+<figure class="example">
+<figcaption>Anchor as button</figcaption>
+<div>
+<a class="btn" data-variant="primary" href="https://kejadlen.github.io/arbitrary-definitions-design-system/components/icon-button/">Next: IconButton</a>
+<a class="btn" data-variant="ghost" href="https://kejadlen.github.io/arbitrary-definitions-design-system/foundations/color/">Back to color</a>
+</div>
+<details><summary>Markup</summary>
+<pre><code><a class="btn" data-variant="primary" href="/arbitrary-definitions-design-system/components/icon-button/">Next: IconButton</a>
+<a class="btn" data-variant="ghost" href="/arbitrary-definitions-design-system/foundations/color/">Back to color</a></code></pre>
+</details>
+</figure>
+
+<h2 id="api">API</h2>
+
+<table>
+ <thead><tr><th scope="col">Element / attribute</th><th scope="col">Values</th><th scope="col">Notes</th></tr></thead>
+ <tbody>
+ <tr><td><code>button</code></td><td></td><td>Required base. A bare <code><button></code> is styled directly — no class. Use <code><a class="btn"></code> instead when the control navigates.</td></tr>
+ <tr><td><code>data-variant</code></td><td><code>primary | dark | ghost | danger</code></td><td>Omit for the default, bordered look.</td></tr>
+ <tr><td><code>data-size</code></td><td><code>sm | lg</code></td><td>Omit for the default (medium) size. Padding only.</td></tr>
+ <tr><td><code>data-block</code></td><td>boolean</td><td>Full width of its container.</td></tr>
+ <tr><td><code>disabled</code></td><td>boolean</td><td>Removes the button from the tab order.</td></tr>
+ <tr><td><code>aria-disabled</code></td><td><code>"true"</code></td><td>Looks disabled, stays focusable.</td></tr>
+ <tr><td><code>[role="group"]</code></td><td></td><td>Wrap adjacent buttons in a group to join their borders and radii.</td></tr>
+ </tbody>
+</table>
+</main>
+<script>
+// Behavior for the pieces of the system that need it. Everything else is
+// CSS and semantic HTML on purpose — a design system that only works with
+// JavaScript running is a component library with extra steps.
+(function () {
+ "use strict";
+
+ var STORAGE_KEY = "ad-theme";
+
+ function readStored() {
+ try {
+ return localStorage.getItem(STORAGE_KEY);
+ } catch (error) {
+ return null;
+ }
+ }
+
+ function writeStored(value) {
+ try {
+ localStorage.setItem(STORAGE_KEY, value);
+ } catch (error) {
+ /* Nothing to do — the media query still gives a sensible default. */
+ }
+ }
+
+ function systemPrefersDark() {
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
+ }
+
+ function currentTheme() {
+ var explicit = document.documentElement.getAttribute("data-theme");
+ if (explicit) return explicit;
+ return systemPrefersDark() ? "dark" : "light";
+ }
+
+ function paintToggle(theme) {
+ document.querySelectorAll("[data-theme-toggle]").forEach(function (toggle) {
+ var next = theme === "dark" ? "light" : "dark";
+ toggle.querySelectorAll("[data-theme-icon]").forEach(function (icon) {
+ icon.hidden = icon.dataset.themeIcon !== theme;
+ });
+ toggle.setAttribute("aria-label", "Switch to the " + next + " theme");
+ toggle.setAttribute("title", "Switch to the " + next + " theme");
+ var label = toggle.querySelector("[data-theme-label]");
+ if (label) label.textContent = "Switch to the " + next + " theme";
+ });
+ }
+
+ function setUpTheme() {
+ paintToggle(currentTheme());
+
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-theme-toggle]");
+ if (!toggle) return;
+
+ var next = currentTheme() === "dark" ? "light" : "dark";
+ document.documentElement.setAttribute("data-theme", next);
+ writeStored(next);
+ paintToggle(next);
+ });
+
+ // Follow the OS while the reader has not made an explicit choice.
+ window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", function () {
+ if (!readStored()) paintToggle(currentTheme());
+ });
+ }
+
+ // Tabs, wired to the WAI-ARIA authoring practices: arrows move, Home/End
+ // jump, and only the selected tab stays in the tab order.
+ function setUpTabs() {
+ document.querySelectorAll("[data-tabs]").forEach(function (root) {
+ var tabs = Array.prototype.slice.call(root.querySelectorAll('[role="tab"]'));
+ if (!tabs.length) return;
+
+ function select(tab, focus) {
+ tabs.forEach(function (candidate) {
+ var selected = candidate === tab;
+ candidate.setAttribute("aria-selected", String(selected));
+ candidate.tabIndex = selected ? 0 : -1;
+ var panel = document.getElementById(candidate.getAttribute("aria-controls"));
+ if (panel) panel.hidden = !selected;
+ });
+ if (focus) tab.focus();
+ }
+
+ root.addEventListener("click", function (event) {
+ var tab = event.target.closest('[role="tab"]');
+ if (tab) select(tab, false);
+ });
+
+ root.addEventListener("keydown", function (event) {
+ var index = tabs.indexOf(document.activeElement);
+ if (index === -1) return;
+
+ var next = null;
+ if (event.key === "ArrowRight") next = tabs[(index + 1) % tabs.length];
+ else if (event.key === "ArrowLeft") next = tabs[(index - 1 + tabs.length) % tabs.length];
+ else if (event.key === "Home") next = tabs[0];
+ else if (event.key === "End") next = tabs[tabs.length - 1];
+
+ if (next) {
+ event.preventDefault();
+ select(next, true);
+ }
+ });
+
+ select(tabs.filter(function (tab) {
+ return tab.getAttribute("aria-selected") === "true";
+ })[0] || tabs[0], false);
+ });
+ }
+
+ function setUpNavToggle() {
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-nav-toggle]");
+ if (!toggle) return;
+
+ var sidebar = toggle.closest("[data-sidebar]");
+ var collapsed = sidebar.dataset.collapsed !== "false";
+ sidebar.dataset.collapsed = String(!collapsed);
+ toggle.setAttribute("aria-expanded", String(collapsed));
+ });
+ }
+
+ setUpTheme();
+ setUpTabs();
+ setUpNavToggle();
+})();
+
+</script>
+</body>
+</html>
diff --git a/design-bundle/components/card-row.html b/design-bundle/components/card-row.html
new file mode 100644
index 0000000..b7b2b77
--- /dev/null
+++ b/design-bundle/components/card-row.html
@@ -0,0 +1,1845 @@
+<!-- @dsCard group="Core" -->
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>CardRow</title>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&display=swap">
+<style>
+/* Arbitrary Definitions — design tokens. Hand-maintained; edit values directly. */
+
+:root {
+ /* Palette ramps — reference only. Components use the aliases below. */
+ --ad-neutral-0: #faf9f7;
+ --ad-neutral-50: #fdfdfb;
+ --ad-neutral-100: #f1f0ed;
+ --ad-neutral-150: #e8e6e2;
+ --ad-neutral-200: #dcdad7;
+ --ad-neutral-300: #c0bdb9;
+ --ad-neutral-500: #888681;
+ --ad-neutral-600: #5f5d59;
+ --ad-neutral-800: #2a2926;
+ --ad-neutral-900: #100f0d;
+ --ad-neutral-dark-0: #0e0d0b;
+ --ad-neutral-dark-50: #151412;
+ --ad-neutral-dark-100: #1e1d1a;
+ --ad-neutral-dark-150: #252421;
+ --ad-neutral-dark-200: #2c2b29;
+ --ad-neutral-dark-300: #44423f;
+ --ad-neutral-dark-500: #7c7a76;
+ --ad-neutral-dark-600: #a09e9a;
+ --ad-neutral-dark-800: #d9d7d4;
+ --ad-neutral-dark-900: #e9e8e6;
+ --ad-accent-teal: #3a6a63;
+ --ad-accent-clay: #9a5a3c;
+ --ad-accent-ink-blue: #2b4a78;
+ --ad-accent-ochre: #93701f;
+ --ad-accent-plum: #6a3a5c;
+
+ /* Surfaces */
+ --ad-color-surface-page: #faf9f7;
+ --ad-color-surface-card: #fdfdfb;
+ --ad-color-surface-fill: #f1f0ed;
+ --ad-color-surface-fill-2: #e8e6e2;
+
+ /* Borders */
+ --ad-color-border-hairline: #dcdad7;
+ --ad-color-border-default: #c0bdb9;
+
+ /* Text */
+ --ad-color-text-primary: #100f0d;
+ --ad-color-text-secondary: #5f5d59;
+ --ad-color-text-tertiary: #888681;
+
+ /* Accent */
+ --ad-color-accent: #3a6a63;
+ --ad-color-accent-fg: #ffffff;
+ --ad-color-accent-ink: color-mix(in oklch, var(--ad-color-accent) 78%, black);
+ --ad-color-accent-soft: color-mix(in oklch, var(--ad-color-accent) 12%, white);
+
+ /* Status (fixed — never swapped like the accent) */
+ --ad-color-success: #3f7a4a;
+ --ad-color-success-bg: color-mix(in oklch, var(--ad-color-success) 12%, white);
+ --ad-color-danger: #ad3a2c;
+ --ad-color-danger-bg: color-mix(in oklch, var(--ad-color-danger) 10%, white);
+
+ /* Type families */
+ --ad-font-sans: -apple-system, "Helvetica Neue", Helvetica, Arial, system-ui, sans-serif;
+ --ad-font-mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, Consolas, "Courier New", monospace;
+ --ad-font-serif: Georgia, "Times New Roman", "Iowan Old Style", serif;
+
+ /* Type steps — fluid (Utopia scale), the design system's own body-text size */
+ --ad-step--2: clamp(0.7813rem, 0.7736rem + 0.0341vw, 0.8rem);
+ --ad-step--1: clamp(0.9375rem, 0.9119rem + 0.1136vw, 1rem);
+ --ad-step-0: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-step-1: clamp(1.35rem, 1.2631rem + 0.3864vw, 1.5625rem);
+ --ad-step-2: clamp(1.62rem, 1.4837rem + 0.6057vw, 1.9531rem);
+ --ad-step-3: clamp(1.944rem, 1.7405rem + 0.9044vw, 2.4414rem);
+ --ad-step-4: clamp(2.3328rem, 2.0387rem + 1.3072vw, 3.0518rem);
+ --ad-step-5: clamp(2.7994rem, 2.384rem + 1.8461vw, 3.8147rem);
+
+ /* Space */
+ --ad-space-3xs: clamp(0.3125rem, 0.3125rem + 0vw, 0.3125rem);
+ --ad-space-2xs: clamp(0.5625rem, 0.5369rem + 0.1136vw, 0.625rem);
+ --ad-space-xs: clamp(0.875rem, 0.8494rem + 0.1136vw, 0.9375rem);
+ --ad-space-s: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-space-m: clamp(1.6875rem, 1.6108rem + 0.3409vw, 1.875rem);
+ --ad-space-l: clamp(2.25rem, 2.1477rem + 0.4545vw, 2.5rem);
+ --ad-space-xl: clamp(3.375rem, 3.2216rem + 0.6818vw, 3.75rem);
+ --ad-space-2xl: clamp(4.5rem, 4.2955rem + 0.9091vw, 5rem);
+ --ad-space-3xl: clamp(6.75rem, 6.4432rem + 1.3636vw, 7.5rem);
+
+ /* Radius */
+ --ad-radius-sm: 4px;
+ --ad-radius-md: 6px;
+ --ad-radius-lg: 10px;
+ --ad-radius-full: 999px;
+ --ad-border-w: 1px;
+
+ /* Elevation */
+ --ad-shadow-sm: 0 1px 2px rgba(20, 18, 14, 0.05);
+ --ad-shadow-float: 0 2px 6px rgba(20, 18, 14, 0.06), 0 10px 24px -12px rgba(20, 18, 14, 0.18);
+
+ /* Motion */
+ --ad-dur-fast: 0.12s;
+ --ad-dur-base: 0.16s;
+ --ad-ease: cubic-bezier(0.2, 0, 0, 1);
+}
+
+@media (prefers-color-scheme: dark) {
+ :root:not([data-theme="light"]) {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+ }
+}
+
+:root[data-theme="dark"] {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ :root {
+ --ad-dur-fast: 0ms;
+ --ad-dur-base: 0ms;
+ }
+}
+
+/* A short reset. Everything it sets is something the system would otherwise
+ have to override in every component.
+
+ The rem values used for this documentation site's own chrome (measure,
+ content width, sidebar width, header height) belong to this site, not to
+ the design system it documents — the source design system defines no
+ container-width tokens at all, so there is nothing under `--ad-*` to reach
+ for here. See the header comment in site/shell.css. */
+
+*,
+*::before,
+*::after { box-sizing: border-box; }
+
+html {
+ -webkit-text-size-adjust: 100%;
+ scroll-behavior: smooth;
+ scroll-padding-top: calc(3.5rem + var(--ad-space-s));
+}
+
+@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;
+ }
+}
+
+body {
+ margin: 0;
+ background: var(--ad-color-surface-page);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ line-height: 1.55;
+ font-synthesis-weight: none;
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+}
+
+::selection {
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+}
+
+h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd { margin: 0; }
+ul[class], ol[class] { list-style: none; margin: 0; padding: 0; }
+
+img, svg, video { display: block; max-width: 100%; height: auto; }
+
+button, input, select, textarea {
+ font: inherit;
+ color: inherit;
+}
+
+hr {
+ height: 1px;
+ margin: var(--ad-space-l) 0;
+ border: 0;
+ background: var(--ad-color-border-hairline);
+}
+
+/* One focus ring, defined once. No component is allowed its own. */
+:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+ border-radius: var(--ad-radius-sm);
+}
+:focus:not(:focus-visible) { outline: none; }
+
+[hidden] { display: none !important; }
+
+.ad-visually-hidden {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip-path: inset(50%);
+ white-space: nowrap;
+ border: 0;
+}
+
+/* Every rule here reads a type token for family and size. Weight, line-height,
+ and letter-spacing are hardcoded to the exact numbers the source ships —
+ there is no `--ad-weight-*` / `--ad-leading-*` / `--ad-tracking-*` scale in
+ the real system, only eight fixed compound styles (see the Typography
+ foundation page, which this file implements verbatim). */
+
+.type-display { font: 600 var(--ad-step-4)/1.08 var(--ad-font-sans); letter-spacing: -0.015em; }
+.type-h1, h1 { font: 600 var(--ad-step-3)/1.12 var(--ad-font-sans); letter-spacing: -0.012em; }
+.type-h2, h2 { font: 600 var(--ad-step-2)/1.16 var(--ad-font-sans); letter-spacing: -0.01em; }
+.type-h3, h3 { font: 600 var(--ad-step-1)/1.25 var(--ad-font-sans); }
+.type-body { font: 400 var(--ad-step-0)/1.55 var(--ad-font-sans); }
+.type-body-sm { font: 400 var(--ad-step--1)/1.5 var(--ad-font-sans); }
+.type-label {
+ font: 500 var(--ad-step--2)/1 var(--ad-font-mono);
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: var(--ad-color-text-secondary);
+}
+.type-mono { font: 400 var(--ad-step--1)/1.5 var(--ad-font-mono); }
+
+/* Prose defaults for the documentation body copy. These read the same
+ tokens the compound classes above do; they are not a second scale. */
+h1, h2, h3, h4 { font-weight: 600; text-wrap: balance; }
+
+h4 { font: 600 var(--ad-step-0)/1.3 var(--ad-font-sans); }
+
+p, ul, ol { margin: 0 0 var(--ad-space-s); }
+li + li { margin-top: var(--ad-space-3xs); }
+
+a {
+ color: var(--ad-color-accent);
+ text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
+ text-underline-offset: 0.18em;
+ text-decoration-thickness: 1px;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ text-decoration-color var(--ad-dur-fast) var(--ad-ease);
+}
+a:hover {
+ color: var(--ad-color-accent-ink);
+ text-decoration-color: currentColor;
+}
+
+strong, b { font-weight: 600; }
+
+small { font-size: var(--ad-step--1); }
+
+.ad-lead {
+ font-size: var(--ad-step-1);
+ line-height: 1.5;
+ color: var(--ad-color-text-secondary);
+ max-width: 68ch;
+ text-wrap: pretty;
+}
+
+.ad-muted { color: var(--ad-color-text-secondary); }
+.ad-subtle { color: var(--ad-color-text-tertiary); }
+
+code, kbd, samp, pre {
+ font-family: var(--ad-font-mono);
+ font-feature-settings: "liga" 0;
+}
+
+:not(pre) > code {
+ padding: 0.15em 0.4em;
+ font-size: 0.875em;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-sm);
+ white-space: nowrap;
+}
+
+kbd {
+ display: inline-block;
+ padding: 0.1em 0.45em;
+ font-size: var(--ad-step--2);
+ line-height: 1.6;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-bottom-width: 2px;
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-secondary);
+}
+
+blockquote {
+ margin-block: var(--ad-space-s);
+ padding-left: var(--ad-space-s);
+ border-left: 2px solid var(--ad-color-accent);
+ color: var(--ad-color-text-secondary);
+}
+
+/* Button
+ A flat, quiet button that recedes rather than performs — no gradients, no
+ shine. Variants: default, primary, dark, ghost, danger. Sizes: sm, md
+ (default), lg — padding and font step only, weight and radius stay put.
+
+ The contract is the element itself: a bare <button> is styled directly, no
+ class required. An <a> that acts like a button (navigates rather than
+ changes something — see the Button page's "Links that act like buttons")
+ opts in with a single `.btn` class, since a bare `a` selector would catch
+ every ordinary prose link on the site. `:not(.icon-button)` keeps this
+ block from leaking onto IconButton, which shares the <button> tag but is
+ an entirely different shape. */
+
+button:not(.icon-button),
+a.btn {
+ --_bg: var(--ad-color-surface-card);
+ --_fg: var(--ad-color-text-primary);
+ --_border: var(--ad-color-border-default);
+ --_pad: var(--ad-space-xs) var(--ad-space-s);
+ --_font: var(--ad-step--1);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: var(--ad-space-2xs);
+ padding: var(--_pad);
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-sans);
+ font-size: var(--_font);
+ font-weight: 550;
+ letter-spacing: -0.01em;
+ line-height: 1.2;
+ text-align: center;
+ text-decoration: none;
+ white-space: nowrap;
+ cursor: pointer;
+ transition: filter var(--ad-dur-fast) var(--ad-ease),
+ border-color var(--ad-dur-fast) var(--ad-ease);
+}
+
+:is(button:not(.icon-button), a.btn):hover:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.96); }
+:is(button:not(.icon-button), a.btn):active:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.92); }
+
+button:not(.icon-button):disabled,
+:is(button:not(.icon-button), a.btn)[aria-disabled="true"] {
+ opacity: 0.5;
+ cursor: default;
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="primary"] {
+ --_bg: var(--ad-color-accent);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-accent);
+}
+
+/* Reserved for the single most-committing action in a view — a final Save,
+ never a whole row of them. */
+:is(button:not(.icon-button), a.btn)[data-variant="dark"] {
+ --_bg: var(--ad-color-text-primary);
+ --_fg: var(--ad-color-surface-page);
+ --_border: var(--ad-color-text-primary);
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="ghost"] {
+ --_bg: transparent;
+ --_fg: var(--ad-color-text-primary);
+ --_border: transparent;
+}
+
+/* The source hardcodes this label white rather than deriving it — danger
+ is not "on a filled accent surface", so --ad-color-accent-fg is reused
+ here only because its resolved value is the same literal white, not
+ because the two roles are the same thing. */
+:is(button:not(.icon-button), a.btn)[data-variant="danger"] {
+ --_bg: var(--ad-color-danger);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-danger);
+}
+
+:is(button:not(.icon-button), a.btn)[data-size="sm"] { --_pad: 0.375rem 0.75rem; --_font: 0.8125rem; }
+:is(button:not(.icon-button), a.btn)[data-size="lg"] { --_pad: 0.75rem 1.375rem; --_font: var(--ad-step-0); }
+
+:is(button:not(.icon-button), a.btn)[data-block] { width: 100%; }
+
+/* Button group — [role="group"] is already the correct ARIA role for a
+ toolbar of related buttons, so it doubles as the styling hook for free. */
+[role="group"] {
+ display: inline-flex;
+ isolation: isolate;
+}
+[role="group"] > button { border-radius: 0; }
+[role="group"] > button:first-child { border-start-start-radius: var(--ad-radius-md); border-end-start-radius: var(--ad-radius-md); }
+[role="group"] > button:last-child { border-start-end-radius: var(--ad-radius-md); border-end-end-radius: var(--ad-radius-md); }
+[role="group"] > button + button { margin-inline-start: -1px; }
+[role="group"] > button:hover,
+[role="group"] > button:focus-visible { z-index: 1; }
+
+/* IconButton — a square icon-only button. Always needs an aria-label; it
+ becomes both the accessible name and the native tooltip. There is no
+ attribute that distinguishes "a button that is only an icon" from a plain
+ Button, so this is the one sanctioned class in this file. */
+.icon-button {
+ --_size: 2.125rem;
+ --_bg: transparent;
+ --_border: transparent;
+ --_fg: var(--ad-color-text-secondary);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ padding: 0;
+ flex: none;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease),
+ color var(--ad-dur-fast) var(--ad-ease);
+}
+.icon-button:hover:not(:disabled) { background: var(--ad-color-surface-fill-2); }
+.icon-button:disabled { opacity: 0.5; cursor: default; }
+
+.icon-button[data-size="sm"] { --_size: 1.75rem; }
+.icon-button[data-size="lg"] { --_size: 2.5rem; }
+
+.icon-button[data-variant="outline"] { --_bg: var(--ad-color-surface-card); --_border: var(--ad-color-border-default); --_fg: var(--ad-color-text-primary); }
+.icon-button[data-variant="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); }
+
+/* Field
+ The labeled field wrapper — a <label> that wraps its own caption text
+ together with the control, so the association is native and needs no
+ `for`/`id` pair. The caption is mono, uppercase, tracked — the same
+ catalog-label convention as Badge and type-label.
+
+ A <label> is reused for three different shapes across this system (this
+ wrapper, Checkbox/Radio's row, Switch's row), so it cannot be styled bare
+ without one shape leaking into another. Field is the one of the three with
+ no distinguishing attribute of its own, so it gets the sanctioned class;
+ Checkbox/Radio tell themselves apart structurally instead (see below). */
+.field {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ min-width: 0;
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Hint and error read the native fine-print element. `role="alert"` is a
+ real live-region role for validation text, so it doubles as the selector
+ that turns a hint red. */
+.field > small {
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+.field > small[role="alert"] { color: var(--ad-color-danger); }
+
+/* Input / Select — bordered, matched to each other exactly. Both tags ARE
+ the component, so they are styled bare; nothing else on this site uses a
+ text input or a select. */
+input:not([type="checkbox"]):not([type="radio"]),
+select {
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ outline: none;
+ transition: border-color var(--ad-dur-fast) var(--ad-ease),
+ box-shadow var(--ad-dur-fast) var(--ad-ease);
+}
+input::placeholder { color: var(--ad-color-text-tertiary); }
+
+input:not([type="checkbox"]):not([type="radio"]):focus,
+select:focus {
+ border-color: var(--ad-color-accent);
+ box-shadow: 0 0 0 2px var(--ad-color-accent-soft);
+}
+
+input:not([type="checkbox"]):not([type="radio"]):disabled,
+select:disabled {
+ opacity: 0.5;
+ cursor: default;
+}
+
+/* The chevron is the one flagged color exception in this component set:
+ CSS cannot point a data-URI background-image at a custom property, so its
+ stroke is a literal hex rather than a var(--ad-*) reference. It is picked
+ to match --ad-color-text-secondary and commented here so it is not
+ mistaken for a stray color. */
+select {
+ appearance: none;
+ padding-inline-end: var(--ad-space-l);
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%235f5d59' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right var(--ad-space-xs) center;
+}
+
+input[aria-invalid="true"],
+select[aria-invalid="true"] {
+ border-color: var(--ad-color-danger);
+ background-color: var(--ad-color-danger-bg);
+}
+
+/* Checkbox / Radio — the real native controls, tinted with accent-color
+ instead of a hand-drawn box. This is not just a rename: it deletes the
+ custom SVG-checkmark markup entirely and lets the browser draw its own
+ (platform-appropriate) check and dot. `role="switch"` is excluded here so
+ Switch — a checkbox playing a different role — can style itself below
+ without this rule fighting it. A <label> wrapping a checkbox/radio is told
+ apart from Field's <label> structurally: it :has() one as a direct child,
+ no class required. */
+input[type="checkbox"]:not([role="switch"]),
+input[type="radio"] {
+ width: 1.125rem;
+ height: 1.125rem;
+ margin: 0;
+ accent-color: var(--ad-color-accent);
+ cursor: pointer;
+}
+input[type="checkbox"]:not([role="switch"]):focus-visible,
+input[type="radio"]:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+}
+
+label:has(> input[type="checkbox"]:not([role="switch"])),
+label:has(> input[type="radio"]) {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 400;
+ text-transform: none;
+ letter-spacing: normal;
+ color: var(--ad-color-text-primary);
+}
+label:has(> input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Switch — for on/off settings only, never a substitute for Checkbox.
+ `input[type="checkbox"][role="switch"]` is real ARIA (the correct role for
+ a toggle), not an invented hook, but the visual track+thumb needs a second
+ element for the thumb's independent position and transition — more than
+ `appearance: none` plus pseudo-elements on the input itself can cleanly
+ carry — so `.switch` is a deliberate, documented fallback class rather
+ than a first choice. */
+.switch {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ color: var(--ad-color-text-primary);
+}
+.switch input[role="switch"] { position: absolute; opacity: 0; width: 2.25rem; height: 1.25rem; margin: 0; }
+
+.switch span {
+ position: relative;
+ flex: none;
+ width: 2.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-border-default);
+ transition: background-color var(--ad-dur-base) var(--ad-ease);
+}
+.switch span::after {
+ content: "";
+ position: absolute;
+ inset-block-start: 2px;
+ inset-inline-start: 2px;
+ width: 1rem;
+ height: 1rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-fg);
+ box-shadow: var(--ad-shadow-sm);
+ transition: translate var(--ad-dur-base) var(--ad-ease);
+}
+.switch input[role="switch"]:checked + span { background: var(--ad-color-accent); }
+.switch input[role="switch"]:checked + span::after { translate: 1rem 0; }
+.switch input[role="switch"]:focus-visible + span { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+.switch:has(input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Rows of fields — used by the settings-form pattern. `<form>`/`<fieldset>`
+ are the elements that already mean this, so they are styled bare. A row
+ grouping two fields side by side has no element of its own, so it keeps
+ one plain class. */
+form {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-s);
+ max-width: 28rem;
+}
+.form-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
+ gap: var(--ad-space-xs);
+}
+fieldset {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ margin: 0;
+ padding: 0;
+ border: 0;
+}
+fieldset > legend {
+ padding: 0 0 var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Badge
+ Mono, uppercase, tracked — the catalog-label convention for status and
+ meta values (SAVED, OVERDUE, RECOMMENDED). A label, never a button.
+
+ A <span> is too generic to style globally — it is used all over running
+ prose for unrelated reasons — so Badge gets the one sanctioned class this
+ shape needs. Tone is an attribute, not a modifier class. */
+
+.badge {
+ --_bg: var(--ad-color-surface-fill);
+ --_fg: var(--ad-color-text-secondary);
+ --_border: var(--ad-color-border-hairline);
+
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.1875rem 0.5rem;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-sm);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ line-height: 1;
+ white-space: nowrap;
+}
+
+.badge[data-tone="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); --_border: transparent; }
+.badge[data-tone="success"] { --_bg: var(--ad-color-success-bg); --_fg: var(--ad-color-success); --_border: transparent; }
+.badge[data-tone="danger"] { --_bg: var(--ad-color-danger-bg); --_fg: var(--ad-color-danger); --_border: transparent; }
+
+.badge-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-2xs);
+}
+
+/* Tag
+ A removable mono chip for freeform tags — locations, categories.
+ `[data-dashed]` renders an "add tag" affordance instead of a value. Same
+ reasoning as Badge: <span> is too generic to select bare, so Tag gets one
+ class. The remove control is a real <button>, styled through the
+ descendant selector rather than a class of its own. */
+
+.tag {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.375rem;
+ padding: 0.25rem 0.625rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-default);
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ line-height: 1;
+}
+
+.tag[data-dashed] {
+ background: transparent;
+ border-style: dashed;
+ color: var(--ad-color-text-tertiary);
+ cursor: pointer;
+}
+
+.tag button {
+ all: unset;
+ cursor: pointer;
+ color: var(--ad-color-text-tertiary);
+ line-height: 1;
+ font-size: 0.75rem;
+}
+.tag button:hover { color: var(--ad-color-text-primary); }
+
+.tag-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.375rem;
+}
+
+/* Card
+ Flat surface, hairline border, radius-lg. `[data-elevated]` is opt-in and
+ reserved for the one focused/primary card in a view — a dialog, the
+ primary capture card — flat is the default everywhere else.
+
+ A <div>/<article> is too generic to select bare, so Card keeps one
+ sanctioned class. When Card's job is holding a list of rows — the common
+ case, entry lists, task lists, settings rows — it is a real <ul class="
+ card">, and CardRow is a bare <li> underneath it: no second class needed,
+ the row look comes from the `.card > li` descendant selector alone. The
+ first row's top border is removed automatically so callers never have to
+ override it by hand. */
+
+.card {
+ display: flex;
+ flex-direction: column;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: none;
+ overflow: hidden;
+}
+
+.card[data-elevated] { box-shadow: var(--ad-shadow-float); }
+
+.card > li {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+}
+.card > li:first-child { border-top: none; }
+
+/* A row whose ENTIRE content is wrapped in a real <button> or <a> — the
+ accessible way to make a row clickable — fills the row edge-to-edge and
+ picks up a hover fill and pointer cursor. `:only-child` is what tells this
+ apart from a row that merely contains a small button among other content
+ (a leading checkbox next to a title and a trailing value, say): only a
+ button/anchor that IS the row's one and only child means "the whole row
+ is the control." */
+.card > li:has(> button:only-child),
+.card > li:has(> a:only-child) {
+ padding: 0;
+}
+.card > li > button:only-child,
+.card > li > a:only-child {
+ all: unset;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ box-sizing: border-box;
+ color: inherit;
+ font: inherit;
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.card > li > button:only-child:hover,
+.card > li > a:only-child:hover { background: var(--ad-color-surface-fill); }
+
+/* Documentation-only extras: a body/footer pair and a responsive grid, used
+ by the prose pages (rationale.md, index.md) to show a Card as a plain
+ content tile rather than a data row. Neither is part of Card's props —
+ they exist because a content tile's inner layout has no element of its
+ own to hang a selector on. */
+.card-body {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-m);
+}
+.card-footer {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ margin-top: auto;
+ padding: var(--ad-space-xs) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+
+.card[data-link] { transition: border-color var(--ad-dur-fast) var(--ad-ease), box-shadow var(--ad-dur-fast) var(--ad-ease); }
+.card[data-link]:hover { border-color: var(--ad-color-border-default); box-shadow: var(--ad-shadow-sm); }
+.card[data-link]:focus-within { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+
+.card-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
+ gap: var(--ad-space-s);
+}
+
+/* Avatar — lives here because it almost never appears outside a card or a
+ row (the Loop dashboard's initials chip, Archive's file-type chip). A
+ <span> is too generic for a bare selector, so it keeps one class. */
+.avatar {
+ --_size: 1.875rem;
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ flex: none;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 600;
+ overflow: hidden;
+}
+.avatar[data-square] { border-radius: var(--ad-radius-md); background: var(--ad-color-surface-fill); color: var(--ad-color-text-secondary); border: 1px solid var(--ad-color-border-hairline); }
+.avatar[data-size="sm"] { --_size: 1.5rem; }
+.avatar[data-size="lg"] { --_size: 2.25rem; }
+
+/* Tooltip — a dark mono label on hover, positioned above the child. Pure CSS:
+ the trigger and bubble are siblings inside one relatively-positioned span,
+ shown on :hover/:focus-within so it works from the keyboard too.
+
+ `[role="tooltip"]` is the correct ARIA role for the bubble itself, so it
+ doubles as the styling hook — zero classes there. The wrapping span has no
+ attribute of its own, but it does not need one: `:has()` picks out "a span
+ that contains a [role=tooltip] bubble" precisely, without reaching for a
+ class on an otherwise-generic element. */
+span:has(> [role="tooltip"]) {
+ position: relative;
+ display: inline-flex;
+}
+
+[role="tooltip"] {
+ position: absolute;
+ bottom: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ margin-bottom: 0.375rem;
+ padding: 0.25rem 0.5rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-text-primary);
+ color: var(--ad-color-surface-page);
+ font-family: var(--ad-font-mono);
+ /* 11px is the source's own literal choice — smaller than --ad-step--2, the
+ smallest step on the scale, because a tooltip is meant to read as a
+ glance-only aside rather than body text. */
+ font-size: 11px;
+ white-space: nowrap;
+ line-height: 1.4;
+ z-index: 20;
+ opacity: 0;
+ visibility: hidden;
+ pointer-events: none;
+ transition: opacity var(--ad-dur-fast) var(--ad-ease);
+}
+span:has(> [role="tooltip"]):hover > [role="tooltip"],
+span:has(> [role="tooltip"]):focus-within > [role="tooltip"] {
+ opacity: 1;
+ visibility: visible;
+}
+
+/* Toast — the real pattern used for delete confirmations: a flat bar with an
+ optional destructive action and a dismiss. `[data-fixed]` pins it
+ bottom-right for real use; without it the toast stays in document flow for
+ documentation. `role="status"` is the correct live-region role for a
+ transient status message, so it is both the accessibility hook and the
+ style selector — no base class needed. The action and dismiss buttons are
+ plain <button>s told apart by `aria-label="Dismiss"`, an attribute they
+ need anyway for the icon-only close control. */
+[role="status"] {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ min-width: 20rem;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+}
+[role="status"][data-fixed] {
+ position: fixed;
+ inset-block-end: var(--ad-space-s);
+ inset-inline-end: var(--ad-space-s);
+ z-index: 60;
+}
+[role="status"] > span { flex: 1; }
+[role="status"] button {
+ all: unset;
+ cursor: pointer;
+ font-weight: 600;
+ color: var(--ad-color-danger);
+}
+[role="status"] button[aria-label="Dismiss"] {
+ color: var(--ad-color-text-tertiary);
+ font-size: 1rem;
+ font-weight: 400;
+ line-height: 1;
+}
+[role="status"] button[aria-label="Dismiss"]:hover { color: var(--ad-color-text-primary); }
+
+/* Dialog — the native <dialog> element: a centered modal, flat surface, no
+ colored header, hairline dividers between title/body/footer. A <header>
+ holds the title and a <footer> holds the actions, matching the elements
+ the tag already wants; everything else in between is the body, styled by
+ excluding those two rather than by a class of its own.
+
+ `.dialog-demo` scopes a static, always-open example to its own preview
+ container instead of the viewport, so it can sit safely inside a
+ documentation page — this is a doc-only affordance, not part of Dialog's
+ contract. It relies on the browser's own dialog centering: the UA
+ stylesheet already gives an open <dialog> `position: absolute; inset: 0;
+ margin: auto`, which centers it inside the nearest positioned ancestor —
+ here, `.dialog-demo` itself. A real `.showModal()` call gets the genuine
+ viewport-fixed overlay and `::backdrop` for free; a plain `open` attribute
+ (used here so the demo renders without JavaScript) does not paint a
+ backdrop at all, since `::backdrop` only exists for a dialog shown
+ modally — wire the real open/close state and call `.showModal()` in the
+ consuming app. */
+.dialog-demo {
+ position: relative;
+ min-height: 20rem;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-lg);
+}
+
+dialog {
+ width: 26rem;
+ max-width: calc(100% - (2 * var(--ad-space-s)));
+ height: fit-content;
+ padding: 0;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ overflow: hidden;
+}
+dialog::backdrop {
+ background: rgba(20, 18, 14, 0.35);
+}
+dialog > header {
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-sans);
+ font-weight: 600;
+ font-size: var(--ad-step-0);
+ color: var(--ad-color-text-primary);
+}
+dialog > footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+}
+dialog > *:not(header):not(footer) {
+ padding: var(--ad-space-m);
+ margin: 0;
+ color: var(--ad-color-text-primary);
+ font-size: var(--ad-step--1);
+}
+
+/* Tabs
+ Follows the real WAI-ARIA tabs pattern: a `[role="tablist"]` of
+ `[role="tab"]` buttons controlling `[role="tabpanel"]`s. Those roles are
+ required for the pattern to be accessible in the first place, so they
+ double as the entire styling contract — no classes at all for the base
+ shape. The keyboard wiring lives in assets/js/system.js and is
+ variant-agnostic — it only cares about `[data-tabs]` / `[role=tab]`.
+
+ Two real variants: `underline` (mono, accent underline — primary section
+ nav, the default) and `segmented` (pill track, active segment lifts to the
+ surface — filters like All · Overdue · Upcoming, never primary
+ navigation), chosen with `data-variant="segmented"` on the tablist since
+ neither has a natural attribute of its own. */
+
+/* Every rule below the list container pairs [role="tablist"] with
+ [role="tab"] rather than styling [role="tab"] alone — Tab buttons are
+ still literal <button> elements, so the compound selector's extra
+ specificity is what keeps Button's base styling (button.css) from
+ winning the cascade on shared properties like padding and background. */
+
+[role="tablist"] {
+ display: flex;
+ gap: var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+[role="tablist"] [role="tab"] {
+ padding: 0.5rem 0;
+ margin-bottom: -1px;
+ background: none;
+ border: 0;
+ border-bottom: 2px solid transparent;
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--1);
+ letter-spacing: 0.02em;
+ cursor: pointer;
+ transition: color var(--ad-dur-fast) var(--ad-ease), border-color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"] [role="tab"]:hover { color: var(--ad-color-text-primary); }
+[role="tablist"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ border-bottom-color: var(--ad-color-accent);
+}
+
+[role="tablist"][data-variant="segmented"] {
+ display: inline-flex;
+ gap: 0.125rem;
+ padding: 0.1875rem;
+ border-bottom: 0;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-md);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"] {
+ all: unset;
+ cursor: pointer;
+ padding: 0.375rem 0.875rem;
+ border-radius: var(--ad-radius-sm);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 550;
+ color: var(--ad-color-text-secondary);
+ transition: background-color var(--ad-dur-fast) var(--ad-ease), color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ background: var(--ad-color-surface-card);
+ box-shadow: var(--ad-shadow-sm);
+}
+
+[role="tabpanel"] { padding-top: var(--ad-space-s); }
+[role="tabpanel"][hidden] { display: none; }
+[role="tabpanel"]:focus-visible { outline-offset: 4px; }
+
+/* Documentation site chrome. Everything below the `.ad-doc-` prefix belongs
+ to this site rather than to the design system it documents — including the
+ width/height constants used directly below. The source design system
+ defines no container-width tokens at all, so there is nothing under
+ `--ad-*` for a doc shell to reach for; the Space foundation page's own
+ notes say to omit that group entirely rather than invent one. */
+
+.ad-doc-skip {
+ position: absolute;
+ inset-inline-start: var(--ad-space-xs);
+ inset-block-start: -4rem;
+ z-index: 40;
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ box-shadow: var(--ad-shadow-float);
+ transition: inset-block-start var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-skip:focus { inset-block-start: var(--ad-space-2xs); }
+
+.ad-doc-header {
+ position: sticky;
+ inset-block-start: 0;
+ z-index: 30;
+ height: 3.5rem;
+ background: color-mix(in srgb, var(--ad-color-surface-page) 92%, transparent);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-header__inner {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ height: 100%;
+ max-width: 72rem;
+ margin-inline: auto;
+ padding-inline: var(--ad-space-xs);
+}
+
+.ad-doc-wordmark {
+ display: flex;
+ align-items: baseline;
+ gap: var(--ad-space-2xs);
+ color: var(--ad-color-text-primary);
+ text-decoration: none;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ font-weight: 600;
+ letter-spacing: -0.01em;
+ white-space: nowrap;
+}
+.ad-doc-wordmark:hover { color: var(--ad-color-text-primary); }
+.ad-doc-wordmark__mark {
+ width: 1.375rem;
+ height: 1.375rem;
+ flex: none;
+ align-self: center;
+ color: var(--ad-color-accent);
+}
+.ad-doc-wordmark__version {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+
+.ad-doc-header__actions { margin-inline-start: auto; display: flex; align-items: center; gap: var(--ad-space-2xs); }
+
+.ad-doc-layout {
+ display: grid;
+ grid-template-columns: 16rem minmax(0, 1fr);
+ gap: var(--ad-space-l);
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-m) var(--ad-space-xs) var(--ad-space-2xl);
+}
+
+.ad-doc-sidebar {
+ position: sticky;
+ inset-block-start: calc(3.5rem + var(--ad-space-m));
+ align-self: start;
+ max-height: calc(100vh - 3.5rem - var(--ad-space-l));
+ overflow-y: auto;
+ padding-inline-end: var(--ad-space-2xs);
+}
+.ad-doc-nav__group + .ad-doc-nav__group { margin-top: var(--ad-space-s); }
+.ad-doc-nav__heading {
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-doc-nav__link {
+ display: block;
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ margin-inline-start: calc(var(--ad-space-xs) * -1);
+ border-inline-start: 2px solid transparent;
+ border-radius: 0 var(--ad-radius-sm) var(--ad-radius-sm) 0;
+ color: var(--ad-color-text-secondary);
+ font-size: var(--ad-step--1);
+ text-decoration: none;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-nav__link:hover { color: var(--ad-color-text-primary); background: var(--ad-color-surface-fill); }
+.ad-doc-nav__link[aria-current="page"] {
+ color: var(--ad-color-accent-ink);
+ border-inline-start-color: var(--ad-color-accent);
+ background: var(--ad-color-accent-soft);
+ font-weight: 500;
+}
+
+.ad-doc-main {
+ min-width: 0;
+ /* The design system's own body-text token (--ad-step-0, 18–20px) is right
+ for a product screen but too large for long-form documentation prose.
+ This site's own copy gets a plain, fixed 16px instead — the token above
+ is unchanged, and specimen tables/demos below still show its real
+ value. */
+ font-size: 1rem;
+}
+.ad-doc-main > * { max-width: 50rem; }
+
+.ad-doc-title { margin-bottom: var(--ad-space-xs); }
+.ad-doc-title__section {
+ display: block;
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-accent);
+}
+
+.ad-doc-main h2 {
+ margin-block: var(--ad-space-2xl) var(--ad-space-xs);
+ padding-bottom: var(--ad-space-2xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-main h3 { margin-block: var(--ad-space-l) var(--ad-space-2xs); }
+.ad-doc-main h4 { margin-block: var(--ad-space-s) var(--ad-space-2xs); }
+.ad-doc-main p,
+.ad-doc-main ul,
+.ad-doc-main ol { max-width: 68ch; }
+.ad-doc-main ul:not([class]),
+.ad-doc-main ol:not([class]) { padding-inline-start: var(--ad-space-s); }
+
+.ad-doc-footer {
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-s) var(--ad-space-xs) var(--ad-space-l);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-tertiary);
+ font-size: var(--ad-step--1);
+}
+.ad-doc-footer__inner { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); justify-content: space-between; }
+
+/* `button.ad-doc-nav-toggle` (element + class) rather than a bare class:
+ button.css's own base rule is `button:not(.icon-button)`, which is also
+ one element + one class in specificity and sets `display: inline-flex` —
+ a bare `.ad-doc-nav-toggle { display: none }` loses that fight and the
+ toggle stays visible at every width. Matching its specificity here (and
+ coming later in the concatenated stylesheet) settles it in source order. */
+button.ad-doc-nav-toggle { display: none; }
+
+@media (max-width: 60rem) {
+ .ad-doc-layout { grid-template-columns: minmax(0, 1fr); gap: var(--ad-space-s); }
+ .ad-doc-sidebar {
+ position: static;
+ max-height: none;
+ }
+ /* The toggle is a normal Button; the panel it opens gets its own card
+ treatment. Putting the same card styling on the <aside> wrapper too
+ nested a button-shaped box inside an outer box for no reason. */
+ .ad-doc-nav {
+ margin-top: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ }
+ .ad-doc-sidebar[data-collapsed="true"] > .ad-doc-nav { display: none; }
+ button.ad-doc-nav-toggle {
+ display: flex;
+ width: 100%;
+ justify-content: space-between;
+ }
+}
+
+@media (max-width: 34rem) {
+ .ad-doc-wordmark__version { display: none; }
+ .ad-doc-layout { padding-inline: var(--ad-space-2xs); }
+}
+
+/* The live-example figure used throughout the docs — see the "How the
+ example demos work" section on the Using the CSS page. One class on the
+ outer <figure>; everything inside is styled by plain element selectors,
+ since figcaption/div/details/summary/pre already say what they are.
+ `data-layout` and `data-surface` on the preview <div> distinguish the
+ handful of demos that need a non-default arrangement. */
+
+.example {
+ margin: var(--ad-space-s) 0 var(--ad-space-l);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+ background: var(--ad-color-surface-card);
+ overflow: hidden;
+}
+
+.example figcaption {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-secondary);
+}
+
+.example > div {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-s);
+}
+/* nowrap is load-bearing: a column-direction flex container with wrap enabled
+ sizes its single line against the container rather than the content, which
+ left every stacked example with a few hundred pixels of dead space. */
+.example > div[data-layout="stack"] { flex-direction: column; flex-wrap: nowrap; align-items: stretch; }
+.example > div[data-layout="grid"] {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
+ align-items: start;
+}
+.example > div[data-surface="sunken"] { background: var(--ad-color-surface-fill); }
+.example > div > * { min-width: 0; }
+
+.example details {
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+.example summary {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ color: var(--ad-color-text-secondary);
+ cursor: pointer;
+ list-style: none;
+ user-select: none;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+}
+.example summary::-webkit-details-marker { display: none; }
+.example summary::before {
+ content: "";
+ width: 0.5rem;
+ height: 0.5rem;
+ border-inline-end: 1.5px solid currentColor;
+ border-block-end: 1.5px solid currentColor;
+ rotate: -45deg;
+ transition: rotate var(--ad-dur-fast) var(--ad-ease);
+}
+.example details[open] > summary::before { rotate: 45deg; }
+.example summary:hover { color: var(--ad-color-text-primary); }
+
+.example pre {
+ margin: 0;
+ padding: var(--ad-space-xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+ tab-size: 2;
+}
+.example pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Standalone fenced code blocks in the prose (unhighlighted — there is no
+ syntax highlighter in this build any more, just Kramdown's plain
+ <pre><code class="language-*">). */
+.ad-doc-main > pre {
+ margin-block: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+}
+.ad-doc-main > pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Presentation for the token reference tables and swatch grids, plus a
+ generic data-table utility. All of this is documentation chrome — the
+ design system itself has no Table component (see the note on the removed
+ `table`/`progress`/`callout` pages in git history). */
+
+.ad-swatch-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs) var(--ad-space-s);
+ padding: 0;
+ list-style: none;
+}
+.ad-swatch {
+ display: flex;
+ flex-direction: column;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ overflow: hidden;
+ background: var(--ad-color-surface-card);
+}
+.ad-swatch__chip {
+ display: flex;
+ align-items: flex-end;
+ justify-content: flex-end;
+ height: 4rem;
+ padding: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+}
+.ad-swatch__meta {
+ display: flex;
+ flex-direction: column;
+ gap: 0.1rem;
+ padding: var(--ad-space-2xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+}
+.ad-swatch__hex { color: var(--ad-color-text-primary); text-transform: uppercase; }
+.ad-swatch__ratio { color: var(--ad-color-text-tertiary); }
+
+.ad-alias-row { display: flex; align-items: center; gap: var(--ad-space-2xs); }
+.ad-alias-chip {
+ width: 1.5rem;
+ height: 1.5rem;
+ flex: none;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+}
+.ad-alias-chip--pair { display: flex; }
+.ad-alias-chip--pair > span { flex: 1; }
+
+/* Type specimen rows. */
+.ad-specimen {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ gap: var(--ad-space-3xs);
+ padding-block: var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-specimen__meta {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-specimen__sample {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: var(--ad-color-text-primary);
+}
+
+/* Space and radius rulers. */
+.ad-ruler { display: flex; flex-direction: column; gap: var(--ad-space-2xs); }
+.ad-ruler__row { display: grid; grid-template-columns: 6.5rem 4.5rem 1fr; align-items: center; gap: var(--ad-space-xs); font-family: var(--ad-font-mono); font-size: 11px; }
+.ad-ruler__bar { height: 0.75rem; background: var(--ad-color-accent); border-radius: var(--ad-radius-sm); }
+.ad-ruler__num { color: var(--ad-color-text-tertiary); text-align: right; }
+
+.ad-radius-grid, .ad-elevation-grid, .ad-motion-grid, .ad-accent-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs);
+ padding: 0;
+ list-style: none;
+}
+.ad-radius-demo {
+ height: 4.5rem;
+ background: var(--ad-color-accent-soft);
+ border: 1px solid var(--ad-color-accent);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-elevation-demo {
+ height: 4.5rem;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-motion-demo {
+ height: 4.5rem;
+ display: grid;
+ place-items: center;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-md);
+ margin-bottom: var(--ad-space-2xs);
+ cursor: pointer;
+ overflow: hidden;
+}
+.ad-motion-demo__dot {
+ width: 1.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent);
+ translate: -3rem 0;
+ transition-property: translate;
+ transition-duration: var(--ad-dur-base);
+ transition-timing-function: var(--ad-ease);
+}
+.ad-motion-demo:hover .ad-motion-demo__dot,
+.ad-motion-demo:focus-visible .ad-motion-demo__dot { translate: 3rem 0; }
+
+.ad-accent-demo {
+ height: 4.5rem;
+ display: flex;
+ align-items: flex-end;
+ padding: var(--ad-space-2xs);
+ border-radius: var(--ad-radius-md);
+ color: #fff;
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ margin-bottom: var(--ad-space-2xs);
+}
+
+/* Token names read `<code>` sitewide, which already carries a mono chip
+ look for running prose (see :not(pre) > code in base/typography.css).
+ Inside these specimen/swatch/ruler rows the old `.ad-token-name` class was
+ plain mono text with no chip, so `<code>` is flattened back to that look
+ here the same way table cells are. */
+.ad-swatch code, .ad-specimen code, .ad-ruler code, .ad-alias-row code {
+ background: none;
+ border: 0;
+ padding: 0;
+ font-size: var(--ad-step--2);
+ color: var(--ad-color-text-primary);
+ white-space: nowrap;
+}
+
+/* A documentation aside. Like `<table>`, this is site chrome for the prose
+ itself, not a system component — the real system has no Callout. */
+.ad-callout {
+ margin-block: var(--ad-space-s);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-left: 3px solid var(--ad-color-accent);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+}
+/* Generic reference tables (component APIs, alias tables, the contrast
+ contract). `<table>` IS a table — no class needed to say so. Every table
+ on this site uses the one compact density, so there is no separate
+ modifier for it. `display: block` plus `overflow-x: auto` gives the table
+ its own horizontal scrollbar on narrow viewports without a wrapper div;
+ the anonymous table box the browser generates around `<thead>`/`<tbody>`
+ still does the row/column layout. `<code>` replaces the old
+ `.ad-token-name` / `.ad-table__code` cells — it is already styled
+ sitewide, just re-flattened here to a plain mono run instead of the
+ chip-with-border look prose code gets, to match this table's original
+ look. `.num` is the one sanctioned class left, for right-aligned numeric
+ columns — there is no attribute that means "this column is a number". */
+table { width: 100%; border-collapse: collapse; font-size: var(--ad-step--1); display: block; overflow-x: auto; }
+table th, table td {
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ text-align: left;
+ vertical-align: top;
+}
+table th { font-family: var(--ad-font-mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ad-color-text-secondary); font-weight: 500; }
+table code { background: none; border: 0; padding: 0; font-size: var(--ad-step--2); white-space: nowrap; }
+.num { text-align: right; font-variant-numeric: tabular-nums; }
+
+/* Home page. */
+.ad-hero { padding-block: var(--ad-space-m) var(--ad-space-l); }
+.ad-hero__title {
+ font-size: var(--ad-step-4);
+ margin-bottom: var(--ad-space-xs);
+}
+.ad-hero__actions { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); margin-top: var(--ad-space-s); }
+.ad-stat-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-l);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+}
+.ad-stat__value {
+ display: block;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-2);
+ font-weight: 600;
+ line-height: 1.16;
+ font-variant-numeric: tabular-nums;
+}
+.ad-stat__label {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+
+/* Card chrome. The pane supplies the frame; this just insets the page. */
+body { padding: var(--ad-space-l) var(--ad-space-m); }
+.ad-doc-main { max-width: 50rem; margin-inline: auto; }
+.ad-doc-main > * { max-width: none; }
+</style>
+</head>
+<body>
+<main class="ad-doc-main">
+<header class="ad-doc-title"><span class="ad-doc-title__section">Components</span>
+ <h1>CardRow</h1>
+</header>
+<p class="ad-lead">A hairline-divided list row for use inside Card — entry lists, task lists, settings rows. The first row's top border removes itself automatically.</p>
+<p>CardRow is a bare <code><li></code> inside a <code><ul class="card"></code> — the divider styling
+comes entirely from the <code>.card > li</code> descendant selector, so there is no
+class of CardRow’s own.</p>
+
+<h2 id="basic">Basic</h2>
+
+<figure class="example">
+<figcaption>Rows</figcaption>
+<div>
+<ul class="card" style="max-width: 22rem;">
+ <li>Renew passport</li>
+ <li>Dentist checkup</li>
+ <li>Rotate mattress</li>
+</ul>
+</div>
+<details open=""><summary>Markup</summary>
+<pre><code><ul class="card" style="max-width: 22rem;">
+ <li>Renew passport</li>
+ <li>Dentist checkup</li>
+ <li>Rotate mattress</li>
+</ul></code></pre>
+</details>
+</figure>
+
+<h2 id="clickable">Clickable</h2>
+
+<p>Wrap the row’s content in a real <code><button></code> or <code><a></code> when the row navigates
+or opens something — an accessible clickable row, not a <code><li></code> with a click
+handler and no keyboard support. It fills the row edge-to-edge and picks up
+a hover fill on its own; no <code>--clickable</code> modifier to remember.</p>
+
+<figure class="example">
+<figcaption>Clickable rows</figcaption>
+<div>
+<ul class="card" style="max-width: 22rem;">
+ <li><button>Tax return 2025.pdf</button></li>
+ <li><button>Warranty card.pdf</button></li>
+</ul>
+</div>
+<details><summary>Markup</summary>
+<pre><code><ul class="card" style="max-width: 22rem;">
+ <li><button>Tax return 2025.pdf</button></li>
+ <li><button>Warranty card.pdf</button></li>
+</ul></code></pre>
+</details>
+</figure>
+
+<h2 id="with-content-on-both-ends">With content on both ends</h2>
+
+<p>The row is a flex container — a leading icon, a two-line middle column, and a
+trailing mono value is the most common shape (see the Loop and Archive
+patterns for it in context).</p>
+
+<figure class="example">
+<figcaption>Leading icon, trailing meta</figcaption>
+<div>
+<ul class="card" style="max-width: 24rem;">
+ <li>
+ <span class="avatar" data-square="">
+ <svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 8 12 3 3 8v8l9 5 9-5V8Z" /><path d="M3 8l9 5 9-5M12 13v8" /></svg>
+ </span>
+ <div style="flex:1; min-width:0;">
+ <div style="font-weight: 550;">Dishwasher manual</div>
+ <div class="type-label" style="margin-top: 2px;">PDF · 2.1MB</div>
+ </div>
+ <span class="type-mono" style="color: var(--ad-color-text-tertiary);">2h</span>
+ </li>
+</ul>
+</div>
+<details><summary>Markup</summary>
+<pre><code><ul class="card" style="max-width: 24rem;">
+ <li>
+ <span class="avatar" data-square>
+ <svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 8 12 3 3 8v8l9 5 9-5V8Z" /><path d="M3 8l9 5 9-5M12 13v8" /></svg>
+ </span>
+ <div style="flex:1; min-width:0;">
+ <div style="font-weight: 550;">Dishwasher manual</div>
+ <div class="type-label" style="margin-top: 2px;">PDF · 2.1MB</div>
+ </div>
+ <span class="type-mono" style="color: var(--ad-color-text-tertiary);">2h</span>
+ </li>
+</ul></code></pre>
+</details>
+</figure>
+
+<h2 id="api">API</h2>
+
+<table>
+ <thead><tr><th scope="col">Element / attribute</th><th scope="col">Values</th><th scope="col">Notes</th></tr></thead>
+ <tbody>
+ <tr><td><code>li</code></td><td></td><td>Required — a direct child of <code>ul.card</code>. No class of its own.</td></tr>
+ <tr><td><code>li > button</code> / <code>li > a</code></td><td></td><td>Wrap the row's content to make it clickable, in place of an <code>onClick</code> prop.</td></tr>
+ </tbody>
+</table>
+</main>
+<script>
+// Behavior for the pieces of the system that need it. Everything else is
+// CSS and semantic HTML on purpose — a design system that only works with
+// JavaScript running is a component library with extra steps.
+(function () {
+ "use strict";
+
+ var STORAGE_KEY = "ad-theme";
+
+ function readStored() {
+ try {
+ return localStorage.getItem(STORAGE_KEY);
+ } catch (error) {
+ return null;
+ }
+ }
+
+ function writeStored(value) {
+ try {
+ localStorage.setItem(STORAGE_KEY, value);
+ } catch (error) {
+ /* Nothing to do — the media query still gives a sensible default. */
+ }
+ }
+
+ function systemPrefersDark() {
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
+ }
+
+ function currentTheme() {
+ var explicit = document.documentElement.getAttribute("data-theme");
+ if (explicit) return explicit;
+ return systemPrefersDark() ? "dark" : "light";
+ }
+
+ function paintToggle(theme) {
+ document.querySelectorAll("[data-theme-toggle]").forEach(function (toggle) {
+ var next = theme === "dark" ? "light" : "dark";
+ toggle.querySelectorAll("[data-theme-icon]").forEach(function (icon) {
+ icon.hidden = icon.dataset.themeIcon !== theme;
+ });
+ toggle.setAttribute("aria-label", "Switch to the " + next + " theme");
+ toggle.setAttribute("title", "Switch to the " + next + " theme");
+ var label = toggle.querySelector("[data-theme-label]");
+ if (label) label.textContent = "Switch to the " + next + " theme";
+ });
+ }
+
+ function setUpTheme() {
+ paintToggle(currentTheme());
+
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-theme-toggle]");
+ if (!toggle) return;
+
+ var next = currentTheme() === "dark" ? "light" : "dark";
+ document.documentElement.setAttribute("data-theme", next);
+ writeStored(next);
+ paintToggle(next);
+ });
+
+ // Follow the OS while the reader has not made an explicit choice.
+ window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", function () {
+ if (!readStored()) paintToggle(currentTheme());
+ });
+ }
+
+ // Tabs, wired to the WAI-ARIA authoring practices: arrows move, Home/End
+ // jump, and only the selected tab stays in the tab order.
+ function setUpTabs() {
+ document.querySelectorAll("[data-tabs]").forEach(function (root) {
+ var tabs = Array.prototype.slice.call(root.querySelectorAll('[role="tab"]'));
+ if (!tabs.length) return;
+
+ function select(tab, focus) {
+ tabs.forEach(function (candidate) {
+ var selected = candidate === tab;
+ candidate.setAttribute("aria-selected", String(selected));
+ candidate.tabIndex = selected ? 0 : -1;
+ var panel = document.getElementById(candidate.getAttribute("aria-controls"));
+ if (panel) panel.hidden = !selected;
+ });
+ if (focus) tab.focus();
+ }
+
+ root.addEventListener("click", function (event) {
+ var tab = event.target.closest('[role="tab"]');
+ if (tab) select(tab, false);
+ });
+
+ root.addEventListener("keydown", function (event) {
+ var index = tabs.indexOf(document.activeElement);
+ if (index === -1) return;
+
+ var next = null;
+ if (event.key === "ArrowRight") next = tabs[(index + 1) % tabs.length];
+ else if (event.key === "ArrowLeft") next = tabs[(index - 1 + tabs.length) % tabs.length];
+ else if (event.key === "Home") next = tabs[0];
+ else if (event.key === "End") next = tabs[tabs.length - 1];
+
+ if (next) {
+ event.preventDefault();
+ select(next, true);
+ }
+ });
+
+ select(tabs.filter(function (tab) {
+ return tab.getAttribute("aria-selected") === "true";
+ })[0] || tabs[0], false);
+ });
+ }
+
+ function setUpNavToggle() {
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-nav-toggle]");
+ if (!toggle) return;
+
+ var sidebar = toggle.closest("[data-sidebar]");
+ var collapsed = sidebar.dataset.collapsed !== "false";
+ sidebar.dataset.collapsed = String(!collapsed);
+ toggle.setAttribute("aria-expanded", String(collapsed));
+ });
+ }
+
+ setUpTheme();
+ setUpTabs();
+ setUpNavToggle();
+})();
+
+</script>
+</body>
+</html>
diff --git a/design-bundle/components/card.html b/design-bundle/components/card.html
new file mode 100644
index 0000000..cfb0e1e
--- /dev/null
+++ b/design-bundle/components/card.html
@@ -0,0 +1,1841 @@
+<!-- @dsCard group="Core" -->
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Card</title>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&display=swap">
+<style>
+/* Arbitrary Definitions — design tokens. Hand-maintained; edit values directly. */
+
+:root {
+ /* Palette ramps — reference only. Components use the aliases below. */
+ --ad-neutral-0: #faf9f7;
+ --ad-neutral-50: #fdfdfb;
+ --ad-neutral-100: #f1f0ed;
+ --ad-neutral-150: #e8e6e2;
+ --ad-neutral-200: #dcdad7;
+ --ad-neutral-300: #c0bdb9;
+ --ad-neutral-500: #888681;
+ --ad-neutral-600: #5f5d59;
+ --ad-neutral-800: #2a2926;
+ --ad-neutral-900: #100f0d;
+ --ad-neutral-dark-0: #0e0d0b;
+ --ad-neutral-dark-50: #151412;
+ --ad-neutral-dark-100: #1e1d1a;
+ --ad-neutral-dark-150: #252421;
+ --ad-neutral-dark-200: #2c2b29;
+ --ad-neutral-dark-300: #44423f;
+ --ad-neutral-dark-500: #7c7a76;
+ --ad-neutral-dark-600: #a09e9a;
+ --ad-neutral-dark-800: #d9d7d4;
+ --ad-neutral-dark-900: #e9e8e6;
+ --ad-accent-teal: #3a6a63;
+ --ad-accent-clay: #9a5a3c;
+ --ad-accent-ink-blue: #2b4a78;
+ --ad-accent-ochre: #93701f;
+ --ad-accent-plum: #6a3a5c;
+
+ /* Surfaces */
+ --ad-color-surface-page: #faf9f7;
+ --ad-color-surface-card: #fdfdfb;
+ --ad-color-surface-fill: #f1f0ed;
+ --ad-color-surface-fill-2: #e8e6e2;
+
+ /* Borders */
+ --ad-color-border-hairline: #dcdad7;
+ --ad-color-border-default: #c0bdb9;
+
+ /* Text */
+ --ad-color-text-primary: #100f0d;
+ --ad-color-text-secondary: #5f5d59;
+ --ad-color-text-tertiary: #888681;
+
+ /* Accent */
+ --ad-color-accent: #3a6a63;
+ --ad-color-accent-fg: #ffffff;
+ --ad-color-accent-ink: color-mix(in oklch, var(--ad-color-accent) 78%, black);
+ --ad-color-accent-soft: color-mix(in oklch, var(--ad-color-accent) 12%, white);
+
+ /* Status (fixed — never swapped like the accent) */
+ --ad-color-success: #3f7a4a;
+ --ad-color-success-bg: color-mix(in oklch, var(--ad-color-success) 12%, white);
+ --ad-color-danger: #ad3a2c;
+ --ad-color-danger-bg: color-mix(in oklch, var(--ad-color-danger) 10%, white);
+
+ /* Type families */
+ --ad-font-sans: -apple-system, "Helvetica Neue", Helvetica, Arial, system-ui, sans-serif;
+ --ad-font-mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, Consolas, "Courier New", monospace;
+ --ad-font-serif: Georgia, "Times New Roman", "Iowan Old Style", serif;
+
+ /* Type steps — fluid (Utopia scale), the design system's own body-text size */
+ --ad-step--2: clamp(0.7813rem, 0.7736rem + 0.0341vw, 0.8rem);
+ --ad-step--1: clamp(0.9375rem, 0.9119rem + 0.1136vw, 1rem);
+ --ad-step-0: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-step-1: clamp(1.35rem, 1.2631rem + 0.3864vw, 1.5625rem);
+ --ad-step-2: clamp(1.62rem, 1.4837rem + 0.6057vw, 1.9531rem);
+ --ad-step-3: clamp(1.944rem, 1.7405rem + 0.9044vw, 2.4414rem);
+ --ad-step-4: clamp(2.3328rem, 2.0387rem + 1.3072vw, 3.0518rem);
+ --ad-step-5: clamp(2.7994rem, 2.384rem + 1.8461vw, 3.8147rem);
+
+ /* Space */
+ --ad-space-3xs: clamp(0.3125rem, 0.3125rem + 0vw, 0.3125rem);
+ --ad-space-2xs: clamp(0.5625rem, 0.5369rem + 0.1136vw, 0.625rem);
+ --ad-space-xs: clamp(0.875rem, 0.8494rem + 0.1136vw, 0.9375rem);
+ --ad-space-s: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-space-m: clamp(1.6875rem, 1.6108rem + 0.3409vw, 1.875rem);
+ --ad-space-l: clamp(2.25rem, 2.1477rem + 0.4545vw, 2.5rem);
+ --ad-space-xl: clamp(3.375rem, 3.2216rem + 0.6818vw, 3.75rem);
+ --ad-space-2xl: clamp(4.5rem, 4.2955rem + 0.9091vw, 5rem);
+ --ad-space-3xl: clamp(6.75rem, 6.4432rem + 1.3636vw, 7.5rem);
+
+ /* Radius */
+ --ad-radius-sm: 4px;
+ --ad-radius-md: 6px;
+ --ad-radius-lg: 10px;
+ --ad-radius-full: 999px;
+ --ad-border-w: 1px;
+
+ /* Elevation */
+ --ad-shadow-sm: 0 1px 2px rgba(20, 18, 14, 0.05);
+ --ad-shadow-float: 0 2px 6px rgba(20, 18, 14, 0.06), 0 10px 24px -12px rgba(20, 18, 14, 0.18);
+
+ /* Motion */
+ --ad-dur-fast: 0.12s;
+ --ad-dur-base: 0.16s;
+ --ad-ease: cubic-bezier(0.2, 0, 0, 1);
+}
+
+@media (prefers-color-scheme: dark) {
+ :root:not([data-theme="light"]) {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+ }
+}
+
+:root[data-theme="dark"] {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ :root {
+ --ad-dur-fast: 0ms;
+ --ad-dur-base: 0ms;
+ }
+}
+
+/* A short reset. Everything it sets is something the system would otherwise
+ have to override in every component.
+
+ The rem values used for this documentation site's own chrome (measure,
+ content width, sidebar width, header height) belong to this site, not to
+ the design system it documents — the source design system defines no
+ container-width tokens at all, so there is nothing under `--ad-*` to reach
+ for here. See the header comment in site/shell.css. */
+
+*,
+*::before,
+*::after { box-sizing: border-box; }
+
+html {
+ -webkit-text-size-adjust: 100%;
+ scroll-behavior: smooth;
+ scroll-padding-top: calc(3.5rem + var(--ad-space-s));
+}
+
+@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;
+ }
+}
+
+body {
+ margin: 0;
+ background: var(--ad-color-surface-page);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ line-height: 1.55;
+ font-synthesis-weight: none;
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+}
+
+::selection {
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+}
+
+h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd { margin: 0; }
+ul[class], ol[class] { list-style: none; margin: 0; padding: 0; }
+
+img, svg, video { display: block; max-width: 100%; height: auto; }
+
+button, input, select, textarea {
+ font: inherit;
+ color: inherit;
+}
+
+hr {
+ height: 1px;
+ margin: var(--ad-space-l) 0;
+ border: 0;
+ background: var(--ad-color-border-hairline);
+}
+
+/* One focus ring, defined once. No component is allowed its own. */
+:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+ border-radius: var(--ad-radius-sm);
+}
+:focus:not(:focus-visible) { outline: none; }
+
+[hidden] { display: none !important; }
+
+.ad-visually-hidden {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip-path: inset(50%);
+ white-space: nowrap;
+ border: 0;
+}
+
+/* Every rule here reads a type token for family and size. Weight, line-height,
+ and letter-spacing are hardcoded to the exact numbers the source ships —
+ there is no `--ad-weight-*` / `--ad-leading-*` / `--ad-tracking-*` scale in
+ the real system, only eight fixed compound styles (see the Typography
+ foundation page, which this file implements verbatim). */
+
+.type-display { font: 600 var(--ad-step-4)/1.08 var(--ad-font-sans); letter-spacing: -0.015em; }
+.type-h1, h1 { font: 600 var(--ad-step-3)/1.12 var(--ad-font-sans); letter-spacing: -0.012em; }
+.type-h2, h2 { font: 600 var(--ad-step-2)/1.16 var(--ad-font-sans); letter-spacing: -0.01em; }
+.type-h3, h3 { font: 600 var(--ad-step-1)/1.25 var(--ad-font-sans); }
+.type-body { font: 400 var(--ad-step-0)/1.55 var(--ad-font-sans); }
+.type-body-sm { font: 400 var(--ad-step--1)/1.5 var(--ad-font-sans); }
+.type-label {
+ font: 500 var(--ad-step--2)/1 var(--ad-font-mono);
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: var(--ad-color-text-secondary);
+}
+.type-mono { font: 400 var(--ad-step--1)/1.5 var(--ad-font-mono); }
+
+/* Prose defaults for the documentation body copy. These read the same
+ tokens the compound classes above do; they are not a second scale. */
+h1, h2, h3, h4 { font-weight: 600; text-wrap: balance; }
+
+h4 { font: 600 var(--ad-step-0)/1.3 var(--ad-font-sans); }
+
+p, ul, ol { margin: 0 0 var(--ad-space-s); }
+li + li { margin-top: var(--ad-space-3xs); }
+
+a {
+ color: var(--ad-color-accent);
+ text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
+ text-underline-offset: 0.18em;
+ text-decoration-thickness: 1px;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ text-decoration-color var(--ad-dur-fast) var(--ad-ease);
+}
+a:hover {
+ color: var(--ad-color-accent-ink);
+ text-decoration-color: currentColor;
+}
+
+strong, b { font-weight: 600; }
+
+small { font-size: var(--ad-step--1); }
+
+.ad-lead {
+ font-size: var(--ad-step-1);
+ line-height: 1.5;
+ color: var(--ad-color-text-secondary);
+ max-width: 68ch;
+ text-wrap: pretty;
+}
+
+.ad-muted { color: var(--ad-color-text-secondary); }
+.ad-subtle { color: var(--ad-color-text-tertiary); }
+
+code, kbd, samp, pre {
+ font-family: var(--ad-font-mono);
+ font-feature-settings: "liga" 0;
+}
+
+:not(pre) > code {
+ padding: 0.15em 0.4em;
+ font-size: 0.875em;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-sm);
+ white-space: nowrap;
+}
+
+kbd {
+ display: inline-block;
+ padding: 0.1em 0.45em;
+ font-size: var(--ad-step--2);
+ line-height: 1.6;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-bottom-width: 2px;
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-secondary);
+}
+
+blockquote {
+ margin-block: var(--ad-space-s);
+ padding-left: var(--ad-space-s);
+ border-left: 2px solid var(--ad-color-accent);
+ color: var(--ad-color-text-secondary);
+}
+
+/* Button
+ A flat, quiet button that recedes rather than performs — no gradients, no
+ shine. Variants: default, primary, dark, ghost, danger. Sizes: sm, md
+ (default), lg — padding and font step only, weight and radius stay put.
+
+ The contract is the element itself: a bare <button> is styled directly, no
+ class required. An <a> that acts like a button (navigates rather than
+ changes something — see the Button page's "Links that act like buttons")
+ opts in with a single `.btn` class, since a bare `a` selector would catch
+ every ordinary prose link on the site. `:not(.icon-button)` keeps this
+ block from leaking onto IconButton, which shares the <button> tag but is
+ an entirely different shape. */
+
+button:not(.icon-button),
+a.btn {
+ --_bg: var(--ad-color-surface-card);
+ --_fg: var(--ad-color-text-primary);
+ --_border: var(--ad-color-border-default);
+ --_pad: var(--ad-space-xs) var(--ad-space-s);
+ --_font: var(--ad-step--1);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: var(--ad-space-2xs);
+ padding: var(--_pad);
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-sans);
+ font-size: var(--_font);
+ font-weight: 550;
+ letter-spacing: -0.01em;
+ line-height: 1.2;
+ text-align: center;
+ text-decoration: none;
+ white-space: nowrap;
+ cursor: pointer;
+ transition: filter var(--ad-dur-fast) var(--ad-ease),
+ border-color var(--ad-dur-fast) var(--ad-ease);
+}
+
+:is(button:not(.icon-button), a.btn):hover:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.96); }
+:is(button:not(.icon-button), a.btn):active:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.92); }
+
+button:not(.icon-button):disabled,
+:is(button:not(.icon-button), a.btn)[aria-disabled="true"] {
+ opacity: 0.5;
+ cursor: default;
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="primary"] {
+ --_bg: var(--ad-color-accent);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-accent);
+}
+
+/* Reserved for the single most-committing action in a view — a final Save,
+ never a whole row of them. */
+:is(button:not(.icon-button), a.btn)[data-variant="dark"] {
+ --_bg: var(--ad-color-text-primary);
+ --_fg: var(--ad-color-surface-page);
+ --_border: var(--ad-color-text-primary);
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="ghost"] {
+ --_bg: transparent;
+ --_fg: var(--ad-color-text-primary);
+ --_border: transparent;
+}
+
+/* The source hardcodes this label white rather than deriving it — danger
+ is not "on a filled accent surface", so --ad-color-accent-fg is reused
+ here only because its resolved value is the same literal white, not
+ because the two roles are the same thing. */
+:is(button:not(.icon-button), a.btn)[data-variant="danger"] {
+ --_bg: var(--ad-color-danger);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-danger);
+}
+
+:is(button:not(.icon-button), a.btn)[data-size="sm"] { --_pad: 0.375rem 0.75rem; --_font: 0.8125rem; }
+:is(button:not(.icon-button), a.btn)[data-size="lg"] { --_pad: 0.75rem 1.375rem; --_font: var(--ad-step-0); }
+
+:is(button:not(.icon-button), a.btn)[data-block] { width: 100%; }
+
+/* Button group — [role="group"] is already the correct ARIA role for a
+ toolbar of related buttons, so it doubles as the styling hook for free. */
+[role="group"] {
+ display: inline-flex;
+ isolation: isolate;
+}
+[role="group"] > button { border-radius: 0; }
+[role="group"] > button:first-child { border-start-start-radius: var(--ad-radius-md); border-end-start-radius: var(--ad-radius-md); }
+[role="group"] > button:last-child { border-start-end-radius: var(--ad-radius-md); border-end-end-radius: var(--ad-radius-md); }
+[role="group"] > button + button { margin-inline-start: -1px; }
+[role="group"] > button:hover,
+[role="group"] > button:focus-visible { z-index: 1; }
+
+/* IconButton — a square icon-only button. Always needs an aria-label; it
+ becomes both the accessible name and the native tooltip. There is no
+ attribute that distinguishes "a button that is only an icon" from a plain
+ Button, so this is the one sanctioned class in this file. */
+.icon-button {
+ --_size: 2.125rem;
+ --_bg: transparent;
+ --_border: transparent;
+ --_fg: var(--ad-color-text-secondary);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ padding: 0;
+ flex: none;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease),
+ color var(--ad-dur-fast) var(--ad-ease);
+}
+.icon-button:hover:not(:disabled) { background: var(--ad-color-surface-fill-2); }
+.icon-button:disabled { opacity: 0.5; cursor: default; }
+
+.icon-button[data-size="sm"] { --_size: 1.75rem; }
+.icon-button[data-size="lg"] { --_size: 2.5rem; }
+
+.icon-button[data-variant="outline"] { --_bg: var(--ad-color-surface-card); --_border: var(--ad-color-border-default); --_fg: var(--ad-color-text-primary); }
+.icon-button[data-variant="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); }
+
+/* Field
+ The labeled field wrapper — a <label> that wraps its own caption text
+ together with the control, so the association is native and needs no
+ `for`/`id` pair. The caption is mono, uppercase, tracked — the same
+ catalog-label convention as Badge and type-label.
+
+ A <label> is reused for three different shapes across this system (this
+ wrapper, Checkbox/Radio's row, Switch's row), so it cannot be styled bare
+ without one shape leaking into another. Field is the one of the three with
+ no distinguishing attribute of its own, so it gets the sanctioned class;
+ Checkbox/Radio tell themselves apart structurally instead (see below). */
+.field {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ min-width: 0;
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Hint and error read the native fine-print element. `role="alert"` is a
+ real live-region role for validation text, so it doubles as the selector
+ that turns a hint red. */
+.field > small {
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+.field > small[role="alert"] { color: var(--ad-color-danger); }
+
+/* Input / Select — bordered, matched to each other exactly. Both tags ARE
+ the component, so they are styled bare; nothing else on this site uses a
+ text input or a select. */
+input:not([type="checkbox"]):not([type="radio"]),
+select {
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ outline: none;
+ transition: border-color var(--ad-dur-fast) var(--ad-ease),
+ box-shadow var(--ad-dur-fast) var(--ad-ease);
+}
+input::placeholder { color: var(--ad-color-text-tertiary); }
+
+input:not([type="checkbox"]):not([type="radio"]):focus,
+select:focus {
+ border-color: var(--ad-color-accent);
+ box-shadow: 0 0 0 2px var(--ad-color-accent-soft);
+}
+
+input:not([type="checkbox"]):not([type="radio"]):disabled,
+select:disabled {
+ opacity: 0.5;
+ cursor: default;
+}
+
+/* The chevron is the one flagged color exception in this component set:
+ CSS cannot point a data-URI background-image at a custom property, so its
+ stroke is a literal hex rather than a var(--ad-*) reference. It is picked
+ to match --ad-color-text-secondary and commented here so it is not
+ mistaken for a stray color. */
+select {
+ appearance: none;
+ padding-inline-end: var(--ad-space-l);
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%235f5d59' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right var(--ad-space-xs) center;
+}
+
+input[aria-invalid="true"],
+select[aria-invalid="true"] {
+ border-color: var(--ad-color-danger);
+ background-color: var(--ad-color-danger-bg);
+}
+
+/* Checkbox / Radio — the real native controls, tinted with accent-color
+ instead of a hand-drawn box. This is not just a rename: it deletes the
+ custom SVG-checkmark markup entirely and lets the browser draw its own
+ (platform-appropriate) check and dot. `role="switch"` is excluded here so
+ Switch — a checkbox playing a different role — can style itself below
+ without this rule fighting it. A <label> wrapping a checkbox/radio is told
+ apart from Field's <label> structurally: it :has() one as a direct child,
+ no class required. */
+input[type="checkbox"]:not([role="switch"]),
+input[type="radio"] {
+ width: 1.125rem;
+ height: 1.125rem;
+ margin: 0;
+ accent-color: var(--ad-color-accent);
+ cursor: pointer;
+}
+input[type="checkbox"]:not([role="switch"]):focus-visible,
+input[type="radio"]:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+}
+
+label:has(> input[type="checkbox"]:not([role="switch"])),
+label:has(> input[type="radio"]) {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 400;
+ text-transform: none;
+ letter-spacing: normal;
+ color: var(--ad-color-text-primary);
+}
+label:has(> input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Switch — for on/off settings only, never a substitute for Checkbox.
+ `input[type="checkbox"][role="switch"]` is real ARIA (the correct role for
+ a toggle), not an invented hook, but the visual track+thumb needs a second
+ element for the thumb's independent position and transition — more than
+ `appearance: none` plus pseudo-elements on the input itself can cleanly
+ carry — so `.switch` is a deliberate, documented fallback class rather
+ than a first choice. */
+.switch {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ color: var(--ad-color-text-primary);
+}
+.switch input[role="switch"] { position: absolute; opacity: 0; width: 2.25rem; height: 1.25rem; margin: 0; }
+
+.switch span {
+ position: relative;
+ flex: none;
+ width: 2.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-border-default);
+ transition: background-color var(--ad-dur-base) var(--ad-ease);
+}
+.switch span::after {
+ content: "";
+ position: absolute;
+ inset-block-start: 2px;
+ inset-inline-start: 2px;
+ width: 1rem;
+ height: 1rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-fg);
+ box-shadow: var(--ad-shadow-sm);
+ transition: translate var(--ad-dur-base) var(--ad-ease);
+}
+.switch input[role="switch"]:checked + span { background: var(--ad-color-accent); }
+.switch input[role="switch"]:checked + span::after { translate: 1rem 0; }
+.switch input[role="switch"]:focus-visible + span { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+.switch:has(input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Rows of fields — used by the settings-form pattern. `<form>`/`<fieldset>`
+ are the elements that already mean this, so they are styled bare. A row
+ grouping two fields side by side has no element of its own, so it keeps
+ one plain class. */
+form {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-s);
+ max-width: 28rem;
+}
+.form-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
+ gap: var(--ad-space-xs);
+}
+fieldset {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ margin: 0;
+ padding: 0;
+ border: 0;
+}
+fieldset > legend {
+ padding: 0 0 var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Badge
+ Mono, uppercase, tracked — the catalog-label convention for status and
+ meta values (SAVED, OVERDUE, RECOMMENDED). A label, never a button.
+
+ A <span> is too generic to style globally — it is used all over running
+ prose for unrelated reasons — so Badge gets the one sanctioned class this
+ shape needs. Tone is an attribute, not a modifier class. */
+
+.badge {
+ --_bg: var(--ad-color-surface-fill);
+ --_fg: var(--ad-color-text-secondary);
+ --_border: var(--ad-color-border-hairline);
+
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.1875rem 0.5rem;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-sm);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ line-height: 1;
+ white-space: nowrap;
+}
+
+.badge[data-tone="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); --_border: transparent; }
+.badge[data-tone="success"] { --_bg: var(--ad-color-success-bg); --_fg: var(--ad-color-success); --_border: transparent; }
+.badge[data-tone="danger"] { --_bg: var(--ad-color-danger-bg); --_fg: var(--ad-color-danger); --_border: transparent; }
+
+.badge-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-2xs);
+}
+
+/* Tag
+ A removable mono chip for freeform tags — locations, categories.
+ `[data-dashed]` renders an "add tag" affordance instead of a value. Same
+ reasoning as Badge: <span> is too generic to select bare, so Tag gets one
+ class. The remove control is a real <button>, styled through the
+ descendant selector rather than a class of its own. */
+
+.tag {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.375rem;
+ padding: 0.25rem 0.625rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-default);
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ line-height: 1;
+}
+
+.tag[data-dashed] {
+ background: transparent;
+ border-style: dashed;
+ color: var(--ad-color-text-tertiary);
+ cursor: pointer;
+}
+
+.tag button {
+ all: unset;
+ cursor: pointer;
+ color: var(--ad-color-text-tertiary);
+ line-height: 1;
+ font-size: 0.75rem;
+}
+.tag button:hover { color: var(--ad-color-text-primary); }
+
+.tag-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.375rem;
+}
+
+/* Card
+ Flat surface, hairline border, radius-lg. `[data-elevated]` is opt-in and
+ reserved for the one focused/primary card in a view — a dialog, the
+ primary capture card — flat is the default everywhere else.
+
+ A <div>/<article> is too generic to select bare, so Card keeps one
+ sanctioned class. When Card's job is holding a list of rows — the common
+ case, entry lists, task lists, settings rows — it is a real <ul class="
+ card">, and CardRow is a bare <li> underneath it: no second class needed,
+ the row look comes from the `.card > li` descendant selector alone. The
+ first row's top border is removed automatically so callers never have to
+ override it by hand. */
+
+.card {
+ display: flex;
+ flex-direction: column;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: none;
+ overflow: hidden;
+}
+
+.card[data-elevated] { box-shadow: var(--ad-shadow-float); }
+
+.card > li {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+}
+.card > li:first-child { border-top: none; }
+
+/* A row whose ENTIRE content is wrapped in a real <button> or <a> — the
+ accessible way to make a row clickable — fills the row edge-to-edge and
+ picks up a hover fill and pointer cursor. `:only-child` is what tells this
+ apart from a row that merely contains a small button among other content
+ (a leading checkbox next to a title and a trailing value, say): only a
+ button/anchor that IS the row's one and only child means "the whole row
+ is the control." */
+.card > li:has(> button:only-child),
+.card > li:has(> a:only-child) {
+ padding: 0;
+}
+.card > li > button:only-child,
+.card > li > a:only-child {
+ all: unset;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ box-sizing: border-box;
+ color: inherit;
+ font: inherit;
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.card > li > button:only-child:hover,
+.card > li > a:only-child:hover { background: var(--ad-color-surface-fill); }
+
+/* Documentation-only extras: a body/footer pair and a responsive grid, used
+ by the prose pages (rationale.md, index.md) to show a Card as a plain
+ content tile rather than a data row. Neither is part of Card's props —
+ they exist because a content tile's inner layout has no element of its
+ own to hang a selector on. */
+.card-body {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-m);
+}
+.card-footer {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ margin-top: auto;
+ padding: var(--ad-space-xs) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+
+.card[data-link] { transition: border-color var(--ad-dur-fast) var(--ad-ease), box-shadow var(--ad-dur-fast) var(--ad-ease); }
+.card[data-link]:hover { border-color: var(--ad-color-border-default); box-shadow: var(--ad-shadow-sm); }
+.card[data-link]:focus-within { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+
+.card-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
+ gap: var(--ad-space-s);
+}
+
+/* Avatar — lives here because it almost never appears outside a card or a
+ row (the Loop dashboard's initials chip, Archive's file-type chip). A
+ <span> is too generic for a bare selector, so it keeps one class. */
+.avatar {
+ --_size: 1.875rem;
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ flex: none;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 600;
+ overflow: hidden;
+}
+.avatar[data-square] { border-radius: var(--ad-radius-md); background: var(--ad-color-surface-fill); color: var(--ad-color-text-secondary); border: 1px solid var(--ad-color-border-hairline); }
+.avatar[data-size="sm"] { --_size: 1.5rem; }
+.avatar[data-size="lg"] { --_size: 2.25rem; }
+
+/* Tooltip — a dark mono label on hover, positioned above the child. Pure CSS:
+ the trigger and bubble are siblings inside one relatively-positioned span,
+ shown on :hover/:focus-within so it works from the keyboard too.
+
+ `[role="tooltip"]` is the correct ARIA role for the bubble itself, so it
+ doubles as the styling hook — zero classes there. The wrapping span has no
+ attribute of its own, but it does not need one: `:has()` picks out "a span
+ that contains a [role=tooltip] bubble" precisely, without reaching for a
+ class on an otherwise-generic element. */
+span:has(> [role="tooltip"]) {
+ position: relative;
+ display: inline-flex;
+}
+
+[role="tooltip"] {
+ position: absolute;
+ bottom: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ margin-bottom: 0.375rem;
+ padding: 0.25rem 0.5rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-text-primary);
+ color: var(--ad-color-surface-page);
+ font-family: var(--ad-font-mono);
+ /* 11px is the source's own literal choice — smaller than --ad-step--2, the
+ smallest step on the scale, because a tooltip is meant to read as a
+ glance-only aside rather than body text. */
+ font-size: 11px;
+ white-space: nowrap;
+ line-height: 1.4;
+ z-index: 20;
+ opacity: 0;
+ visibility: hidden;
+ pointer-events: none;
+ transition: opacity var(--ad-dur-fast) var(--ad-ease);
+}
+span:has(> [role="tooltip"]):hover > [role="tooltip"],
+span:has(> [role="tooltip"]):focus-within > [role="tooltip"] {
+ opacity: 1;
+ visibility: visible;
+}
+
+/* Toast — the real pattern used for delete confirmations: a flat bar with an
+ optional destructive action and a dismiss. `[data-fixed]` pins it
+ bottom-right for real use; without it the toast stays in document flow for
+ documentation. `role="status"` is the correct live-region role for a
+ transient status message, so it is both the accessibility hook and the
+ style selector — no base class needed. The action and dismiss buttons are
+ plain <button>s told apart by `aria-label="Dismiss"`, an attribute they
+ need anyway for the icon-only close control. */
+[role="status"] {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ min-width: 20rem;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+}
+[role="status"][data-fixed] {
+ position: fixed;
+ inset-block-end: var(--ad-space-s);
+ inset-inline-end: var(--ad-space-s);
+ z-index: 60;
+}
+[role="status"] > span { flex: 1; }
+[role="status"] button {
+ all: unset;
+ cursor: pointer;
+ font-weight: 600;
+ color: var(--ad-color-danger);
+}
+[role="status"] button[aria-label="Dismiss"] {
+ color: var(--ad-color-text-tertiary);
+ font-size: 1rem;
+ font-weight: 400;
+ line-height: 1;
+}
+[role="status"] button[aria-label="Dismiss"]:hover { color: var(--ad-color-text-primary); }
+
+/* Dialog — the native <dialog> element: a centered modal, flat surface, no
+ colored header, hairline dividers between title/body/footer. A <header>
+ holds the title and a <footer> holds the actions, matching the elements
+ the tag already wants; everything else in between is the body, styled by
+ excluding those two rather than by a class of its own.
+
+ `.dialog-demo` scopes a static, always-open example to its own preview
+ container instead of the viewport, so it can sit safely inside a
+ documentation page — this is a doc-only affordance, not part of Dialog's
+ contract. It relies on the browser's own dialog centering: the UA
+ stylesheet already gives an open <dialog> `position: absolute; inset: 0;
+ margin: auto`, which centers it inside the nearest positioned ancestor —
+ here, `.dialog-demo` itself. A real `.showModal()` call gets the genuine
+ viewport-fixed overlay and `::backdrop` for free; a plain `open` attribute
+ (used here so the demo renders without JavaScript) does not paint a
+ backdrop at all, since `::backdrop` only exists for a dialog shown
+ modally — wire the real open/close state and call `.showModal()` in the
+ consuming app. */
+.dialog-demo {
+ position: relative;
+ min-height: 20rem;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-lg);
+}
+
+dialog {
+ width: 26rem;
+ max-width: calc(100% - (2 * var(--ad-space-s)));
+ height: fit-content;
+ padding: 0;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ overflow: hidden;
+}
+dialog::backdrop {
+ background: rgba(20, 18, 14, 0.35);
+}
+dialog > header {
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-sans);
+ font-weight: 600;
+ font-size: var(--ad-step-0);
+ color: var(--ad-color-text-primary);
+}
+dialog > footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+}
+dialog > *:not(header):not(footer) {
+ padding: var(--ad-space-m);
+ margin: 0;
+ color: var(--ad-color-text-primary);
+ font-size: var(--ad-step--1);
+}
+
+/* Tabs
+ Follows the real WAI-ARIA tabs pattern: a `[role="tablist"]` of
+ `[role="tab"]` buttons controlling `[role="tabpanel"]`s. Those roles are
+ required for the pattern to be accessible in the first place, so they
+ double as the entire styling contract — no classes at all for the base
+ shape. The keyboard wiring lives in assets/js/system.js and is
+ variant-agnostic — it only cares about `[data-tabs]` / `[role=tab]`.
+
+ Two real variants: `underline` (mono, accent underline — primary section
+ nav, the default) and `segmented` (pill track, active segment lifts to the
+ surface — filters like All · Overdue · Upcoming, never primary
+ navigation), chosen with `data-variant="segmented"` on the tablist since
+ neither has a natural attribute of its own. */
+
+/* Every rule below the list container pairs [role="tablist"] with
+ [role="tab"] rather than styling [role="tab"] alone — Tab buttons are
+ still literal <button> elements, so the compound selector's extra
+ specificity is what keeps Button's base styling (button.css) from
+ winning the cascade on shared properties like padding and background. */
+
+[role="tablist"] {
+ display: flex;
+ gap: var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+[role="tablist"] [role="tab"] {
+ padding: 0.5rem 0;
+ margin-bottom: -1px;
+ background: none;
+ border: 0;
+ border-bottom: 2px solid transparent;
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--1);
+ letter-spacing: 0.02em;
+ cursor: pointer;
+ transition: color var(--ad-dur-fast) var(--ad-ease), border-color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"] [role="tab"]:hover { color: var(--ad-color-text-primary); }
+[role="tablist"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ border-bottom-color: var(--ad-color-accent);
+}
+
+[role="tablist"][data-variant="segmented"] {
+ display: inline-flex;
+ gap: 0.125rem;
+ padding: 0.1875rem;
+ border-bottom: 0;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-md);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"] {
+ all: unset;
+ cursor: pointer;
+ padding: 0.375rem 0.875rem;
+ border-radius: var(--ad-radius-sm);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 550;
+ color: var(--ad-color-text-secondary);
+ transition: background-color var(--ad-dur-fast) var(--ad-ease), color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ background: var(--ad-color-surface-card);
+ box-shadow: var(--ad-shadow-sm);
+}
+
+[role="tabpanel"] { padding-top: var(--ad-space-s); }
+[role="tabpanel"][hidden] { display: none; }
+[role="tabpanel"]:focus-visible { outline-offset: 4px; }
+
+/* Documentation site chrome. Everything below the `.ad-doc-` prefix belongs
+ to this site rather than to the design system it documents — including the
+ width/height constants used directly below. The source design system
+ defines no container-width tokens at all, so there is nothing under
+ `--ad-*` for a doc shell to reach for; the Space foundation page's own
+ notes say to omit that group entirely rather than invent one. */
+
+.ad-doc-skip {
+ position: absolute;
+ inset-inline-start: var(--ad-space-xs);
+ inset-block-start: -4rem;
+ z-index: 40;
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ box-shadow: var(--ad-shadow-float);
+ transition: inset-block-start var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-skip:focus { inset-block-start: var(--ad-space-2xs); }
+
+.ad-doc-header {
+ position: sticky;
+ inset-block-start: 0;
+ z-index: 30;
+ height: 3.5rem;
+ background: color-mix(in srgb, var(--ad-color-surface-page) 92%, transparent);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-header__inner {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ height: 100%;
+ max-width: 72rem;
+ margin-inline: auto;
+ padding-inline: var(--ad-space-xs);
+}
+
+.ad-doc-wordmark {
+ display: flex;
+ align-items: baseline;
+ gap: var(--ad-space-2xs);
+ color: var(--ad-color-text-primary);
+ text-decoration: none;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ font-weight: 600;
+ letter-spacing: -0.01em;
+ white-space: nowrap;
+}
+.ad-doc-wordmark:hover { color: var(--ad-color-text-primary); }
+.ad-doc-wordmark__mark {
+ width: 1.375rem;
+ height: 1.375rem;
+ flex: none;
+ align-self: center;
+ color: var(--ad-color-accent);
+}
+.ad-doc-wordmark__version {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+
+.ad-doc-header__actions { margin-inline-start: auto; display: flex; align-items: center; gap: var(--ad-space-2xs); }
+
+.ad-doc-layout {
+ display: grid;
+ grid-template-columns: 16rem minmax(0, 1fr);
+ gap: var(--ad-space-l);
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-m) var(--ad-space-xs) var(--ad-space-2xl);
+}
+
+.ad-doc-sidebar {
+ position: sticky;
+ inset-block-start: calc(3.5rem + var(--ad-space-m));
+ align-self: start;
+ max-height: calc(100vh - 3.5rem - var(--ad-space-l));
+ overflow-y: auto;
+ padding-inline-end: var(--ad-space-2xs);
+}
+.ad-doc-nav__group + .ad-doc-nav__group { margin-top: var(--ad-space-s); }
+.ad-doc-nav__heading {
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-doc-nav__link {
+ display: block;
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ margin-inline-start: calc(var(--ad-space-xs) * -1);
+ border-inline-start: 2px solid transparent;
+ border-radius: 0 var(--ad-radius-sm) var(--ad-radius-sm) 0;
+ color: var(--ad-color-text-secondary);
+ font-size: var(--ad-step--1);
+ text-decoration: none;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-nav__link:hover { color: var(--ad-color-text-primary); background: var(--ad-color-surface-fill); }
+.ad-doc-nav__link[aria-current="page"] {
+ color: var(--ad-color-accent-ink);
+ border-inline-start-color: var(--ad-color-accent);
+ background: var(--ad-color-accent-soft);
+ font-weight: 500;
+}
+
+.ad-doc-main {
+ min-width: 0;
+ /* The design system's own body-text token (--ad-step-0, 18–20px) is right
+ for a product screen but too large for long-form documentation prose.
+ This site's own copy gets a plain, fixed 16px instead — the token above
+ is unchanged, and specimen tables/demos below still show its real
+ value. */
+ font-size: 1rem;
+}
+.ad-doc-main > * { max-width: 50rem; }
+
+.ad-doc-title { margin-bottom: var(--ad-space-xs); }
+.ad-doc-title__section {
+ display: block;
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-accent);
+}
+
+.ad-doc-main h2 {
+ margin-block: var(--ad-space-2xl) var(--ad-space-xs);
+ padding-bottom: var(--ad-space-2xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-main h3 { margin-block: var(--ad-space-l) var(--ad-space-2xs); }
+.ad-doc-main h4 { margin-block: var(--ad-space-s) var(--ad-space-2xs); }
+.ad-doc-main p,
+.ad-doc-main ul,
+.ad-doc-main ol { max-width: 68ch; }
+.ad-doc-main ul:not([class]),
+.ad-doc-main ol:not([class]) { padding-inline-start: var(--ad-space-s); }
+
+.ad-doc-footer {
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-s) var(--ad-space-xs) var(--ad-space-l);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-tertiary);
+ font-size: var(--ad-step--1);
+}
+.ad-doc-footer__inner { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); justify-content: space-between; }
+
+/* `button.ad-doc-nav-toggle` (element + class) rather than a bare class:
+ button.css's own base rule is `button:not(.icon-button)`, which is also
+ one element + one class in specificity and sets `display: inline-flex` —
+ a bare `.ad-doc-nav-toggle { display: none }` loses that fight and the
+ toggle stays visible at every width. Matching its specificity here (and
+ coming later in the concatenated stylesheet) settles it in source order. */
+button.ad-doc-nav-toggle { display: none; }
+
+@media (max-width: 60rem) {
+ .ad-doc-layout { grid-template-columns: minmax(0, 1fr); gap: var(--ad-space-s); }
+ .ad-doc-sidebar {
+ position: static;
+ max-height: none;
+ }
+ /* The toggle is a normal Button; the panel it opens gets its own card
+ treatment. Putting the same card styling on the <aside> wrapper too
+ nested a button-shaped box inside an outer box for no reason. */
+ .ad-doc-nav {
+ margin-top: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ }
+ .ad-doc-sidebar[data-collapsed="true"] > .ad-doc-nav { display: none; }
+ button.ad-doc-nav-toggle {
+ display: flex;
+ width: 100%;
+ justify-content: space-between;
+ }
+}
+
+@media (max-width: 34rem) {
+ .ad-doc-wordmark__version { display: none; }
+ .ad-doc-layout { padding-inline: var(--ad-space-2xs); }
+}
+
+/* The live-example figure used throughout the docs — see the "How the
+ example demos work" section on the Using the CSS page. One class on the
+ outer <figure>; everything inside is styled by plain element selectors,
+ since figcaption/div/details/summary/pre already say what they are.
+ `data-layout` and `data-surface` on the preview <div> distinguish the
+ handful of demos that need a non-default arrangement. */
+
+.example {
+ margin: var(--ad-space-s) 0 var(--ad-space-l);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+ background: var(--ad-color-surface-card);
+ overflow: hidden;
+}
+
+.example figcaption {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-secondary);
+}
+
+.example > div {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-s);
+}
+/* nowrap is load-bearing: a column-direction flex container with wrap enabled
+ sizes its single line against the container rather than the content, which
+ left every stacked example with a few hundred pixels of dead space. */
+.example > div[data-layout="stack"] { flex-direction: column; flex-wrap: nowrap; align-items: stretch; }
+.example > div[data-layout="grid"] {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
+ align-items: start;
+}
+.example > div[data-surface="sunken"] { background: var(--ad-color-surface-fill); }
+.example > div > * { min-width: 0; }
+
+.example details {
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+.example summary {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ color: var(--ad-color-text-secondary);
+ cursor: pointer;
+ list-style: none;
+ user-select: none;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+}
+.example summary::-webkit-details-marker { display: none; }
+.example summary::before {
+ content: "";
+ width: 0.5rem;
+ height: 0.5rem;
+ border-inline-end: 1.5px solid currentColor;
+ border-block-end: 1.5px solid currentColor;
+ rotate: -45deg;
+ transition: rotate var(--ad-dur-fast) var(--ad-ease);
+}
+.example details[open] > summary::before { rotate: 45deg; }
+.example summary:hover { color: var(--ad-color-text-primary); }
+
+.example pre {
+ margin: 0;
+ padding: var(--ad-space-xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+ tab-size: 2;
+}
+.example pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Standalone fenced code blocks in the prose (unhighlighted — there is no
+ syntax highlighter in this build any more, just Kramdown's plain
+ <pre><code class="language-*">). */
+.ad-doc-main > pre {
+ margin-block: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+}
+.ad-doc-main > pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Presentation for the token reference tables and swatch grids, plus a
+ generic data-table utility. All of this is documentation chrome — the
+ design system itself has no Table component (see the note on the removed
+ `table`/`progress`/`callout` pages in git history). */
+
+.ad-swatch-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs) var(--ad-space-s);
+ padding: 0;
+ list-style: none;
+}
+.ad-swatch {
+ display: flex;
+ flex-direction: column;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ overflow: hidden;
+ background: var(--ad-color-surface-card);
+}
+.ad-swatch__chip {
+ display: flex;
+ align-items: flex-end;
+ justify-content: flex-end;
+ height: 4rem;
+ padding: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+}
+.ad-swatch__meta {
+ display: flex;
+ flex-direction: column;
+ gap: 0.1rem;
+ padding: var(--ad-space-2xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+}
+.ad-swatch__hex { color: var(--ad-color-text-primary); text-transform: uppercase; }
+.ad-swatch__ratio { color: var(--ad-color-text-tertiary); }
+
+.ad-alias-row { display: flex; align-items: center; gap: var(--ad-space-2xs); }
+.ad-alias-chip {
+ width: 1.5rem;
+ height: 1.5rem;
+ flex: none;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+}
+.ad-alias-chip--pair { display: flex; }
+.ad-alias-chip--pair > span { flex: 1; }
+
+/* Type specimen rows. */
+.ad-specimen {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ gap: var(--ad-space-3xs);
+ padding-block: var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-specimen__meta {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-specimen__sample {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: var(--ad-color-text-primary);
+}
+
+/* Space and radius rulers. */
+.ad-ruler { display: flex; flex-direction: column; gap: var(--ad-space-2xs); }
+.ad-ruler__row { display: grid; grid-template-columns: 6.5rem 4.5rem 1fr; align-items: center; gap: var(--ad-space-xs); font-family: var(--ad-font-mono); font-size: 11px; }
+.ad-ruler__bar { height: 0.75rem; background: var(--ad-color-accent); border-radius: var(--ad-radius-sm); }
+.ad-ruler__num { color: var(--ad-color-text-tertiary); text-align: right; }
+
+.ad-radius-grid, .ad-elevation-grid, .ad-motion-grid, .ad-accent-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs);
+ padding: 0;
+ list-style: none;
+}
+.ad-radius-demo {
+ height: 4.5rem;
+ background: var(--ad-color-accent-soft);
+ border: 1px solid var(--ad-color-accent);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-elevation-demo {
+ height: 4.5rem;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-motion-demo {
+ height: 4.5rem;
+ display: grid;
+ place-items: center;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-md);
+ margin-bottom: var(--ad-space-2xs);
+ cursor: pointer;
+ overflow: hidden;
+}
+.ad-motion-demo__dot {
+ width: 1.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent);
+ translate: -3rem 0;
+ transition-property: translate;
+ transition-duration: var(--ad-dur-base);
+ transition-timing-function: var(--ad-ease);
+}
+.ad-motion-demo:hover .ad-motion-demo__dot,
+.ad-motion-demo:focus-visible .ad-motion-demo__dot { translate: 3rem 0; }
+
+.ad-accent-demo {
+ height: 4.5rem;
+ display: flex;
+ align-items: flex-end;
+ padding: var(--ad-space-2xs);
+ border-radius: var(--ad-radius-md);
+ color: #fff;
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ margin-bottom: var(--ad-space-2xs);
+}
+
+/* Token names read `<code>` sitewide, which already carries a mono chip
+ look for running prose (see :not(pre) > code in base/typography.css).
+ Inside these specimen/swatch/ruler rows the old `.ad-token-name` class was
+ plain mono text with no chip, so `<code>` is flattened back to that look
+ here the same way table cells are. */
+.ad-swatch code, .ad-specimen code, .ad-ruler code, .ad-alias-row code {
+ background: none;
+ border: 0;
+ padding: 0;
+ font-size: var(--ad-step--2);
+ color: var(--ad-color-text-primary);
+ white-space: nowrap;
+}
+
+/* A documentation aside. Like `<table>`, this is site chrome for the prose
+ itself, not a system component — the real system has no Callout. */
+.ad-callout {
+ margin-block: var(--ad-space-s);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-left: 3px solid var(--ad-color-accent);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+}
+/* Generic reference tables (component APIs, alias tables, the contrast
+ contract). `<table>` IS a table — no class needed to say so. Every table
+ on this site uses the one compact density, so there is no separate
+ modifier for it. `display: block` plus `overflow-x: auto` gives the table
+ its own horizontal scrollbar on narrow viewports without a wrapper div;
+ the anonymous table box the browser generates around `<thead>`/`<tbody>`
+ still does the row/column layout. `<code>` replaces the old
+ `.ad-token-name` / `.ad-table__code` cells — it is already styled
+ sitewide, just re-flattened here to a plain mono run instead of the
+ chip-with-border look prose code gets, to match this table's original
+ look. `.num` is the one sanctioned class left, for right-aligned numeric
+ columns — there is no attribute that means "this column is a number". */
+table { width: 100%; border-collapse: collapse; font-size: var(--ad-step--1); display: block; overflow-x: auto; }
+table th, table td {
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ text-align: left;
+ vertical-align: top;
+}
+table th { font-family: var(--ad-font-mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ad-color-text-secondary); font-weight: 500; }
+table code { background: none; border: 0; padding: 0; font-size: var(--ad-step--2); white-space: nowrap; }
+.num { text-align: right; font-variant-numeric: tabular-nums; }
+
+/* Home page. */
+.ad-hero { padding-block: var(--ad-space-m) var(--ad-space-l); }
+.ad-hero__title {
+ font-size: var(--ad-step-4);
+ margin-bottom: var(--ad-space-xs);
+}
+.ad-hero__actions { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); margin-top: var(--ad-space-s); }
+.ad-stat-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-l);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+}
+.ad-stat__value {
+ display: block;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-2);
+ font-weight: 600;
+ line-height: 1.16;
+ font-variant-numeric: tabular-nums;
+}
+.ad-stat__label {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+
+/* Card chrome. The pane supplies the frame; this just insets the page. */
+body { padding: var(--ad-space-l) var(--ad-space-m); }
+.ad-doc-main { max-width: 50rem; margin-inline: auto; }
+.ad-doc-main > * { max-width: none; }
+</style>
+</head>
+<body>
+<main class="ad-doc-main">
+<header class="ad-doc-title"><span class="ad-doc-title__section">Components</span>
+ <h1>Card</h1>
+</header>
+<p class="ad-lead">Flat surface, hairline border, radius-lg. Elevation is opt-in, reserved for the one focused card in a view — a dialog, the primary capture card.</p>
+<p>A <code><div></code>/<code><article></code> is too generic to style bare, so Card keeps one
+sanctioned class. <code>[data-elevated]</code> is for the single most-elevated element on
+screen — never a whole grid of cards at once.</p>
+
+<h2 id="flat-vs-elevated">Flat vs. elevated</h2>
+
+<figure class="example">
+<figcaption>Flat and elevated</figcaption>
+<div data-surface="sunken">
+<div class="card" style="width: 14rem; padding: var(--ad-space-s);">Flat — the default.</div>
+<div class="card" data-elevated="" style="width: 14rem; padding: var(--ad-space-s);">Elevated — one per view.</div>
+</div>
+<details><summary>Markup</summary>
+<pre><code><div class="card" style="width: 14rem; padding: var(--ad-space-s);">Flat — the default.</div>
+<div class="card" data-elevated style="width: 14rem; padding: var(--ad-space-s);">Elevated — one per view.</div></code></pre>
+</details>
+</figure>
+
+<h2 id="as-a-content-tile">As a content tile</h2>
+
+<p>Used standalone (not as a row-list), <code>.card</code> is fine on its own — <code>.card-body</code>
+and <code>.card-footer</code> are two more sanctioned classes, for the one shape inside
+it that has no element of its own to hang a selector on.</p>
+
+<figure class="example">
+<figcaption>Content tile</figcaption>
+<div>
+<article class="card" style="max-width: 18rem;">
+ <div class="card-body">
+ <p class="type-label" style="margin:0;">Recommended</p>
+ <h3 style="margin:0; font: 600 var(--ad-step-0)/1.3 var(--ad-font-sans);">Rename the caution color</h3>
+ <p style="margin:0; color: var(--ad-color-text-secondary); font-size: var(--ad-step--1);">Three people have called it "yellow" in review this month.</p>
+ </div>
+ <div class="card-footer">
+ <button data-variant="primary" data-size="sm">Approve</button>
+ <button data-variant="ghost" data-size="sm">Dismiss</button>
+ </div>
+</article>
+</div>
+<details><summary>Markup</summary>
+<pre><code><article class="card" style="max-width: 18rem;">
+ <div class="card-body">
+ <p class="type-label" style="margin:0;">Recommended</p>
+ <h3 style="margin:0; font: 600 var(--ad-step-0)/1.3 var(--ad-font-sans);">Rename the caution color</h3>
+ <p style="margin:0; color: var(--ad-color-text-secondary); font-size: var(--ad-step--1);">Three people have called it "yellow" in review this month.</p>
+ </div>
+ <div class="card-footer">
+ <button data-variant="primary" data-size="sm">Approve</button>
+ <button data-variant="ghost" data-size="sm">Dismiss</button>
+ </div>
+</article></code></pre>
+</details>
+</figure>
+
+<h2 id="as-a-list--with-cardrow">As a list — with CardRow</h2>
+
+<p>Card’s most common job in this system is holding a list of
+<a href="https://kejadlen.github.io/arbitrary-definitions-design-system/components/card-row/">CardRow</a>s: entry lists, task
+lists, settings rows. In that shape Card is a real <code><ul class="card"></code> and
+CardRow is a bare <code><li></code> — the divider between rows comes from the <code>.card >
+li</code> descendant selector, no second class needed.</p>
+
+<figure class="example">
+<figcaption>Card of rows</figcaption>
+<div>
+<ul class="card" style="max-width: 22rem;">
+ <li>Dishwasher manual</li>
+ <li>Sofa receipt</li>
+ <li>Passport scan</li>
+</ul>
+</div>
+<details><summary>Markup</summary>
+<pre><code><ul class="card" style="max-width: 22rem;">
+ <li>Dishwasher manual</li>
+ <li>Sofa receipt</li>
+ <li>Passport scan</li>
+</ul></code></pre>
+</details>
+</figure>
+
+<h2 id="api">API</h2>
+
+<table>
+ <thead><tr><th scope="col">Element / attribute</th><th scope="col">Values</th><th scope="col">Notes</th></tr></thead>
+ <tbody>
+ <tr><td><code>.card</code></td><td></td><td>Required base, on a <code><div></code>/<code><article></code> for a standalone tile or a <code><ul></code> for a list of rows.</td></tr>
+ <tr><td><code>data-elevated</code></td><td>boolean</td><td>Applies <code>--ad-shadow-float</code>. One per view.</td></tr>
+ <tr><td><code>> li</code></td><td></td><td>A CardRow, when Card is a <code><ul></code>. See <a href="https://kejadlen.github.io/arbitrary-definitions-design-system/components/card-row/">CardRow</a>.</td></tr>
+ </tbody>
+</table>
+</main>
+<script>
+// Behavior for the pieces of the system that need it. Everything else is
+// CSS and semantic HTML on purpose — a design system that only works with
+// JavaScript running is a component library with extra steps.
+(function () {
+ "use strict";
+
+ var STORAGE_KEY = "ad-theme";
+
+ function readStored() {
+ try {
+ return localStorage.getItem(STORAGE_KEY);
+ } catch (error) {
+ return null;
+ }
+ }
+
+ function writeStored(value) {
+ try {
+ localStorage.setItem(STORAGE_KEY, value);
+ } catch (error) {
+ /* Nothing to do — the media query still gives a sensible default. */
+ }
+ }
+
+ function systemPrefersDark() {
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
+ }
+
+ function currentTheme() {
+ var explicit = document.documentElement.getAttribute("data-theme");
+ if (explicit) return explicit;
+ return systemPrefersDark() ? "dark" : "light";
+ }
+
+ function paintToggle(theme) {
+ document.querySelectorAll("[data-theme-toggle]").forEach(function (toggle) {
+ var next = theme === "dark" ? "light" : "dark";
+ toggle.querySelectorAll("[data-theme-icon]").forEach(function (icon) {
+ icon.hidden = icon.dataset.themeIcon !== theme;
+ });
+ toggle.setAttribute("aria-label", "Switch to the " + next + " theme");
+ toggle.setAttribute("title", "Switch to the " + next + " theme");
+ var label = toggle.querySelector("[data-theme-label]");
+ if (label) label.textContent = "Switch to the " + next + " theme";
+ });
+ }
+
+ function setUpTheme() {
+ paintToggle(currentTheme());
+
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-theme-toggle]");
+ if (!toggle) return;
+
+ var next = currentTheme() === "dark" ? "light" : "dark";
+ document.documentElement.setAttribute("data-theme", next);
+ writeStored(next);
+ paintToggle(next);
+ });
+
+ // Follow the OS while the reader has not made an explicit choice.
+ window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", function () {
+ if (!readStored()) paintToggle(currentTheme());
+ });
+ }
+
+ // Tabs, wired to the WAI-ARIA authoring practices: arrows move, Home/End
+ // jump, and only the selected tab stays in the tab order.
+ function setUpTabs() {
+ document.querySelectorAll("[data-tabs]").forEach(function (root) {
+ var tabs = Array.prototype.slice.call(root.querySelectorAll('[role="tab"]'));
+ if (!tabs.length) return;
+
+ function select(tab, focus) {
+ tabs.forEach(function (candidate) {
+ var selected = candidate === tab;
+ candidate.setAttribute("aria-selected", String(selected));
+ candidate.tabIndex = selected ? 0 : -1;
+ var panel = document.getElementById(candidate.getAttribute("aria-controls"));
+ if (panel) panel.hidden = !selected;
+ });
+ if (focus) tab.focus();
+ }
+
+ root.addEventListener("click", function (event) {
+ var tab = event.target.closest('[role="tab"]');
+ if (tab) select(tab, false);
+ });
+
+ root.addEventListener("keydown", function (event) {
+ var index = tabs.indexOf(document.activeElement);
+ if (index === -1) return;
+
+ var next = null;
+ if (event.key === "ArrowRight") next = tabs[(index + 1) % tabs.length];
+ else if (event.key === "ArrowLeft") next = tabs[(index - 1 + tabs.length) % tabs.length];
+ else if (event.key === "Home") next = tabs[0];
+ else if (event.key === "End") next = tabs[tabs.length - 1];
+
+ if (next) {
+ event.preventDefault();
+ select(next, true);
+ }
+ });
+
+ select(tabs.filter(function (tab) {
+ return tab.getAttribute("aria-selected") === "true";
+ })[0] || tabs[0], false);
+ });
+ }
+
+ function setUpNavToggle() {
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-nav-toggle]");
+ if (!toggle) return;
+
+ var sidebar = toggle.closest("[data-sidebar]");
+ var collapsed = sidebar.dataset.collapsed !== "false";
+ sidebar.dataset.collapsed = String(!collapsed);
+ toggle.setAttribute("aria-expanded", String(collapsed));
+ });
+ }
+
+ setUpTheme();
+ setUpTabs();
+ setUpNavToggle();
+})();
+
+</script>
+</body>
+</html>
diff --git a/design-bundle/components/checkbox.html b/design-bundle/components/checkbox.html
new file mode 100644
index 0000000..7701288
--- /dev/null
+++ b/design-bundle/components/checkbox.html
@@ -0,0 +1,1847 @@
+<!-- @dsCard group="Forms" -->
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Checkbox</title>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&display=swap">
+<style>
+/* Arbitrary Definitions — design tokens. Hand-maintained; edit values directly. */
+
+:root {
+ /* Palette ramps — reference only. Components use the aliases below. */
+ --ad-neutral-0: #faf9f7;
+ --ad-neutral-50: #fdfdfb;
+ --ad-neutral-100: #f1f0ed;
+ --ad-neutral-150: #e8e6e2;
+ --ad-neutral-200: #dcdad7;
+ --ad-neutral-300: #c0bdb9;
+ --ad-neutral-500: #888681;
+ --ad-neutral-600: #5f5d59;
+ --ad-neutral-800: #2a2926;
+ --ad-neutral-900: #100f0d;
+ --ad-neutral-dark-0: #0e0d0b;
+ --ad-neutral-dark-50: #151412;
+ --ad-neutral-dark-100: #1e1d1a;
+ --ad-neutral-dark-150: #252421;
+ --ad-neutral-dark-200: #2c2b29;
+ --ad-neutral-dark-300: #44423f;
+ --ad-neutral-dark-500: #7c7a76;
+ --ad-neutral-dark-600: #a09e9a;
+ --ad-neutral-dark-800: #d9d7d4;
+ --ad-neutral-dark-900: #e9e8e6;
+ --ad-accent-teal: #3a6a63;
+ --ad-accent-clay: #9a5a3c;
+ --ad-accent-ink-blue: #2b4a78;
+ --ad-accent-ochre: #93701f;
+ --ad-accent-plum: #6a3a5c;
+
+ /* Surfaces */
+ --ad-color-surface-page: #faf9f7;
+ --ad-color-surface-card: #fdfdfb;
+ --ad-color-surface-fill: #f1f0ed;
+ --ad-color-surface-fill-2: #e8e6e2;
+
+ /* Borders */
+ --ad-color-border-hairline: #dcdad7;
+ --ad-color-border-default: #c0bdb9;
+
+ /* Text */
+ --ad-color-text-primary: #100f0d;
+ --ad-color-text-secondary: #5f5d59;
+ --ad-color-text-tertiary: #888681;
+
+ /* Accent */
+ --ad-color-accent: #3a6a63;
+ --ad-color-accent-fg: #ffffff;
+ --ad-color-accent-ink: color-mix(in oklch, var(--ad-color-accent) 78%, black);
+ --ad-color-accent-soft: color-mix(in oklch, var(--ad-color-accent) 12%, white);
+
+ /* Status (fixed — never swapped like the accent) */
+ --ad-color-success: #3f7a4a;
+ --ad-color-success-bg: color-mix(in oklch, var(--ad-color-success) 12%, white);
+ --ad-color-danger: #ad3a2c;
+ --ad-color-danger-bg: color-mix(in oklch, var(--ad-color-danger) 10%, white);
+
+ /* Type families */
+ --ad-font-sans: -apple-system, "Helvetica Neue", Helvetica, Arial, system-ui, sans-serif;
+ --ad-font-mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, Consolas, "Courier New", monospace;
+ --ad-font-serif: Georgia, "Times New Roman", "Iowan Old Style", serif;
+
+ /* Type steps — fluid (Utopia scale), the design system's own body-text size */
+ --ad-step--2: clamp(0.7813rem, 0.7736rem + 0.0341vw, 0.8rem);
+ --ad-step--1: clamp(0.9375rem, 0.9119rem + 0.1136vw, 1rem);
+ --ad-step-0: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-step-1: clamp(1.35rem, 1.2631rem + 0.3864vw, 1.5625rem);
+ --ad-step-2: clamp(1.62rem, 1.4837rem + 0.6057vw, 1.9531rem);
+ --ad-step-3: clamp(1.944rem, 1.7405rem + 0.9044vw, 2.4414rem);
+ --ad-step-4: clamp(2.3328rem, 2.0387rem + 1.3072vw, 3.0518rem);
+ --ad-step-5: clamp(2.7994rem, 2.384rem + 1.8461vw, 3.8147rem);
+
+ /* Space */
+ --ad-space-3xs: clamp(0.3125rem, 0.3125rem + 0vw, 0.3125rem);
+ --ad-space-2xs: clamp(0.5625rem, 0.5369rem + 0.1136vw, 0.625rem);
+ --ad-space-xs: clamp(0.875rem, 0.8494rem + 0.1136vw, 0.9375rem);
+ --ad-space-s: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-space-m: clamp(1.6875rem, 1.6108rem + 0.3409vw, 1.875rem);
+ --ad-space-l: clamp(2.25rem, 2.1477rem + 0.4545vw, 2.5rem);
+ --ad-space-xl: clamp(3.375rem, 3.2216rem + 0.6818vw, 3.75rem);
+ --ad-space-2xl: clamp(4.5rem, 4.2955rem + 0.9091vw, 5rem);
+ --ad-space-3xl: clamp(6.75rem, 6.4432rem + 1.3636vw, 7.5rem);
+
+ /* Radius */
+ --ad-radius-sm: 4px;
+ --ad-radius-md: 6px;
+ --ad-radius-lg: 10px;
+ --ad-radius-full: 999px;
+ --ad-border-w: 1px;
+
+ /* Elevation */
+ --ad-shadow-sm: 0 1px 2px rgba(20, 18, 14, 0.05);
+ --ad-shadow-float: 0 2px 6px rgba(20, 18, 14, 0.06), 0 10px 24px -12px rgba(20, 18, 14, 0.18);
+
+ /* Motion */
+ --ad-dur-fast: 0.12s;
+ --ad-dur-base: 0.16s;
+ --ad-ease: cubic-bezier(0.2, 0, 0, 1);
+}
+
+@media (prefers-color-scheme: dark) {
+ :root:not([data-theme="light"]) {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+ }
+}
+
+:root[data-theme="dark"] {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ :root {
+ --ad-dur-fast: 0ms;
+ --ad-dur-base: 0ms;
+ }
+}
+
+/* A short reset. Everything it sets is something the system would otherwise
+ have to override in every component.
+
+ The rem values used for this documentation site's own chrome (measure,
+ content width, sidebar width, header height) belong to this site, not to
+ the design system it documents — the source design system defines no
+ container-width tokens at all, so there is nothing under `--ad-*` to reach
+ for here. See the header comment in site/shell.css. */
+
+*,
+*::before,
+*::after { box-sizing: border-box; }
+
+html {
+ -webkit-text-size-adjust: 100%;
+ scroll-behavior: smooth;
+ scroll-padding-top: calc(3.5rem + var(--ad-space-s));
+}
+
+@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;
+ }
+}
+
+body {
+ margin: 0;
+ background: var(--ad-color-surface-page);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ line-height: 1.55;
+ font-synthesis-weight: none;
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+}
+
+::selection {
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+}
+
+h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd { margin: 0; }
+ul[class], ol[class] { list-style: none; margin: 0; padding: 0; }
+
+img, svg, video { display: block; max-width: 100%; height: auto; }
+
+button, input, select, textarea {
+ font: inherit;
+ color: inherit;
+}
+
+hr {
+ height: 1px;
+ margin: var(--ad-space-l) 0;
+ border: 0;
+ background: var(--ad-color-border-hairline);
+}
+
+/* One focus ring, defined once. No component is allowed its own. */
+:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+ border-radius: var(--ad-radius-sm);
+}
+:focus:not(:focus-visible) { outline: none; }
+
+[hidden] { display: none !important; }
+
+.ad-visually-hidden {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip-path: inset(50%);
+ white-space: nowrap;
+ border: 0;
+}
+
+/* Every rule here reads a type token for family and size. Weight, line-height,
+ and letter-spacing are hardcoded to the exact numbers the source ships —
+ there is no `--ad-weight-*` / `--ad-leading-*` / `--ad-tracking-*` scale in
+ the real system, only eight fixed compound styles (see the Typography
+ foundation page, which this file implements verbatim). */
+
+.type-display { font: 600 var(--ad-step-4)/1.08 var(--ad-font-sans); letter-spacing: -0.015em; }
+.type-h1, h1 { font: 600 var(--ad-step-3)/1.12 var(--ad-font-sans); letter-spacing: -0.012em; }
+.type-h2, h2 { font: 600 var(--ad-step-2)/1.16 var(--ad-font-sans); letter-spacing: -0.01em; }
+.type-h3, h3 { font: 600 var(--ad-step-1)/1.25 var(--ad-font-sans); }
+.type-body { font: 400 var(--ad-step-0)/1.55 var(--ad-font-sans); }
+.type-body-sm { font: 400 var(--ad-step--1)/1.5 var(--ad-font-sans); }
+.type-label {
+ font: 500 var(--ad-step--2)/1 var(--ad-font-mono);
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: var(--ad-color-text-secondary);
+}
+.type-mono { font: 400 var(--ad-step--1)/1.5 var(--ad-font-mono); }
+
+/* Prose defaults for the documentation body copy. These read the same
+ tokens the compound classes above do; they are not a second scale. */
+h1, h2, h3, h4 { font-weight: 600; text-wrap: balance; }
+
+h4 { font: 600 var(--ad-step-0)/1.3 var(--ad-font-sans); }
+
+p, ul, ol { margin: 0 0 var(--ad-space-s); }
+li + li { margin-top: var(--ad-space-3xs); }
+
+a {
+ color: var(--ad-color-accent);
+ text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
+ text-underline-offset: 0.18em;
+ text-decoration-thickness: 1px;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ text-decoration-color var(--ad-dur-fast) var(--ad-ease);
+}
+a:hover {
+ color: var(--ad-color-accent-ink);
+ text-decoration-color: currentColor;
+}
+
+strong, b { font-weight: 600; }
+
+small { font-size: var(--ad-step--1); }
+
+.ad-lead {
+ font-size: var(--ad-step-1);
+ line-height: 1.5;
+ color: var(--ad-color-text-secondary);
+ max-width: 68ch;
+ text-wrap: pretty;
+}
+
+.ad-muted { color: var(--ad-color-text-secondary); }
+.ad-subtle { color: var(--ad-color-text-tertiary); }
+
+code, kbd, samp, pre {
+ font-family: var(--ad-font-mono);
+ font-feature-settings: "liga" 0;
+}
+
+:not(pre) > code {
+ padding: 0.15em 0.4em;
+ font-size: 0.875em;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-sm);
+ white-space: nowrap;
+}
+
+kbd {
+ display: inline-block;
+ padding: 0.1em 0.45em;
+ font-size: var(--ad-step--2);
+ line-height: 1.6;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-bottom-width: 2px;
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-secondary);
+}
+
+blockquote {
+ margin-block: var(--ad-space-s);
+ padding-left: var(--ad-space-s);
+ border-left: 2px solid var(--ad-color-accent);
+ color: var(--ad-color-text-secondary);
+}
+
+/* Button
+ A flat, quiet button that recedes rather than performs — no gradients, no
+ shine. Variants: default, primary, dark, ghost, danger. Sizes: sm, md
+ (default), lg — padding and font step only, weight and radius stay put.
+
+ The contract is the element itself: a bare <button> is styled directly, no
+ class required. An <a> that acts like a button (navigates rather than
+ changes something — see the Button page's "Links that act like buttons")
+ opts in with a single `.btn` class, since a bare `a` selector would catch
+ every ordinary prose link on the site. `:not(.icon-button)` keeps this
+ block from leaking onto IconButton, which shares the <button> tag but is
+ an entirely different shape. */
+
+button:not(.icon-button),
+a.btn {
+ --_bg: var(--ad-color-surface-card);
+ --_fg: var(--ad-color-text-primary);
+ --_border: var(--ad-color-border-default);
+ --_pad: var(--ad-space-xs) var(--ad-space-s);
+ --_font: var(--ad-step--1);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: var(--ad-space-2xs);
+ padding: var(--_pad);
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-sans);
+ font-size: var(--_font);
+ font-weight: 550;
+ letter-spacing: -0.01em;
+ line-height: 1.2;
+ text-align: center;
+ text-decoration: none;
+ white-space: nowrap;
+ cursor: pointer;
+ transition: filter var(--ad-dur-fast) var(--ad-ease),
+ border-color var(--ad-dur-fast) var(--ad-ease);
+}
+
+:is(button:not(.icon-button), a.btn):hover:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.96); }
+:is(button:not(.icon-button), a.btn):active:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.92); }
+
+button:not(.icon-button):disabled,
+:is(button:not(.icon-button), a.btn)[aria-disabled="true"] {
+ opacity: 0.5;
+ cursor: default;
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="primary"] {
+ --_bg: var(--ad-color-accent);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-accent);
+}
+
+/* Reserved for the single most-committing action in a view — a final Save,
+ never a whole row of them. */
+:is(button:not(.icon-button), a.btn)[data-variant="dark"] {
+ --_bg: var(--ad-color-text-primary);
+ --_fg: var(--ad-color-surface-page);
+ --_border: var(--ad-color-text-primary);
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="ghost"] {
+ --_bg: transparent;
+ --_fg: var(--ad-color-text-primary);
+ --_border: transparent;
+}
+
+/* The source hardcodes this label white rather than deriving it — danger
+ is not "on a filled accent surface", so --ad-color-accent-fg is reused
+ here only because its resolved value is the same literal white, not
+ because the two roles are the same thing. */
+:is(button:not(.icon-button), a.btn)[data-variant="danger"] {
+ --_bg: var(--ad-color-danger);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-danger);
+}
+
+:is(button:not(.icon-button), a.btn)[data-size="sm"] { --_pad: 0.375rem 0.75rem; --_font: 0.8125rem; }
+:is(button:not(.icon-button), a.btn)[data-size="lg"] { --_pad: 0.75rem 1.375rem; --_font: var(--ad-step-0); }
+
+:is(button:not(.icon-button), a.btn)[data-block] { width: 100%; }
+
+/* Button group — [role="group"] is already the correct ARIA role for a
+ toolbar of related buttons, so it doubles as the styling hook for free. */
+[role="group"] {
+ display: inline-flex;
+ isolation: isolate;
+}
+[role="group"] > button { border-radius: 0; }
+[role="group"] > button:first-child { border-start-start-radius: var(--ad-radius-md); border-end-start-radius: var(--ad-radius-md); }
+[role="group"] > button:last-child { border-start-end-radius: var(--ad-radius-md); border-end-end-radius: var(--ad-radius-md); }
+[role="group"] > button + button { margin-inline-start: -1px; }
+[role="group"] > button:hover,
+[role="group"] > button:focus-visible { z-index: 1; }
+
+/* IconButton — a square icon-only button. Always needs an aria-label; it
+ becomes both the accessible name and the native tooltip. There is no
+ attribute that distinguishes "a button that is only an icon" from a plain
+ Button, so this is the one sanctioned class in this file. */
+.icon-button {
+ --_size: 2.125rem;
+ --_bg: transparent;
+ --_border: transparent;
+ --_fg: var(--ad-color-text-secondary);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ padding: 0;
+ flex: none;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease),
+ color var(--ad-dur-fast) var(--ad-ease);
+}
+.icon-button:hover:not(:disabled) { background: var(--ad-color-surface-fill-2); }
+.icon-button:disabled { opacity: 0.5; cursor: default; }
+
+.icon-button[data-size="sm"] { --_size: 1.75rem; }
+.icon-button[data-size="lg"] { --_size: 2.5rem; }
+
+.icon-button[data-variant="outline"] { --_bg: var(--ad-color-surface-card); --_border: var(--ad-color-border-default); --_fg: var(--ad-color-text-primary); }
+.icon-button[data-variant="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); }
+
+/* Field
+ The labeled field wrapper — a <label> that wraps its own caption text
+ together with the control, so the association is native and needs no
+ `for`/`id` pair. The caption is mono, uppercase, tracked — the same
+ catalog-label convention as Badge and type-label.
+
+ A <label> is reused for three different shapes across this system (this
+ wrapper, Checkbox/Radio's row, Switch's row), so it cannot be styled bare
+ without one shape leaking into another. Field is the one of the three with
+ no distinguishing attribute of its own, so it gets the sanctioned class;
+ Checkbox/Radio tell themselves apart structurally instead (see below). */
+.field {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ min-width: 0;
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Hint and error read the native fine-print element. `role="alert"` is a
+ real live-region role for validation text, so it doubles as the selector
+ that turns a hint red. */
+.field > small {
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+.field > small[role="alert"] { color: var(--ad-color-danger); }
+
+/* Input / Select — bordered, matched to each other exactly. Both tags ARE
+ the component, so they are styled bare; nothing else on this site uses a
+ text input or a select. */
+input:not([type="checkbox"]):not([type="radio"]),
+select {
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ outline: none;
+ transition: border-color var(--ad-dur-fast) var(--ad-ease),
+ box-shadow var(--ad-dur-fast) var(--ad-ease);
+}
+input::placeholder { color: var(--ad-color-text-tertiary); }
+
+input:not([type="checkbox"]):not([type="radio"]):focus,
+select:focus {
+ border-color: var(--ad-color-accent);
+ box-shadow: 0 0 0 2px var(--ad-color-accent-soft);
+}
+
+input:not([type="checkbox"]):not([type="radio"]):disabled,
+select:disabled {
+ opacity: 0.5;
+ cursor: default;
+}
+
+/* The chevron is the one flagged color exception in this component set:
+ CSS cannot point a data-URI background-image at a custom property, so its
+ stroke is a literal hex rather than a var(--ad-*) reference. It is picked
+ to match --ad-color-text-secondary and commented here so it is not
+ mistaken for a stray color. */
+select {
+ appearance: none;
+ padding-inline-end: var(--ad-space-l);
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%235f5d59' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right var(--ad-space-xs) center;
+}
+
+input[aria-invalid="true"],
+select[aria-invalid="true"] {
+ border-color: var(--ad-color-danger);
+ background-color: var(--ad-color-danger-bg);
+}
+
+/* Checkbox / Radio — the real native controls, tinted with accent-color
+ instead of a hand-drawn box. This is not just a rename: it deletes the
+ custom SVG-checkmark markup entirely and lets the browser draw its own
+ (platform-appropriate) check and dot. `role="switch"` is excluded here so
+ Switch — a checkbox playing a different role — can style itself below
+ without this rule fighting it. A <label> wrapping a checkbox/radio is told
+ apart from Field's <label> structurally: it :has() one as a direct child,
+ no class required. */
+input[type="checkbox"]:not([role="switch"]),
+input[type="radio"] {
+ width: 1.125rem;
+ height: 1.125rem;
+ margin: 0;
+ accent-color: var(--ad-color-accent);
+ cursor: pointer;
+}
+input[type="checkbox"]:not([role="switch"]):focus-visible,
+input[type="radio"]:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+}
+
+label:has(> input[type="checkbox"]:not([role="switch"])),
+label:has(> input[type="radio"]) {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 400;
+ text-transform: none;
+ letter-spacing: normal;
+ color: var(--ad-color-text-primary);
+}
+label:has(> input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Switch — for on/off settings only, never a substitute for Checkbox.
+ `input[type="checkbox"][role="switch"]` is real ARIA (the correct role for
+ a toggle), not an invented hook, but the visual track+thumb needs a second
+ element for the thumb's independent position and transition — more than
+ `appearance: none` plus pseudo-elements on the input itself can cleanly
+ carry — so `.switch` is a deliberate, documented fallback class rather
+ than a first choice. */
+.switch {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ color: var(--ad-color-text-primary);
+}
+.switch input[role="switch"] { position: absolute; opacity: 0; width: 2.25rem; height: 1.25rem; margin: 0; }
+
+.switch span {
+ position: relative;
+ flex: none;
+ width: 2.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-border-default);
+ transition: background-color var(--ad-dur-base) var(--ad-ease);
+}
+.switch span::after {
+ content: "";
+ position: absolute;
+ inset-block-start: 2px;
+ inset-inline-start: 2px;
+ width: 1rem;
+ height: 1rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-fg);
+ box-shadow: var(--ad-shadow-sm);
+ transition: translate var(--ad-dur-base) var(--ad-ease);
+}
+.switch input[role="switch"]:checked + span { background: var(--ad-color-accent); }
+.switch input[role="switch"]:checked + span::after { translate: 1rem 0; }
+.switch input[role="switch"]:focus-visible + span { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+.switch:has(input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Rows of fields — used by the settings-form pattern. `<form>`/`<fieldset>`
+ are the elements that already mean this, so they are styled bare. A row
+ grouping two fields side by side has no element of its own, so it keeps
+ one plain class. */
+form {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-s);
+ max-width: 28rem;
+}
+.form-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
+ gap: var(--ad-space-xs);
+}
+fieldset {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ margin: 0;
+ padding: 0;
+ border: 0;
+}
+fieldset > legend {
+ padding: 0 0 var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Badge
+ Mono, uppercase, tracked — the catalog-label convention for status and
+ meta values (SAVED, OVERDUE, RECOMMENDED). A label, never a button.
+
+ A <span> is too generic to style globally — it is used all over running
+ prose for unrelated reasons — so Badge gets the one sanctioned class this
+ shape needs. Tone is an attribute, not a modifier class. */
+
+.badge {
+ --_bg: var(--ad-color-surface-fill);
+ --_fg: var(--ad-color-text-secondary);
+ --_border: var(--ad-color-border-hairline);
+
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.1875rem 0.5rem;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-sm);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ line-height: 1;
+ white-space: nowrap;
+}
+
+.badge[data-tone="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); --_border: transparent; }
+.badge[data-tone="success"] { --_bg: var(--ad-color-success-bg); --_fg: var(--ad-color-success); --_border: transparent; }
+.badge[data-tone="danger"] { --_bg: var(--ad-color-danger-bg); --_fg: var(--ad-color-danger); --_border: transparent; }
+
+.badge-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-2xs);
+}
+
+/* Tag
+ A removable mono chip for freeform tags — locations, categories.
+ `[data-dashed]` renders an "add tag" affordance instead of a value. Same
+ reasoning as Badge: <span> is too generic to select bare, so Tag gets one
+ class. The remove control is a real <button>, styled through the
+ descendant selector rather than a class of its own. */
+
+.tag {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.375rem;
+ padding: 0.25rem 0.625rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-default);
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ line-height: 1;
+}
+
+.tag[data-dashed] {
+ background: transparent;
+ border-style: dashed;
+ color: var(--ad-color-text-tertiary);
+ cursor: pointer;
+}
+
+.tag button {
+ all: unset;
+ cursor: pointer;
+ color: var(--ad-color-text-tertiary);
+ line-height: 1;
+ font-size: 0.75rem;
+}
+.tag button:hover { color: var(--ad-color-text-primary); }
+
+.tag-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.375rem;
+}
+
+/* Card
+ Flat surface, hairline border, radius-lg. `[data-elevated]` is opt-in and
+ reserved for the one focused/primary card in a view — a dialog, the
+ primary capture card — flat is the default everywhere else.
+
+ A <div>/<article> is too generic to select bare, so Card keeps one
+ sanctioned class. When Card's job is holding a list of rows — the common
+ case, entry lists, task lists, settings rows — it is a real <ul class="
+ card">, and CardRow is a bare <li> underneath it: no second class needed,
+ the row look comes from the `.card > li` descendant selector alone. The
+ first row's top border is removed automatically so callers never have to
+ override it by hand. */
+
+.card {
+ display: flex;
+ flex-direction: column;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: none;
+ overflow: hidden;
+}
+
+.card[data-elevated] { box-shadow: var(--ad-shadow-float); }
+
+.card > li {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+}
+.card > li:first-child { border-top: none; }
+
+/* A row whose ENTIRE content is wrapped in a real <button> or <a> — the
+ accessible way to make a row clickable — fills the row edge-to-edge and
+ picks up a hover fill and pointer cursor. `:only-child` is what tells this
+ apart from a row that merely contains a small button among other content
+ (a leading checkbox next to a title and a trailing value, say): only a
+ button/anchor that IS the row's one and only child means "the whole row
+ is the control." */
+.card > li:has(> button:only-child),
+.card > li:has(> a:only-child) {
+ padding: 0;
+}
+.card > li > button:only-child,
+.card > li > a:only-child {
+ all: unset;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ box-sizing: border-box;
+ color: inherit;
+ font: inherit;
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.card > li > button:only-child:hover,
+.card > li > a:only-child:hover { background: var(--ad-color-surface-fill); }
+
+/* Documentation-only extras: a body/footer pair and a responsive grid, used
+ by the prose pages (rationale.md, index.md) to show a Card as a plain
+ content tile rather than a data row. Neither is part of Card's props —
+ they exist because a content tile's inner layout has no element of its
+ own to hang a selector on. */
+.card-body {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-m);
+}
+.card-footer {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ margin-top: auto;
+ padding: var(--ad-space-xs) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+
+.card[data-link] { transition: border-color var(--ad-dur-fast) var(--ad-ease), box-shadow var(--ad-dur-fast) var(--ad-ease); }
+.card[data-link]:hover { border-color: var(--ad-color-border-default); box-shadow: var(--ad-shadow-sm); }
+.card[data-link]:focus-within { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+
+.card-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
+ gap: var(--ad-space-s);
+}
+
+/* Avatar — lives here because it almost never appears outside a card or a
+ row (the Loop dashboard's initials chip, Archive's file-type chip). A
+ <span> is too generic for a bare selector, so it keeps one class. */
+.avatar {
+ --_size: 1.875rem;
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ flex: none;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 600;
+ overflow: hidden;
+}
+.avatar[data-square] { border-radius: var(--ad-radius-md); background: var(--ad-color-surface-fill); color: var(--ad-color-text-secondary); border: 1px solid var(--ad-color-border-hairline); }
+.avatar[data-size="sm"] { --_size: 1.5rem; }
+.avatar[data-size="lg"] { --_size: 2.25rem; }
+
+/* Tooltip — a dark mono label on hover, positioned above the child. Pure CSS:
+ the trigger and bubble are siblings inside one relatively-positioned span,
+ shown on :hover/:focus-within so it works from the keyboard too.
+
+ `[role="tooltip"]` is the correct ARIA role for the bubble itself, so it
+ doubles as the styling hook — zero classes there. The wrapping span has no
+ attribute of its own, but it does not need one: `:has()` picks out "a span
+ that contains a [role=tooltip] bubble" precisely, without reaching for a
+ class on an otherwise-generic element. */
+span:has(> [role="tooltip"]) {
+ position: relative;
+ display: inline-flex;
+}
+
+[role="tooltip"] {
+ position: absolute;
+ bottom: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ margin-bottom: 0.375rem;
+ padding: 0.25rem 0.5rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-text-primary);
+ color: var(--ad-color-surface-page);
+ font-family: var(--ad-font-mono);
+ /* 11px is the source's own literal choice — smaller than --ad-step--2, the
+ smallest step on the scale, because a tooltip is meant to read as a
+ glance-only aside rather than body text. */
+ font-size: 11px;
+ white-space: nowrap;
+ line-height: 1.4;
+ z-index: 20;
+ opacity: 0;
+ visibility: hidden;
+ pointer-events: none;
+ transition: opacity var(--ad-dur-fast) var(--ad-ease);
+}
+span:has(> [role="tooltip"]):hover > [role="tooltip"],
+span:has(> [role="tooltip"]):focus-within > [role="tooltip"] {
+ opacity: 1;
+ visibility: visible;
+}
+
+/* Toast — the real pattern used for delete confirmations: a flat bar with an
+ optional destructive action and a dismiss. `[data-fixed]` pins it
+ bottom-right for real use; without it the toast stays in document flow for
+ documentation. `role="status"` is the correct live-region role for a
+ transient status message, so it is both the accessibility hook and the
+ style selector — no base class needed. The action and dismiss buttons are
+ plain <button>s told apart by `aria-label="Dismiss"`, an attribute they
+ need anyway for the icon-only close control. */
+[role="status"] {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ min-width: 20rem;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+}
+[role="status"][data-fixed] {
+ position: fixed;
+ inset-block-end: var(--ad-space-s);
+ inset-inline-end: var(--ad-space-s);
+ z-index: 60;
+}
+[role="status"] > span { flex: 1; }
+[role="status"] button {
+ all: unset;
+ cursor: pointer;
+ font-weight: 600;
+ color: var(--ad-color-danger);
+}
+[role="status"] button[aria-label="Dismiss"] {
+ color: var(--ad-color-text-tertiary);
+ font-size: 1rem;
+ font-weight: 400;
+ line-height: 1;
+}
+[role="status"] button[aria-label="Dismiss"]:hover { color: var(--ad-color-text-primary); }
+
+/* Dialog — the native <dialog> element: a centered modal, flat surface, no
+ colored header, hairline dividers between title/body/footer. A <header>
+ holds the title and a <footer> holds the actions, matching the elements
+ the tag already wants; everything else in between is the body, styled by
+ excluding those two rather than by a class of its own.
+
+ `.dialog-demo` scopes a static, always-open example to its own preview
+ container instead of the viewport, so it can sit safely inside a
+ documentation page — this is a doc-only affordance, not part of Dialog's
+ contract. It relies on the browser's own dialog centering: the UA
+ stylesheet already gives an open <dialog> `position: absolute; inset: 0;
+ margin: auto`, which centers it inside the nearest positioned ancestor —
+ here, `.dialog-demo` itself. A real `.showModal()` call gets the genuine
+ viewport-fixed overlay and `::backdrop` for free; a plain `open` attribute
+ (used here so the demo renders without JavaScript) does not paint a
+ backdrop at all, since `::backdrop` only exists for a dialog shown
+ modally — wire the real open/close state and call `.showModal()` in the
+ consuming app. */
+.dialog-demo {
+ position: relative;
+ min-height: 20rem;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-lg);
+}
+
+dialog {
+ width: 26rem;
+ max-width: calc(100% - (2 * var(--ad-space-s)));
+ height: fit-content;
+ padding: 0;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ overflow: hidden;
+}
+dialog::backdrop {
+ background: rgba(20, 18, 14, 0.35);
+}
+dialog > header {
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-sans);
+ font-weight: 600;
+ font-size: var(--ad-step-0);
+ color: var(--ad-color-text-primary);
+}
+dialog > footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+}
+dialog > *:not(header):not(footer) {
+ padding: var(--ad-space-m);
+ margin: 0;
+ color: var(--ad-color-text-primary);
+ font-size: var(--ad-step--1);
+}
+
+/* Tabs
+ Follows the real WAI-ARIA tabs pattern: a `[role="tablist"]` of
+ `[role="tab"]` buttons controlling `[role="tabpanel"]`s. Those roles are
+ required for the pattern to be accessible in the first place, so they
+ double as the entire styling contract — no classes at all for the base
+ shape. The keyboard wiring lives in assets/js/system.js and is
+ variant-agnostic — it only cares about `[data-tabs]` / `[role=tab]`.
+
+ Two real variants: `underline` (mono, accent underline — primary section
+ nav, the default) and `segmented` (pill track, active segment lifts to the
+ surface — filters like All · Overdue · Upcoming, never primary
+ navigation), chosen with `data-variant="segmented"` on the tablist since
+ neither has a natural attribute of its own. */
+
+/* Every rule below the list container pairs [role="tablist"] with
+ [role="tab"] rather than styling [role="tab"] alone — Tab buttons are
+ still literal <button> elements, so the compound selector's extra
+ specificity is what keeps Button's base styling (button.css) from
+ winning the cascade on shared properties like padding and background. */
+
+[role="tablist"] {
+ display: flex;
+ gap: var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+[role="tablist"] [role="tab"] {
+ padding: 0.5rem 0;
+ margin-bottom: -1px;
+ background: none;
+ border: 0;
+ border-bottom: 2px solid transparent;
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--1);
+ letter-spacing: 0.02em;
+ cursor: pointer;
+ transition: color var(--ad-dur-fast) var(--ad-ease), border-color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"] [role="tab"]:hover { color: var(--ad-color-text-primary); }
+[role="tablist"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ border-bottom-color: var(--ad-color-accent);
+}
+
+[role="tablist"][data-variant="segmented"] {
+ display: inline-flex;
+ gap: 0.125rem;
+ padding: 0.1875rem;
+ border-bottom: 0;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-md);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"] {
+ all: unset;
+ cursor: pointer;
+ padding: 0.375rem 0.875rem;
+ border-radius: var(--ad-radius-sm);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 550;
+ color: var(--ad-color-text-secondary);
+ transition: background-color var(--ad-dur-fast) var(--ad-ease), color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ background: var(--ad-color-surface-card);
+ box-shadow: var(--ad-shadow-sm);
+}
+
+[role="tabpanel"] { padding-top: var(--ad-space-s); }
+[role="tabpanel"][hidden] { display: none; }
+[role="tabpanel"]:focus-visible { outline-offset: 4px; }
+
+/* Documentation site chrome. Everything below the `.ad-doc-` prefix belongs
+ to this site rather than to the design system it documents — including the
+ width/height constants used directly below. The source design system
+ defines no container-width tokens at all, so there is nothing under
+ `--ad-*` for a doc shell to reach for; the Space foundation page's own
+ notes say to omit that group entirely rather than invent one. */
+
+.ad-doc-skip {
+ position: absolute;
+ inset-inline-start: var(--ad-space-xs);
+ inset-block-start: -4rem;
+ z-index: 40;
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ box-shadow: var(--ad-shadow-float);
+ transition: inset-block-start var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-skip:focus { inset-block-start: var(--ad-space-2xs); }
+
+.ad-doc-header {
+ position: sticky;
+ inset-block-start: 0;
+ z-index: 30;
+ height: 3.5rem;
+ background: color-mix(in srgb, var(--ad-color-surface-page) 92%, transparent);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-header__inner {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ height: 100%;
+ max-width: 72rem;
+ margin-inline: auto;
+ padding-inline: var(--ad-space-xs);
+}
+
+.ad-doc-wordmark {
+ display: flex;
+ align-items: baseline;
+ gap: var(--ad-space-2xs);
+ color: var(--ad-color-text-primary);
+ text-decoration: none;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ font-weight: 600;
+ letter-spacing: -0.01em;
+ white-space: nowrap;
+}
+.ad-doc-wordmark:hover { color: var(--ad-color-text-primary); }
+.ad-doc-wordmark__mark {
+ width: 1.375rem;
+ height: 1.375rem;
+ flex: none;
+ align-self: center;
+ color: var(--ad-color-accent);
+}
+.ad-doc-wordmark__version {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+
+.ad-doc-header__actions { margin-inline-start: auto; display: flex; align-items: center; gap: var(--ad-space-2xs); }
+
+.ad-doc-layout {
+ display: grid;
+ grid-template-columns: 16rem minmax(0, 1fr);
+ gap: var(--ad-space-l);
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-m) var(--ad-space-xs) var(--ad-space-2xl);
+}
+
+.ad-doc-sidebar {
+ position: sticky;
+ inset-block-start: calc(3.5rem + var(--ad-space-m));
+ align-self: start;
+ max-height: calc(100vh - 3.5rem - var(--ad-space-l));
+ overflow-y: auto;
+ padding-inline-end: var(--ad-space-2xs);
+}
+.ad-doc-nav__group + .ad-doc-nav__group { margin-top: var(--ad-space-s); }
+.ad-doc-nav__heading {
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-doc-nav__link {
+ display: block;
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ margin-inline-start: calc(var(--ad-space-xs) * -1);
+ border-inline-start: 2px solid transparent;
+ border-radius: 0 var(--ad-radius-sm) var(--ad-radius-sm) 0;
+ color: var(--ad-color-text-secondary);
+ font-size: var(--ad-step--1);
+ text-decoration: none;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-nav__link:hover { color: var(--ad-color-text-primary); background: var(--ad-color-surface-fill); }
+.ad-doc-nav__link[aria-current="page"] {
+ color: var(--ad-color-accent-ink);
+ border-inline-start-color: var(--ad-color-accent);
+ background: var(--ad-color-accent-soft);
+ font-weight: 500;
+}
+
+.ad-doc-main {
+ min-width: 0;
+ /* The design system's own body-text token (--ad-step-0, 18–20px) is right
+ for a product screen but too large for long-form documentation prose.
+ This site's own copy gets a plain, fixed 16px instead — the token above
+ is unchanged, and specimen tables/demos below still show its real
+ value. */
+ font-size: 1rem;
+}
+.ad-doc-main > * { max-width: 50rem; }
+
+.ad-doc-title { margin-bottom: var(--ad-space-xs); }
+.ad-doc-title__section {
+ display: block;
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-accent);
+}
+
+.ad-doc-main h2 {
+ margin-block: var(--ad-space-2xl) var(--ad-space-xs);
+ padding-bottom: var(--ad-space-2xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-main h3 { margin-block: var(--ad-space-l) var(--ad-space-2xs); }
+.ad-doc-main h4 { margin-block: var(--ad-space-s) var(--ad-space-2xs); }
+.ad-doc-main p,
+.ad-doc-main ul,
+.ad-doc-main ol { max-width: 68ch; }
+.ad-doc-main ul:not([class]),
+.ad-doc-main ol:not([class]) { padding-inline-start: var(--ad-space-s); }
+
+.ad-doc-footer {
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-s) var(--ad-space-xs) var(--ad-space-l);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-tertiary);
+ font-size: var(--ad-step--1);
+}
+.ad-doc-footer__inner { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); justify-content: space-between; }
+
+/* `button.ad-doc-nav-toggle` (element + class) rather than a bare class:
+ button.css's own base rule is `button:not(.icon-button)`, which is also
+ one element + one class in specificity and sets `display: inline-flex` —
+ a bare `.ad-doc-nav-toggle { display: none }` loses that fight and the
+ toggle stays visible at every width. Matching its specificity here (and
+ coming later in the concatenated stylesheet) settles it in source order. */
+button.ad-doc-nav-toggle { display: none; }
+
+@media (max-width: 60rem) {
+ .ad-doc-layout { grid-template-columns: minmax(0, 1fr); gap: var(--ad-space-s); }
+ .ad-doc-sidebar {
+ position: static;
+ max-height: none;
+ }
+ /* The toggle is a normal Button; the panel it opens gets its own card
+ treatment. Putting the same card styling on the <aside> wrapper too
+ nested a button-shaped box inside an outer box for no reason. */
+ .ad-doc-nav {
+ margin-top: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ }
+ .ad-doc-sidebar[data-collapsed="true"] > .ad-doc-nav { display: none; }
+ button.ad-doc-nav-toggle {
+ display: flex;
+ width: 100%;
+ justify-content: space-between;
+ }
+}
+
+@media (max-width: 34rem) {
+ .ad-doc-wordmark__version { display: none; }
+ .ad-doc-layout { padding-inline: var(--ad-space-2xs); }
+}
+
+/* The live-example figure used throughout the docs — see the "How the
+ example demos work" section on the Using the CSS page. One class on the
+ outer <figure>; everything inside is styled by plain element selectors,
+ since figcaption/div/details/summary/pre already say what they are.
+ `data-layout` and `data-surface` on the preview <div> distinguish the
+ handful of demos that need a non-default arrangement. */
+
+.example {
+ margin: var(--ad-space-s) 0 var(--ad-space-l);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+ background: var(--ad-color-surface-card);
+ overflow: hidden;
+}
+
+.example figcaption {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-secondary);
+}
+
+.example > div {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-s);
+}
+/* nowrap is load-bearing: a column-direction flex container with wrap enabled
+ sizes its single line against the container rather than the content, which
+ left every stacked example with a few hundred pixels of dead space. */
+.example > div[data-layout="stack"] { flex-direction: column; flex-wrap: nowrap; align-items: stretch; }
+.example > div[data-layout="grid"] {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
+ align-items: start;
+}
+.example > div[data-surface="sunken"] { background: var(--ad-color-surface-fill); }
+.example > div > * { min-width: 0; }
+
+.example details {
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+.example summary {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ color: var(--ad-color-text-secondary);
+ cursor: pointer;
+ list-style: none;
+ user-select: none;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+}
+.example summary::-webkit-details-marker { display: none; }
+.example summary::before {
+ content: "";
+ width: 0.5rem;
+ height: 0.5rem;
+ border-inline-end: 1.5px solid currentColor;
+ border-block-end: 1.5px solid currentColor;
+ rotate: -45deg;
+ transition: rotate var(--ad-dur-fast) var(--ad-ease);
+}
+.example details[open] > summary::before { rotate: 45deg; }
+.example summary:hover { color: var(--ad-color-text-primary); }
+
+.example pre {
+ margin: 0;
+ padding: var(--ad-space-xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+ tab-size: 2;
+}
+.example pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Standalone fenced code blocks in the prose (unhighlighted — there is no
+ syntax highlighter in this build any more, just Kramdown's plain
+ <pre><code class="language-*">). */
+.ad-doc-main > pre {
+ margin-block: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+}
+.ad-doc-main > pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Presentation for the token reference tables and swatch grids, plus a
+ generic data-table utility. All of this is documentation chrome — the
+ design system itself has no Table component (see the note on the removed
+ `table`/`progress`/`callout` pages in git history). */
+
+.ad-swatch-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs) var(--ad-space-s);
+ padding: 0;
+ list-style: none;
+}
+.ad-swatch {
+ display: flex;
+ flex-direction: column;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ overflow: hidden;
+ background: var(--ad-color-surface-card);
+}
+.ad-swatch__chip {
+ display: flex;
+ align-items: flex-end;
+ justify-content: flex-end;
+ height: 4rem;
+ padding: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+}
+.ad-swatch__meta {
+ display: flex;
+ flex-direction: column;
+ gap: 0.1rem;
+ padding: var(--ad-space-2xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+}
+.ad-swatch__hex { color: var(--ad-color-text-primary); text-transform: uppercase; }
+.ad-swatch__ratio { color: var(--ad-color-text-tertiary); }
+
+.ad-alias-row { display: flex; align-items: center; gap: var(--ad-space-2xs); }
+.ad-alias-chip {
+ width: 1.5rem;
+ height: 1.5rem;
+ flex: none;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+}
+.ad-alias-chip--pair { display: flex; }
+.ad-alias-chip--pair > span { flex: 1; }
+
+/* Type specimen rows. */
+.ad-specimen {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ gap: var(--ad-space-3xs);
+ padding-block: var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-specimen__meta {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-specimen__sample {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: var(--ad-color-text-primary);
+}
+
+/* Space and radius rulers. */
+.ad-ruler { display: flex; flex-direction: column; gap: var(--ad-space-2xs); }
+.ad-ruler__row { display: grid; grid-template-columns: 6.5rem 4.5rem 1fr; align-items: center; gap: var(--ad-space-xs); font-family: var(--ad-font-mono); font-size: 11px; }
+.ad-ruler__bar { height: 0.75rem; background: var(--ad-color-accent); border-radius: var(--ad-radius-sm); }
+.ad-ruler__num { color: var(--ad-color-text-tertiary); text-align: right; }
+
+.ad-radius-grid, .ad-elevation-grid, .ad-motion-grid, .ad-accent-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs);
+ padding: 0;
+ list-style: none;
+}
+.ad-radius-demo {
+ height: 4.5rem;
+ background: var(--ad-color-accent-soft);
+ border: 1px solid var(--ad-color-accent);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-elevation-demo {
+ height: 4.5rem;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-motion-demo {
+ height: 4.5rem;
+ display: grid;
+ place-items: center;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-md);
+ margin-bottom: var(--ad-space-2xs);
+ cursor: pointer;
+ overflow: hidden;
+}
+.ad-motion-demo__dot {
+ width: 1.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent);
+ translate: -3rem 0;
+ transition-property: translate;
+ transition-duration: var(--ad-dur-base);
+ transition-timing-function: var(--ad-ease);
+}
+.ad-motion-demo:hover .ad-motion-demo__dot,
+.ad-motion-demo:focus-visible .ad-motion-demo__dot { translate: 3rem 0; }
+
+.ad-accent-demo {
+ height: 4.5rem;
+ display: flex;
+ align-items: flex-end;
+ padding: var(--ad-space-2xs);
+ border-radius: var(--ad-radius-md);
+ color: #fff;
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ margin-bottom: var(--ad-space-2xs);
+}
+
+/* Token names read `<code>` sitewide, which already carries a mono chip
+ look for running prose (see :not(pre) > code in base/typography.css).
+ Inside these specimen/swatch/ruler rows the old `.ad-token-name` class was
+ plain mono text with no chip, so `<code>` is flattened back to that look
+ here the same way table cells are. */
+.ad-swatch code, .ad-specimen code, .ad-ruler code, .ad-alias-row code {
+ background: none;
+ border: 0;
+ padding: 0;
+ font-size: var(--ad-step--2);
+ color: var(--ad-color-text-primary);
+ white-space: nowrap;
+}
+
+/* A documentation aside. Like `<table>`, this is site chrome for the prose
+ itself, not a system component — the real system has no Callout. */
+.ad-callout {
+ margin-block: var(--ad-space-s);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-left: 3px solid var(--ad-color-accent);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+}
+/* Generic reference tables (component APIs, alias tables, the contrast
+ contract). `<table>` IS a table — no class needed to say so. Every table
+ on this site uses the one compact density, so there is no separate
+ modifier for it. `display: block` plus `overflow-x: auto` gives the table
+ its own horizontal scrollbar on narrow viewports without a wrapper div;
+ the anonymous table box the browser generates around `<thead>`/`<tbody>`
+ still does the row/column layout. `<code>` replaces the old
+ `.ad-token-name` / `.ad-table__code` cells — it is already styled
+ sitewide, just re-flattened here to a plain mono run instead of the
+ chip-with-border look prose code gets, to match this table's original
+ look. `.num` is the one sanctioned class left, for right-aligned numeric
+ columns — there is no attribute that means "this column is a number". */
+table { width: 100%; border-collapse: collapse; font-size: var(--ad-step--1); display: block; overflow-x: auto; }
+table th, table td {
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ text-align: left;
+ vertical-align: top;
+}
+table th { font-family: var(--ad-font-mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ad-color-text-secondary); font-weight: 500; }
+table code { background: none; border: 0; padding: 0; font-size: var(--ad-step--2); white-space: nowrap; }
+.num { text-align: right; font-variant-numeric: tabular-nums; }
+
+/* Home page. */
+.ad-hero { padding-block: var(--ad-space-m) var(--ad-space-l); }
+.ad-hero__title {
+ font-size: var(--ad-step-4);
+ margin-bottom: var(--ad-space-xs);
+}
+.ad-hero__actions { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); margin-top: var(--ad-space-s); }
+.ad-stat-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-l);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+}
+.ad-stat__value {
+ display: block;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-2);
+ font-weight: 600;
+ line-height: 1.16;
+ font-variant-numeric: tabular-nums;
+}
+.ad-stat__label {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+
+/* Card chrome. The pane supplies the frame; this just insets the page. */
+body { padding: var(--ad-space-l) var(--ad-space-m); }
+.ad-doc-main { max-width: 50rem; margin-inline: auto; }
+.ad-doc-main > * { max-width: none; }
+</style>
+</head>
+<body>
+<main class="ad-doc-main">
+<header class="ad-doc-title"><span class="ad-doc-title__section">Components</span>
+ <h1>Checkbox</h1>
+</header>
+<p class="ad-lead">Native checkbox, tinted with the accent color. No hand-drawn box.</p>
+<h2 id="basic">Basic</h2>
+
+<p><code>input[type="checkbox"]</code> is the component — styled bare with <code>accent-color</code>
+rather than a custom box, so the browser draws its own check. This is a real
+reduction, not just a rename: it deletes the hand-drawn SVG-checkmark markup
+that a <code>.ad-choice__box</code> span used to need. The wrapping <code><label></code> is told
+apart from Field’s label structurally — it <code>:has()</code> a checkbox as a direct
+child — so it needs no class either.</p>
+
+<figure class="example">
+<figcaption>Checkbox</figcaption>
+<div>
+<label>
+ <input type="checkbox" />
+ Notify me
+</label>
+<label>
+ <input type="checkbox" checked="" />
+ Ship on merge
+</label>
+</div>
+<details open=""><summary>Markup</summary>
+<pre><code><label>
+ <input type="checkbox" />
+ Notify me
+</label>
+<label>
+ <input type="checkbox" checked="" />
+ Ship on merge
+</label></code></pre>
+</details>
+</figure>
+
+<h2 id="disabled">Disabled</h2>
+
+<figure class="example">
+<figcaption>Disabled</figcaption>
+<div>
+<label>
+ <input type="checkbox" disabled="" />
+ Not available on this plan
+</label>
+</div>
+<details><summary>Markup</summary>
+<pre><code><label>
+ <input type="checkbox" disabled="" />
+ Not available on this plan
+</label></code></pre>
+</details>
+</figure>
+
+<h2 id="in-a-fieldset">In a fieldset</h2>
+
+<p><code><fieldset></code>/<code><legend></code> are styled bare too — they already mean this.</p>
+
+<figure class="example">
+<figcaption>Fieldset</figcaption>
+<div>
+<fieldset>
+ <legend>Notify me about</legend>
+ <label>
+ <input type="checkbox" checked="" />
+ Overdue tasks
+ </label>
+ <label>
+ <input type="checkbox" />
+ Weekly digest
+ </label>
+</fieldset>
+</div>
+<details><summary>Markup</summary>
+<pre><code><fieldset>
+ <legend>Notify me about</legend>
+ <label>
+ <input type="checkbox" checked="" />
+ Overdue tasks
+ </label>
+ <label>
+ <input type="checkbox" />
+ Weekly digest
+ </label>
+</fieldset></code></pre>
+</details>
+</figure>
+
+<h2 id="api">API</h2>
+
+<table>
+ <thead><tr><th scope="col">Element / attribute</th><th scope="col">Values</th><th scope="col">Notes</th></tr></thead>
+ <tbody>
+ <tr><td><code>input[type="checkbox"]</code></td><td></td><td>Required base — no class. Tinted with <code>accent-color</code>.</td></tr>
+ <tr><td><code>checked</code></td><td>boolean</td><td></td></tr>
+ <tr><td><code>disabled</code></td><td>boolean</td><td></td></tr>
+ <tr><td><code>label</code></td><td>text content</td><td>Wrap the input directly — <code>label:has(> input[type="checkbox"])</code> is what applies the row layout.</td></tr>
+ </tbody>
+</table>
+</main>
+<script>
+// Behavior for the pieces of the system that need it. Everything else is
+// CSS and semantic HTML on purpose — a design system that only works with
+// JavaScript running is a component library with extra steps.
+(function () {
+ "use strict";
+
+ var STORAGE_KEY = "ad-theme";
+
+ function readStored() {
+ try {
+ return localStorage.getItem(STORAGE_KEY);
+ } catch (error) {
+ return null;
+ }
+ }
+
+ function writeStored(value) {
+ try {
+ localStorage.setItem(STORAGE_KEY, value);
+ } catch (error) {
+ /* Nothing to do — the media query still gives a sensible default. */
+ }
+ }
+
+ function systemPrefersDark() {
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
+ }
+
+ function currentTheme() {
+ var explicit = document.documentElement.getAttribute("data-theme");
+ if (explicit) return explicit;
+ return systemPrefersDark() ? "dark" : "light";
+ }
+
+ function paintToggle(theme) {
+ document.querySelectorAll("[data-theme-toggle]").forEach(function (toggle) {
+ var next = theme === "dark" ? "light" : "dark";
+ toggle.querySelectorAll("[data-theme-icon]").forEach(function (icon) {
+ icon.hidden = icon.dataset.themeIcon !== theme;
+ });
+ toggle.setAttribute("aria-label", "Switch to the " + next + " theme");
+ toggle.setAttribute("title", "Switch to the " + next + " theme");
+ var label = toggle.querySelector("[data-theme-label]");
+ if (label) label.textContent = "Switch to the " + next + " theme";
+ });
+ }
+
+ function setUpTheme() {
+ paintToggle(currentTheme());
+
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-theme-toggle]");
+ if (!toggle) return;
+
+ var next = currentTheme() === "dark" ? "light" : "dark";
+ document.documentElement.setAttribute("data-theme", next);
+ writeStored(next);
+ paintToggle(next);
+ });
+
+ // Follow the OS while the reader has not made an explicit choice.
+ window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", function () {
+ if (!readStored()) paintToggle(currentTheme());
+ });
+ }
+
+ // Tabs, wired to the WAI-ARIA authoring practices: arrows move, Home/End
+ // jump, and only the selected tab stays in the tab order.
+ function setUpTabs() {
+ document.querySelectorAll("[data-tabs]").forEach(function (root) {
+ var tabs = Array.prototype.slice.call(root.querySelectorAll('[role="tab"]'));
+ if (!tabs.length) return;
+
+ function select(tab, focus) {
+ tabs.forEach(function (candidate) {
+ var selected = candidate === tab;
+ candidate.setAttribute("aria-selected", String(selected));
+ candidate.tabIndex = selected ? 0 : -1;
+ var panel = document.getElementById(candidate.getAttribute("aria-controls"));
+ if (panel) panel.hidden = !selected;
+ });
+ if (focus) tab.focus();
+ }
+
+ root.addEventListener("click", function (event) {
+ var tab = event.target.closest('[role="tab"]');
+ if (tab) select(tab, false);
+ });
+
+ root.addEventListener("keydown", function (event) {
+ var index = tabs.indexOf(document.activeElement);
+ if (index === -1) return;
+
+ var next = null;
+ if (event.key === "ArrowRight") next = tabs[(index + 1) % tabs.length];
+ else if (event.key === "ArrowLeft") next = tabs[(index - 1 + tabs.length) % tabs.length];
+ else if (event.key === "Home") next = tabs[0];
+ else if (event.key === "End") next = tabs[tabs.length - 1];
+
+ if (next) {
+ event.preventDefault();
+ select(next, true);
+ }
+ });
+
+ select(tabs.filter(function (tab) {
+ return tab.getAttribute("aria-selected") === "true";
+ })[0] || tabs[0], false);
+ });
+ }
+
+ function setUpNavToggle() {
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-nav-toggle]");
+ if (!toggle) return;
+
+ var sidebar = toggle.closest("[data-sidebar]");
+ var collapsed = sidebar.dataset.collapsed !== "false";
+ sidebar.dataset.collapsed = String(!collapsed);
+ toggle.setAttribute("aria-expanded", String(collapsed));
+ });
+ }
+
+ setUpTheme();
+ setUpTabs();
+ setUpNavToggle();
+})();
+
+</script>
+</body>
+</html>
diff --git a/design-bundle/components/dialog.html b/design-bundle/components/dialog.html
new file mode 100644
index 0000000..24b57cd
--- /dev/null
+++ b/design-bundle/components/dialog.html
@@ -0,0 +1,1825 @@
+<!-- @dsCard group="Feedback" -->
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Dialog</title>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&display=swap">
+<style>
+/* Arbitrary Definitions — design tokens. Hand-maintained; edit values directly. */
+
+:root {
+ /* Palette ramps — reference only. Components use the aliases below. */
+ --ad-neutral-0: #faf9f7;
+ --ad-neutral-50: #fdfdfb;
+ --ad-neutral-100: #f1f0ed;
+ --ad-neutral-150: #e8e6e2;
+ --ad-neutral-200: #dcdad7;
+ --ad-neutral-300: #c0bdb9;
+ --ad-neutral-500: #888681;
+ --ad-neutral-600: #5f5d59;
+ --ad-neutral-800: #2a2926;
+ --ad-neutral-900: #100f0d;
+ --ad-neutral-dark-0: #0e0d0b;
+ --ad-neutral-dark-50: #151412;
+ --ad-neutral-dark-100: #1e1d1a;
+ --ad-neutral-dark-150: #252421;
+ --ad-neutral-dark-200: #2c2b29;
+ --ad-neutral-dark-300: #44423f;
+ --ad-neutral-dark-500: #7c7a76;
+ --ad-neutral-dark-600: #a09e9a;
+ --ad-neutral-dark-800: #d9d7d4;
+ --ad-neutral-dark-900: #e9e8e6;
+ --ad-accent-teal: #3a6a63;
+ --ad-accent-clay: #9a5a3c;
+ --ad-accent-ink-blue: #2b4a78;
+ --ad-accent-ochre: #93701f;
+ --ad-accent-plum: #6a3a5c;
+
+ /* Surfaces */
+ --ad-color-surface-page: #faf9f7;
+ --ad-color-surface-card: #fdfdfb;
+ --ad-color-surface-fill: #f1f0ed;
+ --ad-color-surface-fill-2: #e8e6e2;
+
+ /* Borders */
+ --ad-color-border-hairline: #dcdad7;
+ --ad-color-border-default: #c0bdb9;
+
+ /* Text */
+ --ad-color-text-primary: #100f0d;
+ --ad-color-text-secondary: #5f5d59;
+ --ad-color-text-tertiary: #888681;
+
+ /* Accent */
+ --ad-color-accent: #3a6a63;
+ --ad-color-accent-fg: #ffffff;
+ --ad-color-accent-ink: color-mix(in oklch, var(--ad-color-accent) 78%, black);
+ --ad-color-accent-soft: color-mix(in oklch, var(--ad-color-accent) 12%, white);
+
+ /* Status (fixed — never swapped like the accent) */
+ --ad-color-success: #3f7a4a;
+ --ad-color-success-bg: color-mix(in oklch, var(--ad-color-success) 12%, white);
+ --ad-color-danger: #ad3a2c;
+ --ad-color-danger-bg: color-mix(in oklch, var(--ad-color-danger) 10%, white);
+
+ /* Type families */
+ --ad-font-sans: -apple-system, "Helvetica Neue", Helvetica, Arial, system-ui, sans-serif;
+ --ad-font-mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, Consolas, "Courier New", monospace;
+ --ad-font-serif: Georgia, "Times New Roman", "Iowan Old Style", serif;
+
+ /* Type steps — fluid (Utopia scale), the design system's own body-text size */
+ --ad-step--2: clamp(0.7813rem, 0.7736rem + 0.0341vw, 0.8rem);
+ --ad-step--1: clamp(0.9375rem, 0.9119rem + 0.1136vw, 1rem);
+ --ad-step-0: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-step-1: clamp(1.35rem, 1.2631rem + 0.3864vw, 1.5625rem);
+ --ad-step-2: clamp(1.62rem, 1.4837rem + 0.6057vw, 1.9531rem);
+ --ad-step-3: clamp(1.944rem, 1.7405rem + 0.9044vw, 2.4414rem);
+ --ad-step-4: clamp(2.3328rem, 2.0387rem + 1.3072vw, 3.0518rem);
+ --ad-step-5: clamp(2.7994rem, 2.384rem + 1.8461vw, 3.8147rem);
+
+ /* Space */
+ --ad-space-3xs: clamp(0.3125rem, 0.3125rem + 0vw, 0.3125rem);
+ --ad-space-2xs: clamp(0.5625rem, 0.5369rem + 0.1136vw, 0.625rem);
+ --ad-space-xs: clamp(0.875rem, 0.8494rem + 0.1136vw, 0.9375rem);
+ --ad-space-s: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-space-m: clamp(1.6875rem, 1.6108rem + 0.3409vw, 1.875rem);
+ --ad-space-l: clamp(2.25rem, 2.1477rem + 0.4545vw, 2.5rem);
+ --ad-space-xl: clamp(3.375rem, 3.2216rem + 0.6818vw, 3.75rem);
+ --ad-space-2xl: clamp(4.5rem, 4.2955rem + 0.9091vw, 5rem);
+ --ad-space-3xl: clamp(6.75rem, 6.4432rem + 1.3636vw, 7.5rem);
+
+ /* Radius */
+ --ad-radius-sm: 4px;
+ --ad-radius-md: 6px;
+ --ad-radius-lg: 10px;
+ --ad-radius-full: 999px;
+ --ad-border-w: 1px;
+
+ /* Elevation */
+ --ad-shadow-sm: 0 1px 2px rgba(20, 18, 14, 0.05);
+ --ad-shadow-float: 0 2px 6px rgba(20, 18, 14, 0.06), 0 10px 24px -12px rgba(20, 18, 14, 0.18);
+
+ /* Motion */
+ --ad-dur-fast: 0.12s;
+ --ad-dur-base: 0.16s;
+ --ad-ease: cubic-bezier(0.2, 0, 0, 1);
+}
+
+@media (prefers-color-scheme: dark) {
+ :root:not([data-theme="light"]) {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+ }
+}
+
+:root[data-theme="dark"] {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ :root {
+ --ad-dur-fast: 0ms;
+ --ad-dur-base: 0ms;
+ }
+}
+
+/* A short reset. Everything it sets is something the system would otherwise
+ have to override in every component.
+
+ The rem values used for this documentation site's own chrome (measure,
+ content width, sidebar width, header height) belong to this site, not to
+ the design system it documents — the source design system defines no
+ container-width tokens at all, so there is nothing under `--ad-*` to reach
+ for here. See the header comment in site/shell.css. */
+
+*,
+*::before,
+*::after { box-sizing: border-box; }
+
+html {
+ -webkit-text-size-adjust: 100%;
+ scroll-behavior: smooth;
+ scroll-padding-top: calc(3.5rem + var(--ad-space-s));
+}
+
+@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;
+ }
+}
+
+body {
+ margin: 0;
+ background: var(--ad-color-surface-page);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ line-height: 1.55;
+ font-synthesis-weight: none;
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+}
+
+::selection {
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+}
+
+h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd { margin: 0; }
+ul[class], ol[class] { list-style: none; margin: 0; padding: 0; }
+
+img, svg, video { display: block; max-width: 100%; height: auto; }
+
+button, input, select, textarea {
+ font: inherit;
+ color: inherit;
+}
+
+hr {
+ height: 1px;
+ margin: var(--ad-space-l) 0;
+ border: 0;
+ background: var(--ad-color-border-hairline);
+}
+
+/* One focus ring, defined once. No component is allowed its own. */
+:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+ border-radius: var(--ad-radius-sm);
+}
+:focus:not(:focus-visible) { outline: none; }
+
+[hidden] { display: none !important; }
+
+.ad-visually-hidden {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip-path: inset(50%);
+ white-space: nowrap;
+ border: 0;
+}
+
+/* Every rule here reads a type token for family and size. Weight, line-height,
+ and letter-spacing are hardcoded to the exact numbers the source ships —
+ there is no `--ad-weight-*` / `--ad-leading-*` / `--ad-tracking-*` scale in
+ the real system, only eight fixed compound styles (see the Typography
+ foundation page, which this file implements verbatim). */
+
+.type-display { font: 600 var(--ad-step-4)/1.08 var(--ad-font-sans); letter-spacing: -0.015em; }
+.type-h1, h1 { font: 600 var(--ad-step-3)/1.12 var(--ad-font-sans); letter-spacing: -0.012em; }
+.type-h2, h2 { font: 600 var(--ad-step-2)/1.16 var(--ad-font-sans); letter-spacing: -0.01em; }
+.type-h3, h3 { font: 600 var(--ad-step-1)/1.25 var(--ad-font-sans); }
+.type-body { font: 400 var(--ad-step-0)/1.55 var(--ad-font-sans); }
+.type-body-sm { font: 400 var(--ad-step--1)/1.5 var(--ad-font-sans); }
+.type-label {
+ font: 500 var(--ad-step--2)/1 var(--ad-font-mono);
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: var(--ad-color-text-secondary);
+}
+.type-mono { font: 400 var(--ad-step--1)/1.5 var(--ad-font-mono); }
+
+/* Prose defaults for the documentation body copy. These read the same
+ tokens the compound classes above do; they are not a second scale. */
+h1, h2, h3, h4 { font-weight: 600; text-wrap: balance; }
+
+h4 { font: 600 var(--ad-step-0)/1.3 var(--ad-font-sans); }
+
+p, ul, ol { margin: 0 0 var(--ad-space-s); }
+li + li { margin-top: var(--ad-space-3xs); }
+
+a {
+ color: var(--ad-color-accent);
+ text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
+ text-underline-offset: 0.18em;
+ text-decoration-thickness: 1px;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ text-decoration-color var(--ad-dur-fast) var(--ad-ease);
+}
+a:hover {
+ color: var(--ad-color-accent-ink);
+ text-decoration-color: currentColor;
+}
+
+strong, b { font-weight: 600; }
+
+small { font-size: var(--ad-step--1); }
+
+.ad-lead {
+ font-size: var(--ad-step-1);
+ line-height: 1.5;
+ color: var(--ad-color-text-secondary);
+ max-width: 68ch;
+ text-wrap: pretty;
+}
+
+.ad-muted { color: var(--ad-color-text-secondary); }
+.ad-subtle { color: var(--ad-color-text-tertiary); }
+
+code, kbd, samp, pre {
+ font-family: var(--ad-font-mono);
+ font-feature-settings: "liga" 0;
+}
+
+:not(pre) > code {
+ padding: 0.15em 0.4em;
+ font-size: 0.875em;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-sm);
+ white-space: nowrap;
+}
+
+kbd {
+ display: inline-block;
+ padding: 0.1em 0.45em;
+ font-size: var(--ad-step--2);
+ line-height: 1.6;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-bottom-width: 2px;
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-secondary);
+}
+
+blockquote {
+ margin-block: var(--ad-space-s);
+ padding-left: var(--ad-space-s);
+ border-left: 2px solid var(--ad-color-accent);
+ color: var(--ad-color-text-secondary);
+}
+
+/* Button
+ A flat, quiet button that recedes rather than performs — no gradients, no
+ shine. Variants: default, primary, dark, ghost, danger. Sizes: sm, md
+ (default), lg — padding and font step only, weight and radius stay put.
+
+ The contract is the element itself: a bare <button> is styled directly, no
+ class required. An <a> that acts like a button (navigates rather than
+ changes something — see the Button page's "Links that act like buttons")
+ opts in with a single `.btn` class, since a bare `a` selector would catch
+ every ordinary prose link on the site. `:not(.icon-button)` keeps this
+ block from leaking onto IconButton, which shares the <button> tag but is
+ an entirely different shape. */
+
+button:not(.icon-button),
+a.btn {
+ --_bg: var(--ad-color-surface-card);
+ --_fg: var(--ad-color-text-primary);
+ --_border: var(--ad-color-border-default);
+ --_pad: var(--ad-space-xs) var(--ad-space-s);
+ --_font: var(--ad-step--1);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: var(--ad-space-2xs);
+ padding: var(--_pad);
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-sans);
+ font-size: var(--_font);
+ font-weight: 550;
+ letter-spacing: -0.01em;
+ line-height: 1.2;
+ text-align: center;
+ text-decoration: none;
+ white-space: nowrap;
+ cursor: pointer;
+ transition: filter var(--ad-dur-fast) var(--ad-ease),
+ border-color var(--ad-dur-fast) var(--ad-ease);
+}
+
+:is(button:not(.icon-button), a.btn):hover:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.96); }
+:is(button:not(.icon-button), a.btn):active:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.92); }
+
+button:not(.icon-button):disabled,
+:is(button:not(.icon-button), a.btn)[aria-disabled="true"] {
+ opacity: 0.5;
+ cursor: default;
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="primary"] {
+ --_bg: var(--ad-color-accent);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-accent);
+}
+
+/* Reserved for the single most-committing action in a view — a final Save,
+ never a whole row of them. */
+:is(button:not(.icon-button), a.btn)[data-variant="dark"] {
+ --_bg: var(--ad-color-text-primary);
+ --_fg: var(--ad-color-surface-page);
+ --_border: var(--ad-color-text-primary);
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="ghost"] {
+ --_bg: transparent;
+ --_fg: var(--ad-color-text-primary);
+ --_border: transparent;
+}
+
+/* The source hardcodes this label white rather than deriving it — danger
+ is not "on a filled accent surface", so --ad-color-accent-fg is reused
+ here only because its resolved value is the same literal white, not
+ because the two roles are the same thing. */
+:is(button:not(.icon-button), a.btn)[data-variant="danger"] {
+ --_bg: var(--ad-color-danger);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-danger);
+}
+
+:is(button:not(.icon-button), a.btn)[data-size="sm"] { --_pad: 0.375rem 0.75rem; --_font: 0.8125rem; }
+:is(button:not(.icon-button), a.btn)[data-size="lg"] { --_pad: 0.75rem 1.375rem; --_font: var(--ad-step-0); }
+
+:is(button:not(.icon-button), a.btn)[data-block] { width: 100%; }
+
+/* Button group — [role="group"] is already the correct ARIA role for a
+ toolbar of related buttons, so it doubles as the styling hook for free. */
+[role="group"] {
+ display: inline-flex;
+ isolation: isolate;
+}
+[role="group"] > button { border-radius: 0; }
+[role="group"] > button:first-child { border-start-start-radius: var(--ad-radius-md); border-end-start-radius: var(--ad-radius-md); }
+[role="group"] > button:last-child { border-start-end-radius: var(--ad-radius-md); border-end-end-radius: var(--ad-radius-md); }
+[role="group"] > button + button { margin-inline-start: -1px; }
+[role="group"] > button:hover,
+[role="group"] > button:focus-visible { z-index: 1; }
+
+/* IconButton — a square icon-only button. Always needs an aria-label; it
+ becomes both the accessible name and the native tooltip. There is no
+ attribute that distinguishes "a button that is only an icon" from a plain
+ Button, so this is the one sanctioned class in this file. */
+.icon-button {
+ --_size: 2.125rem;
+ --_bg: transparent;
+ --_border: transparent;
+ --_fg: var(--ad-color-text-secondary);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ padding: 0;
+ flex: none;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease),
+ color var(--ad-dur-fast) var(--ad-ease);
+}
+.icon-button:hover:not(:disabled) { background: var(--ad-color-surface-fill-2); }
+.icon-button:disabled { opacity: 0.5; cursor: default; }
+
+.icon-button[data-size="sm"] { --_size: 1.75rem; }
+.icon-button[data-size="lg"] { --_size: 2.5rem; }
+
+.icon-button[data-variant="outline"] { --_bg: var(--ad-color-surface-card); --_border: var(--ad-color-border-default); --_fg: var(--ad-color-text-primary); }
+.icon-button[data-variant="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); }
+
+/* Field
+ The labeled field wrapper — a <label> that wraps its own caption text
+ together with the control, so the association is native and needs no
+ `for`/`id` pair. The caption is mono, uppercase, tracked — the same
+ catalog-label convention as Badge and type-label.
+
+ A <label> is reused for three different shapes across this system (this
+ wrapper, Checkbox/Radio's row, Switch's row), so it cannot be styled bare
+ without one shape leaking into another. Field is the one of the three with
+ no distinguishing attribute of its own, so it gets the sanctioned class;
+ Checkbox/Radio tell themselves apart structurally instead (see below). */
+.field {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ min-width: 0;
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Hint and error read the native fine-print element. `role="alert"` is a
+ real live-region role for validation text, so it doubles as the selector
+ that turns a hint red. */
+.field > small {
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+.field > small[role="alert"] { color: var(--ad-color-danger); }
+
+/* Input / Select — bordered, matched to each other exactly. Both tags ARE
+ the component, so they are styled bare; nothing else on this site uses a
+ text input or a select. */
+input:not([type="checkbox"]):not([type="radio"]),
+select {
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ outline: none;
+ transition: border-color var(--ad-dur-fast) var(--ad-ease),
+ box-shadow var(--ad-dur-fast) var(--ad-ease);
+}
+input::placeholder { color: var(--ad-color-text-tertiary); }
+
+input:not([type="checkbox"]):not([type="radio"]):focus,
+select:focus {
+ border-color: var(--ad-color-accent);
+ box-shadow: 0 0 0 2px var(--ad-color-accent-soft);
+}
+
+input:not([type="checkbox"]):not([type="radio"]):disabled,
+select:disabled {
+ opacity: 0.5;
+ cursor: default;
+}
+
+/* The chevron is the one flagged color exception in this component set:
+ CSS cannot point a data-URI background-image at a custom property, so its
+ stroke is a literal hex rather than a var(--ad-*) reference. It is picked
+ to match --ad-color-text-secondary and commented here so it is not
+ mistaken for a stray color. */
+select {
+ appearance: none;
+ padding-inline-end: var(--ad-space-l);
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%235f5d59' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right var(--ad-space-xs) center;
+}
+
+input[aria-invalid="true"],
+select[aria-invalid="true"] {
+ border-color: var(--ad-color-danger);
+ background-color: var(--ad-color-danger-bg);
+}
+
+/* Checkbox / Radio — the real native controls, tinted with accent-color
+ instead of a hand-drawn box. This is not just a rename: it deletes the
+ custom SVG-checkmark markup entirely and lets the browser draw its own
+ (platform-appropriate) check and dot. `role="switch"` is excluded here so
+ Switch — a checkbox playing a different role — can style itself below
+ without this rule fighting it. A <label> wrapping a checkbox/radio is told
+ apart from Field's <label> structurally: it :has() one as a direct child,
+ no class required. */
+input[type="checkbox"]:not([role="switch"]),
+input[type="radio"] {
+ width: 1.125rem;
+ height: 1.125rem;
+ margin: 0;
+ accent-color: var(--ad-color-accent);
+ cursor: pointer;
+}
+input[type="checkbox"]:not([role="switch"]):focus-visible,
+input[type="radio"]:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+}
+
+label:has(> input[type="checkbox"]:not([role="switch"])),
+label:has(> input[type="radio"]) {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 400;
+ text-transform: none;
+ letter-spacing: normal;
+ color: var(--ad-color-text-primary);
+}
+label:has(> input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Switch — for on/off settings only, never a substitute for Checkbox.
+ `input[type="checkbox"][role="switch"]` is real ARIA (the correct role for
+ a toggle), not an invented hook, but the visual track+thumb needs a second
+ element for the thumb's independent position and transition — more than
+ `appearance: none` plus pseudo-elements on the input itself can cleanly
+ carry — so `.switch` is a deliberate, documented fallback class rather
+ than a first choice. */
+.switch {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ color: var(--ad-color-text-primary);
+}
+.switch input[role="switch"] { position: absolute; opacity: 0; width: 2.25rem; height: 1.25rem; margin: 0; }
+
+.switch span {
+ position: relative;
+ flex: none;
+ width: 2.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-border-default);
+ transition: background-color var(--ad-dur-base) var(--ad-ease);
+}
+.switch span::after {
+ content: "";
+ position: absolute;
+ inset-block-start: 2px;
+ inset-inline-start: 2px;
+ width: 1rem;
+ height: 1rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-fg);
+ box-shadow: var(--ad-shadow-sm);
+ transition: translate var(--ad-dur-base) var(--ad-ease);
+}
+.switch input[role="switch"]:checked + span { background: var(--ad-color-accent); }
+.switch input[role="switch"]:checked + span::after { translate: 1rem 0; }
+.switch input[role="switch"]:focus-visible + span { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+.switch:has(input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Rows of fields — used by the settings-form pattern. `<form>`/`<fieldset>`
+ are the elements that already mean this, so they are styled bare. A row
+ grouping two fields side by side has no element of its own, so it keeps
+ one plain class. */
+form {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-s);
+ max-width: 28rem;
+}
+.form-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
+ gap: var(--ad-space-xs);
+}
+fieldset {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ margin: 0;
+ padding: 0;
+ border: 0;
+}
+fieldset > legend {
+ padding: 0 0 var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Badge
+ Mono, uppercase, tracked — the catalog-label convention for status and
+ meta values (SAVED, OVERDUE, RECOMMENDED). A label, never a button.
+
+ A <span> is too generic to style globally — it is used all over running
+ prose for unrelated reasons — so Badge gets the one sanctioned class this
+ shape needs. Tone is an attribute, not a modifier class. */
+
+.badge {
+ --_bg: var(--ad-color-surface-fill);
+ --_fg: var(--ad-color-text-secondary);
+ --_border: var(--ad-color-border-hairline);
+
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.1875rem 0.5rem;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-sm);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ line-height: 1;
+ white-space: nowrap;
+}
+
+.badge[data-tone="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); --_border: transparent; }
+.badge[data-tone="success"] { --_bg: var(--ad-color-success-bg); --_fg: var(--ad-color-success); --_border: transparent; }
+.badge[data-tone="danger"] { --_bg: var(--ad-color-danger-bg); --_fg: var(--ad-color-danger); --_border: transparent; }
+
+.badge-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-2xs);
+}
+
+/* Tag
+ A removable mono chip for freeform tags — locations, categories.
+ `[data-dashed]` renders an "add tag" affordance instead of a value. Same
+ reasoning as Badge: <span> is too generic to select bare, so Tag gets one
+ class. The remove control is a real <button>, styled through the
+ descendant selector rather than a class of its own. */
+
+.tag {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.375rem;
+ padding: 0.25rem 0.625rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-default);
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ line-height: 1;
+}
+
+.tag[data-dashed] {
+ background: transparent;
+ border-style: dashed;
+ color: var(--ad-color-text-tertiary);
+ cursor: pointer;
+}
+
+.tag button {
+ all: unset;
+ cursor: pointer;
+ color: var(--ad-color-text-tertiary);
+ line-height: 1;
+ font-size: 0.75rem;
+}
+.tag button:hover { color: var(--ad-color-text-primary); }
+
+.tag-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.375rem;
+}
+
+/* Card
+ Flat surface, hairline border, radius-lg. `[data-elevated]` is opt-in and
+ reserved for the one focused/primary card in a view — a dialog, the
+ primary capture card — flat is the default everywhere else.
+
+ A <div>/<article> is too generic to select bare, so Card keeps one
+ sanctioned class. When Card's job is holding a list of rows — the common
+ case, entry lists, task lists, settings rows — it is a real <ul class="
+ card">, and CardRow is a bare <li> underneath it: no second class needed,
+ the row look comes from the `.card > li` descendant selector alone. The
+ first row's top border is removed automatically so callers never have to
+ override it by hand. */
+
+.card {
+ display: flex;
+ flex-direction: column;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: none;
+ overflow: hidden;
+}
+
+.card[data-elevated] { box-shadow: var(--ad-shadow-float); }
+
+.card > li {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+}
+.card > li:first-child { border-top: none; }
+
+/* A row whose ENTIRE content is wrapped in a real <button> or <a> — the
+ accessible way to make a row clickable — fills the row edge-to-edge and
+ picks up a hover fill and pointer cursor. `:only-child` is what tells this
+ apart from a row that merely contains a small button among other content
+ (a leading checkbox next to a title and a trailing value, say): only a
+ button/anchor that IS the row's one and only child means "the whole row
+ is the control." */
+.card > li:has(> button:only-child),
+.card > li:has(> a:only-child) {
+ padding: 0;
+}
+.card > li > button:only-child,
+.card > li > a:only-child {
+ all: unset;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ box-sizing: border-box;
+ color: inherit;
+ font: inherit;
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.card > li > button:only-child:hover,
+.card > li > a:only-child:hover { background: var(--ad-color-surface-fill); }
+
+/* Documentation-only extras: a body/footer pair and a responsive grid, used
+ by the prose pages (rationale.md, index.md) to show a Card as a plain
+ content tile rather than a data row. Neither is part of Card's props —
+ they exist because a content tile's inner layout has no element of its
+ own to hang a selector on. */
+.card-body {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-m);
+}
+.card-footer {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ margin-top: auto;
+ padding: var(--ad-space-xs) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+
+.card[data-link] { transition: border-color var(--ad-dur-fast) var(--ad-ease), box-shadow var(--ad-dur-fast) var(--ad-ease); }
+.card[data-link]:hover { border-color: var(--ad-color-border-default); box-shadow: var(--ad-shadow-sm); }
+.card[data-link]:focus-within { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+
+.card-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
+ gap: var(--ad-space-s);
+}
+
+/* Avatar — lives here because it almost never appears outside a card or a
+ row (the Loop dashboard's initials chip, Archive's file-type chip). A
+ <span> is too generic for a bare selector, so it keeps one class. */
+.avatar {
+ --_size: 1.875rem;
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ flex: none;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 600;
+ overflow: hidden;
+}
+.avatar[data-square] { border-radius: var(--ad-radius-md); background: var(--ad-color-surface-fill); color: var(--ad-color-text-secondary); border: 1px solid var(--ad-color-border-hairline); }
+.avatar[data-size="sm"] { --_size: 1.5rem; }
+.avatar[data-size="lg"] { --_size: 2.25rem; }
+
+/* Tooltip — a dark mono label on hover, positioned above the child. Pure CSS:
+ the trigger and bubble are siblings inside one relatively-positioned span,
+ shown on :hover/:focus-within so it works from the keyboard too.
+
+ `[role="tooltip"]` is the correct ARIA role for the bubble itself, so it
+ doubles as the styling hook — zero classes there. The wrapping span has no
+ attribute of its own, but it does not need one: `:has()` picks out "a span
+ that contains a [role=tooltip] bubble" precisely, without reaching for a
+ class on an otherwise-generic element. */
+span:has(> [role="tooltip"]) {
+ position: relative;
+ display: inline-flex;
+}
+
+[role="tooltip"] {
+ position: absolute;
+ bottom: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ margin-bottom: 0.375rem;
+ padding: 0.25rem 0.5rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-text-primary);
+ color: var(--ad-color-surface-page);
+ font-family: var(--ad-font-mono);
+ /* 11px is the source's own literal choice — smaller than --ad-step--2, the
+ smallest step on the scale, because a tooltip is meant to read as a
+ glance-only aside rather than body text. */
+ font-size: 11px;
+ white-space: nowrap;
+ line-height: 1.4;
+ z-index: 20;
+ opacity: 0;
+ visibility: hidden;
+ pointer-events: none;
+ transition: opacity var(--ad-dur-fast) var(--ad-ease);
+}
+span:has(> [role="tooltip"]):hover > [role="tooltip"],
+span:has(> [role="tooltip"]):focus-within > [role="tooltip"] {
+ opacity: 1;
+ visibility: visible;
+}
+
+/* Toast — the real pattern used for delete confirmations: a flat bar with an
+ optional destructive action and a dismiss. `[data-fixed]` pins it
+ bottom-right for real use; without it the toast stays in document flow for
+ documentation. `role="status"` is the correct live-region role for a
+ transient status message, so it is both the accessibility hook and the
+ style selector — no base class needed. The action and dismiss buttons are
+ plain <button>s told apart by `aria-label="Dismiss"`, an attribute they
+ need anyway for the icon-only close control. */
+[role="status"] {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ min-width: 20rem;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+}
+[role="status"][data-fixed] {
+ position: fixed;
+ inset-block-end: var(--ad-space-s);
+ inset-inline-end: var(--ad-space-s);
+ z-index: 60;
+}
+[role="status"] > span { flex: 1; }
+[role="status"] button {
+ all: unset;
+ cursor: pointer;
+ font-weight: 600;
+ color: var(--ad-color-danger);
+}
+[role="status"] button[aria-label="Dismiss"] {
+ color: var(--ad-color-text-tertiary);
+ font-size: 1rem;
+ font-weight: 400;
+ line-height: 1;
+}
+[role="status"] button[aria-label="Dismiss"]:hover { color: var(--ad-color-text-primary); }
+
+/* Dialog — the native <dialog> element: a centered modal, flat surface, no
+ colored header, hairline dividers between title/body/footer. A <header>
+ holds the title and a <footer> holds the actions, matching the elements
+ the tag already wants; everything else in between is the body, styled by
+ excluding those two rather than by a class of its own.
+
+ `.dialog-demo` scopes a static, always-open example to its own preview
+ container instead of the viewport, so it can sit safely inside a
+ documentation page — this is a doc-only affordance, not part of Dialog's
+ contract. It relies on the browser's own dialog centering: the UA
+ stylesheet already gives an open <dialog> `position: absolute; inset: 0;
+ margin: auto`, which centers it inside the nearest positioned ancestor —
+ here, `.dialog-demo` itself. A real `.showModal()` call gets the genuine
+ viewport-fixed overlay and `::backdrop` for free; a plain `open` attribute
+ (used here so the demo renders without JavaScript) does not paint a
+ backdrop at all, since `::backdrop` only exists for a dialog shown
+ modally — wire the real open/close state and call `.showModal()` in the
+ consuming app. */
+.dialog-demo {
+ position: relative;
+ min-height: 20rem;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-lg);
+}
+
+dialog {
+ width: 26rem;
+ max-width: calc(100% - (2 * var(--ad-space-s)));
+ height: fit-content;
+ padding: 0;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ overflow: hidden;
+}
+dialog::backdrop {
+ background: rgba(20, 18, 14, 0.35);
+}
+dialog > header {
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-sans);
+ font-weight: 600;
+ font-size: var(--ad-step-0);
+ color: var(--ad-color-text-primary);
+}
+dialog > footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+}
+dialog > *:not(header):not(footer) {
+ padding: var(--ad-space-m);
+ margin: 0;
+ color: var(--ad-color-text-primary);
+ font-size: var(--ad-step--1);
+}
+
+/* Tabs
+ Follows the real WAI-ARIA tabs pattern: a `[role="tablist"]` of
+ `[role="tab"]` buttons controlling `[role="tabpanel"]`s. Those roles are
+ required for the pattern to be accessible in the first place, so they
+ double as the entire styling contract — no classes at all for the base
+ shape. The keyboard wiring lives in assets/js/system.js and is
+ variant-agnostic — it only cares about `[data-tabs]` / `[role=tab]`.
+
+ Two real variants: `underline` (mono, accent underline — primary section
+ nav, the default) and `segmented` (pill track, active segment lifts to the
+ surface — filters like All · Overdue · Upcoming, never primary
+ navigation), chosen with `data-variant="segmented"` on the tablist since
+ neither has a natural attribute of its own. */
+
+/* Every rule below the list container pairs [role="tablist"] with
+ [role="tab"] rather than styling [role="tab"] alone — Tab buttons are
+ still literal <button> elements, so the compound selector's extra
+ specificity is what keeps Button's base styling (button.css) from
+ winning the cascade on shared properties like padding and background. */
+
+[role="tablist"] {
+ display: flex;
+ gap: var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+[role="tablist"] [role="tab"] {
+ padding: 0.5rem 0;
+ margin-bottom: -1px;
+ background: none;
+ border: 0;
+ border-bottom: 2px solid transparent;
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--1);
+ letter-spacing: 0.02em;
+ cursor: pointer;
+ transition: color var(--ad-dur-fast) var(--ad-ease), border-color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"] [role="tab"]:hover { color: var(--ad-color-text-primary); }
+[role="tablist"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ border-bottom-color: var(--ad-color-accent);
+}
+
+[role="tablist"][data-variant="segmented"] {
+ display: inline-flex;
+ gap: 0.125rem;
+ padding: 0.1875rem;
+ border-bottom: 0;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-md);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"] {
+ all: unset;
+ cursor: pointer;
+ padding: 0.375rem 0.875rem;
+ border-radius: var(--ad-radius-sm);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 550;
+ color: var(--ad-color-text-secondary);
+ transition: background-color var(--ad-dur-fast) var(--ad-ease), color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ background: var(--ad-color-surface-card);
+ box-shadow: var(--ad-shadow-sm);
+}
+
+[role="tabpanel"] { padding-top: var(--ad-space-s); }
+[role="tabpanel"][hidden] { display: none; }
+[role="tabpanel"]:focus-visible { outline-offset: 4px; }
+
+/* Documentation site chrome. Everything below the `.ad-doc-` prefix belongs
+ to this site rather than to the design system it documents — including the
+ width/height constants used directly below. The source design system
+ defines no container-width tokens at all, so there is nothing under
+ `--ad-*` for a doc shell to reach for; the Space foundation page's own
+ notes say to omit that group entirely rather than invent one. */
+
+.ad-doc-skip {
+ position: absolute;
+ inset-inline-start: var(--ad-space-xs);
+ inset-block-start: -4rem;
+ z-index: 40;
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ box-shadow: var(--ad-shadow-float);
+ transition: inset-block-start var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-skip:focus { inset-block-start: var(--ad-space-2xs); }
+
+.ad-doc-header {
+ position: sticky;
+ inset-block-start: 0;
+ z-index: 30;
+ height: 3.5rem;
+ background: color-mix(in srgb, var(--ad-color-surface-page) 92%, transparent);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-header__inner {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ height: 100%;
+ max-width: 72rem;
+ margin-inline: auto;
+ padding-inline: var(--ad-space-xs);
+}
+
+.ad-doc-wordmark {
+ display: flex;
+ align-items: baseline;
+ gap: var(--ad-space-2xs);
+ color: var(--ad-color-text-primary);
+ text-decoration: none;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ font-weight: 600;
+ letter-spacing: -0.01em;
+ white-space: nowrap;
+}
+.ad-doc-wordmark:hover { color: var(--ad-color-text-primary); }
+.ad-doc-wordmark__mark {
+ width: 1.375rem;
+ height: 1.375rem;
+ flex: none;
+ align-self: center;
+ color: var(--ad-color-accent);
+}
+.ad-doc-wordmark__version {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+
+.ad-doc-header__actions { margin-inline-start: auto; display: flex; align-items: center; gap: var(--ad-space-2xs); }
+
+.ad-doc-layout {
+ display: grid;
+ grid-template-columns: 16rem minmax(0, 1fr);
+ gap: var(--ad-space-l);
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-m) var(--ad-space-xs) var(--ad-space-2xl);
+}
+
+.ad-doc-sidebar {
+ position: sticky;
+ inset-block-start: calc(3.5rem + var(--ad-space-m));
+ align-self: start;
+ max-height: calc(100vh - 3.5rem - var(--ad-space-l));
+ overflow-y: auto;
+ padding-inline-end: var(--ad-space-2xs);
+}
+.ad-doc-nav__group + .ad-doc-nav__group { margin-top: var(--ad-space-s); }
+.ad-doc-nav__heading {
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-doc-nav__link {
+ display: block;
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ margin-inline-start: calc(var(--ad-space-xs) * -1);
+ border-inline-start: 2px solid transparent;
+ border-radius: 0 var(--ad-radius-sm) var(--ad-radius-sm) 0;
+ color: var(--ad-color-text-secondary);
+ font-size: var(--ad-step--1);
+ text-decoration: none;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-nav__link:hover { color: var(--ad-color-text-primary); background: var(--ad-color-surface-fill); }
+.ad-doc-nav__link[aria-current="page"] {
+ color: var(--ad-color-accent-ink);
+ border-inline-start-color: var(--ad-color-accent);
+ background: var(--ad-color-accent-soft);
+ font-weight: 500;
+}
+
+.ad-doc-main {
+ min-width: 0;
+ /* The design system's own body-text token (--ad-step-0, 18–20px) is right
+ for a product screen but too large for long-form documentation prose.
+ This site's own copy gets a plain, fixed 16px instead — the token above
+ is unchanged, and specimen tables/demos below still show its real
+ value. */
+ font-size: 1rem;
+}
+.ad-doc-main > * { max-width: 50rem; }
+
+.ad-doc-title { margin-bottom: var(--ad-space-xs); }
+.ad-doc-title__section {
+ display: block;
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-accent);
+}
+
+.ad-doc-main h2 {
+ margin-block: var(--ad-space-2xl) var(--ad-space-xs);
+ padding-bottom: var(--ad-space-2xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-main h3 { margin-block: var(--ad-space-l) var(--ad-space-2xs); }
+.ad-doc-main h4 { margin-block: var(--ad-space-s) var(--ad-space-2xs); }
+.ad-doc-main p,
+.ad-doc-main ul,
+.ad-doc-main ol { max-width: 68ch; }
+.ad-doc-main ul:not([class]),
+.ad-doc-main ol:not([class]) { padding-inline-start: var(--ad-space-s); }
+
+.ad-doc-footer {
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-s) var(--ad-space-xs) var(--ad-space-l);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-tertiary);
+ font-size: var(--ad-step--1);
+}
+.ad-doc-footer__inner { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); justify-content: space-between; }
+
+/* `button.ad-doc-nav-toggle` (element + class) rather than a bare class:
+ button.css's own base rule is `button:not(.icon-button)`, which is also
+ one element + one class in specificity and sets `display: inline-flex` —
+ a bare `.ad-doc-nav-toggle { display: none }` loses that fight and the
+ toggle stays visible at every width. Matching its specificity here (and
+ coming later in the concatenated stylesheet) settles it in source order. */
+button.ad-doc-nav-toggle { display: none; }
+
+@media (max-width: 60rem) {
+ .ad-doc-layout { grid-template-columns: minmax(0, 1fr); gap: var(--ad-space-s); }
+ .ad-doc-sidebar {
+ position: static;
+ max-height: none;
+ }
+ /* The toggle is a normal Button; the panel it opens gets its own card
+ treatment. Putting the same card styling on the <aside> wrapper too
+ nested a button-shaped box inside an outer box for no reason. */
+ .ad-doc-nav {
+ margin-top: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ }
+ .ad-doc-sidebar[data-collapsed="true"] > .ad-doc-nav { display: none; }
+ button.ad-doc-nav-toggle {
+ display: flex;
+ width: 100%;
+ justify-content: space-between;
+ }
+}
+
+@media (max-width: 34rem) {
+ .ad-doc-wordmark__version { display: none; }
+ .ad-doc-layout { padding-inline: var(--ad-space-2xs); }
+}
+
+/* The live-example figure used throughout the docs — see the "How the
+ example demos work" section on the Using the CSS page. One class on the
+ outer <figure>; everything inside is styled by plain element selectors,
+ since figcaption/div/details/summary/pre already say what they are.
+ `data-layout` and `data-surface` on the preview <div> distinguish the
+ handful of demos that need a non-default arrangement. */
+
+.example {
+ margin: var(--ad-space-s) 0 var(--ad-space-l);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+ background: var(--ad-color-surface-card);
+ overflow: hidden;
+}
+
+.example figcaption {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-secondary);
+}
+
+.example > div {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-s);
+}
+/* nowrap is load-bearing: a column-direction flex container with wrap enabled
+ sizes its single line against the container rather than the content, which
+ left every stacked example with a few hundred pixels of dead space. */
+.example > div[data-layout="stack"] { flex-direction: column; flex-wrap: nowrap; align-items: stretch; }
+.example > div[data-layout="grid"] {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
+ align-items: start;
+}
+.example > div[data-surface="sunken"] { background: var(--ad-color-surface-fill); }
+.example > div > * { min-width: 0; }
+
+.example details {
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+.example summary {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ color: var(--ad-color-text-secondary);
+ cursor: pointer;
+ list-style: none;
+ user-select: none;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+}
+.example summary::-webkit-details-marker { display: none; }
+.example summary::before {
+ content: "";
+ width: 0.5rem;
+ height: 0.5rem;
+ border-inline-end: 1.5px solid currentColor;
+ border-block-end: 1.5px solid currentColor;
+ rotate: -45deg;
+ transition: rotate var(--ad-dur-fast) var(--ad-ease);
+}
+.example details[open] > summary::before { rotate: 45deg; }
+.example summary:hover { color: var(--ad-color-text-primary); }
+
+.example pre {
+ margin: 0;
+ padding: var(--ad-space-xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+ tab-size: 2;
+}
+.example pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Standalone fenced code blocks in the prose (unhighlighted — there is no
+ syntax highlighter in this build any more, just Kramdown's plain
+ <pre><code class="language-*">). */
+.ad-doc-main > pre {
+ margin-block: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+}
+.ad-doc-main > pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Presentation for the token reference tables and swatch grids, plus a
+ generic data-table utility. All of this is documentation chrome — the
+ design system itself has no Table component (see the note on the removed
+ `table`/`progress`/`callout` pages in git history). */
+
+.ad-swatch-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs) var(--ad-space-s);
+ padding: 0;
+ list-style: none;
+}
+.ad-swatch {
+ display: flex;
+ flex-direction: column;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ overflow: hidden;
+ background: var(--ad-color-surface-card);
+}
+.ad-swatch__chip {
+ display: flex;
+ align-items: flex-end;
+ justify-content: flex-end;
+ height: 4rem;
+ padding: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+}
+.ad-swatch__meta {
+ display: flex;
+ flex-direction: column;
+ gap: 0.1rem;
+ padding: var(--ad-space-2xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+}
+.ad-swatch__hex { color: var(--ad-color-text-primary); text-transform: uppercase; }
+.ad-swatch__ratio { color: var(--ad-color-text-tertiary); }
+
+.ad-alias-row { display: flex; align-items: center; gap: var(--ad-space-2xs); }
+.ad-alias-chip {
+ width: 1.5rem;
+ height: 1.5rem;
+ flex: none;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+}
+.ad-alias-chip--pair { display: flex; }
+.ad-alias-chip--pair > span { flex: 1; }
+
+/* Type specimen rows. */
+.ad-specimen {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ gap: var(--ad-space-3xs);
+ padding-block: var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-specimen__meta {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-specimen__sample {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: var(--ad-color-text-primary);
+}
+
+/* Space and radius rulers. */
+.ad-ruler { display: flex; flex-direction: column; gap: var(--ad-space-2xs); }
+.ad-ruler__row { display: grid; grid-template-columns: 6.5rem 4.5rem 1fr; align-items: center; gap: var(--ad-space-xs); font-family: var(--ad-font-mono); font-size: 11px; }
+.ad-ruler__bar { height: 0.75rem; background: var(--ad-color-accent); border-radius: var(--ad-radius-sm); }
+.ad-ruler__num { color: var(--ad-color-text-tertiary); text-align: right; }
+
+.ad-radius-grid, .ad-elevation-grid, .ad-motion-grid, .ad-accent-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs);
+ padding: 0;
+ list-style: none;
+}
+.ad-radius-demo {
+ height: 4.5rem;
+ background: var(--ad-color-accent-soft);
+ border: 1px solid var(--ad-color-accent);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-elevation-demo {
+ height: 4.5rem;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-motion-demo {
+ height: 4.5rem;
+ display: grid;
+ place-items: center;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-md);
+ margin-bottom: var(--ad-space-2xs);
+ cursor: pointer;
+ overflow: hidden;
+}
+.ad-motion-demo__dot {
+ width: 1.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent);
+ translate: -3rem 0;
+ transition-property: translate;
+ transition-duration: var(--ad-dur-base);
+ transition-timing-function: var(--ad-ease);
+}
+.ad-motion-demo:hover .ad-motion-demo__dot,
+.ad-motion-demo:focus-visible .ad-motion-demo__dot { translate: 3rem 0; }
+
+.ad-accent-demo {
+ height: 4.5rem;
+ display: flex;
+ align-items: flex-end;
+ padding: var(--ad-space-2xs);
+ border-radius: var(--ad-radius-md);
+ color: #fff;
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ margin-bottom: var(--ad-space-2xs);
+}
+
+/* Token names read `<code>` sitewide, which already carries a mono chip
+ look for running prose (see :not(pre) > code in base/typography.css).
+ Inside these specimen/swatch/ruler rows the old `.ad-token-name` class was
+ plain mono text with no chip, so `<code>` is flattened back to that look
+ here the same way table cells are. */
+.ad-swatch code, .ad-specimen code, .ad-ruler code, .ad-alias-row code {
+ background: none;
+ border: 0;
+ padding: 0;
+ font-size: var(--ad-step--2);
+ color: var(--ad-color-text-primary);
+ white-space: nowrap;
+}
+
+/* A documentation aside. Like `<table>`, this is site chrome for the prose
+ itself, not a system component — the real system has no Callout. */
+.ad-callout {
+ margin-block: var(--ad-space-s);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-left: 3px solid var(--ad-color-accent);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+}
+/* Generic reference tables (component APIs, alias tables, the contrast
+ contract). `<table>` IS a table — no class needed to say so. Every table
+ on this site uses the one compact density, so there is no separate
+ modifier for it. `display: block` plus `overflow-x: auto` gives the table
+ its own horizontal scrollbar on narrow viewports without a wrapper div;
+ the anonymous table box the browser generates around `<thead>`/`<tbody>`
+ still does the row/column layout. `<code>` replaces the old
+ `.ad-token-name` / `.ad-table__code` cells — it is already styled
+ sitewide, just re-flattened here to a plain mono run instead of the
+ chip-with-border look prose code gets, to match this table's original
+ look. `.num` is the one sanctioned class left, for right-aligned numeric
+ columns — there is no attribute that means "this column is a number". */
+table { width: 100%; border-collapse: collapse; font-size: var(--ad-step--1); display: block; overflow-x: auto; }
+table th, table td {
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ text-align: left;
+ vertical-align: top;
+}
+table th { font-family: var(--ad-font-mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ad-color-text-secondary); font-weight: 500; }
+table code { background: none; border: 0; padding: 0; font-size: var(--ad-step--2); white-space: nowrap; }
+.num { text-align: right; font-variant-numeric: tabular-nums; }
+
+/* Home page. */
+.ad-hero { padding-block: var(--ad-space-m) var(--ad-space-l); }
+.ad-hero__title {
+ font-size: var(--ad-step-4);
+ margin-bottom: var(--ad-space-xs);
+}
+.ad-hero__actions { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); margin-top: var(--ad-space-s); }
+.ad-stat-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-l);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+}
+.ad-stat__value {
+ display: block;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-2);
+ font-weight: 600;
+ line-height: 1.16;
+ font-variant-numeric: tabular-nums;
+}
+.ad-stat__label {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+
+/* Card chrome. The pane supplies the frame; this just insets the page. */
+body { padding: var(--ad-space-l) var(--ad-space-m); }
+.ad-doc-main { max-width: 50rem; margin-inline: auto; }
+.ad-doc-main > * { max-width: none; }
+</style>
+</head>
+<body>
+<main class="ad-doc-main">
+<header class="ad-doc-title"><span class="ad-doc-title__section">Components</span>
+ <h1>Dialog</h1>
+</header>
+<p class="ad-lead">A centered modal. Flat surface, no colored header, hairline dividers between title, body, and footer.</p>
+<p>Dialog is the native <code><dialog></code> element: a <code><header></code> for the title, a
+<code><footer></code> for the button row, and everything else in between is the body —
+no wrapper classes at all.</p>
+
+<p>The example below is shown with a plain <code>open</code> attribute, scoped to its own
+preview box rather than the real viewport-covering overlay, so it renders
+without JavaScript and can sit safely inside a documentation page. It
+centers itself using the browser’s own default <code><dialog></code> styling
+(<code>position: absolute; inset: 0; margin: auto</code>) against the preview box,
+which is why it needs that box to be <code>position: relative</code>. Call
+<code>.showModal()</code> in the consuming app for the real fixed-overlay behavior —
+a plain <code>open</code> attribute never paints a <code>::backdrop</code>, since that
+pseudo-element only exists for a dialog shown modally.</p>
+
+<figure class="example">
+<figcaption>Dialog</figcaption>
+<div data-layout="stack">
+<div class="dialog-demo">
+ <dialog open="">
+ <header>Delete series?</header>
+ <p>This removes all of its task history. This cannot be undone.</p>
+ <footer>
+ <button>Cancel</button>
+ <button data-variant="danger">Delete</button>
+ </footer>
+ </dialog>
+</div>
+</div>
+<details open=""><summary>Markup</summary>
+<pre><code><dialog open>
+ <header>Delete series?</header>
+ <p>This removes all of its task history. This cannot be undone.</p>
+ <footer>
+ <button>Cancel</button>
+ <button data-variant="danger">Delete</button>
+ </footer>
+</dialog></code></pre>
+</details>
+</figure>
+
+<h2 id="without-a-footer">Without a footer</h2>
+
+<p>A body-only dialog for a notice rather than a decision.</p>
+
+<figure class="example">
+<figcaption>No footer</figcaption>
+<div data-layout="stack">
+<div class="dialog-demo" style="min-height: 14rem;">
+ <dialog open="" style="width: 20rem;">
+ <header>Backup complete</header>
+ <p>412 files, 1.2GB, archived to the household drive.</p>
+ </dialog>
+</div>
+</div>
+<details><summary>Markup</summary>
+<pre><code><dialog open style="width: 20rem;">
+ <header>Backup complete</header>
+ <p>412 files, 1.2GB, archived to the household drive.</p>
+</dialog></code></pre>
+</details>
+</figure>
+
+<h2 id="api">API</h2>
+
+<table>
+ <thead><tr><th scope="col">Element / attribute</th><th scope="col">Values</th><th scope="col">Notes</th></tr></thead>
+ <tbody>
+ <tr><td><code>dialog</code></td><td></td><td>Required base. Call <code>.showModal()</code> to open it for real — a plain <code>open</code> attribute is for static, always-open documentation only.</td></tr>
+ <tr><td><code>dialog > header</code></td><td></td><td>Optional — a body-only dialog omits it.</td></tr>
+ <tr><td><code>dialog > footer</code></td><td></td><td>Optional. Usually a pair of Buttons — the least committing action first.</td></tr>
+ <tr><td><code>dialog::backdrop</code></td><td></td><td>The dimmed overlay. Only renders once the dialog is shown via <code>.showModal()</code>.</td></tr>
+ <tr><td><code>close</code> event</td><td></td><td>Fired on cancel — wire it, or a form's <code>method="dialog"</code>, instead of an <code>onClose</code> prop.</td></tr>
+ </tbody>
+</table>
+</main>
+<script>
+// Behavior for the pieces of the system that need it. Everything else is
+// CSS and semantic HTML on purpose — a design system that only works with
+// JavaScript running is a component library with extra steps.
+(function () {
+ "use strict";
+
+ var STORAGE_KEY = "ad-theme";
+
+ function readStored() {
+ try {
+ return localStorage.getItem(STORAGE_KEY);
+ } catch (error) {
+ return null;
+ }
+ }
+
+ function writeStored(value) {
+ try {
+ localStorage.setItem(STORAGE_KEY, value);
+ } catch (error) {
+ /* Nothing to do — the media query still gives a sensible default. */
+ }
+ }
+
+ function systemPrefersDark() {
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
+ }
+
+ function currentTheme() {
+ var explicit = document.documentElement.getAttribute("data-theme");
+ if (explicit) return explicit;
+ return systemPrefersDark() ? "dark" : "light";
+ }
+
+ function paintToggle(theme) {
+ document.querySelectorAll("[data-theme-toggle]").forEach(function (toggle) {
+ var next = theme === "dark" ? "light" : "dark";
+ toggle.querySelectorAll("[data-theme-icon]").forEach(function (icon) {
+ icon.hidden = icon.dataset.themeIcon !== theme;
+ });
+ toggle.setAttribute("aria-label", "Switch to the " + next + " theme");
+ toggle.setAttribute("title", "Switch to the " + next + " theme");
+ var label = toggle.querySelector("[data-theme-label]");
+ if (label) label.textContent = "Switch to the " + next + " theme";
+ });
+ }
+
+ function setUpTheme() {
+ paintToggle(currentTheme());
+
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-theme-toggle]");
+ if (!toggle) return;
+
+ var next = currentTheme() === "dark" ? "light" : "dark";
+ document.documentElement.setAttribute("data-theme", next);
+ writeStored(next);
+ paintToggle(next);
+ });
+
+ // Follow the OS while the reader has not made an explicit choice.
+ window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", function () {
+ if (!readStored()) paintToggle(currentTheme());
+ });
+ }
+
+ // Tabs, wired to the WAI-ARIA authoring practices: arrows move, Home/End
+ // jump, and only the selected tab stays in the tab order.
+ function setUpTabs() {
+ document.querySelectorAll("[data-tabs]").forEach(function (root) {
+ var tabs = Array.prototype.slice.call(root.querySelectorAll('[role="tab"]'));
+ if (!tabs.length) return;
+
+ function select(tab, focus) {
+ tabs.forEach(function (candidate) {
+ var selected = candidate === tab;
+ candidate.setAttribute("aria-selected", String(selected));
+ candidate.tabIndex = selected ? 0 : -1;
+ var panel = document.getElementById(candidate.getAttribute("aria-controls"));
+ if (panel) panel.hidden = !selected;
+ });
+ if (focus) tab.focus();
+ }
+
+ root.addEventListener("click", function (event) {
+ var tab = event.target.closest('[role="tab"]');
+ if (tab) select(tab, false);
+ });
+
+ root.addEventListener("keydown", function (event) {
+ var index = tabs.indexOf(document.activeElement);
+ if (index === -1) return;
+
+ var next = null;
+ if (event.key === "ArrowRight") next = tabs[(index + 1) % tabs.length];
+ else if (event.key === "ArrowLeft") next = tabs[(index - 1 + tabs.length) % tabs.length];
+ else if (event.key === "Home") next = tabs[0];
+ else if (event.key === "End") next = tabs[tabs.length - 1];
+
+ if (next) {
+ event.preventDefault();
+ select(next, true);
+ }
+ });
+
+ select(tabs.filter(function (tab) {
+ return tab.getAttribute("aria-selected") === "true";
+ })[0] || tabs[0], false);
+ });
+ }
+
+ function setUpNavToggle() {
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-nav-toggle]");
+ if (!toggle) return;
+
+ var sidebar = toggle.closest("[data-sidebar]");
+ var collapsed = sidebar.dataset.collapsed !== "false";
+ sidebar.dataset.collapsed = String(!collapsed);
+ toggle.setAttribute("aria-expanded", String(collapsed));
+ });
+ }
+
+ setUpTheme();
+ setUpTabs();
+ setUpNavToggle();
+})();
+
+</script>
+</body>
+</html>
diff --git a/design-bundle/components/field.html b/design-bundle/components/field.html
new file mode 100644
index 0000000..4d44b8e
--- /dev/null
+++ b/design-bundle/components/field.html
@@ -0,0 +1,1861 @@
+<!-- @dsCard group="Forms" -->
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Field</title>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&display=swap">
+<style>
+/* Arbitrary Definitions — design tokens. Hand-maintained; edit values directly. */
+
+:root {
+ /* Palette ramps — reference only. Components use the aliases below. */
+ --ad-neutral-0: #faf9f7;
+ --ad-neutral-50: #fdfdfb;
+ --ad-neutral-100: #f1f0ed;
+ --ad-neutral-150: #e8e6e2;
+ --ad-neutral-200: #dcdad7;
+ --ad-neutral-300: #c0bdb9;
+ --ad-neutral-500: #888681;
+ --ad-neutral-600: #5f5d59;
+ --ad-neutral-800: #2a2926;
+ --ad-neutral-900: #100f0d;
+ --ad-neutral-dark-0: #0e0d0b;
+ --ad-neutral-dark-50: #151412;
+ --ad-neutral-dark-100: #1e1d1a;
+ --ad-neutral-dark-150: #252421;
+ --ad-neutral-dark-200: #2c2b29;
+ --ad-neutral-dark-300: #44423f;
+ --ad-neutral-dark-500: #7c7a76;
+ --ad-neutral-dark-600: #a09e9a;
+ --ad-neutral-dark-800: #d9d7d4;
+ --ad-neutral-dark-900: #e9e8e6;
+ --ad-accent-teal: #3a6a63;
+ --ad-accent-clay: #9a5a3c;
+ --ad-accent-ink-blue: #2b4a78;
+ --ad-accent-ochre: #93701f;
+ --ad-accent-plum: #6a3a5c;
+
+ /* Surfaces */
+ --ad-color-surface-page: #faf9f7;
+ --ad-color-surface-card: #fdfdfb;
+ --ad-color-surface-fill: #f1f0ed;
+ --ad-color-surface-fill-2: #e8e6e2;
+
+ /* Borders */
+ --ad-color-border-hairline: #dcdad7;
+ --ad-color-border-default: #c0bdb9;
+
+ /* Text */
+ --ad-color-text-primary: #100f0d;
+ --ad-color-text-secondary: #5f5d59;
+ --ad-color-text-tertiary: #888681;
+
+ /* Accent */
+ --ad-color-accent: #3a6a63;
+ --ad-color-accent-fg: #ffffff;
+ --ad-color-accent-ink: color-mix(in oklch, var(--ad-color-accent) 78%, black);
+ --ad-color-accent-soft: color-mix(in oklch, var(--ad-color-accent) 12%, white);
+
+ /* Status (fixed — never swapped like the accent) */
+ --ad-color-success: #3f7a4a;
+ --ad-color-success-bg: color-mix(in oklch, var(--ad-color-success) 12%, white);
+ --ad-color-danger: #ad3a2c;
+ --ad-color-danger-bg: color-mix(in oklch, var(--ad-color-danger) 10%, white);
+
+ /* Type families */
+ --ad-font-sans: -apple-system, "Helvetica Neue", Helvetica, Arial, system-ui, sans-serif;
+ --ad-font-mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, Consolas, "Courier New", monospace;
+ --ad-font-serif: Georgia, "Times New Roman", "Iowan Old Style", serif;
+
+ /* Type steps — fluid (Utopia scale), the design system's own body-text size */
+ --ad-step--2: clamp(0.7813rem, 0.7736rem + 0.0341vw, 0.8rem);
+ --ad-step--1: clamp(0.9375rem, 0.9119rem + 0.1136vw, 1rem);
+ --ad-step-0: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-step-1: clamp(1.35rem, 1.2631rem + 0.3864vw, 1.5625rem);
+ --ad-step-2: clamp(1.62rem, 1.4837rem + 0.6057vw, 1.9531rem);
+ --ad-step-3: clamp(1.944rem, 1.7405rem + 0.9044vw, 2.4414rem);
+ --ad-step-4: clamp(2.3328rem, 2.0387rem + 1.3072vw, 3.0518rem);
+ --ad-step-5: clamp(2.7994rem, 2.384rem + 1.8461vw, 3.8147rem);
+
+ /* Space */
+ --ad-space-3xs: clamp(0.3125rem, 0.3125rem + 0vw, 0.3125rem);
+ --ad-space-2xs: clamp(0.5625rem, 0.5369rem + 0.1136vw, 0.625rem);
+ --ad-space-xs: clamp(0.875rem, 0.8494rem + 0.1136vw, 0.9375rem);
+ --ad-space-s: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-space-m: clamp(1.6875rem, 1.6108rem + 0.3409vw, 1.875rem);
+ --ad-space-l: clamp(2.25rem, 2.1477rem + 0.4545vw, 2.5rem);
+ --ad-space-xl: clamp(3.375rem, 3.2216rem + 0.6818vw, 3.75rem);
+ --ad-space-2xl: clamp(4.5rem, 4.2955rem + 0.9091vw, 5rem);
+ --ad-space-3xl: clamp(6.75rem, 6.4432rem + 1.3636vw, 7.5rem);
+
+ /* Radius */
+ --ad-radius-sm: 4px;
+ --ad-radius-md: 6px;
+ --ad-radius-lg: 10px;
+ --ad-radius-full: 999px;
+ --ad-border-w: 1px;
+
+ /* Elevation */
+ --ad-shadow-sm: 0 1px 2px rgba(20, 18, 14, 0.05);
+ --ad-shadow-float: 0 2px 6px rgba(20, 18, 14, 0.06), 0 10px 24px -12px rgba(20, 18, 14, 0.18);
+
+ /* Motion */
+ --ad-dur-fast: 0.12s;
+ --ad-dur-base: 0.16s;
+ --ad-ease: cubic-bezier(0.2, 0, 0, 1);
+}
+
+@media (prefers-color-scheme: dark) {
+ :root:not([data-theme="light"]) {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+ }
+}
+
+:root[data-theme="dark"] {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ :root {
+ --ad-dur-fast: 0ms;
+ --ad-dur-base: 0ms;
+ }
+}
+
+/* A short reset. Everything it sets is something the system would otherwise
+ have to override in every component.
+
+ The rem values used for this documentation site's own chrome (measure,
+ content width, sidebar width, header height) belong to this site, not to
+ the design system it documents — the source design system defines no
+ container-width tokens at all, so there is nothing under `--ad-*` to reach
+ for here. See the header comment in site/shell.css. */
+
+*,
+*::before,
+*::after { box-sizing: border-box; }
+
+html {
+ -webkit-text-size-adjust: 100%;
+ scroll-behavior: smooth;
+ scroll-padding-top: calc(3.5rem + var(--ad-space-s));
+}
+
+@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;
+ }
+}
+
+body {
+ margin: 0;
+ background: var(--ad-color-surface-page);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ line-height: 1.55;
+ font-synthesis-weight: none;
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+}
+
+::selection {
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+}
+
+h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd { margin: 0; }
+ul[class], ol[class] { list-style: none; margin: 0; padding: 0; }
+
+img, svg, video { display: block; max-width: 100%; height: auto; }
+
+button, input, select, textarea {
+ font: inherit;
+ color: inherit;
+}
+
+hr {
+ height: 1px;
+ margin: var(--ad-space-l) 0;
+ border: 0;
+ background: var(--ad-color-border-hairline);
+}
+
+/* One focus ring, defined once. No component is allowed its own. */
+:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+ border-radius: var(--ad-radius-sm);
+}
+:focus:not(:focus-visible) { outline: none; }
+
+[hidden] { display: none !important; }
+
+.ad-visually-hidden {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip-path: inset(50%);
+ white-space: nowrap;
+ border: 0;
+}
+
+/* Every rule here reads a type token for family and size. Weight, line-height,
+ and letter-spacing are hardcoded to the exact numbers the source ships —
+ there is no `--ad-weight-*` / `--ad-leading-*` / `--ad-tracking-*` scale in
+ the real system, only eight fixed compound styles (see the Typography
+ foundation page, which this file implements verbatim). */
+
+.type-display { font: 600 var(--ad-step-4)/1.08 var(--ad-font-sans); letter-spacing: -0.015em; }
+.type-h1, h1 { font: 600 var(--ad-step-3)/1.12 var(--ad-font-sans); letter-spacing: -0.012em; }
+.type-h2, h2 { font: 600 var(--ad-step-2)/1.16 var(--ad-font-sans); letter-spacing: -0.01em; }
+.type-h3, h3 { font: 600 var(--ad-step-1)/1.25 var(--ad-font-sans); }
+.type-body { font: 400 var(--ad-step-0)/1.55 var(--ad-font-sans); }
+.type-body-sm { font: 400 var(--ad-step--1)/1.5 var(--ad-font-sans); }
+.type-label {
+ font: 500 var(--ad-step--2)/1 var(--ad-font-mono);
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: var(--ad-color-text-secondary);
+}
+.type-mono { font: 400 var(--ad-step--1)/1.5 var(--ad-font-mono); }
+
+/* Prose defaults for the documentation body copy. These read the same
+ tokens the compound classes above do; they are not a second scale. */
+h1, h2, h3, h4 { font-weight: 600; text-wrap: balance; }
+
+h4 { font: 600 var(--ad-step-0)/1.3 var(--ad-font-sans); }
+
+p, ul, ol { margin: 0 0 var(--ad-space-s); }
+li + li { margin-top: var(--ad-space-3xs); }
+
+a {
+ color: var(--ad-color-accent);
+ text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
+ text-underline-offset: 0.18em;
+ text-decoration-thickness: 1px;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ text-decoration-color var(--ad-dur-fast) var(--ad-ease);
+}
+a:hover {
+ color: var(--ad-color-accent-ink);
+ text-decoration-color: currentColor;
+}
+
+strong, b { font-weight: 600; }
+
+small { font-size: var(--ad-step--1); }
+
+.ad-lead {
+ font-size: var(--ad-step-1);
+ line-height: 1.5;
+ color: var(--ad-color-text-secondary);
+ max-width: 68ch;
+ text-wrap: pretty;
+}
+
+.ad-muted { color: var(--ad-color-text-secondary); }
+.ad-subtle { color: var(--ad-color-text-tertiary); }
+
+code, kbd, samp, pre {
+ font-family: var(--ad-font-mono);
+ font-feature-settings: "liga" 0;
+}
+
+:not(pre) > code {
+ padding: 0.15em 0.4em;
+ font-size: 0.875em;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-sm);
+ white-space: nowrap;
+}
+
+kbd {
+ display: inline-block;
+ padding: 0.1em 0.45em;
+ font-size: var(--ad-step--2);
+ line-height: 1.6;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-bottom-width: 2px;
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-secondary);
+}
+
+blockquote {
+ margin-block: var(--ad-space-s);
+ padding-left: var(--ad-space-s);
+ border-left: 2px solid var(--ad-color-accent);
+ color: var(--ad-color-text-secondary);
+}
+
+/* Button
+ A flat, quiet button that recedes rather than performs — no gradients, no
+ shine. Variants: default, primary, dark, ghost, danger. Sizes: sm, md
+ (default), lg — padding and font step only, weight and radius stay put.
+
+ The contract is the element itself: a bare <button> is styled directly, no
+ class required. An <a> that acts like a button (navigates rather than
+ changes something — see the Button page's "Links that act like buttons")
+ opts in with a single `.btn` class, since a bare `a` selector would catch
+ every ordinary prose link on the site. `:not(.icon-button)` keeps this
+ block from leaking onto IconButton, which shares the <button> tag but is
+ an entirely different shape. */
+
+button:not(.icon-button),
+a.btn {
+ --_bg: var(--ad-color-surface-card);
+ --_fg: var(--ad-color-text-primary);
+ --_border: var(--ad-color-border-default);
+ --_pad: var(--ad-space-xs) var(--ad-space-s);
+ --_font: var(--ad-step--1);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: var(--ad-space-2xs);
+ padding: var(--_pad);
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-sans);
+ font-size: var(--_font);
+ font-weight: 550;
+ letter-spacing: -0.01em;
+ line-height: 1.2;
+ text-align: center;
+ text-decoration: none;
+ white-space: nowrap;
+ cursor: pointer;
+ transition: filter var(--ad-dur-fast) var(--ad-ease),
+ border-color var(--ad-dur-fast) var(--ad-ease);
+}
+
+:is(button:not(.icon-button), a.btn):hover:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.96); }
+:is(button:not(.icon-button), a.btn):active:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.92); }
+
+button:not(.icon-button):disabled,
+:is(button:not(.icon-button), a.btn)[aria-disabled="true"] {
+ opacity: 0.5;
+ cursor: default;
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="primary"] {
+ --_bg: var(--ad-color-accent);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-accent);
+}
+
+/* Reserved for the single most-committing action in a view — a final Save,
+ never a whole row of them. */
+:is(button:not(.icon-button), a.btn)[data-variant="dark"] {
+ --_bg: var(--ad-color-text-primary);
+ --_fg: var(--ad-color-surface-page);
+ --_border: var(--ad-color-text-primary);
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="ghost"] {
+ --_bg: transparent;
+ --_fg: var(--ad-color-text-primary);
+ --_border: transparent;
+}
+
+/* The source hardcodes this label white rather than deriving it — danger
+ is not "on a filled accent surface", so --ad-color-accent-fg is reused
+ here only because its resolved value is the same literal white, not
+ because the two roles are the same thing. */
+:is(button:not(.icon-button), a.btn)[data-variant="danger"] {
+ --_bg: var(--ad-color-danger);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-danger);
+}
+
+:is(button:not(.icon-button), a.btn)[data-size="sm"] { --_pad: 0.375rem 0.75rem; --_font: 0.8125rem; }
+:is(button:not(.icon-button), a.btn)[data-size="lg"] { --_pad: 0.75rem 1.375rem; --_font: var(--ad-step-0); }
+
+:is(button:not(.icon-button), a.btn)[data-block] { width: 100%; }
+
+/* Button group — [role="group"] is already the correct ARIA role for a
+ toolbar of related buttons, so it doubles as the styling hook for free. */
+[role="group"] {
+ display: inline-flex;
+ isolation: isolate;
+}
+[role="group"] > button { border-radius: 0; }
+[role="group"] > button:first-child { border-start-start-radius: var(--ad-radius-md); border-end-start-radius: var(--ad-radius-md); }
+[role="group"] > button:last-child { border-start-end-radius: var(--ad-radius-md); border-end-end-radius: var(--ad-radius-md); }
+[role="group"] > button + button { margin-inline-start: -1px; }
+[role="group"] > button:hover,
+[role="group"] > button:focus-visible { z-index: 1; }
+
+/* IconButton — a square icon-only button. Always needs an aria-label; it
+ becomes both the accessible name and the native tooltip. There is no
+ attribute that distinguishes "a button that is only an icon" from a plain
+ Button, so this is the one sanctioned class in this file. */
+.icon-button {
+ --_size: 2.125rem;
+ --_bg: transparent;
+ --_border: transparent;
+ --_fg: var(--ad-color-text-secondary);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ padding: 0;
+ flex: none;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease),
+ color var(--ad-dur-fast) var(--ad-ease);
+}
+.icon-button:hover:not(:disabled) { background: var(--ad-color-surface-fill-2); }
+.icon-button:disabled { opacity: 0.5; cursor: default; }
+
+.icon-button[data-size="sm"] { --_size: 1.75rem; }
+.icon-button[data-size="lg"] { --_size: 2.5rem; }
+
+.icon-button[data-variant="outline"] { --_bg: var(--ad-color-surface-card); --_border: var(--ad-color-border-default); --_fg: var(--ad-color-text-primary); }
+.icon-button[data-variant="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); }
+
+/* Field
+ The labeled field wrapper — a <label> that wraps its own caption text
+ together with the control, so the association is native and needs no
+ `for`/`id` pair. The caption is mono, uppercase, tracked — the same
+ catalog-label convention as Badge and type-label.
+
+ A <label> is reused for three different shapes across this system (this
+ wrapper, Checkbox/Radio's row, Switch's row), so it cannot be styled bare
+ without one shape leaking into another. Field is the one of the three with
+ no distinguishing attribute of its own, so it gets the sanctioned class;
+ Checkbox/Radio tell themselves apart structurally instead (see below). */
+.field {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ min-width: 0;
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Hint and error read the native fine-print element. `role="alert"` is a
+ real live-region role for validation text, so it doubles as the selector
+ that turns a hint red. */
+.field > small {
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+.field > small[role="alert"] { color: var(--ad-color-danger); }
+
+/* Input / Select — bordered, matched to each other exactly. Both tags ARE
+ the component, so they are styled bare; nothing else on this site uses a
+ text input or a select. */
+input:not([type="checkbox"]):not([type="radio"]),
+select {
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ outline: none;
+ transition: border-color var(--ad-dur-fast) var(--ad-ease),
+ box-shadow var(--ad-dur-fast) var(--ad-ease);
+}
+input::placeholder { color: var(--ad-color-text-tertiary); }
+
+input:not([type="checkbox"]):not([type="radio"]):focus,
+select:focus {
+ border-color: var(--ad-color-accent);
+ box-shadow: 0 0 0 2px var(--ad-color-accent-soft);
+}
+
+input:not([type="checkbox"]):not([type="radio"]):disabled,
+select:disabled {
+ opacity: 0.5;
+ cursor: default;
+}
+
+/* The chevron is the one flagged color exception in this component set:
+ CSS cannot point a data-URI background-image at a custom property, so its
+ stroke is a literal hex rather than a var(--ad-*) reference. It is picked
+ to match --ad-color-text-secondary and commented here so it is not
+ mistaken for a stray color. */
+select {
+ appearance: none;
+ padding-inline-end: var(--ad-space-l);
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%235f5d59' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right var(--ad-space-xs) center;
+}
+
+input[aria-invalid="true"],
+select[aria-invalid="true"] {
+ border-color: var(--ad-color-danger);
+ background-color: var(--ad-color-danger-bg);
+}
+
+/* Checkbox / Radio — the real native controls, tinted with accent-color
+ instead of a hand-drawn box. This is not just a rename: it deletes the
+ custom SVG-checkmark markup entirely and lets the browser draw its own
+ (platform-appropriate) check and dot. `role="switch"` is excluded here so
+ Switch — a checkbox playing a different role — can style itself below
+ without this rule fighting it. A <label> wrapping a checkbox/radio is told
+ apart from Field's <label> structurally: it :has() one as a direct child,
+ no class required. */
+input[type="checkbox"]:not([role="switch"]),
+input[type="radio"] {
+ width: 1.125rem;
+ height: 1.125rem;
+ margin: 0;
+ accent-color: var(--ad-color-accent);
+ cursor: pointer;
+}
+input[type="checkbox"]:not([role="switch"]):focus-visible,
+input[type="radio"]:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+}
+
+label:has(> input[type="checkbox"]:not([role="switch"])),
+label:has(> input[type="radio"]) {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 400;
+ text-transform: none;
+ letter-spacing: normal;
+ color: var(--ad-color-text-primary);
+}
+label:has(> input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Switch — for on/off settings only, never a substitute for Checkbox.
+ `input[type="checkbox"][role="switch"]` is real ARIA (the correct role for
+ a toggle), not an invented hook, but the visual track+thumb needs a second
+ element for the thumb's independent position and transition — more than
+ `appearance: none` plus pseudo-elements on the input itself can cleanly
+ carry — so `.switch` is a deliberate, documented fallback class rather
+ than a first choice. */
+.switch {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ color: var(--ad-color-text-primary);
+}
+.switch input[role="switch"] { position: absolute; opacity: 0; width: 2.25rem; height: 1.25rem; margin: 0; }
+
+.switch span {
+ position: relative;
+ flex: none;
+ width: 2.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-border-default);
+ transition: background-color var(--ad-dur-base) var(--ad-ease);
+}
+.switch span::after {
+ content: "";
+ position: absolute;
+ inset-block-start: 2px;
+ inset-inline-start: 2px;
+ width: 1rem;
+ height: 1rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-fg);
+ box-shadow: var(--ad-shadow-sm);
+ transition: translate var(--ad-dur-base) var(--ad-ease);
+}
+.switch input[role="switch"]:checked + span { background: var(--ad-color-accent); }
+.switch input[role="switch"]:checked + span::after { translate: 1rem 0; }
+.switch input[role="switch"]:focus-visible + span { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+.switch:has(input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Rows of fields — used by the settings-form pattern. `<form>`/`<fieldset>`
+ are the elements that already mean this, so they are styled bare. A row
+ grouping two fields side by side has no element of its own, so it keeps
+ one plain class. */
+form {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-s);
+ max-width: 28rem;
+}
+.form-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
+ gap: var(--ad-space-xs);
+}
+fieldset {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ margin: 0;
+ padding: 0;
+ border: 0;
+}
+fieldset > legend {
+ padding: 0 0 var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Badge
+ Mono, uppercase, tracked — the catalog-label convention for status and
+ meta values (SAVED, OVERDUE, RECOMMENDED). A label, never a button.
+
+ A <span> is too generic to style globally — it is used all over running
+ prose for unrelated reasons — so Badge gets the one sanctioned class this
+ shape needs. Tone is an attribute, not a modifier class. */
+
+.badge {
+ --_bg: var(--ad-color-surface-fill);
+ --_fg: var(--ad-color-text-secondary);
+ --_border: var(--ad-color-border-hairline);
+
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.1875rem 0.5rem;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-sm);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ line-height: 1;
+ white-space: nowrap;
+}
+
+.badge[data-tone="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); --_border: transparent; }
+.badge[data-tone="success"] { --_bg: var(--ad-color-success-bg); --_fg: var(--ad-color-success); --_border: transparent; }
+.badge[data-tone="danger"] { --_bg: var(--ad-color-danger-bg); --_fg: var(--ad-color-danger); --_border: transparent; }
+
+.badge-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-2xs);
+}
+
+/* Tag
+ A removable mono chip for freeform tags — locations, categories.
+ `[data-dashed]` renders an "add tag" affordance instead of a value. Same
+ reasoning as Badge: <span> is too generic to select bare, so Tag gets one
+ class. The remove control is a real <button>, styled through the
+ descendant selector rather than a class of its own. */
+
+.tag {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.375rem;
+ padding: 0.25rem 0.625rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-default);
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ line-height: 1;
+}
+
+.tag[data-dashed] {
+ background: transparent;
+ border-style: dashed;
+ color: var(--ad-color-text-tertiary);
+ cursor: pointer;
+}
+
+.tag button {
+ all: unset;
+ cursor: pointer;
+ color: var(--ad-color-text-tertiary);
+ line-height: 1;
+ font-size: 0.75rem;
+}
+.tag button:hover { color: var(--ad-color-text-primary); }
+
+.tag-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.375rem;
+}
+
+/* Card
+ Flat surface, hairline border, radius-lg. `[data-elevated]` is opt-in and
+ reserved for the one focused/primary card in a view — a dialog, the
+ primary capture card — flat is the default everywhere else.
+
+ A <div>/<article> is too generic to select bare, so Card keeps one
+ sanctioned class. When Card's job is holding a list of rows — the common
+ case, entry lists, task lists, settings rows — it is a real <ul class="
+ card">, and CardRow is a bare <li> underneath it: no second class needed,
+ the row look comes from the `.card > li` descendant selector alone. The
+ first row's top border is removed automatically so callers never have to
+ override it by hand. */
+
+.card {
+ display: flex;
+ flex-direction: column;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: none;
+ overflow: hidden;
+}
+
+.card[data-elevated] { box-shadow: var(--ad-shadow-float); }
+
+.card > li {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+}
+.card > li:first-child { border-top: none; }
+
+/* A row whose ENTIRE content is wrapped in a real <button> or <a> — the
+ accessible way to make a row clickable — fills the row edge-to-edge and
+ picks up a hover fill and pointer cursor. `:only-child` is what tells this
+ apart from a row that merely contains a small button among other content
+ (a leading checkbox next to a title and a trailing value, say): only a
+ button/anchor that IS the row's one and only child means "the whole row
+ is the control." */
+.card > li:has(> button:only-child),
+.card > li:has(> a:only-child) {
+ padding: 0;
+}
+.card > li > button:only-child,
+.card > li > a:only-child {
+ all: unset;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ box-sizing: border-box;
+ color: inherit;
+ font: inherit;
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.card > li > button:only-child:hover,
+.card > li > a:only-child:hover { background: var(--ad-color-surface-fill); }
+
+/* Documentation-only extras: a body/footer pair and a responsive grid, used
+ by the prose pages (rationale.md, index.md) to show a Card as a plain
+ content tile rather than a data row. Neither is part of Card's props —
+ they exist because a content tile's inner layout has no element of its
+ own to hang a selector on. */
+.card-body {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-m);
+}
+.card-footer {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ margin-top: auto;
+ padding: var(--ad-space-xs) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+
+.card[data-link] { transition: border-color var(--ad-dur-fast) var(--ad-ease), box-shadow var(--ad-dur-fast) var(--ad-ease); }
+.card[data-link]:hover { border-color: var(--ad-color-border-default); box-shadow: var(--ad-shadow-sm); }
+.card[data-link]:focus-within { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+
+.card-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
+ gap: var(--ad-space-s);
+}
+
+/* Avatar — lives here because it almost never appears outside a card or a
+ row (the Loop dashboard's initials chip, Archive's file-type chip). A
+ <span> is too generic for a bare selector, so it keeps one class. */
+.avatar {
+ --_size: 1.875rem;
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ flex: none;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 600;
+ overflow: hidden;
+}
+.avatar[data-square] { border-radius: var(--ad-radius-md); background: var(--ad-color-surface-fill); color: var(--ad-color-text-secondary); border: 1px solid var(--ad-color-border-hairline); }
+.avatar[data-size="sm"] { --_size: 1.5rem; }
+.avatar[data-size="lg"] { --_size: 2.25rem; }
+
+/* Tooltip — a dark mono label on hover, positioned above the child. Pure CSS:
+ the trigger and bubble are siblings inside one relatively-positioned span,
+ shown on :hover/:focus-within so it works from the keyboard too.
+
+ `[role="tooltip"]` is the correct ARIA role for the bubble itself, so it
+ doubles as the styling hook — zero classes there. The wrapping span has no
+ attribute of its own, but it does not need one: `:has()` picks out "a span
+ that contains a [role=tooltip] bubble" precisely, without reaching for a
+ class on an otherwise-generic element. */
+span:has(> [role="tooltip"]) {
+ position: relative;
+ display: inline-flex;
+}
+
+[role="tooltip"] {
+ position: absolute;
+ bottom: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ margin-bottom: 0.375rem;
+ padding: 0.25rem 0.5rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-text-primary);
+ color: var(--ad-color-surface-page);
+ font-family: var(--ad-font-mono);
+ /* 11px is the source's own literal choice — smaller than --ad-step--2, the
+ smallest step on the scale, because a tooltip is meant to read as a
+ glance-only aside rather than body text. */
+ font-size: 11px;
+ white-space: nowrap;
+ line-height: 1.4;
+ z-index: 20;
+ opacity: 0;
+ visibility: hidden;
+ pointer-events: none;
+ transition: opacity var(--ad-dur-fast) var(--ad-ease);
+}
+span:has(> [role="tooltip"]):hover > [role="tooltip"],
+span:has(> [role="tooltip"]):focus-within > [role="tooltip"] {
+ opacity: 1;
+ visibility: visible;
+}
+
+/* Toast — the real pattern used for delete confirmations: a flat bar with an
+ optional destructive action and a dismiss. `[data-fixed]` pins it
+ bottom-right for real use; without it the toast stays in document flow for
+ documentation. `role="status"` is the correct live-region role for a
+ transient status message, so it is both the accessibility hook and the
+ style selector — no base class needed. The action and dismiss buttons are
+ plain <button>s told apart by `aria-label="Dismiss"`, an attribute they
+ need anyway for the icon-only close control. */
+[role="status"] {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ min-width: 20rem;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+}
+[role="status"][data-fixed] {
+ position: fixed;
+ inset-block-end: var(--ad-space-s);
+ inset-inline-end: var(--ad-space-s);
+ z-index: 60;
+}
+[role="status"] > span { flex: 1; }
+[role="status"] button {
+ all: unset;
+ cursor: pointer;
+ font-weight: 600;
+ color: var(--ad-color-danger);
+}
+[role="status"] button[aria-label="Dismiss"] {
+ color: var(--ad-color-text-tertiary);
+ font-size: 1rem;
+ font-weight: 400;
+ line-height: 1;
+}
+[role="status"] button[aria-label="Dismiss"]:hover { color: var(--ad-color-text-primary); }
+
+/* Dialog — the native <dialog> element: a centered modal, flat surface, no
+ colored header, hairline dividers between title/body/footer. A <header>
+ holds the title and a <footer> holds the actions, matching the elements
+ the tag already wants; everything else in between is the body, styled by
+ excluding those two rather than by a class of its own.
+
+ `.dialog-demo` scopes a static, always-open example to its own preview
+ container instead of the viewport, so it can sit safely inside a
+ documentation page — this is a doc-only affordance, not part of Dialog's
+ contract. It relies on the browser's own dialog centering: the UA
+ stylesheet already gives an open <dialog> `position: absolute; inset: 0;
+ margin: auto`, which centers it inside the nearest positioned ancestor —
+ here, `.dialog-demo` itself. A real `.showModal()` call gets the genuine
+ viewport-fixed overlay and `::backdrop` for free; a plain `open` attribute
+ (used here so the demo renders without JavaScript) does not paint a
+ backdrop at all, since `::backdrop` only exists for a dialog shown
+ modally — wire the real open/close state and call `.showModal()` in the
+ consuming app. */
+.dialog-demo {
+ position: relative;
+ min-height: 20rem;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-lg);
+}
+
+dialog {
+ width: 26rem;
+ max-width: calc(100% - (2 * var(--ad-space-s)));
+ height: fit-content;
+ padding: 0;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ overflow: hidden;
+}
+dialog::backdrop {
+ background: rgba(20, 18, 14, 0.35);
+}
+dialog > header {
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-sans);
+ font-weight: 600;
+ font-size: var(--ad-step-0);
+ color: var(--ad-color-text-primary);
+}
+dialog > footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+}
+dialog > *:not(header):not(footer) {
+ padding: var(--ad-space-m);
+ margin: 0;
+ color: var(--ad-color-text-primary);
+ font-size: var(--ad-step--1);
+}
+
+/* Tabs
+ Follows the real WAI-ARIA tabs pattern: a `[role="tablist"]` of
+ `[role="tab"]` buttons controlling `[role="tabpanel"]`s. Those roles are
+ required for the pattern to be accessible in the first place, so they
+ double as the entire styling contract — no classes at all for the base
+ shape. The keyboard wiring lives in assets/js/system.js and is
+ variant-agnostic — it only cares about `[data-tabs]` / `[role=tab]`.
+
+ Two real variants: `underline` (mono, accent underline — primary section
+ nav, the default) and `segmented` (pill track, active segment lifts to the
+ surface — filters like All · Overdue · Upcoming, never primary
+ navigation), chosen with `data-variant="segmented"` on the tablist since
+ neither has a natural attribute of its own. */
+
+/* Every rule below the list container pairs [role="tablist"] with
+ [role="tab"] rather than styling [role="tab"] alone — Tab buttons are
+ still literal <button> elements, so the compound selector's extra
+ specificity is what keeps Button's base styling (button.css) from
+ winning the cascade on shared properties like padding and background. */
+
+[role="tablist"] {
+ display: flex;
+ gap: var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+[role="tablist"] [role="tab"] {
+ padding: 0.5rem 0;
+ margin-bottom: -1px;
+ background: none;
+ border: 0;
+ border-bottom: 2px solid transparent;
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--1);
+ letter-spacing: 0.02em;
+ cursor: pointer;
+ transition: color var(--ad-dur-fast) var(--ad-ease), border-color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"] [role="tab"]:hover { color: var(--ad-color-text-primary); }
+[role="tablist"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ border-bottom-color: var(--ad-color-accent);
+}
+
+[role="tablist"][data-variant="segmented"] {
+ display: inline-flex;
+ gap: 0.125rem;
+ padding: 0.1875rem;
+ border-bottom: 0;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-md);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"] {
+ all: unset;
+ cursor: pointer;
+ padding: 0.375rem 0.875rem;
+ border-radius: var(--ad-radius-sm);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 550;
+ color: var(--ad-color-text-secondary);
+ transition: background-color var(--ad-dur-fast) var(--ad-ease), color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ background: var(--ad-color-surface-card);
+ box-shadow: var(--ad-shadow-sm);
+}
+
+[role="tabpanel"] { padding-top: var(--ad-space-s); }
+[role="tabpanel"][hidden] { display: none; }
+[role="tabpanel"]:focus-visible { outline-offset: 4px; }
+
+/* Documentation site chrome. Everything below the `.ad-doc-` prefix belongs
+ to this site rather than to the design system it documents — including the
+ width/height constants used directly below. The source design system
+ defines no container-width tokens at all, so there is nothing under
+ `--ad-*` for a doc shell to reach for; the Space foundation page's own
+ notes say to omit that group entirely rather than invent one. */
+
+.ad-doc-skip {
+ position: absolute;
+ inset-inline-start: var(--ad-space-xs);
+ inset-block-start: -4rem;
+ z-index: 40;
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ box-shadow: var(--ad-shadow-float);
+ transition: inset-block-start var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-skip:focus { inset-block-start: var(--ad-space-2xs); }
+
+.ad-doc-header {
+ position: sticky;
+ inset-block-start: 0;
+ z-index: 30;
+ height: 3.5rem;
+ background: color-mix(in srgb, var(--ad-color-surface-page) 92%, transparent);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-header__inner {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ height: 100%;
+ max-width: 72rem;
+ margin-inline: auto;
+ padding-inline: var(--ad-space-xs);
+}
+
+.ad-doc-wordmark {
+ display: flex;
+ align-items: baseline;
+ gap: var(--ad-space-2xs);
+ color: var(--ad-color-text-primary);
+ text-decoration: none;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ font-weight: 600;
+ letter-spacing: -0.01em;
+ white-space: nowrap;
+}
+.ad-doc-wordmark:hover { color: var(--ad-color-text-primary); }
+.ad-doc-wordmark__mark {
+ width: 1.375rem;
+ height: 1.375rem;
+ flex: none;
+ align-self: center;
+ color: var(--ad-color-accent);
+}
+.ad-doc-wordmark__version {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+
+.ad-doc-header__actions { margin-inline-start: auto; display: flex; align-items: center; gap: var(--ad-space-2xs); }
+
+.ad-doc-layout {
+ display: grid;
+ grid-template-columns: 16rem minmax(0, 1fr);
+ gap: var(--ad-space-l);
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-m) var(--ad-space-xs) var(--ad-space-2xl);
+}
+
+.ad-doc-sidebar {
+ position: sticky;
+ inset-block-start: calc(3.5rem + var(--ad-space-m));
+ align-self: start;
+ max-height: calc(100vh - 3.5rem - var(--ad-space-l));
+ overflow-y: auto;
+ padding-inline-end: var(--ad-space-2xs);
+}
+.ad-doc-nav__group + .ad-doc-nav__group { margin-top: var(--ad-space-s); }
+.ad-doc-nav__heading {
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-doc-nav__link {
+ display: block;
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ margin-inline-start: calc(var(--ad-space-xs) * -1);
+ border-inline-start: 2px solid transparent;
+ border-radius: 0 var(--ad-radius-sm) var(--ad-radius-sm) 0;
+ color: var(--ad-color-text-secondary);
+ font-size: var(--ad-step--1);
+ text-decoration: none;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-nav__link:hover { color: var(--ad-color-text-primary); background: var(--ad-color-surface-fill); }
+.ad-doc-nav__link[aria-current="page"] {
+ color: var(--ad-color-accent-ink);
+ border-inline-start-color: var(--ad-color-accent);
+ background: var(--ad-color-accent-soft);
+ font-weight: 500;
+}
+
+.ad-doc-main {
+ min-width: 0;
+ /* The design system's own body-text token (--ad-step-0, 18–20px) is right
+ for a product screen but too large for long-form documentation prose.
+ This site's own copy gets a plain, fixed 16px instead — the token above
+ is unchanged, and specimen tables/demos below still show its real
+ value. */
+ font-size: 1rem;
+}
+.ad-doc-main > * { max-width: 50rem; }
+
+.ad-doc-title { margin-bottom: var(--ad-space-xs); }
+.ad-doc-title__section {
+ display: block;
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-accent);
+}
+
+.ad-doc-main h2 {
+ margin-block: var(--ad-space-2xl) var(--ad-space-xs);
+ padding-bottom: var(--ad-space-2xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-main h3 { margin-block: var(--ad-space-l) var(--ad-space-2xs); }
+.ad-doc-main h4 { margin-block: var(--ad-space-s) var(--ad-space-2xs); }
+.ad-doc-main p,
+.ad-doc-main ul,
+.ad-doc-main ol { max-width: 68ch; }
+.ad-doc-main ul:not([class]),
+.ad-doc-main ol:not([class]) { padding-inline-start: var(--ad-space-s); }
+
+.ad-doc-footer {
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-s) var(--ad-space-xs) var(--ad-space-l);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-tertiary);
+ font-size: var(--ad-step--1);
+}
+.ad-doc-footer__inner { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); justify-content: space-between; }
+
+/* `button.ad-doc-nav-toggle` (element + class) rather than a bare class:
+ button.css's own base rule is `button:not(.icon-button)`, which is also
+ one element + one class in specificity and sets `display: inline-flex` —
+ a bare `.ad-doc-nav-toggle { display: none }` loses that fight and the
+ toggle stays visible at every width. Matching its specificity here (and
+ coming later in the concatenated stylesheet) settles it in source order. */
+button.ad-doc-nav-toggle { display: none; }
+
+@media (max-width: 60rem) {
+ .ad-doc-layout { grid-template-columns: minmax(0, 1fr); gap: var(--ad-space-s); }
+ .ad-doc-sidebar {
+ position: static;
+ max-height: none;
+ }
+ /* The toggle is a normal Button; the panel it opens gets its own card
+ treatment. Putting the same card styling on the <aside> wrapper too
+ nested a button-shaped box inside an outer box for no reason. */
+ .ad-doc-nav {
+ margin-top: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ }
+ .ad-doc-sidebar[data-collapsed="true"] > .ad-doc-nav { display: none; }
+ button.ad-doc-nav-toggle {
+ display: flex;
+ width: 100%;
+ justify-content: space-between;
+ }
+}
+
+@media (max-width: 34rem) {
+ .ad-doc-wordmark__version { display: none; }
+ .ad-doc-layout { padding-inline: var(--ad-space-2xs); }
+}
+
+/* The live-example figure used throughout the docs — see the "How the
+ example demos work" section on the Using the CSS page. One class on the
+ outer <figure>; everything inside is styled by plain element selectors,
+ since figcaption/div/details/summary/pre already say what they are.
+ `data-layout` and `data-surface` on the preview <div> distinguish the
+ handful of demos that need a non-default arrangement. */
+
+.example {
+ margin: var(--ad-space-s) 0 var(--ad-space-l);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+ background: var(--ad-color-surface-card);
+ overflow: hidden;
+}
+
+.example figcaption {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-secondary);
+}
+
+.example > div {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-s);
+}
+/* nowrap is load-bearing: a column-direction flex container with wrap enabled
+ sizes its single line against the container rather than the content, which
+ left every stacked example with a few hundred pixels of dead space. */
+.example > div[data-layout="stack"] { flex-direction: column; flex-wrap: nowrap; align-items: stretch; }
+.example > div[data-layout="grid"] {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
+ align-items: start;
+}
+.example > div[data-surface="sunken"] { background: var(--ad-color-surface-fill); }
+.example > div > * { min-width: 0; }
+
+.example details {
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+.example summary {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ color: var(--ad-color-text-secondary);
+ cursor: pointer;
+ list-style: none;
+ user-select: none;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+}
+.example summary::-webkit-details-marker { display: none; }
+.example summary::before {
+ content: "";
+ width: 0.5rem;
+ height: 0.5rem;
+ border-inline-end: 1.5px solid currentColor;
+ border-block-end: 1.5px solid currentColor;
+ rotate: -45deg;
+ transition: rotate var(--ad-dur-fast) var(--ad-ease);
+}
+.example details[open] > summary::before { rotate: 45deg; }
+.example summary:hover { color: var(--ad-color-text-primary); }
+
+.example pre {
+ margin: 0;
+ padding: var(--ad-space-xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+ tab-size: 2;
+}
+.example pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Standalone fenced code blocks in the prose (unhighlighted — there is no
+ syntax highlighter in this build any more, just Kramdown's plain
+ <pre><code class="language-*">). */
+.ad-doc-main > pre {
+ margin-block: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+}
+.ad-doc-main > pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Presentation for the token reference tables and swatch grids, plus a
+ generic data-table utility. All of this is documentation chrome — the
+ design system itself has no Table component (see the note on the removed
+ `table`/`progress`/`callout` pages in git history). */
+
+.ad-swatch-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs) var(--ad-space-s);
+ padding: 0;
+ list-style: none;
+}
+.ad-swatch {
+ display: flex;
+ flex-direction: column;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ overflow: hidden;
+ background: var(--ad-color-surface-card);
+}
+.ad-swatch__chip {
+ display: flex;
+ align-items: flex-end;
+ justify-content: flex-end;
+ height: 4rem;
+ padding: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+}
+.ad-swatch__meta {
+ display: flex;
+ flex-direction: column;
+ gap: 0.1rem;
+ padding: var(--ad-space-2xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+}
+.ad-swatch__hex { color: var(--ad-color-text-primary); text-transform: uppercase; }
+.ad-swatch__ratio { color: var(--ad-color-text-tertiary); }
+
+.ad-alias-row { display: flex; align-items: center; gap: var(--ad-space-2xs); }
+.ad-alias-chip {
+ width: 1.5rem;
+ height: 1.5rem;
+ flex: none;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+}
+.ad-alias-chip--pair { display: flex; }
+.ad-alias-chip--pair > span { flex: 1; }
+
+/* Type specimen rows. */
+.ad-specimen {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ gap: var(--ad-space-3xs);
+ padding-block: var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-specimen__meta {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-specimen__sample {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: var(--ad-color-text-primary);
+}
+
+/* Space and radius rulers. */
+.ad-ruler { display: flex; flex-direction: column; gap: var(--ad-space-2xs); }
+.ad-ruler__row { display: grid; grid-template-columns: 6.5rem 4.5rem 1fr; align-items: center; gap: var(--ad-space-xs); font-family: var(--ad-font-mono); font-size: 11px; }
+.ad-ruler__bar { height: 0.75rem; background: var(--ad-color-accent); border-radius: var(--ad-radius-sm); }
+.ad-ruler__num { color: var(--ad-color-text-tertiary); text-align: right; }
+
+.ad-radius-grid, .ad-elevation-grid, .ad-motion-grid, .ad-accent-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs);
+ padding: 0;
+ list-style: none;
+}
+.ad-radius-demo {
+ height: 4.5rem;
+ background: var(--ad-color-accent-soft);
+ border: 1px solid var(--ad-color-accent);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-elevation-demo {
+ height: 4.5rem;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-motion-demo {
+ height: 4.5rem;
+ display: grid;
+ place-items: center;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-md);
+ margin-bottom: var(--ad-space-2xs);
+ cursor: pointer;
+ overflow: hidden;
+}
+.ad-motion-demo__dot {
+ width: 1.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent);
+ translate: -3rem 0;
+ transition-property: translate;
+ transition-duration: var(--ad-dur-base);
+ transition-timing-function: var(--ad-ease);
+}
+.ad-motion-demo:hover .ad-motion-demo__dot,
+.ad-motion-demo:focus-visible .ad-motion-demo__dot { translate: 3rem 0; }
+
+.ad-accent-demo {
+ height: 4.5rem;
+ display: flex;
+ align-items: flex-end;
+ padding: var(--ad-space-2xs);
+ border-radius: var(--ad-radius-md);
+ color: #fff;
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ margin-bottom: var(--ad-space-2xs);
+}
+
+/* Token names read `<code>` sitewide, which already carries a mono chip
+ look for running prose (see :not(pre) > code in base/typography.css).
+ Inside these specimen/swatch/ruler rows the old `.ad-token-name` class was
+ plain mono text with no chip, so `<code>` is flattened back to that look
+ here the same way table cells are. */
+.ad-swatch code, .ad-specimen code, .ad-ruler code, .ad-alias-row code {
+ background: none;
+ border: 0;
+ padding: 0;
+ font-size: var(--ad-step--2);
+ color: var(--ad-color-text-primary);
+ white-space: nowrap;
+}
+
+/* A documentation aside. Like `<table>`, this is site chrome for the prose
+ itself, not a system component — the real system has no Callout. */
+.ad-callout {
+ margin-block: var(--ad-space-s);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-left: 3px solid var(--ad-color-accent);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+}
+/* Generic reference tables (component APIs, alias tables, the contrast
+ contract). `<table>` IS a table — no class needed to say so. Every table
+ on this site uses the one compact density, so there is no separate
+ modifier for it. `display: block` plus `overflow-x: auto` gives the table
+ its own horizontal scrollbar on narrow viewports without a wrapper div;
+ the anonymous table box the browser generates around `<thead>`/`<tbody>`
+ still does the row/column layout. `<code>` replaces the old
+ `.ad-token-name` / `.ad-table__code` cells — it is already styled
+ sitewide, just re-flattened here to a plain mono run instead of the
+ chip-with-border look prose code gets, to match this table's original
+ look. `.num` is the one sanctioned class left, for right-aligned numeric
+ columns — there is no attribute that means "this column is a number". */
+table { width: 100%; border-collapse: collapse; font-size: var(--ad-step--1); display: block; overflow-x: auto; }
+table th, table td {
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ text-align: left;
+ vertical-align: top;
+}
+table th { font-family: var(--ad-font-mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ad-color-text-secondary); font-weight: 500; }
+table code { background: none; border: 0; padding: 0; font-size: var(--ad-step--2); white-space: nowrap; }
+.num { text-align: right; font-variant-numeric: tabular-nums; }
+
+/* Home page. */
+.ad-hero { padding-block: var(--ad-space-m) var(--ad-space-l); }
+.ad-hero__title {
+ font-size: var(--ad-step-4);
+ margin-bottom: var(--ad-space-xs);
+}
+.ad-hero__actions { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); margin-top: var(--ad-space-s); }
+.ad-stat-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-l);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+}
+.ad-stat__value {
+ display: block;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-2);
+ font-weight: 600;
+ line-height: 1.16;
+ font-variant-numeric: tabular-nums;
+}
+.ad-stat__label {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+
+/* Card chrome. The pane supplies the frame; this just insets the page. */
+body { padding: var(--ad-space-l) var(--ad-space-m); }
+.ad-doc-main { max-width: 50rem; margin-inline: auto; }
+.ad-doc-main > * { max-width: none; }
+</style>
+</head>
+<body>
+<main class="ad-doc-main">
+<header class="ad-doc-title"><span class="ad-doc-title__section">Components</span>
+ <h1>Field</h1>
+</header>
+<p class="ad-lead">The labeled field wrapper. Mono, uppercase, tracked label — the same catalog-label convention as Badge and type-label — with an optional hint.</p>
+<h2 id="basic">Basic</h2>
+
+<p>Field is a <code><label></code> that wraps its caption text together with the control —
+association is native, no <code>for</code>/<code>id</code> pair to keep in sync. A <code><label></code> plays
+three different roles in this system (this wrapper, Checkbox/Radio’s row,
+Switch’s row), so it cannot be styled bare without one leaking into another;
+<code>.field</code> is the one of the three with no distinguishing attribute, so it is
+the one that keeps a class. Hint text is a native <code><small></code>.</p>
+
+<figure class="example">
+<figcaption>Field</figcaption>
+<div>
+<label class="field" style="max-width: 20rem;">
+ Name
+ <input type="text" placeholder="Call Mom" />
+ <small>First line becomes the display name.</small>
+</label>
+</div>
+<details open=""><summary>Markup</summary>
+<pre><code><label class="field" style="max-width: 20rem;">
+ Name
+ <input type="text" placeholder="Call Mom" />
+ <small>First line becomes the display name.</small>
+</label></code></pre>
+</details>
+</figure>
+
+<h2 id="holding-any-control">Holding any control</h2>
+
+<p>Field wraps <a href="https://kejadlen.github.io/arbitrary-definitions-design-system/components/input/">Input</a>,
+<a href="https://kejadlen.github.io/arbitrary-definitions-design-system/components/select/">Select</a>, or anything else — it
+only owns the caption and the hint line, never the control’s own styling.</p>
+
+<figure class="example">
+<figcaption>Field with a select</figcaption>
+<div>
+<label class="field" style="max-width: 20rem;">
+ Unit
+ <select>
+ <option>Day</option>
+ <option selected="">Week</option>
+ <option>Month</option>
+ <option>Year</option>
+ </select>
+</label>
+</div>
+<details><summary>Markup</summary>
+<pre><code><label class="field" style="max-width: 20rem;">
+ Unit
+ <select>
+ <option>Day</option>
+ <option selected="">Week</option>
+ <option>Month</option>
+ <option>Year</option>
+ </select>
+</label></code></pre>
+</details>
+</figure>
+
+<h2 id="in-a-row">In a row</h2>
+
+<p><code>.form-row</code> is a sanctioned class for the one shape here with no element of
+its own — a row grouping two fields side by side.</p>
+
+<figure class="example">
+<figcaption>Two fields in a row</figcaption>
+<div>
+<form style="max-width: none;">
+ <div class="form-row">
+ <label class="field">
+ Every
+ <input type="number" placeholder="2" />
+ </label>
+ <label class="field">
+ Unit
+ <select>
+ <option>Day</option><option selected="">Week</option><option>Month</option>
+ </select>
+ </label>
+ </div>
+</form>
+</div>
+<details><summary>Markup</summary>
+<pre><code><form style="max-width: none;">
+ <div class="form-row">
+ <label class="field">
+ Every
+ <input type="number" placeholder="2" />
+ </label>
+ <label class="field">
+ Unit
+ <select>
+ <option>Day</option><option selected="">Week</option><option>Month</option>
+ </select>
+ </label>
+ </div>
+</form></code></pre>
+</details>
+</figure>
+
+<h2 id="api">API</h2>
+
+<table>
+ <thead><tr><th scope="col">Element / attribute</th><th scope="col">Values</th><th scope="col">Notes</th></tr></thead>
+ <tbody>
+ <tr><td><code>label.field</code></td><td></td><td>Required base. Wraps the caption text and the control together.</td></tr>
+ <tr><td><code>> small</code></td><td>string</td><td>The hint. Renders below the control.</td></tr>
+ <tr><td><code>> small[role="alert"]</code></td><td>string</td><td>An error instead of a hint — see <a href="https://kejadlen.github.io/arbitrary-definitions-design-system/components/input/">Input</a>.</td></tr>
+ </tbody>
+</table>
+</main>
+<script>
+// Behavior for the pieces of the system that need it. Everything else is
+// CSS and semantic HTML on purpose — a design system that only works with
+// JavaScript running is a component library with extra steps.
+(function () {
+ "use strict";
+
+ var STORAGE_KEY = "ad-theme";
+
+ function readStored() {
+ try {
+ return localStorage.getItem(STORAGE_KEY);
+ } catch (error) {
+ return null;
+ }
+ }
+
+ function writeStored(value) {
+ try {
+ localStorage.setItem(STORAGE_KEY, value);
+ } catch (error) {
+ /* Nothing to do — the media query still gives a sensible default. */
+ }
+ }
+
+ function systemPrefersDark() {
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
+ }
+
+ function currentTheme() {
+ var explicit = document.documentElement.getAttribute("data-theme");
+ if (explicit) return explicit;
+ return systemPrefersDark() ? "dark" : "light";
+ }
+
+ function paintToggle(theme) {
+ document.querySelectorAll("[data-theme-toggle]").forEach(function (toggle) {
+ var next = theme === "dark" ? "light" : "dark";
+ toggle.querySelectorAll("[data-theme-icon]").forEach(function (icon) {
+ icon.hidden = icon.dataset.themeIcon !== theme;
+ });
+ toggle.setAttribute("aria-label", "Switch to the " + next + " theme");
+ toggle.setAttribute("title", "Switch to the " + next + " theme");
+ var label = toggle.querySelector("[data-theme-label]");
+ if (label) label.textContent = "Switch to the " + next + " theme";
+ });
+ }
+
+ function setUpTheme() {
+ paintToggle(currentTheme());
+
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-theme-toggle]");
+ if (!toggle) return;
+
+ var next = currentTheme() === "dark" ? "light" : "dark";
+ document.documentElement.setAttribute("data-theme", next);
+ writeStored(next);
+ paintToggle(next);
+ });
+
+ // Follow the OS while the reader has not made an explicit choice.
+ window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", function () {
+ if (!readStored()) paintToggle(currentTheme());
+ });
+ }
+
+ // Tabs, wired to the WAI-ARIA authoring practices: arrows move, Home/End
+ // jump, and only the selected tab stays in the tab order.
+ function setUpTabs() {
+ document.querySelectorAll("[data-tabs]").forEach(function (root) {
+ var tabs = Array.prototype.slice.call(root.querySelectorAll('[role="tab"]'));
+ if (!tabs.length) return;
+
+ function select(tab, focus) {
+ tabs.forEach(function (candidate) {
+ var selected = candidate === tab;
+ candidate.setAttribute("aria-selected", String(selected));
+ candidate.tabIndex = selected ? 0 : -1;
+ var panel = document.getElementById(candidate.getAttribute("aria-controls"));
+ if (panel) panel.hidden = !selected;
+ });
+ if (focus) tab.focus();
+ }
+
+ root.addEventListener("click", function (event) {
+ var tab = event.target.closest('[role="tab"]');
+ if (tab) select(tab, false);
+ });
+
+ root.addEventListener("keydown", function (event) {
+ var index = tabs.indexOf(document.activeElement);
+ if (index === -1) return;
+
+ var next = null;
+ if (event.key === "ArrowRight") next = tabs[(index + 1) % tabs.length];
+ else if (event.key === "ArrowLeft") next = tabs[(index - 1 + tabs.length) % tabs.length];
+ else if (event.key === "Home") next = tabs[0];
+ else if (event.key === "End") next = tabs[tabs.length - 1];
+
+ if (next) {
+ event.preventDefault();
+ select(next, true);
+ }
+ });
+
+ select(tabs.filter(function (tab) {
+ return tab.getAttribute("aria-selected") === "true";
+ })[0] || tabs[0], false);
+ });
+ }
+
+ function setUpNavToggle() {
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-nav-toggle]");
+ if (!toggle) return;
+
+ var sidebar = toggle.closest("[data-sidebar]");
+ var collapsed = sidebar.dataset.collapsed !== "false";
+ sidebar.dataset.collapsed = String(!collapsed);
+ toggle.setAttribute("aria-expanded", String(collapsed));
+ });
+ }
+
+ setUpTheme();
+ setUpTabs();
+ setUpNavToggle();
+})();
+
+</script>
+</body>
+</html>
diff --git a/design-bundle/components/icon-button.html b/design-bundle/components/icon-button.html
new file mode 100644
index 0000000..dda31e2
--- /dev/null
+++ b/design-bundle/components/icon-button.html
@@ -0,0 +1,1840 @@
+<!-- @dsCard group="Core" -->
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>IconButton</title>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&display=swap">
+<style>
+/* Arbitrary Definitions — design tokens. Hand-maintained; edit values directly. */
+
+:root {
+ /* Palette ramps — reference only. Components use the aliases below. */
+ --ad-neutral-0: #faf9f7;
+ --ad-neutral-50: #fdfdfb;
+ --ad-neutral-100: #f1f0ed;
+ --ad-neutral-150: #e8e6e2;
+ --ad-neutral-200: #dcdad7;
+ --ad-neutral-300: #c0bdb9;
+ --ad-neutral-500: #888681;
+ --ad-neutral-600: #5f5d59;
+ --ad-neutral-800: #2a2926;
+ --ad-neutral-900: #100f0d;
+ --ad-neutral-dark-0: #0e0d0b;
+ --ad-neutral-dark-50: #151412;
+ --ad-neutral-dark-100: #1e1d1a;
+ --ad-neutral-dark-150: #252421;
+ --ad-neutral-dark-200: #2c2b29;
+ --ad-neutral-dark-300: #44423f;
+ --ad-neutral-dark-500: #7c7a76;
+ --ad-neutral-dark-600: #a09e9a;
+ --ad-neutral-dark-800: #d9d7d4;
+ --ad-neutral-dark-900: #e9e8e6;
+ --ad-accent-teal: #3a6a63;
+ --ad-accent-clay: #9a5a3c;
+ --ad-accent-ink-blue: #2b4a78;
+ --ad-accent-ochre: #93701f;
+ --ad-accent-plum: #6a3a5c;
+
+ /* Surfaces */
+ --ad-color-surface-page: #faf9f7;
+ --ad-color-surface-card: #fdfdfb;
+ --ad-color-surface-fill: #f1f0ed;
+ --ad-color-surface-fill-2: #e8e6e2;
+
+ /* Borders */
+ --ad-color-border-hairline: #dcdad7;
+ --ad-color-border-default: #c0bdb9;
+
+ /* Text */
+ --ad-color-text-primary: #100f0d;
+ --ad-color-text-secondary: #5f5d59;
+ --ad-color-text-tertiary: #888681;
+
+ /* Accent */
+ --ad-color-accent: #3a6a63;
+ --ad-color-accent-fg: #ffffff;
+ --ad-color-accent-ink: color-mix(in oklch, var(--ad-color-accent) 78%, black);
+ --ad-color-accent-soft: color-mix(in oklch, var(--ad-color-accent) 12%, white);
+
+ /* Status (fixed — never swapped like the accent) */
+ --ad-color-success: #3f7a4a;
+ --ad-color-success-bg: color-mix(in oklch, var(--ad-color-success) 12%, white);
+ --ad-color-danger: #ad3a2c;
+ --ad-color-danger-bg: color-mix(in oklch, var(--ad-color-danger) 10%, white);
+
+ /* Type families */
+ --ad-font-sans: -apple-system, "Helvetica Neue", Helvetica, Arial, system-ui, sans-serif;
+ --ad-font-mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, Consolas, "Courier New", monospace;
+ --ad-font-serif: Georgia, "Times New Roman", "Iowan Old Style", serif;
+
+ /* Type steps — fluid (Utopia scale), the design system's own body-text size */
+ --ad-step--2: clamp(0.7813rem, 0.7736rem + 0.0341vw, 0.8rem);
+ --ad-step--1: clamp(0.9375rem, 0.9119rem + 0.1136vw, 1rem);
+ --ad-step-0: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-step-1: clamp(1.35rem, 1.2631rem + 0.3864vw, 1.5625rem);
+ --ad-step-2: clamp(1.62rem, 1.4837rem + 0.6057vw, 1.9531rem);
+ --ad-step-3: clamp(1.944rem, 1.7405rem + 0.9044vw, 2.4414rem);
+ --ad-step-4: clamp(2.3328rem, 2.0387rem + 1.3072vw, 3.0518rem);
+ --ad-step-5: clamp(2.7994rem, 2.384rem + 1.8461vw, 3.8147rem);
+
+ /* Space */
+ --ad-space-3xs: clamp(0.3125rem, 0.3125rem + 0vw, 0.3125rem);
+ --ad-space-2xs: clamp(0.5625rem, 0.5369rem + 0.1136vw, 0.625rem);
+ --ad-space-xs: clamp(0.875rem, 0.8494rem + 0.1136vw, 0.9375rem);
+ --ad-space-s: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-space-m: clamp(1.6875rem, 1.6108rem + 0.3409vw, 1.875rem);
+ --ad-space-l: clamp(2.25rem, 2.1477rem + 0.4545vw, 2.5rem);
+ --ad-space-xl: clamp(3.375rem, 3.2216rem + 0.6818vw, 3.75rem);
+ --ad-space-2xl: clamp(4.5rem, 4.2955rem + 0.9091vw, 5rem);
+ --ad-space-3xl: clamp(6.75rem, 6.4432rem + 1.3636vw, 7.5rem);
+
+ /* Radius */
+ --ad-radius-sm: 4px;
+ --ad-radius-md: 6px;
+ --ad-radius-lg: 10px;
+ --ad-radius-full: 999px;
+ --ad-border-w: 1px;
+
+ /* Elevation */
+ --ad-shadow-sm: 0 1px 2px rgba(20, 18, 14, 0.05);
+ --ad-shadow-float: 0 2px 6px rgba(20, 18, 14, 0.06), 0 10px 24px -12px rgba(20, 18, 14, 0.18);
+
+ /* Motion */
+ --ad-dur-fast: 0.12s;
+ --ad-dur-base: 0.16s;
+ --ad-ease: cubic-bezier(0.2, 0, 0, 1);
+}
+
+@media (prefers-color-scheme: dark) {
+ :root:not([data-theme="light"]) {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+ }
+}
+
+:root[data-theme="dark"] {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ :root {
+ --ad-dur-fast: 0ms;
+ --ad-dur-base: 0ms;
+ }
+}
+
+/* A short reset. Everything it sets is something the system would otherwise
+ have to override in every component.
+
+ The rem values used for this documentation site's own chrome (measure,
+ content width, sidebar width, header height) belong to this site, not to
+ the design system it documents — the source design system defines no
+ container-width tokens at all, so there is nothing under `--ad-*` to reach
+ for here. See the header comment in site/shell.css. */
+
+*,
+*::before,
+*::after { box-sizing: border-box; }
+
+html {
+ -webkit-text-size-adjust: 100%;
+ scroll-behavior: smooth;
+ scroll-padding-top: calc(3.5rem + var(--ad-space-s));
+}
+
+@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;
+ }
+}
+
+body {
+ margin: 0;
+ background: var(--ad-color-surface-page);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ line-height: 1.55;
+ font-synthesis-weight: none;
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+}
+
+::selection {
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+}
+
+h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd { margin: 0; }
+ul[class], ol[class] { list-style: none; margin: 0; padding: 0; }
+
+img, svg, video { display: block; max-width: 100%; height: auto; }
+
+button, input, select, textarea {
+ font: inherit;
+ color: inherit;
+}
+
+hr {
+ height: 1px;
+ margin: var(--ad-space-l) 0;
+ border: 0;
+ background: var(--ad-color-border-hairline);
+}
+
+/* One focus ring, defined once. No component is allowed its own. */
+:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+ border-radius: var(--ad-radius-sm);
+}
+:focus:not(:focus-visible) { outline: none; }
+
+[hidden] { display: none !important; }
+
+.ad-visually-hidden {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip-path: inset(50%);
+ white-space: nowrap;
+ border: 0;
+}
+
+/* Every rule here reads a type token for family and size. Weight, line-height,
+ and letter-spacing are hardcoded to the exact numbers the source ships —
+ there is no `--ad-weight-*` / `--ad-leading-*` / `--ad-tracking-*` scale in
+ the real system, only eight fixed compound styles (see the Typography
+ foundation page, which this file implements verbatim). */
+
+.type-display { font: 600 var(--ad-step-4)/1.08 var(--ad-font-sans); letter-spacing: -0.015em; }
+.type-h1, h1 { font: 600 var(--ad-step-3)/1.12 var(--ad-font-sans); letter-spacing: -0.012em; }
+.type-h2, h2 { font: 600 var(--ad-step-2)/1.16 var(--ad-font-sans); letter-spacing: -0.01em; }
+.type-h3, h3 { font: 600 var(--ad-step-1)/1.25 var(--ad-font-sans); }
+.type-body { font: 400 var(--ad-step-0)/1.55 var(--ad-font-sans); }
+.type-body-sm { font: 400 var(--ad-step--1)/1.5 var(--ad-font-sans); }
+.type-label {
+ font: 500 var(--ad-step--2)/1 var(--ad-font-mono);
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: var(--ad-color-text-secondary);
+}
+.type-mono { font: 400 var(--ad-step--1)/1.5 var(--ad-font-mono); }
+
+/* Prose defaults for the documentation body copy. These read the same
+ tokens the compound classes above do; they are not a second scale. */
+h1, h2, h3, h4 { font-weight: 600; text-wrap: balance; }
+
+h4 { font: 600 var(--ad-step-0)/1.3 var(--ad-font-sans); }
+
+p, ul, ol { margin: 0 0 var(--ad-space-s); }
+li + li { margin-top: var(--ad-space-3xs); }
+
+a {
+ color: var(--ad-color-accent);
+ text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
+ text-underline-offset: 0.18em;
+ text-decoration-thickness: 1px;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ text-decoration-color var(--ad-dur-fast) var(--ad-ease);
+}
+a:hover {
+ color: var(--ad-color-accent-ink);
+ text-decoration-color: currentColor;
+}
+
+strong, b { font-weight: 600; }
+
+small { font-size: var(--ad-step--1); }
+
+.ad-lead {
+ font-size: var(--ad-step-1);
+ line-height: 1.5;
+ color: var(--ad-color-text-secondary);
+ max-width: 68ch;
+ text-wrap: pretty;
+}
+
+.ad-muted { color: var(--ad-color-text-secondary); }
+.ad-subtle { color: var(--ad-color-text-tertiary); }
+
+code, kbd, samp, pre {
+ font-family: var(--ad-font-mono);
+ font-feature-settings: "liga" 0;
+}
+
+:not(pre) > code {
+ padding: 0.15em 0.4em;
+ font-size: 0.875em;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-sm);
+ white-space: nowrap;
+}
+
+kbd {
+ display: inline-block;
+ padding: 0.1em 0.45em;
+ font-size: var(--ad-step--2);
+ line-height: 1.6;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-bottom-width: 2px;
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-secondary);
+}
+
+blockquote {
+ margin-block: var(--ad-space-s);
+ padding-left: var(--ad-space-s);
+ border-left: 2px solid var(--ad-color-accent);
+ color: var(--ad-color-text-secondary);
+}
+
+/* Button
+ A flat, quiet button that recedes rather than performs — no gradients, no
+ shine. Variants: default, primary, dark, ghost, danger. Sizes: sm, md
+ (default), lg — padding and font step only, weight and radius stay put.
+
+ The contract is the element itself: a bare <button> is styled directly, no
+ class required. An <a> that acts like a button (navigates rather than
+ changes something — see the Button page's "Links that act like buttons")
+ opts in with a single `.btn` class, since a bare `a` selector would catch
+ every ordinary prose link on the site. `:not(.icon-button)` keeps this
+ block from leaking onto IconButton, which shares the <button> tag but is
+ an entirely different shape. */
+
+button:not(.icon-button),
+a.btn {
+ --_bg: var(--ad-color-surface-card);
+ --_fg: var(--ad-color-text-primary);
+ --_border: var(--ad-color-border-default);
+ --_pad: var(--ad-space-xs) var(--ad-space-s);
+ --_font: var(--ad-step--1);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: var(--ad-space-2xs);
+ padding: var(--_pad);
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-sans);
+ font-size: var(--_font);
+ font-weight: 550;
+ letter-spacing: -0.01em;
+ line-height: 1.2;
+ text-align: center;
+ text-decoration: none;
+ white-space: nowrap;
+ cursor: pointer;
+ transition: filter var(--ad-dur-fast) var(--ad-ease),
+ border-color var(--ad-dur-fast) var(--ad-ease);
+}
+
+:is(button:not(.icon-button), a.btn):hover:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.96); }
+:is(button:not(.icon-button), a.btn):active:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.92); }
+
+button:not(.icon-button):disabled,
+:is(button:not(.icon-button), a.btn)[aria-disabled="true"] {
+ opacity: 0.5;
+ cursor: default;
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="primary"] {
+ --_bg: var(--ad-color-accent);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-accent);
+}
+
+/* Reserved for the single most-committing action in a view — a final Save,
+ never a whole row of them. */
+:is(button:not(.icon-button), a.btn)[data-variant="dark"] {
+ --_bg: var(--ad-color-text-primary);
+ --_fg: var(--ad-color-surface-page);
+ --_border: var(--ad-color-text-primary);
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="ghost"] {
+ --_bg: transparent;
+ --_fg: var(--ad-color-text-primary);
+ --_border: transparent;
+}
+
+/* The source hardcodes this label white rather than deriving it — danger
+ is not "on a filled accent surface", so --ad-color-accent-fg is reused
+ here only because its resolved value is the same literal white, not
+ because the two roles are the same thing. */
+:is(button:not(.icon-button), a.btn)[data-variant="danger"] {
+ --_bg: var(--ad-color-danger);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-danger);
+}
+
+:is(button:not(.icon-button), a.btn)[data-size="sm"] { --_pad: 0.375rem 0.75rem; --_font: 0.8125rem; }
+:is(button:not(.icon-button), a.btn)[data-size="lg"] { --_pad: 0.75rem 1.375rem; --_font: var(--ad-step-0); }
+
+:is(button:not(.icon-button), a.btn)[data-block] { width: 100%; }
+
+/* Button group — [role="group"] is already the correct ARIA role for a
+ toolbar of related buttons, so it doubles as the styling hook for free. */
+[role="group"] {
+ display: inline-flex;
+ isolation: isolate;
+}
+[role="group"] > button { border-radius: 0; }
+[role="group"] > button:first-child { border-start-start-radius: var(--ad-radius-md); border-end-start-radius: var(--ad-radius-md); }
+[role="group"] > button:last-child { border-start-end-radius: var(--ad-radius-md); border-end-end-radius: var(--ad-radius-md); }
+[role="group"] > button + button { margin-inline-start: -1px; }
+[role="group"] > button:hover,
+[role="group"] > button:focus-visible { z-index: 1; }
+
+/* IconButton — a square icon-only button. Always needs an aria-label; it
+ becomes both the accessible name and the native tooltip. There is no
+ attribute that distinguishes "a button that is only an icon" from a plain
+ Button, so this is the one sanctioned class in this file. */
+.icon-button {
+ --_size: 2.125rem;
+ --_bg: transparent;
+ --_border: transparent;
+ --_fg: var(--ad-color-text-secondary);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ padding: 0;
+ flex: none;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease),
+ color var(--ad-dur-fast) var(--ad-ease);
+}
+.icon-button:hover:not(:disabled) { background: var(--ad-color-surface-fill-2); }
+.icon-button:disabled { opacity: 0.5; cursor: default; }
+
+.icon-button[data-size="sm"] { --_size: 1.75rem; }
+.icon-button[data-size="lg"] { --_size: 2.5rem; }
+
+.icon-button[data-variant="outline"] { --_bg: var(--ad-color-surface-card); --_border: var(--ad-color-border-default); --_fg: var(--ad-color-text-primary); }
+.icon-button[data-variant="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); }
+
+/* Field
+ The labeled field wrapper — a <label> that wraps its own caption text
+ together with the control, so the association is native and needs no
+ `for`/`id` pair. The caption is mono, uppercase, tracked — the same
+ catalog-label convention as Badge and type-label.
+
+ A <label> is reused for three different shapes across this system (this
+ wrapper, Checkbox/Radio's row, Switch's row), so it cannot be styled bare
+ without one shape leaking into another. Field is the one of the three with
+ no distinguishing attribute of its own, so it gets the sanctioned class;
+ Checkbox/Radio tell themselves apart structurally instead (see below). */
+.field {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ min-width: 0;
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Hint and error read the native fine-print element. `role="alert"` is a
+ real live-region role for validation text, so it doubles as the selector
+ that turns a hint red. */
+.field > small {
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+.field > small[role="alert"] { color: var(--ad-color-danger); }
+
+/* Input / Select — bordered, matched to each other exactly. Both tags ARE
+ the component, so they are styled bare; nothing else on this site uses a
+ text input or a select. */
+input:not([type="checkbox"]):not([type="radio"]),
+select {
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ outline: none;
+ transition: border-color var(--ad-dur-fast) var(--ad-ease),
+ box-shadow var(--ad-dur-fast) var(--ad-ease);
+}
+input::placeholder { color: var(--ad-color-text-tertiary); }
+
+input:not([type="checkbox"]):not([type="radio"]):focus,
+select:focus {
+ border-color: var(--ad-color-accent);
+ box-shadow: 0 0 0 2px var(--ad-color-accent-soft);
+}
+
+input:not([type="checkbox"]):not([type="radio"]):disabled,
+select:disabled {
+ opacity: 0.5;
+ cursor: default;
+}
+
+/* The chevron is the one flagged color exception in this component set:
+ CSS cannot point a data-URI background-image at a custom property, so its
+ stroke is a literal hex rather than a var(--ad-*) reference. It is picked
+ to match --ad-color-text-secondary and commented here so it is not
+ mistaken for a stray color. */
+select {
+ appearance: none;
+ padding-inline-end: var(--ad-space-l);
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%235f5d59' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right var(--ad-space-xs) center;
+}
+
+input[aria-invalid="true"],
+select[aria-invalid="true"] {
+ border-color: var(--ad-color-danger);
+ background-color: var(--ad-color-danger-bg);
+}
+
+/* Checkbox / Radio — the real native controls, tinted with accent-color
+ instead of a hand-drawn box. This is not just a rename: it deletes the
+ custom SVG-checkmark markup entirely and lets the browser draw its own
+ (platform-appropriate) check and dot. `role="switch"` is excluded here so
+ Switch — a checkbox playing a different role — can style itself below
+ without this rule fighting it. A <label> wrapping a checkbox/radio is told
+ apart from Field's <label> structurally: it :has() one as a direct child,
+ no class required. */
+input[type="checkbox"]:not([role="switch"]),
+input[type="radio"] {
+ width: 1.125rem;
+ height: 1.125rem;
+ margin: 0;
+ accent-color: var(--ad-color-accent);
+ cursor: pointer;
+}
+input[type="checkbox"]:not([role="switch"]):focus-visible,
+input[type="radio"]:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+}
+
+label:has(> input[type="checkbox"]:not([role="switch"])),
+label:has(> input[type="radio"]) {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 400;
+ text-transform: none;
+ letter-spacing: normal;
+ color: var(--ad-color-text-primary);
+}
+label:has(> input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Switch — for on/off settings only, never a substitute for Checkbox.
+ `input[type="checkbox"][role="switch"]` is real ARIA (the correct role for
+ a toggle), not an invented hook, but the visual track+thumb needs a second
+ element for the thumb's independent position and transition — more than
+ `appearance: none` plus pseudo-elements on the input itself can cleanly
+ carry — so `.switch` is a deliberate, documented fallback class rather
+ than a first choice. */
+.switch {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ color: var(--ad-color-text-primary);
+}
+.switch input[role="switch"] { position: absolute; opacity: 0; width: 2.25rem; height: 1.25rem; margin: 0; }
+
+.switch span {
+ position: relative;
+ flex: none;
+ width: 2.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-border-default);
+ transition: background-color var(--ad-dur-base) var(--ad-ease);
+}
+.switch span::after {
+ content: "";
+ position: absolute;
+ inset-block-start: 2px;
+ inset-inline-start: 2px;
+ width: 1rem;
+ height: 1rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-fg);
+ box-shadow: var(--ad-shadow-sm);
+ transition: translate var(--ad-dur-base) var(--ad-ease);
+}
+.switch input[role="switch"]:checked + span { background: var(--ad-color-accent); }
+.switch input[role="switch"]:checked + span::after { translate: 1rem 0; }
+.switch input[role="switch"]:focus-visible + span { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+.switch:has(input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Rows of fields — used by the settings-form pattern. `<form>`/`<fieldset>`
+ are the elements that already mean this, so they are styled bare. A row
+ grouping two fields side by side has no element of its own, so it keeps
+ one plain class. */
+form {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-s);
+ max-width: 28rem;
+}
+.form-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
+ gap: var(--ad-space-xs);
+}
+fieldset {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ margin: 0;
+ padding: 0;
+ border: 0;
+}
+fieldset > legend {
+ padding: 0 0 var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Badge
+ Mono, uppercase, tracked — the catalog-label convention for status and
+ meta values (SAVED, OVERDUE, RECOMMENDED). A label, never a button.
+
+ A <span> is too generic to style globally — it is used all over running
+ prose for unrelated reasons — so Badge gets the one sanctioned class this
+ shape needs. Tone is an attribute, not a modifier class. */
+
+.badge {
+ --_bg: var(--ad-color-surface-fill);
+ --_fg: var(--ad-color-text-secondary);
+ --_border: var(--ad-color-border-hairline);
+
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.1875rem 0.5rem;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-sm);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ line-height: 1;
+ white-space: nowrap;
+}
+
+.badge[data-tone="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); --_border: transparent; }
+.badge[data-tone="success"] { --_bg: var(--ad-color-success-bg); --_fg: var(--ad-color-success); --_border: transparent; }
+.badge[data-tone="danger"] { --_bg: var(--ad-color-danger-bg); --_fg: var(--ad-color-danger); --_border: transparent; }
+
+.badge-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-2xs);
+}
+
+/* Tag
+ A removable mono chip for freeform tags — locations, categories.
+ `[data-dashed]` renders an "add tag" affordance instead of a value. Same
+ reasoning as Badge: <span> is too generic to select bare, so Tag gets one
+ class. The remove control is a real <button>, styled through the
+ descendant selector rather than a class of its own. */
+
+.tag {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.375rem;
+ padding: 0.25rem 0.625rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-default);
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ line-height: 1;
+}
+
+.tag[data-dashed] {
+ background: transparent;
+ border-style: dashed;
+ color: var(--ad-color-text-tertiary);
+ cursor: pointer;
+}
+
+.tag button {
+ all: unset;
+ cursor: pointer;
+ color: var(--ad-color-text-tertiary);
+ line-height: 1;
+ font-size: 0.75rem;
+}
+.tag button:hover { color: var(--ad-color-text-primary); }
+
+.tag-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.375rem;
+}
+
+/* Card
+ Flat surface, hairline border, radius-lg. `[data-elevated]` is opt-in and
+ reserved for the one focused/primary card in a view — a dialog, the
+ primary capture card — flat is the default everywhere else.
+
+ A <div>/<article> is too generic to select bare, so Card keeps one
+ sanctioned class. When Card's job is holding a list of rows — the common
+ case, entry lists, task lists, settings rows — it is a real <ul class="
+ card">, and CardRow is a bare <li> underneath it: no second class needed,
+ the row look comes from the `.card > li` descendant selector alone. The
+ first row's top border is removed automatically so callers never have to
+ override it by hand. */
+
+.card {
+ display: flex;
+ flex-direction: column;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: none;
+ overflow: hidden;
+}
+
+.card[data-elevated] { box-shadow: var(--ad-shadow-float); }
+
+.card > li {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+}
+.card > li:first-child { border-top: none; }
+
+/* A row whose ENTIRE content is wrapped in a real <button> or <a> — the
+ accessible way to make a row clickable — fills the row edge-to-edge and
+ picks up a hover fill and pointer cursor. `:only-child` is what tells this
+ apart from a row that merely contains a small button among other content
+ (a leading checkbox next to a title and a trailing value, say): only a
+ button/anchor that IS the row's one and only child means "the whole row
+ is the control." */
+.card > li:has(> button:only-child),
+.card > li:has(> a:only-child) {
+ padding: 0;
+}
+.card > li > button:only-child,
+.card > li > a:only-child {
+ all: unset;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ box-sizing: border-box;
+ color: inherit;
+ font: inherit;
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.card > li > button:only-child:hover,
+.card > li > a:only-child:hover { background: var(--ad-color-surface-fill); }
+
+/* Documentation-only extras: a body/footer pair and a responsive grid, used
+ by the prose pages (rationale.md, index.md) to show a Card as a plain
+ content tile rather than a data row. Neither is part of Card's props —
+ they exist because a content tile's inner layout has no element of its
+ own to hang a selector on. */
+.card-body {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-m);
+}
+.card-footer {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ margin-top: auto;
+ padding: var(--ad-space-xs) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+
+.card[data-link] { transition: border-color var(--ad-dur-fast) var(--ad-ease), box-shadow var(--ad-dur-fast) var(--ad-ease); }
+.card[data-link]:hover { border-color: var(--ad-color-border-default); box-shadow: var(--ad-shadow-sm); }
+.card[data-link]:focus-within { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+
+.card-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
+ gap: var(--ad-space-s);
+}
+
+/* Avatar — lives here because it almost never appears outside a card or a
+ row (the Loop dashboard's initials chip, Archive's file-type chip). A
+ <span> is too generic for a bare selector, so it keeps one class. */
+.avatar {
+ --_size: 1.875rem;
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ flex: none;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 600;
+ overflow: hidden;
+}
+.avatar[data-square] { border-radius: var(--ad-radius-md); background: var(--ad-color-surface-fill); color: var(--ad-color-text-secondary); border: 1px solid var(--ad-color-border-hairline); }
+.avatar[data-size="sm"] { --_size: 1.5rem; }
+.avatar[data-size="lg"] { --_size: 2.25rem; }
+
+/* Tooltip — a dark mono label on hover, positioned above the child. Pure CSS:
+ the trigger and bubble are siblings inside one relatively-positioned span,
+ shown on :hover/:focus-within so it works from the keyboard too.
+
+ `[role="tooltip"]` is the correct ARIA role for the bubble itself, so it
+ doubles as the styling hook — zero classes there. The wrapping span has no
+ attribute of its own, but it does not need one: `:has()` picks out "a span
+ that contains a [role=tooltip] bubble" precisely, without reaching for a
+ class on an otherwise-generic element. */
+span:has(> [role="tooltip"]) {
+ position: relative;
+ display: inline-flex;
+}
+
+[role="tooltip"] {
+ position: absolute;
+ bottom: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ margin-bottom: 0.375rem;
+ padding: 0.25rem 0.5rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-text-primary);
+ color: var(--ad-color-surface-page);
+ font-family: var(--ad-font-mono);
+ /* 11px is the source's own literal choice — smaller than --ad-step--2, the
+ smallest step on the scale, because a tooltip is meant to read as a
+ glance-only aside rather than body text. */
+ font-size: 11px;
+ white-space: nowrap;
+ line-height: 1.4;
+ z-index: 20;
+ opacity: 0;
+ visibility: hidden;
+ pointer-events: none;
+ transition: opacity var(--ad-dur-fast) var(--ad-ease);
+}
+span:has(> [role="tooltip"]):hover > [role="tooltip"],
+span:has(> [role="tooltip"]):focus-within > [role="tooltip"] {
+ opacity: 1;
+ visibility: visible;
+}
+
+/* Toast — the real pattern used for delete confirmations: a flat bar with an
+ optional destructive action and a dismiss. `[data-fixed]` pins it
+ bottom-right for real use; without it the toast stays in document flow for
+ documentation. `role="status"` is the correct live-region role for a
+ transient status message, so it is both the accessibility hook and the
+ style selector — no base class needed. The action and dismiss buttons are
+ plain <button>s told apart by `aria-label="Dismiss"`, an attribute they
+ need anyway for the icon-only close control. */
+[role="status"] {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ min-width: 20rem;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+}
+[role="status"][data-fixed] {
+ position: fixed;
+ inset-block-end: var(--ad-space-s);
+ inset-inline-end: var(--ad-space-s);
+ z-index: 60;
+}
+[role="status"] > span { flex: 1; }
+[role="status"] button {
+ all: unset;
+ cursor: pointer;
+ font-weight: 600;
+ color: var(--ad-color-danger);
+}
+[role="status"] button[aria-label="Dismiss"] {
+ color: var(--ad-color-text-tertiary);
+ font-size: 1rem;
+ font-weight: 400;
+ line-height: 1;
+}
+[role="status"] button[aria-label="Dismiss"]:hover { color: var(--ad-color-text-primary); }
+
+/* Dialog — the native <dialog> element: a centered modal, flat surface, no
+ colored header, hairline dividers between title/body/footer. A <header>
+ holds the title and a <footer> holds the actions, matching the elements
+ the tag already wants; everything else in between is the body, styled by
+ excluding those two rather than by a class of its own.
+
+ `.dialog-demo` scopes a static, always-open example to its own preview
+ container instead of the viewport, so it can sit safely inside a
+ documentation page — this is a doc-only affordance, not part of Dialog's
+ contract. It relies on the browser's own dialog centering: the UA
+ stylesheet already gives an open <dialog> `position: absolute; inset: 0;
+ margin: auto`, which centers it inside the nearest positioned ancestor —
+ here, `.dialog-demo` itself. A real `.showModal()` call gets the genuine
+ viewport-fixed overlay and `::backdrop` for free; a plain `open` attribute
+ (used here so the demo renders without JavaScript) does not paint a
+ backdrop at all, since `::backdrop` only exists for a dialog shown
+ modally — wire the real open/close state and call `.showModal()` in the
+ consuming app. */
+.dialog-demo {
+ position: relative;
+ min-height: 20rem;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-lg);
+}
+
+dialog {
+ width: 26rem;
+ max-width: calc(100% - (2 * var(--ad-space-s)));
+ height: fit-content;
+ padding: 0;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ overflow: hidden;
+}
+dialog::backdrop {
+ background: rgba(20, 18, 14, 0.35);
+}
+dialog > header {
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-sans);
+ font-weight: 600;
+ font-size: var(--ad-step-0);
+ color: var(--ad-color-text-primary);
+}
+dialog > footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+}
+dialog > *:not(header):not(footer) {
+ padding: var(--ad-space-m);
+ margin: 0;
+ color: var(--ad-color-text-primary);
+ font-size: var(--ad-step--1);
+}
+
+/* Tabs
+ Follows the real WAI-ARIA tabs pattern: a `[role="tablist"]` of
+ `[role="tab"]` buttons controlling `[role="tabpanel"]`s. Those roles are
+ required for the pattern to be accessible in the first place, so they
+ double as the entire styling contract — no classes at all for the base
+ shape. The keyboard wiring lives in assets/js/system.js and is
+ variant-agnostic — it only cares about `[data-tabs]` / `[role=tab]`.
+
+ Two real variants: `underline` (mono, accent underline — primary section
+ nav, the default) and `segmented` (pill track, active segment lifts to the
+ surface — filters like All · Overdue · Upcoming, never primary
+ navigation), chosen with `data-variant="segmented"` on the tablist since
+ neither has a natural attribute of its own. */
+
+/* Every rule below the list container pairs [role="tablist"] with
+ [role="tab"] rather than styling [role="tab"] alone — Tab buttons are
+ still literal <button> elements, so the compound selector's extra
+ specificity is what keeps Button's base styling (button.css) from
+ winning the cascade on shared properties like padding and background. */
+
+[role="tablist"] {
+ display: flex;
+ gap: var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+[role="tablist"] [role="tab"] {
+ padding: 0.5rem 0;
+ margin-bottom: -1px;
+ background: none;
+ border: 0;
+ border-bottom: 2px solid transparent;
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--1);
+ letter-spacing: 0.02em;
+ cursor: pointer;
+ transition: color var(--ad-dur-fast) var(--ad-ease), border-color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"] [role="tab"]:hover { color: var(--ad-color-text-primary); }
+[role="tablist"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ border-bottom-color: var(--ad-color-accent);
+}
+
+[role="tablist"][data-variant="segmented"] {
+ display: inline-flex;
+ gap: 0.125rem;
+ padding: 0.1875rem;
+ border-bottom: 0;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-md);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"] {
+ all: unset;
+ cursor: pointer;
+ padding: 0.375rem 0.875rem;
+ border-radius: var(--ad-radius-sm);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 550;
+ color: var(--ad-color-text-secondary);
+ transition: background-color var(--ad-dur-fast) var(--ad-ease), color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ background: var(--ad-color-surface-card);
+ box-shadow: var(--ad-shadow-sm);
+}
+
+[role="tabpanel"] { padding-top: var(--ad-space-s); }
+[role="tabpanel"][hidden] { display: none; }
+[role="tabpanel"]:focus-visible { outline-offset: 4px; }
+
+/* Documentation site chrome. Everything below the `.ad-doc-` prefix belongs
+ to this site rather than to the design system it documents — including the
+ width/height constants used directly below. The source design system
+ defines no container-width tokens at all, so there is nothing under
+ `--ad-*` for a doc shell to reach for; the Space foundation page's own
+ notes say to omit that group entirely rather than invent one. */
+
+.ad-doc-skip {
+ position: absolute;
+ inset-inline-start: var(--ad-space-xs);
+ inset-block-start: -4rem;
+ z-index: 40;
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ box-shadow: var(--ad-shadow-float);
+ transition: inset-block-start var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-skip:focus { inset-block-start: var(--ad-space-2xs); }
+
+.ad-doc-header {
+ position: sticky;
+ inset-block-start: 0;
+ z-index: 30;
+ height: 3.5rem;
+ background: color-mix(in srgb, var(--ad-color-surface-page) 92%, transparent);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-header__inner {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ height: 100%;
+ max-width: 72rem;
+ margin-inline: auto;
+ padding-inline: var(--ad-space-xs);
+}
+
+.ad-doc-wordmark {
+ display: flex;
+ align-items: baseline;
+ gap: var(--ad-space-2xs);
+ color: var(--ad-color-text-primary);
+ text-decoration: none;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ font-weight: 600;
+ letter-spacing: -0.01em;
+ white-space: nowrap;
+}
+.ad-doc-wordmark:hover { color: var(--ad-color-text-primary); }
+.ad-doc-wordmark__mark {
+ width: 1.375rem;
+ height: 1.375rem;
+ flex: none;
+ align-self: center;
+ color: var(--ad-color-accent);
+}
+.ad-doc-wordmark__version {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+
+.ad-doc-header__actions { margin-inline-start: auto; display: flex; align-items: center; gap: var(--ad-space-2xs); }
+
+.ad-doc-layout {
+ display: grid;
+ grid-template-columns: 16rem minmax(0, 1fr);
+ gap: var(--ad-space-l);
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-m) var(--ad-space-xs) var(--ad-space-2xl);
+}
+
+.ad-doc-sidebar {
+ position: sticky;
+ inset-block-start: calc(3.5rem + var(--ad-space-m));
+ align-self: start;
+ max-height: calc(100vh - 3.5rem - var(--ad-space-l));
+ overflow-y: auto;
+ padding-inline-end: var(--ad-space-2xs);
+}
+.ad-doc-nav__group + .ad-doc-nav__group { margin-top: var(--ad-space-s); }
+.ad-doc-nav__heading {
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-doc-nav__link {
+ display: block;
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ margin-inline-start: calc(var(--ad-space-xs) * -1);
+ border-inline-start: 2px solid transparent;
+ border-radius: 0 var(--ad-radius-sm) var(--ad-radius-sm) 0;
+ color: var(--ad-color-text-secondary);
+ font-size: var(--ad-step--1);
+ text-decoration: none;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-nav__link:hover { color: var(--ad-color-text-primary); background: var(--ad-color-surface-fill); }
+.ad-doc-nav__link[aria-current="page"] {
+ color: var(--ad-color-accent-ink);
+ border-inline-start-color: var(--ad-color-accent);
+ background: var(--ad-color-accent-soft);
+ font-weight: 500;
+}
+
+.ad-doc-main {
+ min-width: 0;
+ /* The design system's own body-text token (--ad-step-0, 18–20px) is right
+ for a product screen but too large for long-form documentation prose.
+ This site's own copy gets a plain, fixed 16px instead — the token above
+ is unchanged, and specimen tables/demos below still show its real
+ value. */
+ font-size: 1rem;
+}
+.ad-doc-main > * { max-width: 50rem; }
+
+.ad-doc-title { margin-bottom: var(--ad-space-xs); }
+.ad-doc-title__section {
+ display: block;
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-accent);
+}
+
+.ad-doc-main h2 {
+ margin-block: var(--ad-space-2xl) var(--ad-space-xs);
+ padding-bottom: var(--ad-space-2xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-main h3 { margin-block: var(--ad-space-l) var(--ad-space-2xs); }
+.ad-doc-main h4 { margin-block: var(--ad-space-s) var(--ad-space-2xs); }
+.ad-doc-main p,
+.ad-doc-main ul,
+.ad-doc-main ol { max-width: 68ch; }
+.ad-doc-main ul:not([class]),
+.ad-doc-main ol:not([class]) { padding-inline-start: var(--ad-space-s); }
+
+.ad-doc-footer {
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-s) var(--ad-space-xs) var(--ad-space-l);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-tertiary);
+ font-size: var(--ad-step--1);
+}
+.ad-doc-footer__inner { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); justify-content: space-between; }
+
+/* `button.ad-doc-nav-toggle` (element + class) rather than a bare class:
+ button.css's own base rule is `button:not(.icon-button)`, which is also
+ one element + one class in specificity and sets `display: inline-flex` —
+ a bare `.ad-doc-nav-toggle { display: none }` loses that fight and the
+ toggle stays visible at every width. Matching its specificity here (and
+ coming later in the concatenated stylesheet) settles it in source order. */
+button.ad-doc-nav-toggle { display: none; }
+
+@media (max-width: 60rem) {
+ .ad-doc-layout { grid-template-columns: minmax(0, 1fr); gap: var(--ad-space-s); }
+ .ad-doc-sidebar {
+ position: static;
+ max-height: none;
+ }
+ /* The toggle is a normal Button; the panel it opens gets its own card
+ treatment. Putting the same card styling on the <aside> wrapper too
+ nested a button-shaped box inside an outer box for no reason. */
+ .ad-doc-nav {
+ margin-top: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ }
+ .ad-doc-sidebar[data-collapsed="true"] > .ad-doc-nav { display: none; }
+ button.ad-doc-nav-toggle {
+ display: flex;
+ width: 100%;
+ justify-content: space-between;
+ }
+}
+
+@media (max-width: 34rem) {
+ .ad-doc-wordmark__version { display: none; }
+ .ad-doc-layout { padding-inline: var(--ad-space-2xs); }
+}
+
+/* The live-example figure used throughout the docs — see the "How the
+ example demos work" section on the Using the CSS page. One class on the
+ outer <figure>; everything inside is styled by plain element selectors,
+ since figcaption/div/details/summary/pre already say what they are.
+ `data-layout` and `data-surface` on the preview <div> distinguish the
+ handful of demos that need a non-default arrangement. */
+
+.example {
+ margin: var(--ad-space-s) 0 var(--ad-space-l);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+ background: var(--ad-color-surface-card);
+ overflow: hidden;
+}
+
+.example figcaption {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-secondary);
+}
+
+.example > div {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-s);
+}
+/* nowrap is load-bearing: a column-direction flex container with wrap enabled
+ sizes its single line against the container rather than the content, which
+ left every stacked example with a few hundred pixels of dead space. */
+.example > div[data-layout="stack"] { flex-direction: column; flex-wrap: nowrap; align-items: stretch; }
+.example > div[data-layout="grid"] {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
+ align-items: start;
+}
+.example > div[data-surface="sunken"] { background: var(--ad-color-surface-fill); }
+.example > div > * { min-width: 0; }
+
+.example details {
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+.example summary {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ color: var(--ad-color-text-secondary);
+ cursor: pointer;
+ list-style: none;
+ user-select: none;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+}
+.example summary::-webkit-details-marker { display: none; }
+.example summary::before {
+ content: "";
+ width: 0.5rem;
+ height: 0.5rem;
+ border-inline-end: 1.5px solid currentColor;
+ border-block-end: 1.5px solid currentColor;
+ rotate: -45deg;
+ transition: rotate var(--ad-dur-fast) var(--ad-ease);
+}
+.example details[open] > summary::before { rotate: 45deg; }
+.example summary:hover { color: var(--ad-color-text-primary); }
+
+.example pre {
+ margin: 0;
+ padding: var(--ad-space-xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+ tab-size: 2;
+}
+.example pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Standalone fenced code blocks in the prose (unhighlighted — there is no
+ syntax highlighter in this build any more, just Kramdown's plain
+ <pre><code class="language-*">). */
+.ad-doc-main > pre {
+ margin-block: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+}
+.ad-doc-main > pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Presentation for the token reference tables and swatch grids, plus a
+ generic data-table utility. All of this is documentation chrome — the
+ design system itself has no Table component (see the note on the removed
+ `table`/`progress`/`callout` pages in git history). */
+
+.ad-swatch-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs) var(--ad-space-s);
+ padding: 0;
+ list-style: none;
+}
+.ad-swatch {
+ display: flex;
+ flex-direction: column;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ overflow: hidden;
+ background: var(--ad-color-surface-card);
+}
+.ad-swatch__chip {
+ display: flex;
+ align-items: flex-end;
+ justify-content: flex-end;
+ height: 4rem;
+ padding: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+}
+.ad-swatch__meta {
+ display: flex;
+ flex-direction: column;
+ gap: 0.1rem;
+ padding: var(--ad-space-2xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+}
+.ad-swatch__hex { color: var(--ad-color-text-primary); text-transform: uppercase; }
+.ad-swatch__ratio { color: var(--ad-color-text-tertiary); }
+
+.ad-alias-row { display: flex; align-items: center; gap: var(--ad-space-2xs); }
+.ad-alias-chip {
+ width: 1.5rem;
+ height: 1.5rem;
+ flex: none;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+}
+.ad-alias-chip--pair { display: flex; }
+.ad-alias-chip--pair > span { flex: 1; }
+
+/* Type specimen rows. */
+.ad-specimen {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ gap: var(--ad-space-3xs);
+ padding-block: var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-specimen__meta {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-specimen__sample {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: var(--ad-color-text-primary);
+}
+
+/* Space and radius rulers. */
+.ad-ruler { display: flex; flex-direction: column; gap: var(--ad-space-2xs); }
+.ad-ruler__row { display: grid; grid-template-columns: 6.5rem 4.5rem 1fr; align-items: center; gap: var(--ad-space-xs); font-family: var(--ad-font-mono); font-size: 11px; }
+.ad-ruler__bar { height: 0.75rem; background: var(--ad-color-accent); border-radius: var(--ad-radius-sm); }
+.ad-ruler__num { color: var(--ad-color-text-tertiary); text-align: right; }
+
+.ad-radius-grid, .ad-elevation-grid, .ad-motion-grid, .ad-accent-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs);
+ padding: 0;
+ list-style: none;
+}
+.ad-radius-demo {
+ height: 4.5rem;
+ background: var(--ad-color-accent-soft);
+ border: 1px solid var(--ad-color-accent);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-elevation-demo {
+ height: 4.5rem;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-motion-demo {
+ height: 4.5rem;
+ display: grid;
+ place-items: center;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-md);
+ margin-bottom: var(--ad-space-2xs);
+ cursor: pointer;
+ overflow: hidden;
+}
+.ad-motion-demo__dot {
+ width: 1.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent);
+ translate: -3rem 0;
+ transition-property: translate;
+ transition-duration: var(--ad-dur-base);
+ transition-timing-function: var(--ad-ease);
+}
+.ad-motion-demo:hover .ad-motion-demo__dot,
+.ad-motion-demo:focus-visible .ad-motion-demo__dot { translate: 3rem 0; }
+
+.ad-accent-demo {
+ height: 4.5rem;
+ display: flex;
+ align-items: flex-end;
+ padding: var(--ad-space-2xs);
+ border-radius: var(--ad-radius-md);
+ color: #fff;
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ margin-bottom: var(--ad-space-2xs);
+}
+
+/* Token names read `<code>` sitewide, which already carries a mono chip
+ look for running prose (see :not(pre) > code in base/typography.css).
+ Inside these specimen/swatch/ruler rows the old `.ad-token-name` class was
+ plain mono text with no chip, so `<code>` is flattened back to that look
+ here the same way table cells are. */
+.ad-swatch code, .ad-specimen code, .ad-ruler code, .ad-alias-row code {
+ background: none;
+ border: 0;
+ padding: 0;
+ font-size: var(--ad-step--2);
+ color: var(--ad-color-text-primary);
+ white-space: nowrap;
+}
+
+/* A documentation aside. Like `<table>`, this is site chrome for the prose
+ itself, not a system component — the real system has no Callout. */
+.ad-callout {
+ margin-block: var(--ad-space-s);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-left: 3px solid var(--ad-color-accent);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+}
+/* Generic reference tables (component APIs, alias tables, the contrast
+ contract). `<table>` IS a table — no class needed to say so. Every table
+ on this site uses the one compact density, so there is no separate
+ modifier for it. `display: block` plus `overflow-x: auto` gives the table
+ its own horizontal scrollbar on narrow viewports without a wrapper div;
+ the anonymous table box the browser generates around `<thead>`/`<tbody>`
+ still does the row/column layout. `<code>` replaces the old
+ `.ad-token-name` / `.ad-table__code` cells — it is already styled
+ sitewide, just re-flattened here to a plain mono run instead of the
+ chip-with-border look prose code gets, to match this table's original
+ look. `.num` is the one sanctioned class left, for right-aligned numeric
+ columns — there is no attribute that means "this column is a number". */
+table { width: 100%; border-collapse: collapse; font-size: var(--ad-step--1); display: block; overflow-x: auto; }
+table th, table td {
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ text-align: left;
+ vertical-align: top;
+}
+table th { font-family: var(--ad-font-mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ad-color-text-secondary); font-weight: 500; }
+table code { background: none; border: 0; padding: 0; font-size: var(--ad-step--2); white-space: nowrap; }
+.num { text-align: right; font-variant-numeric: tabular-nums; }
+
+/* Home page. */
+.ad-hero { padding-block: var(--ad-space-m) var(--ad-space-l); }
+.ad-hero__title {
+ font-size: var(--ad-step-4);
+ margin-bottom: var(--ad-space-xs);
+}
+.ad-hero__actions { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); margin-top: var(--ad-space-s); }
+.ad-stat-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-l);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+}
+.ad-stat__value {
+ display: block;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-2);
+ font-weight: 600;
+ line-height: 1.16;
+ font-variant-numeric: tabular-nums;
+}
+.ad-stat__label {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+
+/* Card chrome. The pane supplies the frame; this just insets the page. */
+body { padding: var(--ad-space-l) var(--ad-space-m); }
+.ad-doc-main { max-width: 50rem; margin-inline: auto; }
+.ad-doc-main > * { max-width: none; }
+</style>
+</head>
+<body>
+<main class="ad-doc-main">
+<header class="ad-doc-title"><span class="ad-doc-title__section">Components</span>
+ <h1>IconButton</h1>
+</header>
+<p class="ad-lead">A square icon-only button. Always pass a label — it becomes the accessible name and the native tooltip, since there is no visible text to fall back on.</p>
+<h2 id="variants">Variants</h2>
+
+<p><code>ghost</code> is the default and transparent. <code>outline</code> is a bordered surface, for
+an icon button sitting alone rather than in a toolbar. <code>accent</code> is a soft
+accent tint, for a toggled or active state — the segmented-tabs pattern this
+system does not otherwise reach for.</p>
+
+<p>A square icon-only button is still a <code><button></code>, but there is no attribute
+that tells it apart from a plain <a href="https://kejadlen.github.io/arbitrary-definitions-design-system/components/button/">Button</a>
+sitting alone — <code>variant</code>/<code>size</code> mean different things for each — so
+<code>.icon-button</code> is the one class this component needs.</p>
+
+<figure class="example">
+<figcaption>Variants</figcaption>
+<div>
+<button class="icon-button" aria-label="Delete">
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13" /></svg>
+</button>
+<button class="icon-button" data-variant="outline" aria-label="Settings">
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="3" /><path d="M12 2.5v3M12 18.5v3M3.6 7.2l2.6 1.5M17.8 15.3l2.6 1.5M3.6 16.8l2.6-1.5M17.8 8.7l2.6-1.5" /></svg>
+</button>
+<button class="icon-button" data-variant="accent" aria-label="Filter (active)">
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 5h16l-6 8v5l-4 2v-7z" /></svg>
+</button>
+</div>
+<details open=""><summary>Markup</summary>
+<pre><code><button class="icon-button" aria-label="Delete">
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13" /></svg>
+</button>
+<button class="icon-button" data-variant="outline" aria-label="Settings">
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="3" /><path d="M12 2.5v3M12 18.5v3M3.6 7.2l2.6 1.5M17.8 15.3l2.6 1.5M3.6 16.8l2.6-1.5M17.8 8.7l2.6-1.5" /></svg>
+</button>
+<button class="icon-button" data-variant="accent" aria-label="Filter (active)">
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 5h16l-6 8v5l-4 2v-7z" /></svg>
+</button></code></pre>
+</details>
+</figure>
+
+<h2 id="sizes">Sizes</h2>
+
+<figure class="example">
+<figcaption>Sizes</figcaption>
+<div>
+<button class="icon-button" data-variant="outline" data-size="sm" aria-label="Back"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 18l-6-6 6-6" /></svg></button>
+<button class="icon-button" data-variant="outline" aria-label="Back"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 18l-6-6 6-6" /></svg></button>
+<button class="icon-button" data-variant="outline" data-size="lg" aria-label="Back"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 18l-6-6 6-6" /></svg></button>
+</div>
+<details><summary>Markup</summary>
+<pre><code><button class="icon-button" data-variant="outline" data-size="sm" aria-label="Back"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 18l-6-6 6-6" /></svg></button>
+<button class="icon-button" data-variant="outline" aria-label="Back"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 18l-6-6 6-6" /></svg></button>
+<button class="icon-button" data-variant="outline" data-size="lg" aria-label="Back"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 18l-6-6 6-6" /></svg></button></code></pre>
+</details>
+</figure>
+
+<h2 id="paired-with-tooltip">Paired with Tooltip</h2>
+
+<p>An icon-only control almost always wants a <a href="https://kejadlen.github.io/arbitrary-definitions-design-system/components/tooltip/">Tooltip</a> too — the <code>aria-label</code> covers accessibility, the tooltip covers a sighted mouse user glancing for a hint.</p>
+
+<figure class="example">
+<figcaption>With a tooltip</figcaption>
+<div>
+<span>
+ <button class="icon-button" aria-label="Delete" aria-describedby="icon-btn-tip">
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13" /></svg>
+ </button>
+ <span role="tooltip" id="icon-btn-tip">Delete</span>
+</span>
+</div>
+<details><summary>Markup</summary>
+<pre><code><span>
+ <button class="icon-button" aria-label="Delete" aria-describedby="icon-btn-tip">
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13" /></svg>
+ </button>
+ <span role="tooltip" id="icon-btn-tip">Delete</span>
+</span></code></pre>
+</details>
+</figure>
+
+<h2 id="api">API</h2>
+
+<table>
+ <thead><tr><th scope="col">Element / attribute</th><th scope="col">Values</th><th scope="col">Notes</th></tr></thead>
+ <tbody>
+ <tr><td><code>button.icon-button</code></td><td></td><td>Required base — the one class in this component, since nothing distinguishes it from Button structurally.</td></tr>
+ <tr><td><code>aria-label</code></td><td>string</td><td>Required — becomes the accessible name and the native tooltip.</td></tr>
+ <tr><td><code>data-size</code></td><td><code>sm | lg</code></td><td>28 / 40px square. Omit for the default 34px.</td></tr>
+ <tr><td><code>data-variant</code></td><td><code>outline | accent</code></td><td>Omit for the default, transparent ghost look.</td></tr>
+ </tbody>
+</table>
+</main>
+<script>
+// Behavior for the pieces of the system that need it. Everything else is
+// CSS and semantic HTML on purpose — a design system that only works with
+// JavaScript running is a component library with extra steps.
+(function () {
+ "use strict";
+
+ var STORAGE_KEY = "ad-theme";
+
+ function readStored() {
+ try {
+ return localStorage.getItem(STORAGE_KEY);
+ } catch (error) {
+ return null;
+ }
+ }
+
+ function writeStored(value) {
+ try {
+ localStorage.setItem(STORAGE_KEY, value);
+ } catch (error) {
+ /* Nothing to do — the media query still gives a sensible default. */
+ }
+ }
+
+ function systemPrefersDark() {
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
+ }
+
+ function currentTheme() {
+ var explicit = document.documentElement.getAttribute("data-theme");
+ if (explicit) return explicit;
+ return systemPrefersDark() ? "dark" : "light";
+ }
+
+ function paintToggle(theme) {
+ document.querySelectorAll("[data-theme-toggle]").forEach(function (toggle) {
+ var next = theme === "dark" ? "light" : "dark";
+ toggle.querySelectorAll("[data-theme-icon]").forEach(function (icon) {
+ icon.hidden = icon.dataset.themeIcon !== theme;
+ });
+ toggle.setAttribute("aria-label", "Switch to the " + next + " theme");
+ toggle.setAttribute("title", "Switch to the " + next + " theme");
+ var label = toggle.querySelector("[data-theme-label]");
+ if (label) label.textContent = "Switch to the " + next + " theme";
+ });
+ }
+
+ function setUpTheme() {
+ paintToggle(currentTheme());
+
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-theme-toggle]");
+ if (!toggle) return;
+
+ var next = currentTheme() === "dark" ? "light" : "dark";
+ document.documentElement.setAttribute("data-theme", next);
+ writeStored(next);
+ paintToggle(next);
+ });
+
+ // Follow the OS while the reader has not made an explicit choice.
+ window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", function () {
+ if (!readStored()) paintToggle(currentTheme());
+ });
+ }
+
+ // Tabs, wired to the WAI-ARIA authoring practices: arrows move, Home/End
+ // jump, and only the selected tab stays in the tab order.
+ function setUpTabs() {
+ document.querySelectorAll("[data-tabs]").forEach(function (root) {
+ var tabs = Array.prototype.slice.call(root.querySelectorAll('[role="tab"]'));
+ if (!tabs.length) return;
+
+ function select(tab, focus) {
+ tabs.forEach(function (candidate) {
+ var selected = candidate === tab;
+ candidate.setAttribute("aria-selected", String(selected));
+ candidate.tabIndex = selected ? 0 : -1;
+ var panel = document.getElementById(candidate.getAttribute("aria-controls"));
+ if (panel) panel.hidden = !selected;
+ });
+ if (focus) tab.focus();
+ }
+
+ root.addEventListener("click", function (event) {
+ var tab = event.target.closest('[role="tab"]');
+ if (tab) select(tab, false);
+ });
+
+ root.addEventListener("keydown", function (event) {
+ var index = tabs.indexOf(document.activeElement);
+ if (index === -1) return;
+
+ var next = null;
+ if (event.key === "ArrowRight") next = tabs[(index + 1) % tabs.length];
+ else if (event.key === "ArrowLeft") next = tabs[(index - 1 + tabs.length) % tabs.length];
+ else if (event.key === "Home") next = tabs[0];
+ else if (event.key === "End") next = tabs[tabs.length - 1];
+
+ if (next) {
+ event.preventDefault();
+ select(next, true);
+ }
+ });
+
+ select(tabs.filter(function (tab) {
+ return tab.getAttribute("aria-selected") === "true";
+ })[0] || tabs[0], false);
+ });
+ }
+
+ function setUpNavToggle() {
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-nav-toggle]");
+ if (!toggle) return;
+
+ var sidebar = toggle.closest("[data-sidebar]");
+ var collapsed = sidebar.dataset.collapsed !== "false";
+ sidebar.dataset.collapsed = String(!collapsed);
+ toggle.setAttribute("aria-expanded", String(collapsed));
+ });
+ }
+
+ setUpTheme();
+ setUpTabs();
+ setUpNavToggle();
+})();
+
+</script>
+</body>
+</html>
diff --git a/design-bundle/components/input.html b/design-bundle/components/input.html
new file mode 100644
index 0000000..cfa85fb
--- /dev/null
+++ b/design-bundle/components/input.html
@@ -0,0 +1,1817 @@
+<!-- @dsCard group="Forms" -->
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Input</title>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&display=swap">
+<style>
+/* Arbitrary Definitions — design tokens. Hand-maintained; edit values directly. */
+
+:root {
+ /* Palette ramps — reference only. Components use the aliases below. */
+ --ad-neutral-0: #faf9f7;
+ --ad-neutral-50: #fdfdfb;
+ --ad-neutral-100: #f1f0ed;
+ --ad-neutral-150: #e8e6e2;
+ --ad-neutral-200: #dcdad7;
+ --ad-neutral-300: #c0bdb9;
+ --ad-neutral-500: #888681;
+ --ad-neutral-600: #5f5d59;
+ --ad-neutral-800: #2a2926;
+ --ad-neutral-900: #100f0d;
+ --ad-neutral-dark-0: #0e0d0b;
+ --ad-neutral-dark-50: #151412;
+ --ad-neutral-dark-100: #1e1d1a;
+ --ad-neutral-dark-150: #252421;
+ --ad-neutral-dark-200: #2c2b29;
+ --ad-neutral-dark-300: #44423f;
+ --ad-neutral-dark-500: #7c7a76;
+ --ad-neutral-dark-600: #a09e9a;
+ --ad-neutral-dark-800: #d9d7d4;
+ --ad-neutral-dark-900: #e9e8e6;
+ --ad-accent-teal: #3a6a63;
+ --ad-accent-clay: #9a5a3c;
+ --ad-accent-ink-blue: #2b4a78;
+ --ad-accent-ochre: #93701f;
+ --ad-accent-plum: #6a3a5c;
+
+ /* Surfaces */
+ --ad-color-surface-page: #faf9f7;
+ --ad-color-surface-card: #fdfdfb;
+ --ad-color-surface-fill: #f1f0ed;
+ --ad-color-surface-fill-2: #e8e6e2;
+
+ /* Borders */
+ --ad-color-border-hairline: #dcdad7;
+ --ad-color-border-default: #c0bdb9;
+
+ /* Text */
+ --ad-color-text-primary: #100f0d;
+ --ad-color-text-secondary: #5f5d59;
+ --ad-color-text-tertiary: #888681;
+
+ /* Accent */
+ --ad-color-accent: #3a6a63;
+ --ad-color-accent-fg: #ffffff;
+ --ad-color-accent-ink: color-mix(in oklch, var(--ad-color-accent) 78%, black);
+ --ad-color-accent-soft: color-mix(in oklch, var(--ad-color-accent) 12%, white);
+
+ /* Status (fixed — never swapped like the accent) */
+ --ad-color-success: #3f7a4a;
+ --ad-color-success-bg: color-mix(in oklch, var(--ad-color-success) 12%, white);
+ --ad-color-danger: #ad3a2c;
+ --ad-color-danger-bg: color-mix(in oklch, var(--ad-color-danger) 10%, white);
+
+ /* Type families */
+ --ad-font-sans: -apple-system, "Helvetica Neue", Helvetica, Arial, system-ui, sans-serif;
+ --ad-font-mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, Consolas, "Courier New", monospace;
+ --ad-font-serif: Georgia, "Times New Roman", "Iowan Old Style", serif;
+
+ /* Type steps — fluid (Utopia scale), the design system's own body-text size */
+ --ad-step--2: clamp(0.7813rem, 0.7736rem + 0.0341vw, 0.8rem);
+ --ad-step--1: clamp(0.9375rem, 0.9119rem + 0.1136vw, 1rem);
+ --ad-step-0: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-step-1: clamp(1.35rem, 1.2631rem + 0.3864vw, 1.5625rem);
+ --ad-step-2: clamp(1.62rem, 1.4837rem + 0.6057vw, 1.9531rem);
+ --ad-step-3: clamp(1.944rem, 1.7405rem + 0.9044vw, 2.4414rem);
+ --ad-step-4: clamp(2.3328rem, 2.0387rem + 1.3072vw, 3.0518rem);
+ --ad-step-5: clamp(2.7994rem, 2.384rem + 1.8461vw, 3.8147rem);
+
+ /* Space */
+ --ad-space-3xs: clamp(0.3125rem, 0.3125rem + 0vw, 0.3125rem);
+ --ad-space-2xs: clamp(0.5625rem, 0.5369rem + 0.1136vw, 0.625rem);
+ --ad-space-xs: clamp(0.875rem, 0.8494rem + 0.1136vw, 0.9375rem);
+ --ad-space-s: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-space-m: clamp(1.6875rem, 1.6108rem + 0.3409vw, 1.875rem);
+ --ad-space-l: clamp(2.25rem, 2.1477rem + 0.4545vw, 2.5rem);
+ --ad-space-xl: clamp(3.375rem, 3.2216rem + 0.6818vw, 3.75rem);
+ --ad-space-2xl: clamp(4.5rem, 4.2955rem + 0.9091vw, 5rem);
+ --ad-space-3xl: clamp(6.75rem, 6.4432rem + 1.3636vw, 7.5rem);
+
+ /* Radius */
+ --ad-radius-sm: 4px;
+ --ad-radius-md: 6px;
+ --ad-radius-lg: 10px;
+ --ad-radius-full: 999px;
+ --ad-border-w: 1px;
+
+ /* Elevation */
+ --ad-shadow-sm: 0 1px 2px rgba(20, 18, 14, 0.05);
+ --ad-shadow-float: 0 2px 6px rgba(20, 18, 14, 0.06), 0 10px 24px -12px rgba(20, 18, 14, 0.18);
+
+ /* Motion */
+ --ad-dur-fast: 0.12s;
+ --ad-dur-base: 0.16s;
+ --ad-ease: cubic-bezier(0.2, 0, 0, 1);
+}
+
+@media (prefers-color-scheme: dark) {
+ :root:not([data-theme="light"]) {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+ }
+}
+
+:root[data-theme="dark"] {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ :root {
+ --ad-dur-fast: 0ms;
+ --ad-dur-base: 0ms;
+ }
+}
+
+/* A short reset. Everything it sets is something the system would otherwise
+ have to override in every component.
+
+ The rem values used for this documentation site's own chrome (measure,
+ content width, sidebar width, header height) belong to this site, not to
+ the design system it documents — the source design system defines no
+ container-width tokens at all, so there is nothing under `--ad-*` to reach
+ for here. See the header comment in site/shell.css. */
+
+*,
+*::before,
+*::after { box-sizing: border-box; }
+
+html {
+ -webkit-text-size-adjust: 100%;
+ scroll-behavior: smooth;
+ scroll-padding-top: calc(3.5rem + var(--ad-space-s));
+}
+
+@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;
+ }
+}
+
+body {
+ margin: 0;
+ background: var(--ad-color-surface-page);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ line-height: 1.55;
+ font-synthesis-weight: none;
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+}
+
+::selection {
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+}
+
+h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd { margin: 0; }
+ul[class], ol[class] { list-style: none; margin: 0; padding: 0; }
+
+img, svg, video { display: block; max-width: 100%; height: auto; }
+
+button, input, select, textarea {
+ font: inherit;
+ color: inherit;
+}
+
+hr {
+ height: 1px;
+ margin: var(--ad-space-l) 0;
+ border: 0;
+ background: var(--ad-color-border-hairline);
+}
+
+/* One focus ring, defined once. No component is allowed its own. */
+:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+ border-radius: var(--ad-radius-sm);
+}
+:focus:not(:focus-visible) { outline: none; }
+
+[hidden] { display: none !important; }
+
+.ad-visually-hidden {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip-path: inset(50%);
+ white-space: nowrap;
+ border: 0;
+}
+
+/* Every rule here reads a type token for family and size. Weight, line-height,
+ and letter-spacing are hardcoded to the exact numbers the source ships —
+ there is no `--ad-weight-*` / `--ad-leading-*` / `--ad-tracking-*` scale in
+ the real system, only eight fixed compound styles (see the Typography
+ foundation page, which this file implements verbatim). */
+
+.type-display { font: 600 var(--ad-step-4)/1.08 var(--ad-font-sans); letter-spacing: -0.015em; }
+.type-h1, h1 { font: 600 var(--ad-step-3)/1.12 var(--ad-font-sans); letter-spacing: -0.012em; }
+.type-h2, h2 { font: 600 var(--ad-step-2)/1.16 var(--ad-font-sans); letter-spacing: -0.01em; }
+.type-h3, h3 { font: 600 var(--ad-step-1)/1.25 var(--ad-font-sans); }
+.type-body { font: 400 var(--ad-step-0)/1.55 var(--ad-font-sans); }
+.type-body-sm { font: 400 var(--ad-step--1)/1.5 var(--ad-font-sans); }
+.type-label {
+ font: 500 var(--ad-step--2)/1 var(--ad-font-mono);
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: var(--ad-color-text-secondary);
+}
+.type-mono { font: 400 var(--ad-step--1)/1.5 var(--ad-font-mono); }
+
+/* Prose defaults for the documentation body copy. These read the same
+ tokens the compound classes above do; they are not a second scale. */
+h1, h2, h3, h4 { font-weight: 600; text-wrap: balance; }
+
+h4 { font: 600 var(--ad-step-0)/1.3 var(--ad-font-sans); }
+
+p, ul, ol { margin: 0 0 var(--ad-space-s); }
+li + li { margin-top: var(--ad-space-3xs); }
+
+a {
+ color: var(--ad-color-accent);
+ text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
+ text-underline-offset: 0.18em;
+ text-decoration-thickness: 1px;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ text-decoration-color var(--ad-dur-fast) var(--ad-ease);
+}
+a:hover {
+ color: var(--ad-color-accent-ink);
+ text-decoration-color: currentColor;
+}
+
+strong, b { font-weight: 600; }
+
+small { font-size: var(--ad-step--1); }
+
+.ad-lead {
+ font-size: var(--ad-step-1);
+ line-height: 1.5;
+ color: var(--ad-color-text-secondary);
+ max-width: 68ch;
+ text-wrap: pretty;
+}
+
+.ad-muted { color: var(--ad-color-text-secondary); }
+.ad-subtle { color: var(--ad-color-text-tertiary); }
+
+code, kbd, samp, pre {
+ font-family: var(--ad-font-mono);
+ font-feature-settings: "liga" 0;
+}
+
+:not(pre) > code {
+ padding: 0.15em 0.4em;
+ font-size: 0.875em;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-sm);
+ white-space: nowrap;
+}
+
+kbd {
+ display: inline-block;
+ padding: 0.1em 0.45em;
+ font-size: var(--ad-step--2);
+ line-height: 1.6;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-bottom-width: 2px;
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-secondary);
+}
+
+blockquote {
+ margin-block: var(--ad-space-s);
+ padding-left: var(--ad-space-s);
+ border-left: 2px solid var(--ad-color-accent);
+ color: var(--ad-color-text-secondary);
+}
+
+/* Button
+ A flat, quiet button that recedes rather than performs — no gradients, no
+ shine. Variants: default, primary, dark, ghost, danger. Sizes: sm, md
+ (default), lg — padding and font step only, weight and radius stay put.
+
+ The contract is the element itself: a bare <button> is styled directly, no
+ class required. An <a> that acts like a button (navigates rather than
+ changes something — see the Button page's "Links that act like buttons")
+ opts in with a single `.btn` class, since a bare `a` selector would catch
+ every ordinary prose link on the site. `:not(.icon-button)` keeps this
+ block from leaking onto IconButton, which shares the <button> tag but is
+ an entirely different shape. */
+
+button:not(.icon-button),
+a.btn {
+ --_bg: var(--ad-color-surface-card);
+ --_fg: var(--ad-color-text-primary);
+ --_border: var(--ad-color-border-default);
+ --_pad: var(--ad-space-xs) var(--ad-space-s);
+ --_font: var(--ad-step--1);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: var(--ad-space-2xs);
+ padding: var(--_pad);
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-sans);
+ font-size: var(--_font);
+ font-weight: 550;
+ letter-spacing: -0.01em;
+ line-height: 1.2;
+ text-align: center;
+ text-decoration: none;
+ white-space: nowrap;
+ cursor: pointer;
+ transition: filter var(--ad-dur-fast) var(--ad-ease),
+ border-color var(--ad-dur-fast) var(--ad-ease);
+}
+
+:is(button:not(.icon-button), a.btn):hover:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.96); }
+:is(button:not(.icon-button), a.btn):active:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.92); }
+
+button:not(.icon-button):disabled,
+:is(button:not(.icon-button), a.btn)[aria-disabled="true"] {
+ opacity: 0.5;
+ cursor: default;
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="primary"] {
+ --_bg: var(--ad-color-accent);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-accent);
+}
+
+/* Reserved for the single most-committing action in a view — a final Save,
+ never a whole row of them. */
+:is(button:not(.icon-button), a.btn)[data-variant="dark"] {
+ --_bg: var(--ad-color-text-primary);
+ --_fg: var(--ad-color-surface-page);
+ --_border: var(--ad-color-text-primary);
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="ghost"] {
+ --_bg: transparent;
+ --_fg: var(--ad-color-text-primary);
+ --_border: transparent;
+}
+
+/* The source hardcodes this label white rather than deriving it — danger
+ is not "on a filled accent surface", so --ad-color-accent-fg is reused
+ here only because its resolved value is the same literal white, not
+ because the two roles are the same thing. */
+:is(button:not(.icon-button), a.btn)[data-variant="danger"] {
+ --_bg: var(--ad-color-danger);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-danger);
+}
+
+:is(button:not(.icon-button), a.btn)[data-size="sm"] { --_pad: 0.375rem 0.75rem; --_font: 0.8125rem; }
+:is(button:not(.icon-button), a.btn)[data-size="lg"] { --_pad: 0.75rem 1.375rem; --_font: var(--ad-step-0); }
+
+:is(button:not(.icon-button), a.btn)[data-block] { width: 100%; }
+
+/* Button group — [role="group"] is already the correct ARIA role for a
+ toolbar of related buttons, so it doubles as the styling hook for free. */
+[role="group"] {
+ display: inline-flex;
+ isolation: isolate;
+}
+[role="group"] > button { border-radius: 0; }
+[role="group"] > button:first-child { border-start-start-radius: var(--ad-radius-md); border-end-start-radius: var(--ad-radius-md); }
+[role="group"] > button:last-child { border-start-end-radius: var(--ad-radius-md); border-end-end-radius: var(--ad-radius-md); }
+[role="group"] > button + button { margin-inline-start: -1px; }
+[role="group"] > button:hover,
+[role="group"] > button:focus-visible { z-index: 1; }
+
+/* IconButton — a square icon-only button. Always needs an aria-label; it
+ becomes both the accessible name and the native tooltip. There is no
+ attribute that distinguishes "a button that is only an icon" from a plain
+ Button, so this is the one sanctioned class in this file. */
+.icon-button {
+ --_size: 2.125rem;
+ --_bg: transparent;
+ --_border: transparent;
+ --_fg: var(--ad-color-text-secondary);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ padding: 0;
+ flex: none;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease),
+ color var(--ad-dur-fast) var(--ad-ease);
+}
+.icon-button:hover:not(:disabled) { background: var(--ad-color-surface-fill-2); }
+.icon-button:disabled { opacity: 0.5; cursor: default; }
+
+.icon-button[data-size="sm"] { --_size: 1.75rem; }
+.icon-button[data-size="lg"] { --_size: 2.5rem; }
+
+.icon-button[data-variant="outline"] { --_bg: var(--ad-color-surface-card); --_border: var(--ad-color-border-default); --_fg: var(--ad-color-text-primary); }
+.icon-button[data-variant="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); }
+
+/* Field
+ The labeled field wrapper — a <label> that wraps its own caption text
+ together with the control, so the association is native and needs no
+ `for`/`id` pair. The caption is mono, uppercase, tracked — the same
+ catalog-label convention as Badge and type-label.
+
+ A <label> is reused for three different shapes across this system (this
+ wrapper, Checkbox/Radio's row, Switch's row), so it cannot be styled bare
+ without one shape leaking into another. Field is the one of the three with
+ no distinguishing attribute of its own, so it gets the sanctioned class;
+ Checkbox/Radio tell themselves apart structurally instead (see below). */
+.field {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ min-width: 0;
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Hint and error read the native fine-print element. `role="alert"` is a
+ real live-region role for validation text, so it doubles as the selector
+ that turns a hint red. */
+.field > small {
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+.field > small[role="alert"] { color: var(--ad-color-danger); }
+
+/* Input / Select — bordered, matched to each other exactly. Both tags ARE
+ the component, so they are styled bare; nothing else on this site uses a
+ text input or a select. */
+input:not([type="checkbox"]):not([type="radio"]),
+select {
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ outline: none;
+ transition: border-color var(--ad-dur-fast) var(--ad-ease),
+ box-shadow var(--ad-dur-fast) var(--ad-ease);
+}
+input::placeholder { color: var(--ad-color-text-tertiary); }
+
+input:not([type="checkbox"]):not([type="radio"]):focus,
+select:focus {
+ border-color: var(--ad-color-accent);
+ box-shadow: 0 0 0 2px var(--ad-color-accent-soft);
+}
+
+input:not([type="checkbox"]):not([type="radio"]):disabled,
+select:disabled {
+ opacity: 0.5;
+ cursor: default;
+}
+
+/* The chevron is the one flagged color exception in this component set:
+ CSS cannot point a data-URI background-image at a custom property, so its
+ stroke is a literal hex rather than a var(--ad-*) reference. It is picked
+ to match --ad-color-text-secondary and commented here so it is not
+ mistaken for a stray color. */
+select {
+ appearance: none;
+ padding-inline-end: var(--ad-space-l);
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%235f5d59' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right var(--ad-space-xs) center;
+}
+
+input[aria-invalid="true"],
+select[aria-invalid="true"] {
+ border-color: var(--ad-color-danger);
+ background-color: var(--ad-color-danger-bg);
+}
+
+/* Checkbox / Radio — the real native controls, tinted with accent-color
+ instead of a hand-drawn box. This is not just a rename: it deletes the
+ custom SVG-checkmark markup entirely and lets the browser draw its own
+ (platform-appropriate) check and dot. `role="switch"` is excluded here so
+ Switch — a checkbox playing a different role — can style itself below
+ without this rule fighting it. A <label> wrapping a checkbox/radio is told
+ apart from Field's <label> structurally: it :has() one as a direct child,
+ no class required. */
+input[type="checkbox"]:not([role="switch"]),
+input[type="radio"] {
+ width: 1.125rem;
+ height: 1.125rem;
+ margin: 0;
+ accent-color: var(--ad-color-accent);
+ cursor: pointer;
+}
+input[type="checkbox"]:not([role="switch"]):focus-visible,
+input[type="radio"]:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+}
+
+label:has(> input[type="checkbox"]:not([role="switch"])),
+label:has(> input[type="radio"]) {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 400;
+ text-transform: none;
+ letter-spacing: normal;
+ color: var(--ad-color-text-primary);
+}
+label:has(> input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Switch — for on/off settings only, never a substitute for Checkbox.
+ `input[type="checkbox"][role="switch"]` is real ARIA (the correct role for
+ a toggle), not an invented hook, but the visual track+thumb needs a second
+ element for the thumb's independent position and transition — more than
+ `appearance: none` plus pseudo-elements on the input itself can cleanly
+ carry — so `.switch` is a deliberate, documented fallback class rather
+ than a first choice. */
+.switch {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ color: var(--ad-color-text-primary);
+}
+.switch input[role="switch"] { position: absolute; opacity: 0; width: 2.25rem; height: 1.25rem; margin: 0; }
+
+.switch span {
+ position: relative;
+ flex: none;
+ width: 2.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-border-default);
+ transition: background-color var(--ad-dur-base) var(--ad-ease);
+}
+.switch span::after {
+ content: "";
+ position: absolute;
+ inset-block-start: 2px;
+ inset-inline-start: 2px;
+ width: 1rem;
+ height: 1rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-fg);
+ box-shadow: var(--ad-shadow-sm);
+ transition: translate var(--ad-dur-base) var(--ad-ease);
+}
+.switch input[role="switch"]:checked + span { background: var(--ad-color-accent); }
+.switch input[role="switch"]:checked + span::after { translate: 1rem 0; }
+.switch input[role="switch"]:focus-visible + span { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+.switch:has(input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Rows of fields — used by the settings-form pattern. `<form>`/`<fieldset>`
+ are the elements that already mean this, so they are styled bare. A row
+ grouping two fields side by side has no element of its own, so it keeps
+ one plain class. */
+form {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-s);
+ max-width: 28rem;
+}
+.form-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
+ gap: var(--ad-space-xs);
+}
+fieldset {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ margin: 0;
+ padding: 0;
+ border: 0;
+}
+fieldset > legend {
+ padding: 0 0 var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Badge
+ Mono, uppercase, tracked — the catalog-label convention for status and
+ meta values (SAVED, OVERDUE, RECOMMENDED). A label, never a button.
+
+ A <span> is too generic to style globally — it is used all over running
+ prose for unrelated reasons — so Badge gets the one sanctioned class this
+ shape needs. Tone is an attribute, not a modifier class. */
+
+.badge {
+ --_bg: var(--ad-color-surface-fill);
+ --_fg: var(--ad-color-text-secondary);
+ --_border: var(--ad-color-border-hairline);
+
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.1875rem 0.5rem;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-sm);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ line-height: 1;
+ white-space: nowrap;
+}
+
+.badge[data-tone="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); --_border: transparent; }
+.badge[data-tone="success"] { --_bg: var(--ad-color-success-bg); --_fg: var(--ad-color-success); --_border: transparent; }
+.badge[data-tone="danger"] { --_bg: var(--ad-color-danger-bg); --_fg: var(--ad-color-danger); --_border: transparent; }
+
+.badge-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-2xs);
+}
+
+/* Tag
+ A removable mono chip for freeform tags — locations, categories.
+ `[data-dashed]` renders an "add tag" affordance instead of a value. Same
+ reasoning as Badge: <span> is too generic to select bare, so Tag gets one
+ class. The remove control is a real <button>, styled through the
+ descendant selector rather than a class of its own. */
+
+.tag {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.375rem;
+ padding: 0.25rem 0.625rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-default);
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ line-height: 1;
+}
+
+.tag[data-dashed] {
+ background: transparent;
+ border-style: dashed;
+ color: var(--ad-color-text-tertiary);
+ cursor: pointer;
+}
+
+.tag button {
+ all: unset;
+ cursor: pointer;
+ color: var(--ad-color-text-tertiary);
+ line-height: 1;
+ font-size: 0.75rem;
+}
+.tag button:hover { color: var(--ad-color-text-primary); }
+
+.tag-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.375rem;
+}
+
+/* Card
+ Flat surface, hairline border, radius-lg. `[data-elevated]` is opt-in and
+ reserved for the one focused/primary card in a view — a dialog, the
+ primary capture card — flat is the default everywhere else.
+
+ A <div>/<article> is too generic to select bare, so Card keeps one
+ sanctioned class. When Card's job is holding a list of rows — the common
+ case, entry lists, task lists, settings rows — it is a real <ul class="
+ card">, and CardRow is a bare <li> underneath it: no second class needed,
+ the row look comes from the `.card > li` descendant selector alone. The
+ first row's top border is removed automatically so callers never have to
+ override it by hand. */
+
+.card {
+ display: flex;
+ flex-direction: column;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: none;
+ overflow: hidden;
+}
+
+.card[data-elevated] { box-shadow: var(--ad-shadow-float); }
+
+.card > li {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+}
+.card > li:first-child { border-top: none; }
+
+/* A row whose ENTIRE content is wrapped in a real <button> or <a> — the
+ accessible way to make a row clickable — fills the row edge-to-edge and
+ picks up a hover fill and pointer cursor. `:only-child` is what tells this
+ apart from a row that merely contains a small button among other content
+ (a leading checkbox next to a title and a trailing value, say): only a
+ button/anchor that IS the row's one and only child means "the whole row
+ is the control." */
+.card > li:has(> button:only-child),
+.card > li:has(> a:only-child) {
+ padding: 0;
+}
+.card > li > button:only-child,
+.card > li > a:only-child {
+ all: unset;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ box-sizing: border-box;
+ color: inherit;
+ font: inherit;
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.card > li > button:only-child:hover,
+.card > li > a:only-child:hover { background: var(--ad-color-surface-fill); }
+
+/* Documentation-only extras: a body/footer pair and a responsive grid, used
+ by the prose pages (rationale.md, index.md) to show a Card as a plain
+ content tile rather than a data row. Neither is part of Card's props —
+ they exist because a content tile's inner layout has no element of its
+ own to hang a selector on. */
+.card-body {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-m);
+}
+.card-footer {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ margin-top: auto;
+ padding: var(--ad-space-xs) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+
+.card[data-link] { transition: border-color var(--ad-dur-fast) var(--ad-ease), box-shadow var(--ad-dur-fast) var(--ad-ease); }
+.card[data-link]:hover { border-color: var(--ad-color-border-default); box-shadow: var(--ad-shadow-sm); }
+.card[data-link]:focus-within { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+
+.card-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
+ gap: var(--ad-space-s);
+}
+
+/* Avatar — lives here because it almost never appears outside a card or a
+ row (the Loop dashboard's initials chip, Archive's file-type chip). A
+ <span> is too generic for a bare selector, so it keeps one class. */
+.avatar {
+ --_size: 1.875rem;
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ flex: none;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 600;
+ overflow: hidden;
+}
+.avatar[data-square] { border-radius: var(--ad-radius-md); background: var(--ad-color-surface-fill); color: var(--ad-color-text-secondary); border: 1px solid var(--ad-color-border-hairline); }
+.avatar[data-size="sm"] { --_size: 1.5rem; }
+.avatar[data-size="lg"] { --_size: 2.25rem; }
+
+/* Tooltip — a dark mono label on hover, positioned above the child. Pure CSS:
+ the trigger and bubble are siblings inside one relatively-positioned span,
+ shown on :hover/:focus-within so it works from the keyboard too.
+
+ `[role="tooltip"]` is the correct ARIA role for the bubble itself, so it
+ doubles as the styling hook — zero classes there. The wrapping span has no
+ attribute of its own, but it does not need one: `:has()` picks out "a span
+ that contains a [role=tooltip] bubble" precisely, without reaching for a
+ class on an otherwise-generic element. */
+span:has(> [role="tooltip"]) {
+ position: relative;
+ display: inline-flex;
+}
+
+[role="tooltip"] {
+ position: absolute;
+ bottom: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ margin-bottom: 0.375rem;
+ padding: 0.25rem 0.5rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-text-primary);
+ color: var(--ad-color-surface-page);
+ font-family: var(--ad-font-mono);
+ /* 11px is the source's own literal choice — smaller than --ad-step--2, the
+ smallest step on the scale, because a tooltip is meant to read as a
+ glance-only aside rather than body text. */
+ font-size: 11px;
+ white-space: nowrap;
+ line-height: 1.4;
+ z-index: 20;
+ opacity: 0;
+ visibility: hidden;
+ pointer-events: none;
+ transition: opacity var(--ad-dur-fast) var(--ad-ease);
+}
+span:has(> [role="tooltip"]):hover > [role="tooltip"],
+span:has(> [role="tooltip"]):focus-within > [role="tooltip"] {
+ opacity: 1;
+ visibility: visible;
+}
+
+/* Toast — the real pattern used for delete confirmations: a flat bar with an
+ optional destructive action and a dismiss. `[data-fixed]` pins it
+ bottom-right for real use; without it the toast stays in document flow for
+ documentation. `role="status"` is the correct live-region role for a
+ transient status message, so it is both the accessibility hook and the
+ style selector — no base class needed. The action and dismiss buttons are
+ plain <button>s told apart by `aria-label="Dismiss"`, an attribute they
+ need anyway for the icon-only close control. */
+[role="status"] {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ min-width: 20rem;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+}
+[role="status"][data-fixed] {
+ position: fixed;
+ inset-block-end: var(--ad-space-s);
+ inset-inline-end: var(--ad-space-s);
+ z-index: 60;
+}
+[role="status"] > span { flex: 1; }
+[role="status"] button {
+ all: unset;
+ cursor: pointer;
+ font-weight: 600;
+ color: var(--ad-color-danger);
+}
+[role="status"] button[aria-label="Dismiss"] {
+ color: var(--ad-color-text-tertiary);
+ font-size: 1rem;
+ font-weight: 400;
+ line-height: 1;
+}
+[role="status"] button[aria-label="Dismiss"]:hover { color: var(--ad-color-text-primary); }
+
+/* Dialog — the native <dialog> element: a centered modal, flat surface, no
+ colored header, hairline dividers between title/body/footer. A <header>
+ holds the title and a <footer> holds the actions, matching the elements
+ the tag already wants; everything else in between is the body, styled by
+ excluding those two rather than by a class of its own.
+
+ `.dialog-demo` scopes a static, always-open example to its own preview
+ container instead of the viewport, so it can sit safely inside a
+ documentation page — this is a doc-only affordance, not part of Dialog's
+ contract. It relies on the browser's own dialog centering: the UA
+ stylesheet already gives an open <dialog> `position: absolute; inset: 0;
+ margin: auto`, which centers it inside the nearest positioned ancestor —
+ here, `.dialog-demo` itself. A real `.showModal()` call gets the genuine
+ viewport-fixed overlay and `::backdrop` for free; a plain `open` attribute
+ (used here so the demo renders without JavaScript) does not paint a
+ backdrop at all, since `::backdrop` only exists for a dialog shown
+ modally — wire the real open/close state and call `.showModal()` in the
+ consuming app. */
+.dialog-demo {
+ position: relative;
+ min-height: 20rem;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-lg);
+}
+
+dialog {
+ width: 26rem;
+ max-width: calc(100% - (2 * var(--ad-space-s)));
+ height: fit-content;
+ padding: 0;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ overflow: hidden;
+}
+dialog::backdrop {
+ background: rgba(20, 18, 14, 0.35);
+}
+dialog > header {
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-sans);
+ font-weight: 600;
+ font-size: var(--ad-step-0);
+ color: var(--ad-color-text-primary);
+}
+dialog > footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+}
+dialog > *:not(header):not(footer) {
+ padding: var(--ad-space-m);
+ margin: 0;
+ color: var(--ad-color-text-primary);
+ font-size: var(--ad-step--1);
+}
+
+/* Tabs
+ Follows the real WAI-ARIA tabs pattern: a `[role="tablist"]` of
+ `[role="tab"]` buttons controlling `[role="tabpanel"]`s. Those roles are
+ required for the pattern to be accessible in the first place, so they
+ double as the entire styling contract — no classes at all for the base
+ shape. The keyboard wiring lives in assets/js/system.js and is
+ variant-agnostic — it only cares about `[data-tabs]` / `[role=tab]`.
+
+ Two real variants: `underline` (mono, accent underline — primary section
+ nav, the default) and `segmented` (pill track, active segment lifts to the
+ surface — filters like All · Overdue · Upcoming, never primary
+ navigation), chosen with `data-variant="segmented"` on the tablist since
+ neither has a natural attribute of its own. */
+
+/* Every rule below the list container pairs [role="tablist"] with
+ [role="tab"] rather than styling [role="tab"] alone — Tab buttons are
+ still literal <button> elements, so the compound selector's extra
+ specificity is what keeps Button's base styling (button.css) from
+ winning the cascade on shared properties like padding and background. */
+
+[role="tablist"] {
+ display: flex;
+ gap: var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+[role="tablist"] [role="tab"] {
+ padding: 0.5rem 0;
+ margin-bottom: -1px;
+ background: none;
+ border: 0;
+ border-bottom: 2px solid transparent;
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--1);
+ letter-spacing: 0.02em;
+ cursor: pointer;
+ transition: color var(--ad-dur-fast) var(--ad-ease), border-color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"] [role="tab"]:hover { color: var(--ad-color-text-primary); }
+[role="tablist"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ border-bottom-color: var(--ad-color-accent);
+}
+
+[role="tablist"][data-variant="segmented"] {
+ display: inline-flex;
+ gap: 0.125rem;
+ padding: 0.1875rem;
+ border-bottom: 0;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-md);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"] {
+ all: unset;
+ cursor: pointer;
+ padding: 0.375rem 0.875rem;
+ border-radius: var(--ad-radius-sm);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 550;
+ color: var(--ad-color-text-secondary);
+ transition: background-color var(--ad-dur-fast) var(--ad-ease), color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ background: var(--ad-color-surface-card);
+ box-shadow: var(--ad-shadow-sm);
+}
+
+[role="tabpanel"] { padding-top: var(--ad-space-s); }
+[role="tabpanel"][hidden] { display: none; }
+[role="tabpanel"]:focus-visible { outline-offset: 4px; }
+
+/* Documentation site chrome. Everything below the `.ad-doc-` prefix belongs
+ to this site rather than to the design system it documents — including the
+ width/height constants used directly below. The source design system
+ defines no container-width tokens at all, so there is nothing under
+ `--ad-*` for a doc shell to reach for; the Space foundation page's own
+ notes say to omit that group entirely rather than invent one. */
+
+.ad-doc-skip {
+ position: absolute;
+ inset-inline-start: var(--ad-space-xs);
+ inset-block-start: -4rem;
+ z-index: 40;
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ box-shadow: var(--ad-shadow-float);
+ transition: inset-block-start var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-skip:focus { inset-block-start: var(--ad-space-2xs); }
+
+.ad-doc-header {
+ position: sticky;
+ inset-block-start: 0;
+ z-index: 30;
+ height: 3.5rem;
+ background: color-mix(in srgb, var(--ad-color-surface-page) 92%, transparent);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-header__inner {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ height: 100%;
+ max-width: 72rem;
+ margin-inline: auto;
+ padding-inline: var(--ad-space-xs);
+}
+
+.ad-doc-wordmark {
+ display: flex;
+ align-items: baseline;
+ gap: var(--ad-space-2xs);
+ color: var(--ad-color-text-primary);
+ text-decoration: none;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ font-weight: 600;
+ letter-spacing: -0.01em;
+ white-space: nowrap;
+}
+.ad-doc-wordmark:hover { color: var(--ad-color-text-primary); }
+.ad-doc-wordmark__mark {
+ width: 1.375rem;
+ height: 1.375rem;
+ flex: none;
+ align-self: center;
+ color: var(--ad-color-accent);
+}
+.ad-doc-wordmark__version {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+
+.ad-doc-header__actions { margin-inline-start: auto; display: flex; align-items: center; gap: var(--ad-space-2xs); }
+
+.ad-doc-layout {
+ display: grid;
+ grid-template-columns: 16rem minmax(0, 1fr);
+ gap: var(--ad-space-l);
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-m) var(--ad-space-xs) var(--ad-space-2xl);
+}
+
+.ad-doc-sidebar {
+ position: sticky;
+ inset-block-start: calc(3.5rem + var(--ad-space-m));
+ align-self: start;
+ max-height: calc(100vh - 3.5rem - var(--ad-space-l));
+ overflow-y: auto;
+ padding-inline-end: var(--ad-space-2xs);
+}
+.ad-doc-nav__group + .ad-doc-nav__group { margin-top: var(--ad-space-s); }
+.ad-doc-nav__heading {
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-doc-nav__link {
+ display: block;
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ margin-inline-start: calc(var(--ad-space-xs) * -1);
+ border-inline-start: 2px solid transparent;
+ border-radius: 0 var(--ad-radius-sm) var(--ad-radius-sm) 0;
+ color: var(--ad-color-text-secondary);
+ font-size: var(--ad-step--1);
+ text-decoration: none;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-nav__link:hover { color: var(--ad-color-text-primary); background: var(--ad-color-surface-fill); }
+.ad-doc-nav__link[aria-current="page"] {
+ color: var(--ad-color-accent-ink);
+ border-inline-start-color: var(--ad-color-accent);
+ background: var(--ad-color-accent-soft);
+ font-weight: 500;
+}
+
+.ad-doc-main {
+ min-width: 0;
+ /* The design system's own body-text token (--ad-step-0, 18–20px) is right
+ for a product screen but too large for long-form documentation prose.
+ This site's own copy gets a plain, fixed 16px instead — the token above
+ is unchanged, and specimen tables/demos below still show its real
+ value. */
+ font-size: 1rem;
+}
+.ad-doc-main > * { max-width: 50rem; }
+
+.ad-doc-title { margin-bottom: var(--ad-space-xs); }
+.ad-doc-title__section {
+ display: block;
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-accent);
+}
+
+.ad-doc-main h2 {
+ margin-block: var(--ad-space-2xl) var(--ad-space-xs);
+ padding-bottom: var(--ad-space-2xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-main h3 { margin-block: var(--ad-space-l) var(--ad-space-2xs); }
+.ad-doc-main h4 { margin-block: var(--ad-space-s) var(--ad-space-2xs); }
+.ad-doc-main p,
+.ad-doc-main ul,
+.ad-doc-main ol { max-width: 68ch; }
+.ad-doc-main ul:not([class]),
+.ad-doc-main ol:not([class]) { padding-inline-start: var(--ad-space-s); }
+
+.ad-doc-footer {
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-s) var(--ad-space-xs) var(--ad-space-l);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-tertiary);
+ font-size: var(--ad-step--1);
+}
+.ad-doc-footer__inner { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); justify-content: space-between; }
+
+/* `button.ad-doc-nav-toggle` (element + class) rather than a bare class:
+ button.css's own base rule is `button:not(.icon-button)`, which is also
+ one element + one class in specificity and sets `display: inline-flex` —
+ a bare `.ad-doc-nav-toggle { display: none }` loses that fight and the
+ toggle stays visible at every width. Matching its specificity here (and
+ coming later in the concatenated stylesheet) settles it in source order. */
+button.ad-doc-nav-toggle { display: none; }
+
+@media (max-width: 60rem) {
+ .ad-doc-layout { grid-template-columns: minmax(0, 1fr); gap: var(--ad-space-s); }
+ .ad-doc-sidebar {
+ position: static;
+ max-height: none;
+ }
+ /* The toggle is a normal Button; the panel it opens gets its own card
+ treatment. Putting the same card styling on the <aside> wrapper too
+ nested a button-shaped box inside an outer box for no reason. */
+ .ad-doc-nav {
+ margin-top: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ }
+ .ad-doc-sidebar[data-collapsed="true"] > .ad-doc-nav { display: none; }
+ button.ad-doc-nav-toggle {
+ display: flex;
+ width: 100%;
+ justify-content: space-between;
+ }
+}
+
+@media (max-width: 34rem) {
+ .ad-doc-wordmark__version { display: none; }
+ .ad-doc-layout { padding-inline: var(--ad-space-2xs); }
+}
+
+/* The live-example figure used throughout the docs — see the "How the
+ example demos work" section on the Using the CSS page. One class on the
+ outer <figure>; everything inside is styled by plain element selectors,
+ since figcaption/div/details/summary/pre already say what they are.
+ `data-layout` and `data-surface` on the preview <div> distinguish the
+ handful of demos that need a non-default arrangement. */
+
+.example {
+ margin: var(--ad-space-s) 0 var(--ad-space-l);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+ background: var(--ad-color-surface-card);
+ overflow: hidden;
+}
+
+.example figcaption {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-secondary);
+}
+
+.example > div {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-s);
+}
+/* nowrap is load-bearing: a column-direction flex container with wrap enabled
+ sizes its single line against the container rather than the content, which
+ left every stacked example with a few hundred pixels of dead space. */
+.example > div[data-layout="stack"] { flex-direction: column; flex-wrap: nowrap; align-items: stretch; }
+.example > div[data-layout="grid"] {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
+ align-items: start;
+}
+.example > div[data-surface="sunken"] { background: var(--ad-color-surface-fill); }
+.example > div > * { min-width: 0; }
+
+.example details {
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+.example summary {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ color: var(--ad-color-text-secondary);
+ cursor: pointer;
+ list-style: none;
+ user-select: none;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+}
+.example summary::-webkit-details-marker { display: none; }
+.example summary::before {
+ content: "";
+ width: 0.5rem;
+ height: 0.5rem;
+ border-inline-end: 1.5px solid currentColor;
+ border-block-end: 1.5px solid currentColor;
+ rotate: -45deg;
+ transition: rotate var(--ad-dur-fast) var(--ad-ease);
+}
+.example details[open] > summary::before { rotate: 45deg; }
+.example summary:hover { color: var(--ad-color-text-primary); }
+
+.example pre {
+ margin: 0;
+ padding: var(--ad-space-xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+ tab-size: 2;
+}
+.example pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Standalone fenced code blocks in the prose (unhighlighted — there is no
+ syntax highlighter in this build any more, just Kramdown's plain
+ <pre><code class="language-*">). */
+.ad-doc-main > pre {
+ margin-block: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+}
+.ad-doc-main > pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Presentation for the token reference tables and swatch grids, plus a
+ generic data-table utility. All of this is documentation chrome — the
+ design system itself has no Table component (see the note on the removed
+ `table`/`progress`/`callout` pages in git history). */
+
+.ad-swatch-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs) var(--ad-space-s);
+ padding: 0;
+ list-style: none;
+}
+.ad-swatch {
+ display: flex;
+ flex-direction: column;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ overflow: hidden;
+ background: var(--ad-color-surface-card);
+}
+.ad-swatch__chip {
+ display: flex;
+ align-items: flex-end;
+ justify-content: flex-end;
+ height: 4rem;
+ padding: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+}
+.ad-swatch__meta {
+ display: flex;
+ flex-direction: column;
+ gap: 0.1rem;
+ padding: var(--ad-space-2xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+}
+.ad-swatch__hex { color: var(--ad-color-text-primary); text-transform: uppercase; }
+.ad-swatch__ratio { color: var(--ad-color-text-tertiary); }
+
+.ad-alias-row { display: flex; align-items: center; gap: var(--ad-space-2xs); }
+.ad-alias-chip {
+ width: 1.5rem;
+ height: 1.5rem;
+ flex: none;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+}
+.ad-alias-chip--pair { display: flex; }
+.ad-alias-chip--pair > span { flex: 1; }
+
+/* Type specimen rows. */
+.ad-specimen {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ gap: var(--ad-space-3xs);
+ padding-block: var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-specimen__meta {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-specimen__sample {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: var(--ad-color-text-primary);
+}
+
+/* Space and radius rulers. */
+.ad-ruler { display: flex; flex-direction: column; gap: var(--ad-space-2xs); }
+.ad-ruler__row { display: grid; grid-template-columns: 6.5rem 4.5rem 1fr; align-items: center; gap: var(--ad-space-xs); font-family: var(--ad-font-mono); font-size: 11px; }
+.ad-ruler__bar { height: 0.75rem; background: var(--ad-color-accent); border-radius: var(--ad-radius-sm); }
+.ad-ruler__num { color: var(--ad-color-text-tertiary); text-align: right; }
+
+.ad-radius-grid, .ad-elevation-grid, .ad-motion-grid, .ad-accent-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs);
+ padding: 0;
+ list-style: none;
+}
+.ad-radius-demo {
+ height: 4.5rem;
+ background: var(--ad-color-accent-soft);
+ border: 1px solid var(--ad-color-accent);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-elevation-demo {
+ height: 4.5rem;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-motion-demo {
+ height: 4.5rem;
+ display: grid;
+ place-items: center;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-md);
+ margin-bottom: var(--ad-space-2xs);
+ cursor: pointer;
+ overflow: hidden;
+}
+.ad-motion-demo__dot {
+ width: 1.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent);
+ translate: -3rem 0;
+ transition-property: translate;
+ transition-duration: var(--ad-dur-base);
+ transition-timing-function: var(--ad-ease);
+}
+.ad-motion-demo:hover .ad-motion-demo__dot,
+.ad-motion-demo:focus-visible .ad-motion-demo__dot { translate: 3rem 0; }
+
+.ad-accent-demo {
+ height: 4.5rem;
+ display: flex;
+ align-items: flex-end;
+ padding: var(--ad-space-2xs);
+ border-radius: var(--ad-radius-md);
+ color: #fff;
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ margin-bottom: var(--ad-space-2xs);
+}
+
+/* Token names read `<code>` sitewide, which already carries a mono chip
+ look for running prose (see :not(pre) > code in base/typography.css).
+ Inside these specimen/swatch/ruler rows the old `.ad-token-name` class was
+ plain mono text with no chip, so `<code>` is flattened back to that look
+ here the same way table cells are. */
+.ad-swatch code, .ad-specimen code, .ad-ruler code, .ad-alias-row code {
+ background: none;
+ border: 0;
+ padding: 0;
+ font-size: var(--ad-step--2);
+ color: var(--ad-color-text-primary);
+ white-space: nowrap;
+}
+
+/* A documentation aside. Like `<table>`, this is site chrome for the prose
+ itself, not a system component — the real system has no Callout. */
+.ad-callout {
+ margin-block: var(--ad-space-s);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-left: 3px solid var(--ad-color-accent);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+}
+/* Generic reference tables (component APIs, alias tables, the contrast
+ contract). `<table>` IS a table — no class needed to say so. Every table
+ on this site uses the one compact density, so there is no separate
+ modifier for it. `display: block` plus `overflow-x: auto` gives the table
+ its own horizontal scrollbar on narrow viewports without a wrapper div;
+ the anonymous table box the browser generates around `<thead>`/`<tbody>`
+ still does the row/column layout. `<code>` replaces the old
+ `.ad-token-name` / `.ad-table__code` cells — it is already styled
+ sitewide, just re-flattened here to a plain mono run instead of the
+ chip-with-border look prose code gets, to match this table's original
+ look. `.num` is the one sanctioned class left, for right-aligned numeric
+ columns — there is no attribute that means "this column is a number". */
+table { width: 100%; border-collapse: collapse; font-size: var(--ad-step--1); display: block; overflow-x: auto; }
+table th, table td {
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ text-align: left;
+ vertical-align: top;
+}
+table th { font-family: var(--ad-font-mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ad-color-text-secondary); font-weight: 500; }
+table code { background: none; border: 0; padding: 0; font-size: var(--ad-step--2); white-space: nowrap; }
+.num { text-align: right; font-variant-numeric: tabular-nums; }
+
+/* Home page. */
+.ad-hero { padding-block: var(--ad-space-m) var(--ad-space-l); }
+.ad-hero__title {
+ font-size: var(--ad-step-4);
+ margin-bottom: var(--ad-space-xs);
+}
+.ad-hero__actions { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); margin-top: var(--ad-space-s); }
+.ad-stat-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-l);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+}
+.ad-stat__value {
+ display: block;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-2);
+ font-weight: 600;
+ line-height: 1.16;
+ font-variant-numeric: tabular-nums;
+}
+.ad-stat__label {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+
+/* Card chrome. The pane supplies the frame; this just insets the page. */
+body { padding: var(--ad-space-l) var(--ad-space-m); }
+.ad-doc-main { max-width: 50rem; margin-inline: auto; }
+.ad-doc-main > * { max-width: none; }
+</style>
+</head>
+<body>
+<main class="ad-doc-main">
+<header class="ad-doc-title"><span class="ad-doc-title__section">Components</span>
+ <h1>Input</h1>
+</header>
+<p class="ad-lead">A bordered text input, styled to match Select exactly. Usually wrapped in Field for its label and hint.</p>
+<h2 id="basic">Basic</h2>
+
+<p><code><input></code> is the component — styled bare, no class. Nothing else on this
+site uses a text input, so there is nothing for the selector to clash with.</p>
+
+<figure class="example">
+<figcaption>Input</figcaption>
+<div>
+<input type="text" placeholder="Dishwasher manual" style="max-width: 20rem;" />
+</div>
+<details open=""><summary>Markup</summary>
+<pre><code><input type="text" placeholder="Dishwasher manual" style="max-width: 20rem;" /></code></pre>
+</details>
+</figure>
+
+<h2 id="focus-and-disabled">Focus and disabled</h2>
+
+<figure class="example">
+<figcaption>States</figcaption>
+<div data-layout="stack">
+<input type="text" value="Focused" style="max-width: 20rem; border-color: var(--ad-color-accent); box-shadow: 0 0 0 2px var(--ad-color-accent-soft);" />
+<input type="text" value="Disabled" disabled="" style="max-width: 20rem;" />
+</div>
+<details><summary>Markup</summary>
+<pre><code><input type="text" value="Focused" style="max-width: 20rem; border-color: var(--ad-color-accent); box-shadow: 0 0 0 2px var(--ad-color-accent-soft);" />
+<input type="text" value="Disabled" disabled="" style="max-width: 20rem;" /></code></pre>
+</details>
+</figure>
+
+<h2 id="invalid">Invalid</h2>
+
+<p>Invalid state is expressed on the control itself, so it works with native
+form validation, plus an error line below. <code>role="alert"</code> is a real
+live-region role for validation text, so it doubles as the selector that
+turns a <code><small></code> hint red.</p>
+
+<figure class="example">
+<figcaption>Invalid</figcaption>
+<div>
+<label class="field" style="max-width: 20rem;">
+ Accent hex
+ <input type="text" value="not-a-color" aria-invalid="true" />
+ <small role="alert">Not a valid hex color.</small>
+</label>
+</div>
+<details><summary>Markup</summary>
+<pre><code><label class="field" style="max-width: 20rem;">
+ Accent hex
+ <input type="text" value="not-a-color" aria-invalid="true" />
+ <small role="alert">Not a valid hex color.</small>
+</label></code></pre>
+</details>
+</figure>
+
+<h2 id="api">API</h2>
+
+<table>
+ <thead><tr><th scope="col">Element / attribute</th><th scope="col">Values</th><th scope="col">Notes</th></tr></thead>
+ <tbody>
+ <tr><td><code>input</code></td><td></td><td>Required base — no class.</td></tr>
+ <tr><td><code>type</code></td><td>string</td><td>Any native input type except <code>checkbox</code>/<code>radio</code>, which are their own components.</td></tr>
+ <tr><td><code>placeholder</code></td><td>string</td><td></td></tr>
+ <tr><td><code>disabled</code></td><td>boolean</td><td></td></tr>
+ <tr><td><code>aria-invalid</code></td><td><code>"true"</code></td><td>Reddens the border and fill.</td></tr>
+ </tbody>
+</table>
+</main>
+<script>
+// Behavior for the pieces of the system that need it. Everything else is
+// CSS and semantic HTML on purpose — a design system that only works with
+// JavaScript running is a component library with extra steps.
+(function () {
+ "use strict";
+
+ var STORAGE_KEY = "ad-theme";
+
+ function readStored() {
+ try {
+ return localStorage.getItem(STORAGE_KEY);
+ } catch (error) {
+ return null;
+ }
+ }
+
+ function writeStored(value) {
+ try {
+ localStorage.setItem(STORAGE_KEY, value);
+ } catch (error) {
+ /* Nothing to do — the media query still gives a sensible default. */
+ }
+ }
+
+ function systemPrefersDark() {
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
+ }
+
+ function currentTheme() {
+ var explicit = document.documentElement.getAttribute("data-theme");
+ if (explicit) return explicit;
+ return systemPrefersDark() ? "dark" : "light";
+ }
+
+ function paintToggle(theme) {
+ document.querySelectorAll("[data-theme-toggle]").forEach(function (toggle) {
+ var next = theme === "dark" ? "light" : "dark";
+ toggle.querySelectorAll("[data-theme-icon]").forEach(function (icon) {
+ icon.hidden = icon.dataset.themeIcon !== theme;
+ });
+ toggle.setAttribute("aria-label", "Switch to the " + next + " theme");
+ toggle.setAttribute("title", "Switch to the " + next + " theme");
+ var label = toggle.querySelector("[data-theme-label]");
+ if (label) label.textContent = "Switch to the " + next + " theme";
+ });
+ }
+
+ function setUpTheme() {
+ paintToggle(currentTheme());
+
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-theme-toggle]");
+ if (!toggle) return;
+
+ var next = currentTheme() === "dark" ? "light" : "dark";
+ document.documentElement.setAttribute("data-theme", next);
+ writeStored(next);
+ paintToggle(next);
+ });
+
+ // Follow the OS while the reader has not made an explicit choice.
+ window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", function () {
+ if (!readStored()) paintToggle(currentTheme());
+ });
+ }
+
+ // Tabs, wired to the WAI-ARIA authoring practices: arrows move, Home/End
+ // jump, and only the selected tab stays in the tab order.
+ function setUpTabs() {
+ document.querySelectorAll("[data-tabs]").forEach(function (root) {
+ var tabs = Array.prototype.slice.call(root.querySelectorAll('[role="tab"]'));
+ if (!tabs.length) return;
+
+ function select(tab, focus) {
+ tabs.forEach(function (candidate) {
+ var selected = candidate === tab;
+ candidate.setAttribute("aria-selected", String(selected));
+ candidate.tabIndex = selected ? 0 : -1;
+ var panel = document.getElementById(candidate.getAttribute("aria-controls"));
+ if (panel) panel.hidden = !selected;
+ });
+ if (focus) tab.focus();
+ }
+
+ root.addEventListener("click", function (event) {
+ var tab = event.target.closest('[role="tab"]');
+ if (tab) select(tab, false);
+ });
+
+ root.addEventListener("keydown", function (event) {
+ var index = tabs.indexOf(document.activeElement);
+ if (index === -1) return;
+
+ var next = null;
+ if (event.key === "ArrowRight") next = tabs[(index + 1) % tabs.length];
+ else if (event.key === "ArrowLeft") next = tabs[(index - 1 + tabs.length) % tabs.length];
+ else if (event.key === "Home") next = tabs[0];
+ else if (event.key === "End") next = tabs[tabs.length - 1];
+
+ if (next) {
+ event.preventDefault();
+ select(next, true);
+ }
+ });
+
+ select(tabs.filter(function (tab) {
+ return tab.getAttribute("aria-selected") === "true";
+ })[0] || tabs[0], false);
+ });
+ }
+
+ function setUpNavToggle() {
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-nav-toggle]");
+ if (!toggle) return;
+
+ var sidebar = toggle.closest("[data-sidebar]");
+ var collapsed = sidebar.dataset.collapsed !== "false";
+ sidebar.dataset.collapsed = String(!collapsed);
+ toggle.setAttribute("aria-expanded", String(collapsed));
+ });
+ }
+
+ setUpTheme();
+ setUpTabs();
+ setUpNavToggle();
+})();
+
+</script>
+</body>
+</html>
diff --git a/design-bundle/components/radio.html b/design-bundle/components/radio.html
new file mode 100644
index 0000000..1f7a554
--- /dev/null
+++ b/design-bundle/components/radio.html
@@ -0,0 +1,1828 @@
+<!-- @dsCard group="Forms" -->
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Radio</title>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&display=swap">
+<style>
+/* Arbitrary Definitions — design tokens. Hand-maintained; edit values directly. */
+
+:root {
+ /* Palette ramps — reference only. Components use the aliases below. */
+ --ad-neutral-0: #faf9f7;
+ --ad-neutral-50: #fdfdfb;
+ --ad-neutral-100: #f1f0ed;
+ --ad-neutral-150: #e8e6e2;
+ --ad-neutral-200: #dcdad7;
+ --ad-neutral-300: #c0bdb9;
+ --ad-neutral-500: #888681;
+ --ad-neutral-600: #5f5d59;
+ --ad-neutral-800: #2a2926;
+ --ad-neutral-900: #100f0d;
+ --ad-neutral-dark-0: #0e0d0b;
+ --ad-neutral-dark-50: #151412;
+ --ad-neutral-dark-100: #1e1d1a;
+ --ad-neutral-dark-150: #252421;
+ --ad-neutral-dark-200: #2c2b29;
+ --ad-neutral-dark-300: #44423f;
+ --ad-neutral-dark-500: #7c7a76;
+ --ad-neutral-dark-600: #a09e9a;
+ --ad-neutral-dark-800: #d9d7d4;
+ --ad-neutral-dark-900: #e9e8e6;
+ --ad-accent-teal: #3a6a63;
+ --ad-accent-clay: #9a5a3c;
+ --ad-accent-ink-blue: #2b4a78;
+ --ad-accent-ochre: #93701f;
+ --ad-accent-plum: #6a3a5c;
+
+ /* Surfaces */
+ --ad-color-surface-page: #faf9f7;
+ --ad-color-surface-card: #fdfdfb;
+ --ad-color-surface-fill: #f1f0ed;
+ --ad-color-surface-fill-2: #e8e6e2;
+
+ /* Borders */
+ --ad-color-border-hairline: #dcdad7;
+ --ad-color-border-default: #c0bdb9;
+
+ /* Text */
+ --ad-color-text-primary: #100f0d;
+ --ad-color-text-secondary: #5f5d59;
+ --ad-color-text-tertiary: #888681;
+
+ /* Accent */
+ --ad-color-accent: #3a6a63;
+ --ad-color-accent-fg: #ffffff;
+ --ad-color-accent-ink: color-mix(in oklch, var(--ad-color-accent) 78%, black);
+ --ad-color-accent-soft: color-mix(in oklch, var(--ad-color-accent) 12%, white);
+
+ /* Status (fixed — never swapped like the accent) */
+ --ad-color-success: #3f7a4a;
+ --ad-color-success-bg: color-mix(in oklch, var(--ad-color-success) 12%, white);
+ --ad-color-danger: #ad3a2c;
+ --ad-color-danger-bg: color-mix(in oklch, var(--ad-color-danger) 10%, white);
+
+ /* Type families */
+ --ad-font-sans: -apple-system, "Helvetica Neue", Helvetica, Arial, system-ui, sans-serif;
+ --ad-font-mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, Consolas, "Courier New", monospace;
+ --ad-font-serif: Georgia, "Times New Roman", "Iowan Old Style", serif;
+
+ /* Type steps — fluid (Utopia scale), the design system's own body-text size */
+ --ad-step--2: clamp(0.7813rem, 0.7736rem + 0.0341vw, 0.8rem);
+ --ad-step--1: clamp(0.9375rem, 0.9119rem + 0.1136vw, 1rem);
+ --ad-step-0: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-step-1: clamp(1.35rem, 1.2631rem + 0.3864vw, 1.5625rem);
+ --ad-step-2: clamp(1.62rem, 1.4837rem + 0.6057vw, 1.9531rem);
+ --ad-step-3: clamp(1.944rem, 1.7405rem + 0.9044vw, 2.4414rem);
+ --ad-step-4: clamp(2.3328rem, 2.0387rem + 1.3072vw, 3.0518rem);
+ --ad-step-5: clamp(2.7994rem, 2.384rem + 1.8461vw, 3.8147rem);
+
+ /* Space */
+ --ad-space-3xs: clamp(0.3125rem, 0.3125rem + 0vw, 0.3125rem);
+ --ad-space-2xs: clamp(0.5625rem, 0.5369rem + 0.1136vw, 0.625rem);
+ --ad-space-xs: clamp(0.875rem, 0.8494rem + 0.1136vw, 0.9375rem);
+ --ad-space-s: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-space-m: clamp(1.6875rem, 1.6108rem + 0.3409vw, 1.875rem);
+ --ad-space-l: clamp(2.25rem, 2.1477rem + 0.4545vw, 2.5rem);
+ --ad-space-xl: clamp(3.375rem, 3.2216rem + 0.6818vw, 3.75rem);
+ --ad-space-2xl: clamp(4.5rem, 4.2955rem + 0.9091vw, 5rem);
+ --ad-space-3xl: clamp(6.75rem, 6.4432rem + 1.3636vw, 7.5rem);
+
+ /* Radius */
+ --ad-radius-sm: 4px;
+ --ad-radius-md: 6px;
+ --ad-radius-lg: 10px;
+ --ad-radius-full: 999px;
+ --ad-border-w: 1px;
+
+ /* Elevation */
+ --ad-shadow-sm: 0 1px 2px rgba(20, 18, 14, 0.05);
+ --ad-shadow-float: 0 2px 6px rgba(20, 18, 14, 0.06), 0 10px 24px -12px rgba(20, 18, 14, 0.18);
+
+ /* Motion */
+ --ad-dur-fast: 0.12s;
+ --ad-dur-base: 0.16s;
+ --ad-ease: cubic-bezier(0.2, 0, 0, 1);
+}
+
+@media (prefers-color-scheme: dark) {
+ :root:not([data-theme="light"]) {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+ }
+}
+
+:root[data-theme="dark"] {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ :root {
+ --ad-dur-fast: 0ms;
+ --ad-dur-base: 0ms;
+ }
+}
+
+/* A short reset. Everything it sets is something the system would otherwise
+ have to override in every component.
+
+ The rem values used for this documentation site's own chrome (measure,
+ content width, sidebar width, header height) belong to this site, not to
+ the design system it documents — the source design system defines no
+ container-width tokens at all, so there is nothing under `--ad-*` to reach
+ for here. See the header comment in site/shell.css. */
+
+*,
+*::before,
+*::after { box-sizing: border-box; }
+
+html {
+ -webkit-text-size-adjust: 100%;
+ scroll-behavior: smooth;
+ scroll-padding-top: calc(3.5rem + var(--ad-space-s));
+}
+
+@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;
+ }
+}
+
+body {
+ margin: 0;
+ background: var(--ad-color-surface-page);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ line-height: 1.55;
+ font-synthesis-weight: none;
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+}
+
+::selection {
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+}
+
+h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd { margin: 0; }
+ul[class], ol[class] { list-style: none; margin: 0; padding: 0; }
+
+img, svg, video { display: block; max-width: 100%; height: auto; }
+
+button, input, select, textarea {
+ font: inherit;
+ color: inherit;
+}
+
+hr {
+ height: 1px;
+ margin: var(--ad-space-l) 0;
+ border: 0;
+ background: var(--ad-color-border-hairline);
+}
+
+/* One focus ring, defined once. No component is allowed its own. */
+:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+ border-radius: var(--ad-radius-sm);
+}
+:focus:not(:focus-visible) { outline: none; }
+
+[hidden] { display: none !important; }
+
+.ad-visually-hidden {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip-path: inset(50%);
+ white-space: nowrap;
+ border: 0;
+}
+
+/* Every rule here reads a type token for family and size. Weight, line-height,
+ and letter-spacing are hardcoded to the exact numbers the source ships —
+ there is no `--ad-weight-*` / `--ad-leading-*` / `--ad-tracking-*` scale in
+ the real system, only eight fixed compound styles (see the Typography
+ foundation page, which this file implements verbatim). */
+
+.type-display { font: 600 var(--ad-step-4)/1.08 var(--ad-font-sans); letter-spacing: -0.015em; }
+.type-h1, h1 { font: 600 var(--ad-step-3)/1.12 var(--ad-font-sans); letter-spacing: -0.012em; }
+.type-h2, h2 { font: 600 var(--ad-step-2)/1.16 var(--ad-font-sans); letter-spacing: -0.01em; }
+.type-h3, h3 { font: 600 var(--ad-step-1)/1.25 var(--ad-font-sans); }
+.type-body { font: 400 var(--ad-step-0)/1.55 var(--ad-font-sans); }
+.type-body-sm { font: 400 var(--ad-step--1)/1.5 var(--ad-font-sans); }
+.type-label {
+ font: 500 var(--ad-step--2)/1 var(--ad-font-mono);
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: var(--ad-color-text-secondary);
+}
+.type-mono { font: 400 var(--ad-step--1)/1.5 var(--ad-font-mono); }
+
+/* Prose defaults for the documentation body copy. These read the same
+ tokens the compound classes above do; they are not a second scale. */
+h1, h2, h3, h4 { font-weight: 600; text-wrap: balance; }
+
+h4 { font: 600 var(--ad-step-0)/1.3 var(--ad-font-sans); }
+
+p, ul, ol { margin: 0 0 var(--ad-space-s); }
+li + li { margin-top: var(--ad-space-3xs); }
+
+a {
+ color: var(--ad-color-accent);
+ text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
+ text-underline-offset: 0.18em;
+ text-decoration-thickness: 1px;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ text-decoration-color var(--ad-dur-fast) var(--ad-ease);
+}
+a:hover {
+ color: var(--ad-color-accent-ink);
+ text-decoration-color: currentColor;
+}
+
+strong, b { font-weight: 600; }
+
+small { font-size: var(--ad-step--1); }
+
+.ad-lead {
+ font-size: var(--ad-step-1);
+ line-height: 1.5;
+ color: var(--ad-color-text-secondary);
+ max-width: 68ch;
+ text-wrap: pretty;
+}
+
+.ad-muted { color: var(--ad-color-text-secondary); }
+.ad-subtle { color: var(--ad-color-text-tertiary); }
+
+code, kbd, samp, pre {
+ font-family: var(--ad-font-mono);
+ font-feature-settings: "liga" 0;
+}
+
+:not(pre) > code {
+ padding: 0.15em 0.4em;
+ font-size: 0.875em;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-sm);
+ white-space: nowrap;
+}
+
+kbd {
+ display: inline-block;
+ padding: 0.1em 0.45em;
+ font-size: var(--ad-step--2);
+ line-height: 1.6;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-bottom-width: 2px;
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-secondary);
+}
+
+blockquote {
+ margin-block: var(--ad-space-s);
+ padding-left: var(--ad-space-s);
+ border-left: 2px solid var(--ad-color-accent);
+ color: var(--ad-color-text-secondary);
+}
+
+/* Button
+ A flat, quiet button that recedes rather than performs — no gradients, no
+ shine. Variants: default, primary, dark, ghost, danger. Sizes: sm, md
+ (default), lg — padding and font step only, weight and radius stay put.
+
+ The contract is the element itself: a bare <button> is styled directly, no
+ class required. An <a> that acts like a button (navigates rather than
+ changes something — see the Button page's "Links that act like buttons")
+ opts in with a single `.btn` class, since a bare `a` selector would catch
+ every ordinary prose link on the site. `:not(.icon-button)` keeps this
+ block from leaking onto IconButton, which shares the <button> tag but is
+ an entirely different shape. */
+
+button:not(.icon-button),
+a.btn {
+ --_bg: var(--ad-color-surface-card);
+ --_fg: var(--ad-color-text-primary);
+ --_border: var(--ad-color-border-default);
+ --_pad: var(--ad-space-xs) var(--ad-space-s);
+ --_font: var(--ad-step--1);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: var(--ad-space-2xs);
+ padding: var(--_pad);
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-sans);
+ font-size: var(--_font);
+ font-weight: 550;
+ letter-spacing: -0.01em;
+ line-height: 1.2;
+ text-align: center;
+ text-decoration: none;
+ white-space: nowrap;
+ cursor: pointer;
+ transition: filter var(--ad-dur-fast) var(--ad-ease),
+ border-color var(--ad-dur-fast) var(--ad-ease);
+}
+
+:is(button:not(.icon-button), a.btn):hover:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.96); }
+:is(button:not(.icon-button), a.btn):active:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.92); }
+
+button:not(.icon-button):disabled,
+:is(button:not(.icon-button), a.btn)[aria-disabled="true"] {
+ opacity: 0.5;
+ cursor: default;
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="primary"] {
+ --_bg: var(--ad-color-accent);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-accent);
+}
+
+/* Reserved for the single most-committing action in a view — a final Save,
+ never a whole row of them. */
+:is(button:not(.icon-button), a.btn)[data-variant="dark"] {
+ --_bg: var(--ad-color-text-primary);
+ --_fg: var(--ad-color-surface-page);
+ --_border: var(--ad-color-text-primary);
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="ghost"] {
+ --_bg: transparent;
+ --_fg: var(--ad-color-text-primary);
+ --_border: transparent;
+}
+
+/* The source hardcodes this label white rather than deriving it — danger
+ is not "on a filled accent surface", so --ad-color-accent-fg is reused
+ here only because its resolved value is the same literal white, not
+ because the two roles are the same thing. */
+:is(button:not(.icon-button), a.btn)[data-variant="danger"] {
+ --_bg: var(--ad-color-danger);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-danger);
+}
+
+:is(button:not(.icon-button), a.btn)[data-size="sm"] { --_pad: 0.375rem 0.75rem; --_font: 0.8125rem; }
+:is(button:not(.icon-button), a.btn)[data-size="lg"] { --_pad: 0.75rem 1.375rem; --_font: var(--ad-step-0); }
+
+:is(button:not(.icon-button), a.btn)[data-block] { width: 100%; }
+
+/* Button group — [role="group"] is already the correct ARIA role for a
+ toolbar of related buttons, so it doubles as the styling hook for free. */
+[role="group"] {
+ display: inline-flex;
+ isolation: isolate;
+}
+[role="group"] > button { border-radius: 0; }
+[role="group"] > button:first-child { border-start-start-radius: var(--ad-radius-md); border-end-start-radius: var(--ad-radius-md); }
+[role="group"] > button:last-child { border-start-end-radius: var(--ad-radius-md); border-end-end-radius: var(--ad-radius-md); }
+[role="group"] > button + button { margin-inline-start: -1px; }
+[role="group"] > button:hover,
+[role="group"] > button:focus-visible { z-index: 1; }
+
+/* IconButton — a square icon-only button. Always needs an aria-label; it
+ becomes both the accessible name and the native tooltip. There is no
+ attribute that distinguishes "a button that is only an icon" from a plain
+ Button, so this is the one sanctioned class in this file. */
+.icon-button {
+ --_size: 2.125rem;
+ --_bg: transparent;
+ --_border: transparent;
+ --_fg: var(--ad-color-text-secondary);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ padding: 0;
+ flex: none;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease),
+ color var(--ad-dur-fast) var(--ad-ease);
+}
+.icon-button:hover:not(:disabled) { background: var(--ad-color-surface-fill-2); }
+.icon-button:disabled { opacity: 0.5; cursor: default; }
+
+.icon-button[data-size="sm"] { --_size: 1.75rem; }
+.icon-button[data-size="lg"] { --_size: 2.5rem; }
+
+.icon-button[data-variant="outline"] { --_bg: var(--ad-color-surface-card); --_border: var(--ad-color-border-default); --_fg: var(--ad-color-text-primary); }
+.icon-button[data-variant="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); }
+
+/* Field
+ The labeled field wrapper — a <label> that wraps its own caption text
+ together with the control, so the association is native and needs no
+ `for`/`id` pair. The caption is mono, uppercase, tracked — the same
+ catalog-label convention as Badge and type-label.
+
+ A <label> is reused for three different shapes across this system (this
+ wrapper, Checkbox/Radio's row, Switch's row), so it cannot be styled bare
+ without one shape leaking into another. Field is the one of the three with
+ no distinguishing attribute of its own, so it gets the sanctioned class;
+ Checkbox/Radio tell themselves apart structurally instead (see below). */
+.field {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ min-width: 0;
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Hint and error read the native fine-print element. `role="alert"` is a
+ real live-region role for validation text, so it doubles as the selector
+ that turns a hint red. */
+.field > small {
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+.field > small[role="alert"] { color: var(--ad-color-danger); }
+
+/* Input / Select — bordered, matched to each other exactly. Both tags ARE
+ the component, so they are styled bare; nothing else on this site uses a
+ text input or a select. */
+input:not([type="checkbox"]):not([type="radio"]),
+select {
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ outline: none;
+ transition: border-color var(--ad-dur-fast) var(--ad-ease),
+ box-shadow var(--ad-dur-fast) var(--ad-ease);
+}
+input::placeholder { color: var(--ad-color-text-tertiary); }
+
+input:not([type="checkbox"]):not([type="radio"]):focus,
+select:focus {
+ border-color: var(--ad-color-accent);
+ box-shadow: 0 0 0 2px var(--ad-color-accent-soft);
+}
+
+input:not([type="checkbox"]):not([type="radio"]):disabled,
+select:disabled {
+ opacity: 0.5;
+ cursor: default;
+}
+
+/* The chevron is the one flagged color exception in this component set:
+ CSS cannot point a data-URI background-image at a custom property, so its
+ stroke is a literal hex rather than a var(--ad-*) reference. It is picked
+ to match --ad-color-text-secondary and commented here so it is not
+ mistaken for a stray color. */
+select {
+ appearance: none;
+ padding-inline-end: var(--ad-space-l);
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%235f5d59' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right var(--ad-space-xs) center;
+}
+
+input[aria-invalid="true"],
+select[aria-invalid="true"] {
+ border-color: var(--ad-color-danger);
+ background-color: var(--ad-color-danger-bg);
+}
+
+/* Checkbox / Radio — the real native controls, tinted with accent-color
+ instead of a hand-drawn box. This is not just a rename: it deletes the
+ custom SVG-checkmark markup entirely and lets the browser draw its own
+ (platform-appropriate) check and dot. `role="switch"` is excluded here so
+ Switch — a checkbox playing a different role — can style itself below
+ without this rule fighting it. A <label> wrapping a checkbox/radio is told
+ apart from Field's <label> structurally: it :has() one as a direct child,
+ no class required. */
+input[type="checkbox"]:not([role="switch"]),
+input[type="radio"] {
+ width: 1.125rem;
+ height: 1.125rem;
+ margin: 0;
+ accent-color: var(--ad-color-accent);
+ cursor: pointer;
+}
+input[type="checkbox"]:not([role="switch"]):focus-visible,
+input[type="radio"]:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+}
+
+label:has(> input[type="checkbox"]:not([role="switch"])),
+label:has(> input[type="radio"]) {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 400;
+ text-transform: none;
+ letter-spacing: normal;
+ color: var(--ad-color-text-primary);
+}
+label:has(> input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Switch — for on/off settings only, never a substitute for Checkbox.
+ `input[type="checkbox"][role="switch"]` is real ARIA (the correct role for
+ a toggle), not an invented hook, but the visual track+thumb needs a second
+ element for the thumb's independent position and transition — more than
+ `appearance: none` plus pseudo-elements on the input itself can cleanly
+ carry — so `.switch` is a deliberate, documented fallback class rather
+ than a first choice. */
+.switch {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ color: var(--ad-color-text-primary);
+}
+.switch input[role="switch"] { position: absolute; opacity: 0; width: 2.25rem; height: 1.25rem; margin: 0; }
+
+.switch span {
+ position: relative;
+ flex: none;
+ width: 2.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-border-default);
+ transition: background-color var(--ad-dur-base) var(--ad-ease);
+}
+.switch span::after {
+ content: "";
+ position: absolute;
+ inset-block-start: 2px;
+ inset-inline-start: 2px;
+ width: 1rem;
+ height: 1rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-fg);
+ box-shadow: var(--ad-shadow-sm);
+ transition: translate var(--ad-dur-base) var(--ad-ease);
+}
+.switch input[role="switch"]:checked + span { background: var(--ad-color-accent); }
+.switch input[role="switch"]:checked + span::after { translate: 1rem 0; }
+.switch input[role="switch"]:focus-visible + span { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+.switch:has(input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Rows of fields — used by the settings-form pattern. `<form>`/`<fieldset>`
+ are the elements that already mean this, so they are styled bare. A row
+ grouping two fields side by side has no element of its own, so it keeps
+ one plain class. */
+form {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-s);
+ max-width: 28rem;
+}
+.form-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
+ gap: var(--ad-space-xs);
+}
+fieldset {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ margin: 0;
+ padding: 0;
+ border: 0;
+}
+fieldset > legend {
+ padding: 0 0 var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Badge
+ Mono, uppercase, tracked — the catalog-label convention for status and
+ meta values (SAVED, OVERDUE, RECOMMENDED). A label, never a button.
+
+ A <span> is too generic to style globally — it is used all over running
+ prose for unrelated reasons — so Badge gets the one sanctioned class this
+ shape needs. Tone is an attribute, not a modifier class. */
+
+.badge {
+ --_bg: var(--ad-color-surface-fill);
+ --_fg: var(--ad-color-text-secondary);
+ --_border: var(--ad-color-border-hairline);
+
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.1875rem 0.5rem;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-sm);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ line-height: 1;
+ white-space: nowrap;
+}
+
+.badge[data-tone="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); --_border: transparent; }
+.badge[data-tone="success"] { --_bg: var(--ad-color-success-bg); --_fg: var(--ad-color-success); --_border: transparent; }
+.badge[data-tone="danger"] { --_bg: var(--ad-color-danger-bg); --_fg: var(--ad-color-danger); --_border: transparent; }
+
+.badge-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-2xs);
+}
+
+/* Tag
+ A removable mono chip for freeform tags — locations, categories.
+ `[data-dashed]` renders an "add tag" affordance instead of a value. Same
+ reasoning as Badge: <span> is too generic to select bare, so Tag gets one
+ class. The remove control is a real <button>, styled through the
+ descendant selector rather than a class of its own. */
+
+.tag {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.375rem;
+ padding: 0.25rem 0.625rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-default);
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ line-height: 1;
+}
+
+.tag[data-dashed] {
+ background: transparent;
+ border-style: dashed;
+ color: var(--ad-color-text-tertiary);
+ cursor: pointer;
+}
+
+.tag button {
+ all: unset;
+ cursor: pointer;
+ color: var(--ad-color-text-tertiary);
+ line-height: 1;
+ font-size: 0.75rem;
+}
+.tag button:hover { color: var(--ad-color-text-primary); }
+
+.tag-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.375rem;
+}
+
+/* Card
+ Flat surface, hairline border, radius-lg. `[data-elevated]` is opt-in and
+ reserved for the one focused/primary card in a view — a dialog, the
+ primary capture card — flat is the default everywhere else.
+
+ A <div>/<article> is too generic to select bare, so Card keeps one
+ sanctioned class. When Card's job is holding a list of rows — the common
+ case, entry lists, task lists, settings rows — it is a real <ul class="
+ card">, and CardRow is a bare <li> underneath it: no second class needed,
+ the row look comes from the `.card > li` descendant selector alone. The
+ first row's top border is removed automatically so callers never have to
+ override it by hand. */
+
+.card {
+ display: flex;
+ flex-direction: column;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: none;
+ overflow: hidden;
+}
+
+.card[data-elevated] { box-shadow: var(--ad-shadow-float); }
+
+.card > li {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+}
+.card > li:first-child { border-top: none; }
+
+/* A row whose ENTIRE content is wrapped in a real <button> or <a> — the
+ accessible way to make a row clickable — fills the row edge-to-edge and
+ picks up a hover fill and pointer cursor. `:only-child` is what tells this
+ apart from a row that merely contains a small button among other content
+ (a leading checkbox next to a title and a trailing value, say): only a
+ button/anchor that IS the row's one and only child means "the whole row
+ is the control." */
+.card > li:has(> button:only-child),
+.card > li:has(> a:only-child) {
+ padding: 0;
+}
+.card > li > button:only-child,
+.card > li > a:only-child {
+ all: unset;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ box-sizing: border-box;
+ color: inherit;
+ font: inherit;
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.card > li > button:only-child:hover,
+.card > li > a:only-child:hover { background: var(--ad-color-surface-fill); }
+
+/* Documentation-only extras: a body/footer pair and a responsive grid, used
+ by the prose pages (rationale.md, index.md) to show a Card as a plain
+ content tile rather than a data row. Neither is part of Card's props —
+ they exist because a content tile's inner layout has no element of its
+ own to hang a selector on. */
+.card-body {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-m);
+}
+.card-footer {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ margin-top: auto;
+ padding: var(--ad-space-xs) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+
+.card[data-link] { transition: border-color var(--ad-dur-fast) var(--ad-ease), box-shadow var(--ad-dur-fast) var(--ad-ease); }
+.card[data-link]:hover { border-color: var(--ad-color-border-default); box-shadow: var(--ad-shadow-sm); }
+.card[data-link]:focus-within { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+
+.card-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
+ gap: var(--ad-space-s);
+}
+
+/* Avatar — lives here because it almost never appears outside a card or a
+ row (the Loop dashboard's initials chip, Archive's file-type chip). A
+ <span> is too generic for a bare selector, so it keeps one class. */
+.avatar {
+ --_size: 1.875rem;
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ flex: none;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 600;
+ overflow: hidden;
+}
+.avatar[data-square] { border-radius: var(--ad-radius-md); background: var(--ad-color-surface-fill); color: var(--ad-color-text-secondary); border: 1px solid var(--ad-color-border-hairline); }
+.avatar[data-size="sm"] { --_size: 1.5rem; }
+.avatar[data-size="lg"] { --_size: 2.25rem; }
+
+/* Tooltip — a dark mono label on hover, positioned above the child. Pure CSS:
+ the trigger and bubble are siblings inside one relatively-positioned span,
+ shown on :hover/:focus-within so it works from the keyboard too.
+
+ `[role="tooltip"]` is the correct ARIA role for the bubble itself, so it
+ doubles as the styling hook — zero classes there. The wrapping span has no
+ attribute of its own, but it does not need one: `:has()` picks out "a span
+ that contains a [role=tooltip] bubble" precisely, without reaching for a
+ class on an otherwise-generic element. */
+span:has(> [role="tooltip"]) {
+ position: relative;
+ display: inline-flex;
+}
+
+[role="tooltip"] {
+ position: absolute;
+ bottom: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ margin-bottom: 0.375rem;
+ padding: 0.25rem 0.5rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-text-primary);
+ color: var(--ad-color-surface-page);
+ font-family: var(--ad-font-mono);
+ /* 11px is the source's own literal choice — smaller than --ad-step--2, the
+ smallest step on the scale, because a tooltip is meant to read as a
+ glance-only aside rather than body text. */
+ font-size: 11px;
+ white-space: nowrap;
+ line-height: 1.4;
+ z-index: 20;
+ opacity: 0;
+ visibility: hidden;
+ pointer-events: none;
+ transition: opacity var(--ad-dur-fast) var(--ad-ease);
+}
+span:has(> [role="tooltip"]):hover > [role="tooltip"],
+span:has(> [role="tooltip"]):focus-within > [role="tooltip"] {
+ opacity: 1;
+ visibility: visible;
+}
+
+/* Toast — the real pattern used for delete confirmations: a flat bar with an
+ optional destructive action and a dismiss. `[data-fixed]` pins it
+ bottom-right for real use; without it the toast stays in document flow for
+ documentation. `role="status"` is the correct live-region role for a
+ transient status message, so it is both the accessibility hook and the
+ style selector — no base class needed. The action and dismiss buttons are
+ plain <button>s told apart by `aria-label="Dismiss"`, an attribute they
+ need anyway for the icon-only close control. */
+[role="status"] {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ min-width: 20rem;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+}
+[role="status"][data-fixed] {
+ position: fixed;
+ inset-block-end: var(--ad-space-s);
+ inset-inline-end: var(--ad-space-s);
+ z-index: 60;
+}
+[role="status"] > span { flex: 1; }
+[role="status"] button {
+ all: unset;
+ cursor: pointer;
+ font-weight: 600;
+ color: var(--ad-color-danger);
+}
+[role="status"] button[aria-label="Dismiss"] {
+ color: var(--ad-color-text-tertiary);
+ font-size: 1rem;
+ font-weight: 400;
+ line-height: 1;
+}
+[role="status"] button[aria-label="Dismiss"]:hover { color: var(--ad-color-text-primary); }
+
+/* Dialog — the native <dialog> element: a centered modal, flat surface, no
+ colored header, hairline dividers between title/body/footer. A <header>
+ holds the title and a <footer> holds the actions, matching the elements
+ the tag already wants; everything else in between is the body, styled by
+ excluding those two rather than by a class of its own.
+
+ `.dialog-demo` scopes a static, always-open example to its own preview
+ container instead of the viewport, so it can sit safely inside a
+ documentation page — this is a doc-only affordance, not part of Dialog's
+ contract. It relies on the browser's own dialog centering: the UA
+ stylesheet already gives an open <dialog> `position: absolute; inset: 0;
+ margin: auto`, which centers it inside the nearest positioned ancestor —
+ here, `.dialog-demo` itself. A real `.showModal()` call gets the genuine
+ viewport-fixed overlay and `::backdrop` for free; a plain `open` attribute
+ (used here so the demo renders without JavaScript) does not paint a
+ backdrop at all, since `::backdrop` only exists for a dialog shown
+ modally — wire the real open/close state and call `.showModal()` in the
+ consuming app. */
+.dialog-demo {
+ position: relative;
+ min-height: 20rem;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-lg);
+}
+
+dialog {
+ width: 26rem;
+ max-width: calc(100% - (2 * var(--ad-space-s)));
+ height: fit-content;
+ padding: 0;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ overflow: hidden;
+}
+dialog::backdrop {
+ background: rgba(20, 18, 14, 0.35);
+}
+dialog > header {
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-sans);
+ font-weight: 600;
+ font-size: var(--ad-step-0);
+ color: var(--ad-color-text-primary);
+}
+dialog > footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+}
+dialog > *:not(header):not(footer) {
+ padding: var(--ad-space-m);
+ margin: 0;
+ color: var(--ad-color-text-primary);
+ font-size: var(--ad-step--1);
+}
+
+/* Tabs
+ Follows the real WAI-ARIA tabs pattern: a `[role="tablist"]` of
+ `[role="tab"]` buttons controlling `[role="tabpanel"]`s. Those roles are
+ required for the pattern to be accessible in the first place, so they
+ double as the entire styling contract — no classes at all for the base
+ shape. The keyboard wiring lives in assets/js/system.js and is
+ variant-agnostic — it only cares about `[data-tabs]` / `[role=tab]`.
+
+ Two real variants: `underline` (mono, accent underline — primary section
+ nav, the default) and `segmented` (pill track, active segment lifts to the
+ surface — filters like All · Overdue · Upcoming, never primary
+ navigation), chosen with `data-variant="segmented"` on the tablist since
+ neither has a natural attribute of its own. */
+
+/* Every rule below the list container pairs [role="tablist"] with
+ [role="tab"] rather than styling [role="tab"] alone — Tab buttons are
+ still literal <button> elements, so the compound selector's extra
+ specificity is what keeps Button's base styling (button.css) from
+ winning the cascade on shared properties like padding and background. */
+
+[role="tablist"] {
+ display: flex;
+ gap: var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+[role="tablist"] [role="tab"] {
+ padding: 0.5rem 0;
+ margin-bottom: -1px;
+ background: none;
+ border: 0;
+ border-bottom: 2px solid transparent;
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--1);
+ letter-spacing: 0.02em;
+ cursor: pointer;
+ transition: color var(--ad-dur-fast) var(--ad-ease), border-color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"] [role="tab"]:hover { color: var(--ad-color-text-primary); }
+[role="tablist"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ border-bottom-color: var(--ad-color-accent);
+}
+
+[role="tablist"][data-variant="segmented"] {
+ display: inline-flex;
+ gap: 0.125rem;
+ padding: 0.1875rem;
+ border-bottom: 0;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-md);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"] {
+ all: unset;
+ cursor: pointer;
+ padding: 0.375rem 0.875rem;
+ border-radius: var(--ad-radius-sm);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 550;
+ color: var(--ad-color-text-secondary);
+ transition: background-color var(--ad-dur-fast) var(--ad-ease), color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ background: var(--ad-color-surface-card);
+ box-shadow: var(--ad-shadow-sm);
+}
+
+[role="tabpanel"] { padding-top: var(--ad-space-s); }
+[role="tabpanel"][hidden] { display: none; }
+[role="tabpanel"]:focus-visible { outline-offset: 4px; }
+
+/* Documentation site chrome. Everything below the `.ad-doc-` prefix belongs
+ to this site rather than to the design system it documents — including the
+ width/height constants used directly below. The source design system
+ defines no container-width tokens at all, so there is nothing under
+ `--ad-*` for a doc shell to reach for; the Space foundation page's own
+ notes say to omit that group entirely rather than invent one. */
+
+.ad-doc-skip {
+ position: absolute;
+ inset-inline-start: var(--ad-space-xs);
+ inset-block-start: -4rem;
+ z-index: 40;
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ box-shadow: var(--ad-shadow-float);
+ transition: inset-block-start var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-skip:focus { inset-block-start: var(--ad-space-2xs); }
+
+.ad-doc-header {
+ position: sticky;
+ inset-block-start: 0;
+ z-index: 30;
+ height: 3.5rem;
+ background: color-mix(in srgb, var(--ad-color-surface-page) 92%, transparent);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-header__inner {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ height: 100%;
+ max-width: 72rem;
+ margin-inline: auto;
+ padding-inline: var(--ad-space-xs);
+}
+
+.ad-doc-wordmark {
+ display: flex;
+ align-items: baseline;
+ gap: var(--ad-space-2xs);
+ color: var(--ad-color-text-primary);
+ text-decoration: none;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ font-weight: 600;
+ letter-spacing: -0.01em;
+ white-space: nowrap;
+}
+.ad-doc-wordmark:hover { color: var(--ad-color-text-primary); }
+.ad-doc-wordmark__mark {
+ width: 1.375rem;
+ height: 1.375rem;
+ flex: none;
+ align-self: center;
+ color: var(--ad-color-accent);
+}
+.ad-doc-wordmark__version {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+
+.ad-doc-header__actions { margin-inline-start: auto; display: flex; align-items: center; gap: var(--ad-space-2xs); }
+
+.ad-doc-layout {
+ display: grid;
+ grid-template-columns: 16rem minmax(0, 1fr);
+ gap: var(--ad-space-l);
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-m) var(--ad-space-xs) var(--ad-space-2xl);
+}
+
+.ad-doc-sidebar {
+ position: sticky;
+ inset-block-start: calc(3.5rem + var(--ad-space-m));
+ align-self: start;
+ max-height: calc(100vh - 3.5rem - var(--ad-space-l));
+ overflow-y: auto;
+ padding-inline-end: var(--ad-space-2xs);
+}
+.ad-doc-nav__group + .ad-doc-nav__group { margin-top: var(--ad-space-s); }
+.ad-doc-nav__heading {
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-doc-nav__link {
+ display: block;
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ margin-inline-start: calc(var(--ad-space-xs) * -1);
+ border-inline-start: 2px solid transparent;
+ border-radius: 0 var(--ad-radius-sm) var(--ad-radius-sm) 0;
+ color: var(--ad-color-text-secondary);
+ font-size: var(--ad-step--1);
+ text-decoration: none;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-nav__link:hover { color: var(--ad-color-text-primary); background: var(--ad-color-surface-fill); }
+.ad-doc-nav__link[aria-current="page"] {
+ color: var(--ad-color-accent-ink);
+ border-inline-start-color: var(--ad-color-accent);
+ background: var(--ad-color-accent-soft);
+ font-weight: 500;
+}
+
+.ad-doc-main {
+ min-width: 0;
+ /* The design system's own body-text token (--ad-step-0, 18–20px) is right
+ for a product screen but too large for long-form documentation prose.
+ This site's own copy gets a plain, fixed 16px instead — the token above
+ is unchanged, and specimen tables/demos below still show its real
+ value. */
+ font-size: 1rem;
+}
+.ad-doc-main > * { max-width: 50rem; }
+
+.ad-doc-title { margin-bottom: var(--ad-space-xs); }
+.ad-doc-title__section {
+ display: block;
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-accent);
+}
+
+.ad-doc-main h2 {
+ margin-block: var(--ad-space-2xl) var(--ad-space-xs);
+ padding-bottom: var(--ad-space-2xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-main h3 { margin-block: var(--ad-space-l) var(--ad-space-2xs); }
+.ad-doc-main h4 { margin-block: var(--ad-space-s) var(--ad-space-2xs); }
+.ad-doc-main p,
+.ad-doc-main ul,
+.ad-doc-main ol { max-width: 68ch; }
+.ad-doc-main ul:not([class]),
+.ad-doc-main ol:not([class]) { padding-inline-start: var(--ad-space-s); }
+
+.ad-doc-footer {
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-s) var(--ad-space-xs) var(--ad-space-l);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-tertiary);
+ font-size: var(--ad-step--1);
+}
+.ad-doc-footer__inner { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); justify-content: space-between; }
+
+/* `button.ad-doc-nav-toggle` (element + class) rather than a bare class:
+ button.css's own base rule is `button:not(.icon-button)`, which is also
+ one element + one class in specificity and sets `display: inline-flex` —
+ a bare `.ad-doc-nav-toggle { display: none }` loses that fight and the
+ toggle stays visible at every width. Matching its specificity here (and
+ coming later in the concatenated stylesheet) settles it in source order. */
+button.ad-doc-nav-toggle { display: none; }
+
+@media (max-width: 60rem) {
+ .ad-doc-layout { grid-template-columns: minmax(0, 1fr); gap: var(--ad-space-s); }
+ .ad-doc-sidebar {
+ position: static;
+ max-height: none;
+ }
+ /* The toggle is a normal Button; the panel it opens gets its own card
+ treatment. Putting the same card styling on the <aside> wrapper too
+ nested a button-shaped box inside an outer box for no reason. */
+ .ad-doc-nav {
+ margin-top: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ }
+ .ad-doc-sidebar[data-collapsed="true"] > .ad-doc-nav { display: none; }
+ button.ad-doc-nav-toggle {
+ display: flex;
+ width: 100%;
+ justify-content: space-between;
+ }
+}
+
+@media (max-width: 34rem) {
+ .ad-doc-wordmark__version { display: none; }
+ .ad-doc-layout { padding-inline: var(--ad-space-2xs); }
+}
+
+/* The live-example figure used throughout the docs — see the "How the
+ example demos work" section on the Using the CSS page. One class on the
+ outer <figure>; everything inside is styled by plain element selectors,
+ since figcaption/div/details/summary/pre already say what they are.
+ `data-layout` and `data-surface` on the preview <div> distinguish the
+ handful of demos that need a non-default arrangement. */
+
+.example {
+ margin: var(--ad-space-s) 0 var(--ad-space-l);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+ background: var(--ad-color-surface-card);
+ overflow: hidden;
+}
+
+.example figcaption {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-secondary);
+}
+
+.example > div {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-s);
+}
+/* nowrap is load-bearing: a column-direction flex container with wrap enabled
+ sizes its single line against the container rather than the content, which
+ left every stacked example with a few hundred pixels of dead space. */
+.example > div[data-layout="stack"] { flex-direction: column; flex-wrap: nowrap; align-items: stretch; }
+.example > div[data-layout="grid"] {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
+ align-items: start;
+}
+.example > div[data-surface="sunken"] { background: var(--ad-color-surface-fill); }
+.example > div > * { min-width: 0; }
+
+.example details {
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+.example summary {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ color: var(--ad-color-text-secondary);
+ cursor: pointer;
+ list-style: none;
+ user-select: none;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+}
+.example summary::-webkit-details-marker { display: none; }
+.example summary::before {
+ content: "";
+ width: 0.5rem;
+ height: 0.5rem;
+ border-inline-end: 1.5px solid currentColor;
+ border-block-end: 1.5px solid currentColor;
+ rotate: -45deg;
+ transition: rotate var(--ad-dur-fast) var(--ad-ease);
+}
+.example details[open] > summary::before { rotate: 45deg; }
+.example summary:hover { color: var(--ad-color-text-primary); }
+
+.example pre {
+ margin: 0;
+ padding: var(--ad-space-xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+ tab-size: 2;
+}
+.example pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Standalone fenced code blocks in the prose (unhighlighted — there is no
+ syntax highlighter in this build any more, just Kramdown's plain
+ <pre><code class="language-*">). */
+.ad-doc-main > pre {
+ margin-block: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+}
+.ad-doc-main > pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Presentation for the token reference tables and swatch grids, plus a
+ generic data-table utility. All of this is documentation chrome — the
+ design system itself has no Table component (see the note on the removed
+ `table`/`progress`/`callout` pages in git history). */
+
+.ad-swatch-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs) var(--ad-space-s);
+ padding: 0;
+ list-style: none;
+}
+.ad-swatch {
+ display: flex;
+ flex-direction: column;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ overflow: hidden;
+ background: var(--ad-color-surface-card);
+}
+.ad-swatch__chip {
+ display: flex;
+ align-items: flex-end;
+ justify-content: flex-end;
+ height: 4rem;
+ padding: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+}
+.ad-swatch__meta {
+ display: flex;
+ flex-direction: column;
+ gap: 0.1rem;
+ padding: var(--ad-space-2xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+}
+.ad-swatch__hex { color: var(--ad-color-text-primary); text-transform: uppercase; }
+.ad-swatch__ratio { color: var(--ad-color-text-tertiary); }
+
+.ad-alias-row { display: flex; align-items: center; gap: var(--ad-space-2xs); }
+.ad-alias-chip {
+ width: 1.5rem;
+ height: 1.5rem;
+ flex: none;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+}
+.ad-alias-chip--pair { display: flex; }
+.ad-alias-chip--pair > span { flex: 1; }
+
+/* Type specimen rows. */
+.ad-specimen {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ gap: var(--ad-space-3xs);
+ padding-block: var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-specimen__meta {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-specimen__sample {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: var(--ad-color-text-primary);
+}
+
+/* Space and radius rulers. */
+.ad-ruler { display: flex; flex-direction: column; gap: var(--ad-space-2xs); }
+.ad-ruler__row { display: grid; grid-template-columns: 6.5rem 4.5rem 1fr; align-items: center; gap: var(--ad-space-xs); font-family: var(--ad-font-mono); font-size: 11px; }
+.ad-ruler__bar { height: 0.75rem; background: var(--ad-color-accent); border-radius: var(--ad-radius-sm); }
+.ad-ruler__num { color: var(--ad-color-text-tertiary); text-align: right; }
+
+.ad-radius-grid, .ad-elevation-grid, .ad-motion-grid, .ad-accent-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs);
+ padding: 0;
+ list-style: none;
+}
+.ad-radius-demo {
+ height: 4.5rem;
+ background: var(--ad-color-accent-soft);
+ border: 1px solid var(--ad-color-accent);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-elevation-demo {
+ height: 4.5rem;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-motion-demo {
+ height: 4.5rem;
+ display: grid;
+ place-items: center;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-md);
+ margin-bottom: var(--ad-space-2xs);
+ cursor: pointer;
+ overflow: hidden;
+}
+.ad-motion-demo__dot {
+ width: 1.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent);
+ translate: -3rem 0;
+ transition-property: translate;
+ transition-duration: var(--ad-dur-base);
+ transition-timing-function: var(--ad-ease);
+}
+.ad-motion-demo:hover .ad-motion-demo__dot,
+.ad-motion-demo:focus-visible .ad-motion-demo__dot { translate: 3rem 0; }
+
+.ad-accent-demo {
+ height: 4.5rem;
+ display: flex;
+ align-items: flex-end;
+ padding: var(--ad-space-2xs);
+ border-radius: var(--ad-radius-md);
+ color: #fff;
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ margin-bottom: var(--ad-space-2xs);
+}
+
+/* Token names read `<code>` sitewide, which already carries a mono chip
+ look for running prose (see :not(pre) > code in base/typography.css).
+ Inside these specimen/swatch/ruler rows the old `.ad-token-name` class was
+ plain mono text with no chip, so `<code>` is flattened back to that look
+ here the same way table cells are. */
+.ad-swatch code, .ad-specimen code, .ad-ruler code, .ad-alias-row code {
+ background: none;
+ border: 0;
+ padding: 0;
+ font-size: var(--ad-step--2);
+ color: var(--ad-color-text-primary);
+ white-space: nowrap;
+}
+
+/* A documentation aside. Like `<table>`, this is site chrome for the prose
+ itself, not a system component — the real system has no Callout. */
+.ad-callout {
+ margin-block: var(--ad-space-s);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-left: 3px solid var(--ad-color-accent);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+}
+/* Generic reference tables (component APIs, alias tables, the contrast
+ contract). `<table>` IS a table — no class needed to say so. Every table
+ on this site uses the one compact density, so there is no separate
+ modifier for it. `display: block` plus `overflow-x: auto` gives the table
+ its own horizontal scrollbar on narrow viewports without a wrapper div;
+ the anonymous table box the browser generates around `<thead>`/`<tbody>`
+ still does the row/column layout. `<code>` replaces the old
+ `.ad-token-name` / `.ad-table__code` cells — it is already styled
+ sitewide, just re-flattened here to a plain mono run instead of the
+ chip-with-border look prose code gets, to match this table's original
+ look. `.num` is the one sanctioned class left, for right-aligned numeric
+ columns — there is no attribute that means "this column is a number". */
+table { width: 100%; border-collapse: collapse; font-size: var(--ad-step--1); display: block; overflow-x: auto; }
+table th, table td {
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ text-align: left;
+ vertical-align: top;
+}
+table th { font-family: var(--ad-font-mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ad-color-text-secondary); font-weight: 500; }
+table code { background: none; border: 0; padding: 0; font-size: var(--ad-step--2); white-space: nowrap; }
+.num { text-align: right; font-variant-numeric: tabular-nums; }
+
+/* Home page. */
+.ad-hero { padding-block: var(--ad-space-m) var(--ad-space-l); }
+.ad-hero__title {
+ font-size: var(--ad-step-4);
+ margin-bottom: var(--ad-space-xs);
+}
+.ad-hero__actions { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); margin-top: var(--ad-space-s); }
+.ad-stat-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-l);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+}
+.ad-stat__value {
+ display: block;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-2);
+ font-weight: 600;
+ line-height: 1.16;
+ font-variant-numeric: tabular-nums;
+}
+.ad-stat__label {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+
+/* Card chrome. The pane supplies the frame; this just insets the page. */
+body { padding: var(--ad-space-l) var(--ad-space-m); }
+.ad-doc-main { max-width: 50rem; margin-inline: auto; }
+.ad-doc-main > * { max-width: none; }
+</style>
+</head>
+<body>
+<main class="ad-doc-main">
+<header class="ad-doc-title"><span class="ad-doc-title__section">Components</span>
+ <h1>Radio</h1>
+</header>
+<p class="ad-lead">Native radio, the same accent-color treatment as Checkbox.</p>
+<h2 id="basic">Basic</h2>
+
+<figure class="example">
+<figcaption>Radio</figcaption>
+<div>
+<label>
+ <input type="radio" name="demo-radio-1" checked="" />
+ Weekly
+</label>
+<label>
+ <input type="radio" name="demo-radio-1" />
+ Monthly
+</label>
+</div>
+<details open=""><summary>Markup</summary>
+<pre><code><label>
+ <input type="radio" name="demo-radio-1" checked="" />
+ Weekly
+</label>
+<label>
+ <input type="radio" name="demo-radio-1" />
+ Monthly
+</label></code></pre>
+</details>
+</figure>
+
+<h2 id="as-a-group">As a group</h2>
+
+<figure class="example">
+<figcaption>Interval</figcaption>
+<div>
+<fieldset>
+ <legend>Repeat</legend>
+ <label>
+ <input type="radio" name="demo-radio-2" checked="" />
+ Every week
+ </label>
+ <label>
+ <input type="radio" name="demo-radio-2" />
+ Every month
+ </label>
+ <label>
+ <input type="radio" name="demo-radio-2" />
+ Every year
+ </label>
+</fieldset>
+</div>
+<details><summary>Markup</summary>
+<pre><code><fieldset>
+ <legend>Repeat</legend>
+ <label>
+ <input type="radio" name="demo-radio-2" checked="" />
+ Every week
+ </label>
+ <label>
+ <input type="radio" name="demo-radio-2" />
+ Every month
+ </label>
+ <label>
+ <input type="radio" name="demo-radio-2" />
+ Every year
+ </label>
+</fieldset></code></pre>
+</details>
+</figure>
+
+<h2 id="api">API</h2>
+
+<table>
+ <thead><tr><th scope="col">Element / attribute</th><th scope="col">Values</th><th scope="col">Notes</th></tr></thead>
+ <tbody>
+ <tr><td><code>input[type="radio"]</code></td><td></td><td>Required base — no class. Tinted with <code>accent-color</code>.</td></tr>
+ <tr><td><code>checked</code></td><td>boolean</td><td></td></tr>
+ <tr><td><code>name</code></td><td>string</td><td>Groups mutually exclusive radios.</td></tr>
+ <tr><td><code>label</code></td><td>text content</td><td>Wrap the input directly — <code>label:has(> input[type="radio"])</code> is what applies the row layout.</td></tr>
+ </tbody>
+</table>
+</main>
+<script>
+// Behavior for the pieces of the system that need it. Everything else is
+// CSS and semantic HTML on purpose — a design system that only works with
+// JavaScript running is a component library with extra steps.
+(function () {
+ "use strict";
+
+ var STORAGE_KEY = "ad-theme";
+
+ function readStored() {
+ try {
+ return localStorage.getItem(STORAGE_KEY);
+ } catch (error) {
+ return null;
+ }
+ }
+
+ function writeStored(value) {
+ try {
+ localStorage.setItem(STORAGE_KEY, value);
+ } catch (error) {
+ /* Nothing to do — the media query still gives a sensible default. */
+ }
+ }
+
+ function systemPrefersDark() {
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
+ }
+
+ function currentTheme() {
+ var explicit = document.documentElement.getAttribute("data-theme");
+ if (explicit) return explicit;
+ return systemPrefersDark() ? "dark" : "light";
+ }
+
+ function paintToggle(theme) {
+ document.querySelectorAll("[data-theme-toggle]").forEach(function (toggle) {
+ var next = theme === "dark" ? "light" : "dark";
+ toggle.querySelectorAll("[data-theme-icon]").forEach(function (icon) {
+ icon.hidden = icon.dataset.themeIcon !== theme;
+ });
+ toggle.setAttribute("aria-label", "Switch to the " + next + " theme");
+ toggle.setAttribute("title", "Switch to the " + next + " theme");
+ var label = toggle.querySelector("[data-theme-label]");
+ if (label) label.textContent = "Switch to the " + next + " theme";
+ });
+ }
+
+ function setUpTheme() {
+ paintToggle(currentTheme());
+
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-theme-toggle]");
+ if (!toggle) return;
+
+ var next = currentTheme() === "dark" ? "light" : "dark";
+ document.documentElement.setAttribute("data-theme", next);
+ writeStored(next);
+ paintToggle(next);
+ });
+
+ // Follow the OS while the reader has not made an explicit choice.
+ window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", function () {
+ if (!readStored()) paintToggle(currentTheme());
+ });
+ }
+
+ // Tabs, wired to the WAI-ARIA authoring practices: arrows move, Home/End
+ // jump, and only the selected tab stays in the tab order.
+ function setUpTabs() {
+ document.querySelectorAll("[data-tabs]").forEach(function (root) {
+ var tabs = Array.prototype.slice.call(root.querySelectorAll('[role="tab"]'));
+ if (!tabs.length) return;
+
+ function select(tab, focus) {
+ tabs.forEach(function (candidate) {
+ var selected = candidate === tab;
+ candidate.setAttribute("aria-selected", String(selected));
+ candidate.tabIndex = selected ? 0 : -1;
+ var panel = document.getElementById(candidate.getAttribute("aria-controls"));
+ if (panel) panel.hidden = !selected;
+ });
+ if (focus) tab.focus();
+ }
+
+ root.addEventListener("click", function (event) {
+ var tab = event.target.closest('[role="tab"]');
+ if (tab) select(tab, false);
+ });
+
+ root.addEventListener("keydown", function (event) {
+ var index = tabs.indexOf(document.activeElement);
+ if (index === -1) return;
+
+ var next = null;
+ if (event.key === "ArrowRight") next = tabs[(index + 1) % tabs.length];
+ else if (event.key === "ArrowLeft") next = tabs[(index - 1 + tabs.length) % tabs.length];
+ else if (event.key === "Home") next = tabs[0];
+ else if (event.key === "End") next = tabs[tabs.length - 1];
+
+ if (next) {
+ event.preventDefault();
+ select(next, true);
+ }
+ });
+
+ select(tabs.filter(function (tab) {
+ return tab.getAttribute("aria-selected") === "true";
+ })[0] || tabs[0], false);
+ });
+ }
+
+ function setUpNavToggle() {
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-nav-toggle]");
+ if (!toggle) return;
+
+ var sidebar = toggle.closest("[data-sidebar]");
+ var collapsed = sidebar.dataset.collapsed !== "false";
+ sidebar.dataset.collapsed = String(!collapsed);
+ toggle.setAttribute("aria-expanded", String(collapsed));
+ });
+ }
+
+ setUpTheme();
+ setUpTabs();
+ setUpNavToggle();
+})();
+
+</script>
+</body>
+</html>
diff --git a/design-bundle/components/select.html b/design-bundle/components/select.html
new file mode 100644
index 0000000..e4efce9
--- /dev/null
+++ b/design-bundle/components/select.html
@@ -0,0 +1,1806 @@
+<!-- @dsCard group="Forms" -->
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Select</title>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&display=swap">
+<style>
+/* Arbitrary Definitions — design tokens. Hand-maintained; edit values directly. */
+
+:root {
+ /* Palette ramps — reference only. Components use the aliases below. */
+ --ad-neutral-0: #faf9f7;
+ --ad-neutral-50: #fdfdfb;
+ --ad-neutral-100: #f1f0ed;
+ --ad-neutral-150: #e8e6e2;
+ --ad-neutral-200: #dcdad7;
+ --ad-neutral-300: #c0bdb9;
+ --ad-neutral-500: #888681;
+ --ad-neutral-600: #5f5d59;
+ --ad-neutral-800: #2a2926;
+ --ad-neutral-900: #100f0d;
+ --ad-neutral-dark-0: #0e0d0b;
+ --ad-neutral-dark-50: #151412;
+ --ad-neutral-dark-100: #1e1d1a;
+ --ad-neutral-dark-150: #252421;
+ --ad-neutral-dark-200: #2c2b29;
+ --ad-neutral-dark-300: #44423f;
+ --ad-neutral-dark-500: #7c7a76;
+ --ad-neutral-dark-600: #a09e9a;
+ --ad-neutral-dark-800: #d9d7d4;
+ --ad-neutral-dark-900: #e9e8e6;
+ --ad-accent-teal: #3a6a63;
+ --ad-accent-clay: #9a5a3c;
+ --ad-accent-ink-blue: #2b4a78;
+ --ad-accent-ochre: #93701f;
+ --ad-accent-plum: #6a3a5c;
+
+ /* Surfaces */
+ --ad-color-surface-page: #faf9f7;
+ --ad-color-surface-card: #fdfdfb;
+ --ad-color-surface-fill: #f1f0ed;
+ --ad-color-surface-fill-2: #e8e6e2;
+
+ /* Borders */
+ --ad-color-border-hairline: #dcdad7;
+ --ad-color-border-default: #c0bdb9;
+
+ /* Text */
+ --ad-color-text-primary: #100f0d;
+ --ad-color-text-secondary: #5f5d59;
+ --ad-color-text-tertiary: #888681;
+
+ /* Accent */
+ --ad-color-accent: #3a6a63;
+ --ad-color-accent-fg: #ffffff;
+ --ad-color-accent-ink: color-mix(in oklch, var(--ad-color-accent) 78%, black);
+ --ad-color-accent-soft: color-mix(in oklch, var(--ad-color-accent) 12%, white);
+
+ /* Status (fixed — never swapped like the accent) */
+ --ad-color-success: #3f7a4a;
+ --ad-color-success-bg: color-mix(in oklch, var(--ad-color-success) 12%, white);
+ --ad-color-danger: #ad3a2c;
+ --ad-color-danger-bg: color-mix(in oklch, var(--ad-color-danger) 10%, white);
+
+ /* Type families */
+ --ad-font-sans: -apple-system, "Helvetica Neue", Helvetica, Arial, system-ui, sans-serif;
+ --ad-font-mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, Consolas, "Courier New", monospace;
+ --ad-font-serif: Georgia, "Times New Roman", "Iowan Old Style", serif;
+
+ /* Type steps — fluid (Utopia scale), the design system's own body-text size */
+ --ad-step--2: clamp(0.7813rem, 0.7736rem + 0.0341vw, 0.8rem);
+ --ad-step--1: clamp(0.9375rem, 0.9119rem + 0.1136vw, 1rem);
+ --ad-step-0: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-step-1: clamp(1.35rem, 1.2631rem + 0.3864vw, 1.5625rem);
+ --ad-step-2: clamp(1.62rem, 1.4837rem + 0.6057vw, 1.9531rem);
+ --ad-step-3: clamp(1.944rem, 1.7405rem + 0.9044vw, 2.4414rem);
+ --ad-step-4: clamp(2.3328rem, 2.0387rem + 1.3072vw, 3.0518rem);
+ --ad-step-5: clamp(2.7994rem, 2.384rem + 1.8461vw, 3.8147rem);
+
+ /* Space */
+ --ad-space-3xs: clamp(0.3125rem, 0.3125rem + 0vw, 0.3125rem);
+ --ad-space-2xs: clamp(0.5625rem, 0.5369rem + 0.1136vw, 0.625rem);
+ --ad-space-xs: clamp(0.875rem, 0.8494rem + 0.1136vw, 0.9375rem);
+ --ad-space-s: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-space-m: clamp(1.6875rem, 1.6108rem + 0.3409vw, 1.875rem);
+ --ad-space-l: clamp(2.25rem, 2.1477rem + 0.4545vw, 2.5rem);
+ --ad-space-xl: clamp(3.375rem, 3.2216rem + 0.6818vw, 3.75rem);
+ --ad-space-2xl: clamp(4.5rem, 4.2955rem + 0.9091vw, 5rem);
+ --ad-space-3xl: clamp(6.75rem, 6.4432rem + 1.3636vw, 7.5rem);
+
+ /* Radius */
+ --ad-radius-sm: 4px;
+ --ad-radius-md: 6px;
+ --ad-radius-lg: 10px;
+ --ad-radius-full: 999px;
+ --ad-border-w: 1px;
+
+ /* Elevation */
+ --ad-shadow-sm: 0 1px 2px rgba(20, 18, 14, 0.05);
+ --ad-shadow-float: 0 2px 6px rgba(20, 18, 14, 0.06), 0 10px 24px -12px rgba(20, 18, 14, 0.18);
+
+ /* Motion */
+ --ad-dur-fast: 0.12s;
+ --ad-dur-base: 0.16s;
+ --ad-ease: cubic-bezier(0.2, 0, 0, 1);
+}
+
+@media (prefers-color-scheme: dark) {
+ :root:not([data-theme="light"]) {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+ }
+}
+
+:root[data-theme="dark"] {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ :root {
+ --ad-dur-fast: 0ms;
+ --ad-dur-base: 0ms;
+ }
+}
+
+/* A short reset. Everything it sets is something the system would otherwise
+ have to override in every component.
+
+ The rem values used for this documentation site's own chrome (measure,
+ content width, sidebar width, header height) belong to this site, not to
+ the design system it documents — the source design system defines no
+ container-width tokens at all, so there is nothing under `--ad-*` to reach
+ for here. See the header comment in site/shell.css. */
+
+*,
+*::before,
+*::after { box-sizing: border-box; }
+
+html {
+ -webkit-text-size-adjust: 100%;
+ scroll-behavior: smooth;
+ scroll-padding-top: calc(3.5rem + var(--ad-space-s));
+}
+
+@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;
+ }
+}
+
+body {
+ margin: 0;
+ background: var(--ad-color-surface-page);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ line-height: 1.55;
+ font-synthesis-weight: none;
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+}
+
+::selection {
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+}
+
+h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd { margin: 0; }
+ul[class], ol[class] { list-style: none; margin: 0; padding: 0; }
+
+img, svg, video { display: block; max-width: 100%; height: auto; }
+
+button, input, select, textarea {
+ font: inherit;
+ color: inherit;
+}
+
+hr {
+ height: 1px;
+ margin: var(--ad-space-l) 0;
+ border: 0;
+ background: var(--ad-color-border-hairline);
+}
+
+/* One focus ring, defined once. No component is allowed its own. */
+:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+ border-radius: var(--ad-radius-sm);
+}
+:focus:not(:focus-visible) { outline: none; }
+
+[hidden] { display: none !important; }
+
+.ad-visually-hidden {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip-path: inset(50%);
+ white-space: nowrap;
+ border: 0;
+}
+
+/* Every rule here reads a type token for family and size. Weight, line-height,
+ and letter-spacing are hardcoded to the exact numbers the source ships —
+ there is no `--ad-weight-*` / `--ad-leading-*` / `--ad-tracking-*` scale in
+ the real system, only eight fixed compound styles (see the Typography
+ foundation page, which this file implements verbatim). */
+
+.type-display { font: 600 var(--ad-step-4)/1.08 var(--ad-font-sans); letter-spacing: -0.015em; }
+.type-h1, h1 { font: 600 var(--ad-step-3)/1.12 var(--ad-font-sans); letter-spacing: -0.012em; }
+.type-h2, h2 { font: 600 var(--ad-step-2)/1.16 var(--ad-font-sans); letter-spacing: -0.01em; }
+.type-h3, h3 { font: 600 var(--ad-step-1)/1.25 var(--ad-font-sans); }
+.type-body { font: 400 var(--ad-step-0)/1.55 var(--ad-font-sans); }
+.type-body-sm { font: 400 var(--ad-step--1)/1.5 var(--ad-font-sans); }
+.type-label {
+ font: 500 var(--ad-step--2)/1 var(--ad-font-mono);
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: var(--ad-color-text-secondary);
+}
+.type-mono { font: 400 var(--ad-step--1)/1.5 var(--ad-font-mono); }
+
+/* Prose defaults for the documentation body copy. These read the same
+ tokens the compound classes above do; they are not a second scale. */
+h1, h2, h3, h4 { font-weight: 600; text-wrap: balance; }
+
+h4 { font: 600 var(--ad-step-0)/1.3 var(--ad-font-sans); }
+
+p, ul, ol { margin: 0 0 var(--ad-space-s); }
+li + li { margin-top: var(--ad-space-3xs); }
+
+a {
+ color: var(--ad-color-accent);
+ text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
+ text-underline-offset: 0.18em;
+ text-decoration-thickness: 1px;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ text-decoration-color var(--ad-dur-fast) var(--ad-ease);
+}
+a:hover {
+ color: var(--ad-color-accent-ink);
+ text-decoration-color: currentColor;
+}
+
+strong, b { font-weight: 600; }
+
+small { font-size: var(--ad-step--1); }
+
+.ad-lead {
+ font-size: var(--ad-step-1);
+ line-height: 1.5;
+ color: var(--ad-color-text-secondary);
+ max-width: 68ch;
+ text-wrap: pretty;
+}
+
+.ad-muted { color: var(--ad-color-text-secondary); }
+.ad-subtle { color: var(--ad-color-text-tertiary); }
+
+code, kbd, samp, pre {
+ font-family: var(--ad-font-mono);
+ font-feature-settings: "liga" 0;
+}
+
+:not(pre) > code {
+ padding: 0.15em 0.4em;
+ font-size: 0.875em;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-sm);
+ white-space: nowrap;
+}
+
+kbd {
+ display: inline-block;
+ padding: 0.1em 0.45em;
+ font-size: var(--ad-step--2);
+ line-height: 1.6;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-bottom-width: 2px;
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-secondary);
+}
+
+blockquote {
+ margin-block: var(--ad-space-s);
+ padding-left: var(--ad-space-s);
+ border-left: 2px solid var(--ad-color-accent);
+ color: var(--ad-color-text-secondary);
+}
+
+/* Button
+ A flat, quiet button that recedes rather than performs — no gradients, no
+ shine. Variants: default, primary, dark, ghost, danger. Sizes: sm, md
+ (default), lg — padding and font step only, weight and radius stay put.
+
+ The contract is the element itself: a bare <button> is styled directly, no
+ class required. An <a> that acts like a button (navigates rather than
+ changes something — see the Button page's "Links that act like buttons")
+ opts in with a single `.btn` class, since a bare `a` selector would catch
+ every ordinary prose link on the site. `:not(.icon-button)` keeps this
+ block from leaking onto IconButton, which shares the <button> tag but is
+ an entirely different shape. */
+
+button:not(.icon-button),
+a.btn {
+ --_bg: var(--ad-color-surface-card);
+ --_fg: var(--ad-color-text-primary);
+ --_border: var(--ad-color-border-default);
+ --_pad: var(--ad-space-xs) var(--ad-space-s);
+ --_font: var(--ad-step--1);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: var(--ad-space-2xs);
+ padding: var(--_pad);
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-sans);
+ font-size: var(--_font);
+ font-weight: 550;
+ letter-spacing: -0.01em;
+ line-height: 1.2;
+ text-align: center;
+ text-decoration: none;
+ white-space: nowrap;
+ cursor: pointer;
+ transition: filter var(--ad-dur-fast) var(--ad-ease),
+ border-color var(--ad-dur-fast) var(--ad-ease);
+}
+
+:is(button:not(.icon-button), a.btn):hover:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.96); }
+:is(button:not(.icon-button), a.btn):active:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.92); }
+
+button:not(.icon-button):disabled,
+:is(button:not(.icon-button), a.btn)[aria-disabled="true"] {
+ opacity: 0.5;
+ cursor: default;
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="primary"] {
+ --_bg: var(--ad-color-accent);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-accent);
+}
+
+/* Reserved for the single most-committing action in a view — a final Save,
+ never a whole row of them. */
+:is(button:not(.icon-button), a.btn)[data-variant="dark"] {
+ --_bg: var(--ad-color-text-primary);
+ --_fg: var(--ad-color-surface-page);
+ --_border: var(--ad-color-text-primary);
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="ghost"] {
+ --_bg: transparent;
+ --_fg: var(--ad-color-text-primary);
+ --_border: transparent;
+}
+
+/* The source hardcodes this label white rather than deriving it — danger
+ is not "on a filled accent surface", so --ad-color-accent-fg is reused
+ here only because its resolved value is the same literal white, not
+ because the two roles are the same thing. */
+:is(button:not(.icon-button), a.btn)[data-variant="danger"] {
+ --_bg: var(--ad-color-danger);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-danger);
+}
+
+:is(button:not(.icon-button), a.btn)[data-size="sm"] { --_pad: 0.375rem 0.75rem; --_font: 0.8125rem; }
+:is(button:not(.icon-button), a.btn)[data-size="lg"] { --_pad: 0.75rem 1.375rem; --_font: var(--ad-step-0); }
+
+:is(button:not(.icon-button), a.btn)[data-block] { width: 100%; }
+
+/* Button group — [role="group"] is already the correct ARIA role for a
+ toolbar of related buttons, so it doubles as the styling hook for free. */
+[role="group"] {
+ display: inline-flex;
+ isolation: isolate;
+}
+[role="group"] > button { border-radius: 0; }
+[role="group"] > button:first-child { border-start-start-radius: var(--ad-radius-md); border-end-start-radius: var(--ad-radius-md); }
+[role="group"] > button:last-child { border-start-end-radius: var(--ad-radius-md); border-end-end-radius: var(--ad-radius-md); }
+[role="group"] > button + button { margin-inline-start: -1px; }
+[role="group"] > button:hover,
+[role="group"] > button:focus-visible { z-index: 1; }
+
+/* IconButton — a square icon-only button. Always needs an aria-label; it
+ becomes both the accessible name and the native tooltip. There is no
+ attribute that distinguishes "a button that is only an icon" from a plain
+ Button, so this is the one sanctioned class in this file. */
+.icon-button {
+ --_size: 2.125rem;
+ --_bg: transparent;
+ --_border: transparent;
+ --_fg: var(--ad-color-text-secondary);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ padding: 0;
+ flex: none;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease),
+ color var(--ad-dur-fast) var(--ad-ease);
+}
+.icon-button:hover:not(:disabled) { background: var(--ad-color-surface-fill-2); }
+.icon-button:disabled { opacity: 0.5; cursor: default; }
+
+.icon-button[data-size="sm"] { --_size: 1.75rem; }
+.icon-button[data-size="lg"] { --_size: 2.5rem; }
+
+.icon-button[data-variant="outline"] { --_bg: var(--ad-color-surface-card); --_border: var(--ad-color-border-default); --_fg: var(--ad-color-text-primary); }
+.icon-button[data-variant="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); }
+
+/* Field
+ The labeled field wrapper — a <label> that wraps its own caption text
+ together with the control, so the association is native and needs no
+ `for`/`id` pair. The caption is mono, uppercase, tracked — the same
+ catalog-label convention as Badge and type-label.
+
+ A <label> is reused for three different shapes across this system (this
+ wrapper, Checkbox/Radio's row, Switch's row), so it cannot be styled bare
+ without one shape leaking into another. Field is the one of the three with
+ no distinguishing attribute of its own, so it gets the sanctioned class;
+ Checkbox/Radio tell themselves apart structurally instead (see below). */
+.field {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ min-width: 0;
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Hint and error read the native fine-print element. `role="alert"` is a
+ real live-region role for validation text, so it doubles as the selector
+ that turns a hint red. */
+.field > small {
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+.field > small[role="alert"] { color: var(--ad-color-danger); }
+
+/* Input / Select — bordered, matched to each other exactly. Both tags ARE
+ the component, so they are styled bare; nothing else on this site uses a
+ text input or a select. */
+input:not([type="checkbox"]):not([type="radio"]),
+select {
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ outline: none;
+ transition: border-color var(--ad-dur-fast) var(--ad-ease),
+ box-shadow var(--ad-dur-fast) var(--ad-ease);
+}
+input::placeholder { color: var(--ad-color-text-tertiary); }
+
+input:not([type="checkbox"]):not([type="radio"]):focus,
+select:focus {
+ border-color: var(--ad-color-accent);
+ box-shadow: 0 0 0 2px var(--ad-color-accent-soft);
+}
+
+input:not([type="checkbox"]):not([type="radio"]):disabled,
+select:disabled {
+ opacity: 0.5;
+ cursor: default;
+}
+
+/* The chevron is the one flagged color exception in this component set:
+ CSS cannot point a data-URI background-image at a custom property, so its
+ stroke is a literal hex rather than a var(--ad-*) reference. It is picked
+ to match --ad-color-text-secondary and commented here so it is not
+ mistaken for a stray color. */
+select {
+ appearance: none;
+ padding-inline-end: var(--ad-space-l);
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%235f5d59' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right var(--ad-space-xs) center;
+}
+
+input[aria-invalid="true"],
+select[aria-invalid="true"] {
+ border-color: var(--ad-color-danger);
+ background-color: var(--ad-color-danger-bg);
+}
+
+/* Checkbox / Radio — the real native controls, tinted with accent-color
+ instead of a hand-drawn box. This is not just a rename: it deletes the
+ custom SVG-checkmark markup entirely and lets the browser draw its own
+ (platform-appropriate) check and dot. `role="switch"` is excluded here so
+ Switch — a checkbox playing a different role — can style itself below
+ without this rule fighting it. A <label> wrapping a checkbox/radio is told
+ apart from Field's <label> structurally: it :has() one as a direct child,
+ no class required. */
+input[type="checkbox"]:not([role="switch"]),
+input[type="radio"] {
+ width: 1.125rem;
+ height: 1.125rem;
+ margin: 0;
+ accent-color: var(--ad-color-accent);
+ cursor: pointer;
+}
+input[type="checkbox"]:not([role="switch"]):focus-visible,
+input[type="radio"]:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+}
+
+label:has(> input[type="checkbox"]:not([role="switch"])),
+label:has(> input[type="radio"]) {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 400;
+ text-transform: none;
+ letter-spacing: normal;
+ color: var(--ad-color-text-primary);
+}
+label:has(> input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Switch — for on/off settings only, never a substitute for Checkbox.
+ `input[type="checkbox"][role="switch"]` is real ARIA (the correct role for
+ a toggle), not an invented hook, but the visual track+thumb needs a second
+ element for the thumb's independent position and transition — more than
+ `appearance: none` plus pseudo-elements on the input itself can cleanly
+ carry — so `.switch` is a deliberate, documented fallback class rather
+ than a first choice. */
+.switch {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ color: var(--ad-color-text-primary);
+}
+.switch input[role="switch"] { position: absolute; opacity: 0; width: 2.25rem; height: 1.25rem; margin: 0; }
+
+.switch span {
+ position: relative;
+ flex: none;
+ width: 2.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-border-default);
+ transition: background-color var(--ad-dur-base) var(--ad-ease);
+}
+.switch span::after {
+ content: "";
+ position: absolute;
+ inset-block-start: 2px;
+ inset-inline-start: 2px;
+ width: 1rem;
+ height: 1rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-fg);
+ box-shadow: var(--ad-shadow-sm);
+ transition: translate var(--ad-dur-base) var(--ad-ease);
+}
+.switch input[role="switch"]:checked + span { background: var(--ad-color-accent); }
+.switch input[role="switch"]:checked + span::after { translate: 1rem 0; }
+.switch input[role="switch"]:focus-visible + span { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+.switch:has(input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Rows of fields — used by the settings-form pattern. `<form>`/`<fieldset>`
+ are the elements that already mean this, so they are styled bare. A row
+ grouping two fields side by side has no element of its own, so it keeps
+ one plain class. */
+form {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-s);
+ max-width: 28rem;
+}
+.form-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
+ gap: var(--ad-space-xs);
+}
+fieldset {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ margin: 0;
+ padding: 0;
+ border: 0;
+}
+fieldset > legend {
+ padding: 0 0 var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Badge
+ Mono, uppercase, tracked — the catalog-label convention for status and
+ meta values (SAVED, OVERDUE, RECOMMENDED). A label, never a button.
+
+ A <span> is too generic to style globally — it is used all over running
+ prose for unrelated reasons — so Badge gets the one sanctioned class this
+ shape needs. Tone is an attribute, not a modifier class. */
+
+.badge {
+ --_bg: var(--ad-color-surface-fill);
+ --_fg: var(--ad-color-text-secondary);
+ --_border: var(--ad-color-border-hairline);
+
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.1875rem 0.5rem;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-sm);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ line-height: 1;
+ white-space: nowrap;
+}
+
+.badge[data-tone="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); --_border: transparent; }
+.badge[data-tone="success"] { --_bg: var(--ad-color-success-bg); --_fg: var(--ad-color-success); --_border: transparent; }
+.badge[data-tone="danger"] { --_bg: var(--ad-color-danger-bg); --_fg: var(--ad-color-danger); --_border: transparent; }
+
+.badge-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-2xs);
+}
+
+/* Tag
+ A removable mono chip for freeform tags — locations, categories.
+ `[data-dashed]` renders an "add tag" affordance instead of a value. Same
+ reasoning as Badge: <span> is too generic to select bare, so Tag gets one
+ class. The remove control is a real <button>, styled through the
+ descendant selector rather than a class of its own. */
+
+.tag {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.375rem;
+ padding: 0.25rem 0.625rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-default);
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ line-height: 1;
+}
+
+.tag[data-dashed] {
+ background: transparent;
+ border-style: dashed;
+ color: var(--ad-color-text-tertiary);
+ cursor: pointer;
+}
+
+.tag button {
+ all: unset;
+ cursor: pointer;
+ color: var(--ad-color-text-tertiary);
+ line-height: 1;
+ font-size: 0.75rem;
+}
+.tag button:hover { color: var(--ad-color-text-primary); }
+
+.tag-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.375rem;
+}
+
+/* Card
+ Flat surface, hairline border, radius-lg. `[data-elevated]` is opt-in and
+ reserved for the one focused/primary card in a view — a dialog, the
+ primary capture card — flat is the default everywhere else.
+
+ A <div>/<article> is too generic to select bare, so Card keeps one
+ sanctioned class. When Card's job is holding a list of rows — the common
+ case, entry lists, task lists, settings rows — it is a real <ul class="
+ card">, and CardRow is a bare <li> underneath it: no second class needed,
+ the row look comes from the `.card > li` descendant selector alone. The
+ first row's top border is removed automatically so callers never have to
+ override it by hand. */
+
+.card {
+ display: flex;
+ flex-direction: column;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: none;
+ overflow: hidden;
+}
+
+.card[data-elevated] { box-shadow: var(--ad-shadow-float); }
+
+.card > li {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+}
+.card > li:first-child { border-top: none; }
+
+/* A row whose ENTIRE content is wrapped in a real <button> or <a> — the
+ accessible way to make a row clickable — fills the row edge-to-edge and
+ picks up a hover fill and pointer cursor. `:only-child` is what tells this
+ apart from a row that merely contains a small button among other content
+ (a leading checkbox next to a title and a trailing value, say): only a
+ button/anchor that IS the row's one and only child means "the whole row
+ is the control." */
+.card > li:has(> button:only-child),
+.card > li:has(> a:only-child) {
+ padding: 0;
+}
+.card > li > button:only-child,
+.card > li > a:only-child {
+ all: unset;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ box-sizing: border-box;
+ color: inherit;
+ font: inherit;
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.card > li > button:only-child:hover,
+.card > li > a:only-child:hover { background: var(--ad-color-surface-fill); }
+
+/* Documentation-only extras: a body/footer pair and a responsive grid, used
+ by the prose pages (rationale.md, index.md) to show a Card as a plain
+ content tile rather than a data row. Neither is part of Card's props —
+ they exist because a content tile's inner layout has no element of its
+ own to hang a selector on. */
+.card-body {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-m);
+}
+.card-footer {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ margin-top: auto;
+ padding: var(--ad-space-xs) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+
+.card[data-link] { transition: border-color var(--ad-dur-fast) var(--ad-ease), box-shadow var(--ad-dur-fast) var(--ad-ease); }
+.card[data-link]:hover { border-color: var(--ad-color-border-default); box-shadow: var(--ad-shadow-sm); }
+.card[data-link]:focus-within { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+
+.card-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
+ gap: var(--ad-space-s);
+}
+
+/* Avatar — lives here because it almost never appears outside a card or a
+ row (the Loop dashboard's initials chip, Archive's file-type chip). A
+ <span> is too generic for a bare selector, so it keeps one class. */
+.avatar {
+ --_size: 1.875rem;
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ flex: none;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 600;
+ overflow: hidden;
+}
+.avatar[data-square] { border-radius: var(--ad-radius-md); background: var(--ad-color-surface-fill); color: var(--ad-color-text-secondary); border: 1px solid var(--ad-color-border-hairline); }
+.avatar[data-size="sm"] { --_size: 1.5rem; }
+.avatar[data-size="lg"] { --_size: 2.25rem; }
+
+/* Tooltip — a dark mono label on hover, positioned above the child. Pure CSS:
+ the trigger and bubble are siblings inside one relatively-positioned span,
+ shown on :hover/:focus-within so it works from the keyboard too.
+
+ `[role="tooltip"]` is the correct ARIA role for the bubble itself, so it
+ doubles as the styling hook — zero classes there. The wrapping span has no
+ attribute of its own, but it does not need one: `:has()` picks out "a span
+ that contains a [role=tooltip] bubble" precisely, without reaching for a
+ class on an otherwise-generic element. */
+span:has(> [role="tooltip"]) {
+ position: relative;
+ display: inline-flex;
+}
+
+[role="tooltip"] {
+ position: absolute;
+ bottom: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ margin-bottom: 0.375rem;
+ padding: 0.25rem 0.5rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-text-primary);
+ color: var(--ad-color-surface-page);
+ font-family: var(--ad-font-mono);
+ /* 11px is the source's own literal choice — smaller than --ad-step--2, the
+ smallest step on the scale, because a tooltip is meant to read as a
+ glance-only aside rather than body text. */
+ font-size: 11px;
+ white-space: nowrap;
+ line-height: 1.4;
+ z-index: 20;
+ opacity: 0;
+ visibility: hidden;
+ pointer-events: none;
+ transition: opacity var(--ad-dur-fast) var(--ad-ease);
+}
+span:has(> [role="tooltip"]):hover > [role="tooltip"],
+span:has(> [role="tooltip"]):focus-within > [role="tooltip"] {
+ opacity: 1;
+ visibility: visible;
+}
+
+/* Toast — the real pattern used for delete confirmations: a flat bar with an
+ optional destructive action and a dismiss. `[data-fixed]` pins it
+ bottom-right for real use; without it the toast stays in document flow for
+ documentation. `role="status"` is the correct live-region role for a
+ transient status message, so it is both the accessibility hook and the
+ style selector — no base class needed. The action and dismiss buttons are
+ plain <button>s told apart by `aria-label="Dismiss"`, an attribute they
+ need anyway for the icon-only close control. */
+[role="status"] {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ min-width: 20rem;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+}
+[role="status"][data-fixed] {
+ position: fixed;
+ inset-block-end: var(--ad-space-s);
+ inset-inline-end: var(--ad-space-s);
+ z-index: 60;
+}
+[role="status"] > span { flex: 1; }
+[role="status"] button {
+ all: unset;
+ cursor: pointer;
+ font-weight: 600;
+ color: var(--ad-color-danger);
+}
+[role="status"] button[aria-label="Dismiss"] {
+ color: var(--ad-color-text-tertiary);
+ font-size: 1rem;
+ font-weight: 400;
+ line-height: 1;
+}
+[role="status"] button[aria-label="Dismiss"]:hover { color: var(--ad-color-text-primary); }
+
+/* Dialog — the native <dialog> element: a centered modal, flat surface, no
+ colored header, hairline dividers between title/body/footer. A <header>
+ holds the title and a <footer> holds the actions, matching the elements
+ the tag already wants; everything else in between is the body, styled by
+ excluding those two rather than by a class of its own.
+
+ `.dialog-demo` scopes a static, always-open example to its own preview
+ container instead of the viewport, so it can sit safely inside a
+ documentation page — this is a doc-only affordance, not part of Dialog's
+ contract. It relies on the browser's own dialog centering: the UA
+ stylesheet already gives an open <dialog> `position: absolute; inset: 0;
+ margin: auto`, which centers it inside the nearest positioned ancestor —
+ here, `.dialog-demo` itself. A real `.showModal()` call gets the genuine
+ viewport-fixed overlay and `::backdrop` for free; a plain `open` attribute
+ (used here so the demo renders without JavaScript) does not paint a
+ backdrop at all, since `::backdrop` only exists for a dialog shown
+ modally — wire the real open/close state and call `.showModal()` in the
+ consuming app. */
+.dialog-demo {
+ position: relative;
+ min-height: 20rem;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-lg);
+}
+
+dialog {
+ width: 26rem;
+ max-width: calc(100% - (2 * var(--ad-space-s)));
+ height: fit-content;
+ padding: 0;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ overflow: hidden;
+}
+dialog::backdrop {
+ background: rgba(20, 18, 14, 0.35);
+}
+dialog > header {
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-sans);
+ font-weight: 600;
+ font-size: var(--ad-step-0);
+ color: var(--ad-color-text-primary);
+}
+dialog > footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+}
+dialog > *:not(header):not(footer) {
+ padding: var(--ad-space-m);
+ margin: 0;
+ color: var(--ad-color-text-primary);
+ font-size: var(--ad-step--1);
+}
+
+/* Tabs
+ Follows the real WAI-ARIA tabs pattern: a `[role="tablist"]` of
+ `[role="tab"]` buttons controlling `[role="tabpanel"]`s. Those roles are
+ required for the pattern to be accessible in the first place, so they
+ double as the entire styling contract — no classes at all for the base
+ shape. The keyboard wiring lives in assets/js/system.js and is
+ variant-agnostic — it only cares about `[data-tabs]` / `[role=tab]`.
+
+ Two real variants: `underline` (mono, accent underline — primary section
+ nav, the default) and `segmented` (pill track, active segment lifts to the
+ surface — filters like All · Overdue · Upcoming, never primary
+ navigation), chosen with `data-variant="segmented"` on the tablist since
+ neither has a natural attribute of its own. */
+
+/* Every rule below the list container pairs [role="tablist"] with
+ [role="tab"] rather than styling [role="tab"] alone — Tab buttons are
+ still literal <button> elements, so the compound selector's extra
+ specificity is what keeps Button's base styling (button.css) from
+ winning the cascade on shared properties like padding and background. */
+
+[role="tablist"] {
+ display: flex;
+ gap: var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+[role="tablist"] [role="tab"] {
+ padding: 0.5rem 0;
+ margin-bottom: -1px;
+ background: none;
+ border: 0;
+ border-bottom: 2px solid transparent;
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--1);
+ letter-spacing: 0.02em;
+ cursor: pointer;
+ transition: color var(--ad-dur-fast) var(--ad-ease), border-color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"] [role="tab"]:hover { color: var(--ad-color-text-primary); }
+[role="tablist"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ border-bottom-color: var(--ad-color-accent);
+}
+
+[role="tablist"][data-variant="segmented"] {
+ display: inline-flex;
+ gap: 0.125rem;
+ padding: 0.1875rem;
+ border-bottom: 0;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-md);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"] {
+ all: unset;
+ cursor: pointer;
+ padding: 0.375rem 0.875rem;
+ border-radius: var(--ad-radius-sm);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 550;
+ color: var(--ad-color-text-secondary);
+ transition: background-color var(--ad-dur-fast) var(--ad-ease), color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ background: var(--ad-color-surface-card);
+ box-shadow: var(--ad-shadow-sm);
+}
+
+[role="tabpanel"] { padding-top: var(--ad-space-s); }
+[role="tabpanel"][hidden] { display: none; }
+[role="tabpanel"]:focus-visible { outline-offset: 4px; }
+
+/* Documentation site chrome. Everything below the `.ad-doc-` prefix belongs
+ to this site rather than to the design system it documents — including the
+ width/height constants used directly below. The source design system
+ defines no container-width tokens at all, so there is nothing under
+ `--ad-*` for a doc shell to reach for; the Space foundation page's own
+ notes say to omit that group entirely rather than invent one. */
+
+.ad-doc-skip {
+ position: absolute;
+ inset-inline-start: var(--ad-space-xs);
+ inset-block-start: -4rem;
+ z-index: 40;
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ box-shadow: var(--ad-shadow-float);
+ transition: inset-block-start var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-skip:focus { inset-block-start: var(--ad-space-2xs); }
+
+.ad-doc-header {
+ position: sticky;
+ inset-block-start: 0;
+ z-index: 30;
+ height: 3.5rem;
+ background: color-mix(in srgb, var(--ad-color-surface-page) 92%, transparent);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-header__inner {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ height: 100%;
+ max-width: 72rem;
+ margin-inline: auto;
+ padding-inline: var(--ad-space-xs);
+}
+
+.ad-doc-wordmark {
+ display: flex;
+ align-items: baseline;
+ gap: var(--ad-space-2xs);
+ color: var(--ad-color-text-primary);
+ text-decoration: none;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ font-weight: 600;
+ letter-spacing: -0.01em;
+ white-space: nowrap;
+}
+.ad-doc-wordmark:hover { color: var(--ad-color-text-primary); }
+.ad-doc-wordmark__mark {
+ width: 1.375rem;
+ height: 1.375rem;
+ flex: none;
+ align-self: center;
+ color: var(--ad-color-accent);
+}
+.ad-doc-wordmark__version {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+
+.ad-doc-header__actions { margin-inline-start: auto; display: flex; align-items: center; gap: var(--ad-space-2xs); }
+
+.ad-doc-layout {
+ display: grid;
+ grid-template-columns: 16rem minmax(0, 1fr);
+ gap: var(--ad-space-l);
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-m) var(--ad-space-xs) var(--ad-space-2xl);
+}
+
+.ad-doc-sidebar {
+ position: sticky;
+ inset-block-start: calc(3.5rem + var(--ad-space-m));
+ align-self: start;
+ max-height: calc(100vh - 3.5rem - var(--ad-space-l));
+ overflow-y: auto;
+ padding-inline-end: var(--ad-space-2xs);
+}
+.ad-doc-nav__group + .ad-doc-nav__group { margin-top: var(--ad-space-s); }
+.ad-doc-nav__heading {
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-doc-nav__link {
+ display: block;
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ margin-inline-start: calc(var(--ad-space-xs) * -1);
+ border-inline-start: 2px solid transparent;
+ border-radius: 0 var(--ad-radius-sm) var(--ad-radius-sm) 0;
+ color: var(--ad-color-text-secondary);
+ font-size: var(--ad-step--1);
+ text-decoration: none;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-nav__link:hover { color: var(--ad-color-text-primary); background: var(--ad-color-surface-fill); }
+.ad-doc-nav__link[aria-current="page"] {
+ color: var(--ad-color-accent-ink);
+ border-inline-start-color: var(--ad-color-accent);
+ background: var(--ad-color-accent-soft);
+ font-weight: 500;
+}
+
+.ad-doc-main {
+ min-width: 0;
+ /* The design system's own body-text token (--ad-step-0, 18–20px) is right
+ for a product screen but too large for long-form documentation prose.
+ This site's own copy gets a plain, fixed 16px instead — the token above
+ is unchanged, and specimen tables/demos below still show its real
+ value. */
+ font-size: 1rem;
+}
+.ad-doc-main > * { max-width: 50rem; }
+
+.ad-doc-title { margin-bottom: var(--ad-space-xs); }
+.ad-doc-title__section {
+ display: block;
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-accent);
+}
+
+.ad-doc-main h2 {
+ margin-block: var(--ad-space-2xl) var(--ad-space-xs);
+ padding-bottom: var(--ad-space-2xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-main h3 { margin-block: var(--ad-space-l) var(--ad-space-2xs); }
+.ad-doc-main h4 { margin-block: var(--ad-space-s) var(--ad-space-2xs); }
+.ad-doc-main p,
+.ad-doc-main ul,
+.ad-doc-main ol { max-width: 68ch; }
+.ad-doc-main ul:not([class]),
+.ad-doc-main ol:not([class]) { padding-inline-start: var(--ad-space-s); }
+
+.ad-doc-footer {
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-s) var(--ad-space-xs) var(--ad-space-l);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-tertiary);
+ font-size: var(--ad-step--1);
+}
+.ad-doc-footer__inner { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); justify-content: space-between; }
+
+/* `button.ad-doc-nav-toggle` (element + class) rather than a bare class:
+ button.css's own base rule is `button:not(.icon-button)`, which is also
+ one element + one class in specificity and sets `display: inline-flex` —
+ a bare `.ad-doc-nav-toggle { display: none }` loses that fight and the
+ toggle stays visible at every width. Matching its specificity here (and
+ coming later in the concatenated stylesheet) settles it in source order. */
+button.ad-doc-nav-toggle { display: none; }
+
+@media (max-width: 60rem) {
+ .ad-doc-layout { grid-template-columns: minmax(0, 1fr); gap: var(--ad-space-s); }
+ .ad-doc-sidebar {
+ position: static;
+ max-height: none;
+ }
+ /* The toggle is a normal Button; the panel it opens gets its own card
+ treatment. Putting the same card styling on the <aside> wrapper too
+ nested a button-shaped box inside an outer box for no reason. */
+ .ad-doc-nav {
+ margin-top: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ }
+ .ad-doc-sidebar[data-collapsed="true"] > .ad-doc-nav { display: none; }
+ button.ad-doc-nav-toggle {
+ display: flex;
+ width: 100%;
+ justify-content: space-between;
+ }
+}
+
+@media (max-width: 34rem) {
+ .ad-doc-wordmark__version { display: none; }
+ .ad-doc-layout { padding-inline: var(--ad-space-2xs); }
+}
+
+/* The live-example figure used throughout the docs — see the "How the
+ example demos work" section on the Using the CSS page. One class on the
+ outer <figure>; everything inside is styled by plain element selectors,
+ since figcaption/div/details/summary/pre already say what they are.
+ `data-layout` and `data-surface` on the preview <div> distinguish the
+ handful of demos that need a non-default arrangement. */
+
+.example {
+ margin: var(--ad-space-s) 0 var(--ad-space-l);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+ background: var(--ad-color-surface-card);
+ overflow: hidden;
+}
+
+.example figcaption {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-secondary);
+}
+
+.example > div {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-s);
+}
+/* nowrap is load-bearing: a column-direction flex container with wrap enabled
+ sizes its single line against the container rather than the content, which
+ left every stacked example with a few hundred pixels of dead space. */
+.example > div[data-layout="stack"] { flex-direction: column; flex-wrap: nowrap; align-items: stretch; }
+.example > div[data-layout="grid"] {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
+ align-items: start;
+}
+.example > div[data-surface="sunken"] { background: var(--ad-color-surface-fill); }
+.example > div > * { min-width: 0; }
+
+.example details {
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+.example summary {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ color: var(--ad-color-text-secondary);
+ cursor: pointer;
+ list-style: none;
+ user-select: none;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+}
+.example summary::-webkit-details-marker { display: none; }
+.example summary::before {
+ content: "";
+ width: 0.5rem;
+ height: 0.5rem;
+ border-inline-end: 1.5px solid currentColor;
+ border-block-end: 1.5px solid currentColor;
+ rotate: -45deg;
+ transition: rotate var(--ad-dur-fast) var(--ad-ease);
+}
+.example details[open] > summary::before { rotate: 45deg; }
+.example summary:hover { color: var(--ad-color-text-primary); }
+
+.example pre {
+ margin: 0;
+ padding: var(--ad-space-xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+ tab-size: 2;
+}
+.example pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Standalone fenced code blocks in the prose (unhighlighted — there is no
+ syntax highlighter in this build any more, just Kramdown's plain
+ <pre><code class="language-*">). */
+.ad-doc-main > pre {
+ margin-block: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+}
+.ad-doc-main > pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Presentation for the token reference tables and swatch grids, plus a
+ generic data-table utility. All of this is documentation chrome — the
+ design system itself has no Table component (see the note on the removed
+ `table`/`progress`/`callout` pages in git history). */
+
+.ad-swatch-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs) var(--ad-space-s);
+ padding: 0;
+ list-style: none;
+}
+.ad-swatch {
+ display: flex;
+ flex-direction: column;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ overflow: hidden;
+ background: var(--ad-color-surface-card);
+}
+.ad-swatch__chip {
+ display: flex;
+ align-items: flex-end;
+ justify-content: flex-end;
+ height: 4rem;
+ padding: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+}
+.ad-swatch__meta {
+ display: flex;
+ flex-direction: column;
+ gap: 0.1rem;
+ padding: var(--ad-space-2xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+}
+.ad-swatch__hex { color: var(--ad-color-text-primary); text-transform: uppercase; }
+.ad-swatch__ratio { color: var(--ad-color-text-tertiary); }
+
+.ad-alias-row { display: flex; align-items: center; gap: var(--ad-space-2xs); }
+.ad-alias-chip {
+ width: 1.5rem;
+ height: 1.5rem;
+ flex: none;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+}
+.ad-alias-chip--pair { display: flex; }
+.ad-alias-chip--pair > span { flex: 1; }
+
+/* Type specimen rows. */
+.ad-specimen {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ gap: var(--ad-space-3xs);
+ padding-block: var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-specimen__meta {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-specimen__sample {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: var(--ad-color-text-primary);
+}
+
+/* Space and radius rulers. */
+.ad-ruler { display: flex; flex-direction: column; gap: var(--ad-space-2xs); }
+.ad-ruler__row { display: grid; grid-template-columns: 6.5rem 4.5rem 1fr; align-items: center; gap: var(--ad-space-xs); font-family: var(--ad-font-mono); font-size: 11px; }
+.ad-ruler__bar { height: 0.75rem; background: var(--ad-color-accent); border-radius: var(--ad-radius-sm); }
+.ad-ruler__num { color: var(--ad-color-text-tertiary); text-align: right; }
+
+.ad-radius-grid, .ad-elevation-grid, .ad-motion-grid, .ad-accent-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs);
+ padding: 0;
+ list-style: none;
+}
+.ad-radius-demo {
+ height: 4.5rem;
+ background: var(--ad-color-accent-soft);
+ border: 1px solid var(--ad-color-accent);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-elevation-demo {
+ height: 4.5rem;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-motion-demo {
+ height: 4.5rem;
+ display: grid;
+ place-items: center;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-md);
+ margin-bottom: var(--ad-space-2xs);
+ cursor: pointer;
+ overflow: hidden;
+}
+.ad-motion-demo__dot {
+ width: 1.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent);
+ translate: -3rem 0;
+ transition-property: translate;
+ transition-duration: var(--ad-dur-base);
+ transition-timing-function: var(--ad-ease);
+}
+.ad-motion-demo:hover .ad-motion-demo__dot,
+.ad-motion-demo:focus-visible .ad-motion-demo__dot { translate: 3rem 0; }
+
+.ad-accent-demo {
+ height: 4.5rem;
+ display: flex;
+ align-items: flex-end;
+ padding: var(--ad-space-2xs);
+ border-radius: var(--ad-radius-md);
+ color: #fff;
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ margin-bottom: var(--ad-space-2xs);
+}
+
+/* Token names read `<code>` sitewide, which already carries a mono chip
+ look for running prose (see :not(pre) > code in base/typography.css).
+ Inside these specimen/swatch/ruler rows the old `.ad-token-name` class was
+ plain mono text with no chip, so `<code>` is flattened back to that look
+ here the same way table cells are. */
+.ad-swatch code, .ad-specimen code, .ad-ruler code, .ad-alias-row code {
+ background: none;
+ border: 0;
+ padding: 0;
+ font-size: var(--ad-step--2);
+ color: var(--ad-color-text-primary);
+ white-space: nowrap;
+}
+
+/* A documentation aside. Like `<table>`, this is site chrome for the prose
+ itself, not a system component — the real system has no Callout. */
+.ad-callout {
+ margin-block: var(--ad-space-s);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-left: 3px solid var(--ad-color-accent);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+}
+/* Generic reference tables (component APIs, alias tables, the contrast
+ contract). `<table>` IS a table — no class needed to say so. Every table
+ on this site uses the one compact density, so there is no separate
+ modifier for it. `display: block` plus `overflow-x: auto` gives the table
+ its own horizontal scrollbar on narrow viewports without a wrapper div;
+ the anonymous table box the browser generates around `<thead>`/`<tbody>`
+ still does the row/column layout. `<code>` replaces the old
+ `.ad-token-name` / `.ad-table__code` cells — it is already styled
+ sitewide, just re-flattened here to a plain mono run instead of the
+ chip-with-border look prose code gets, to match this table's original
+ look. `.num` is the one sanctioned class left, for right-aligned numeric
+ columns — there is no attribute that means "this column is a number". */
+table { width: 100%; border-collapse: collapse; font-size: var(--ad-step--1); display: block; overflow-x: auto; }
+table th, table td {
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ text-align: left;
+ vertical-align: top;
+}
+table th { font-family: var(--ad-font-mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ad-color-text-secondary); font-weight: 500; }
+table code { background: none; border: 0; padding: 0; font-size: var(--ad-step--2); white-space: nowrap; }
+.num { text-align: right; font-variant-numeric: tabular-nums; }
+
+/* Home page. */
+.ad-hero { padding-block: var(--ad-space-m) var(--ad-space-l); }
+.ad-hero__title {
+ font-size: var(--ad-step-4);
+ margin-bottom: var(--ad-space-xs);
+}
+.ad-hero__actions { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); margin-top: var(--ad-space-s); }
+.ad-stat-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-l);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+}
+.ad-stat__value {
+ display: block;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-2);
+ font-weight: 600;
+ line-height: 1.16;
+ font-variant-numeric: tabular-nums;
+}
+.ad-stat__label {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+
+/* Card chrome. The pane supplies the frame; this just insets the page. */
+body { padding: var(--ad-space-l) var(--ad-space-m); }
+.ad-doc-main { max-width: 50rem; margin-inline: auto; }
+.ad-doc-main > * { max-width: none; }
+</style>
+</head>
+<body>
+<main class="ad-doc-main">
+<header class="ad-doc-title"><span class="ad-doc-title__section">Components</span>
+ <h1>Select</h1>
+</header>
+<p class="ad-lead">Native select, styled to match Input exactly.</p>
+<h2 id="basic">Basic</h2>
+
+<p><code><select></code> is the component — styled bare, no class.</p>
+
+<figure class="example">
+<figcaption>Select</figcaption>
+<div>
+<select style="max-width: 20rem;">
+ <option>Day</option>
+ <option selected="">Week</option>
+ <option>Month</option>
+ <option>Year</option>
+</select>
+</div>
+<details open=""><summary>Markup</summary>
+<pre><code><select style="max-width: 20rem;">
+ <option>Day</option>
+ <option selected="">Week</option>
+ <option>Month</option>
+ <option>Year</option>
+</select></code></pre>
+</details>
+</figure>
+
+<h2 id="disabled">Disabled</h2>
+
+<figure class="example">
+<figcaption>Disabled</figcaption>
+<div>
+<select disabled="" style="max-width: 20rem;"><option>Week</option></select>
+</div>
+<details><summary>Markup</summary>
+<pre><code><select disabled="" style="max-width: 20rem;"><option>Week</option></select></code></pre>
+</details>
+</figure>
+
+<div class="ad-callout">
+ <p style="margin:0; font-size: var(--ad-step--1);">
+ The chevron is the one place in this component set that cannot reach a
+ <code>var(--ad-*)</code> token — a data-URI <code>background-image</code>
+ cannot reference a custom property, so its stroke is a literal hex, kept
+ close to <code>--ad-color-text-secondary</code> and commented in the source.
+ </p>
+</div>
+
+<h2 id="api">API</h2>
+
+<table>
+ <thead><tr><th scope="col">Element / attribute</th><th scope="col">Values</th><th scope="col">Notes</th></tr></thead>
+ <tbody>
+ <tr><td><code>select</code></td><td></td><td>Required base — no class.</td></tr>
+ <tr><td><code>option</code></td><td></td><td></td></tr>
+ <tr><td><code>disabled</code></td><td>boolean</td><td></td></tr>
+ </tbody>
+</table>
+</main>
+<script>
+// Behavior for the pieces of the system that need it. Everything else is
+// CSS and semantic HTML on purpose — a design system that only works with
+// JavaScript running is a component library with extra steps.
+(function () {
+ "use strict";
+
+ var STORAGE_KEY = "ad-theme";
+
+ function readStored() {
+ try {
+ return localStorage.getItem(STORAGE_KEY);
+ } catch (error) {
+ return null;
+ }
+ }
+
+ function writeStored(value) {
+ try {
+ localStorage.setItem(STORAGE_KEY, value);
+ } catch (error) {
+ /* Nothing to do — the media query still gives a sensible default. */
+ }
+ }
+
+ function systemPrefersDark() {
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
+ }
+
+ function currentTheme() {
+ var explicit = document.documentElement.getAttribute("data-theme");
+ if (explicit) return explicit;
+ return systemPrefersDark() ? "dark" : "light";
+ }
+
+ function paintToggle(theme) {
+ document.querySelectorAll("[data-theme-toggle]").forEach(function (toggle) {
+ var next = theme === "dark" ? "light" : "dark";
+ toggle.querySelectorAll("[data-theme-icon]").forEach(function (icon) {
+ icon.hidden = icon.dataset.themeIcon !== theme;
+ });
+ toggle.setAttribute("aria-label", "Switch to the " + next + " theme");
+ toggle.setAttribute("title", "Switch to the " + next + " theme");
+ var label = toggle.querySelector("[data-theme-label]");
+ if (label) label.textContent = "Switch to the " + next + " theme";
+ });
+ }
+
+ function setUpTheme() {
+ paintToggle(currentTheme());
+
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-theme-toggle]");
+ if (!toggle) return;
+
+ var next = currentTheme() === "dark" ? "light" : "dark";
+ document.documentElement.setAttribute("data-theme", next);
+ writeStored(next);
+ paintToggle(next);
+ });
+
+ // Follow the OS while the reader has not made an explicit choice.
+ window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", function () {
+ if (!readStored()) paintToggle(currentTheme());
+ });
+ }
+
+ // Tabs, wired to the WAI-ARIA authoring practices: arrows move, Home/End
+ // jump, and only the selected tab stays in the tab order.
+ function setUpTabs() {
+ document.querySelectorAll("[data-tabs]").forEach(function (root) {
+ var tabs = Array.prototype.slice.call(root.querySelectorAll('[role="tab"]'));
+ if (!tabs.length) return;
+
+ function select(tab, focus) {
+ tabs.forEach(function (candidate) {
+ var selected = candidate === tab;
+ candidate.setAttribute("aria-selected", String(selected));
+ candidate.tabIndex = selected ? 0 : -1;
+ var panel = document.getElementById(candidate.getAttribute("aria-controls"));
+ if (panel) panel.hidden = !selected;
+ });
+ if (focus) tab.focus();
+ }
+
+ root.addEventListener("click", function (event) {
+ var tab = event.target.closest('[role="tab"]');
+ if (tab) select(tab, false);
+ });
+
+ root.addEventListener("keydown", function (event) {
+ var index = tabs.indexOf(document.activeElement);
+ if (index === -1) return;
+
+ var next = null;
+ if (event.key === "ArrowRight") next = tabs[(index + 1) % tabs.length];
+ else if (event.key === "ArrowLeft") next = tabs[(index - 1 + tabs.length) % tabs.length];
+ else if (event.key === "Home") next = tabs[0];
+ else if (event.key === "End") next = tabs[tabs.length - 1];
+
+ if (next) {
+ event.preventDefault();
+ select(next, true);
+ }
+ });
+
+ select(tabs.filter(function (tab) {
+ return tab.getAttribute("aria-selected") === "true";
+ })[0] || tabs[0], false);
+ });
+ }
+
+ function setUpNavToggle() {
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-nav-toggle]");
+ if (!toggle) return;
+
+ var sidebar = toggle.closest("[data-sidebar]");
+ var collapsed = sidebar.dataset.collapsed !== "false";
+ sidebar.dataset.collapsed = String(!collapsed);
+ toggle.setAttribute("aria-expanded", String(collapsed));
+ });
+ }
+
+ setUpTheme();
+ setUpTabs();
+ setUpNavToggle();
+})();
+
+</script>
+</body>
+</html>
diff --git a/design-bundle/components/switch.html b/design-bundle/components/switch.html
new file mode 100644
index 0000000..9c91dc2
--- /dev/null
+++ b/design-bundle/components/switch.html
@@ -0,0 +1,1821 @@
+<!-- @dsCard group="Forms" -->
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Switch</title>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&display=swap">
+<style>
+/* Arbitrary Definitions — design tokens. Hand-maintained; edit values directly. */
+
+:root {
+ /* Palette ramps — reference only. Components use the aliases below. */
+ --ad-neutral-0: #faf9f7;
+ --ad-neutral-50: #fdfdfb;
+ --ad-neutral-100: #f1f0ed;
+ --ad-neutral-150: #e8e6e2;
+ --ad-neutral-200: #dcdad7;
+ --ad-neutral-300: #c0bdb9;
+ --ad-neutral-500: #888681;
+ --ad-neutral-600: #5f5d59;
+ --ad-neutral-800: #2a2926;
+ --ad-neutral-900: #100f0d;
+ --ad-neutral-dark-0: #0e0d0b;
+ --ad-neutral-dark-50: #151412;
+ --ad-neutral-dark-100: #1e1d1a;
+ --ad-neutral-dark-150: #252421;
+ --ad-neutral-dark-200: #2c2b29;
+ --ad-neutral-dark-300: #44423f;
+ --ad-neutral-dark-500: #7c7a76;
+ --ad-neutral-dark-600: #a09e9a;
+ --ad-neutral-dark-800: #d9d7d4;
+ --ad-neutral-dark-900: #e9e8e6;
+ --ad-accent-teal: #3a6a63;
+ --ad-accent-clay: #9a5a3c;
+ --ad-accent-ink-blue: #2b4a78;
+ --ad-accent-ochre: #93701f;
+ --ad-accent-plum: #6a3a5c;
+
+ /* Surfaces */
+ --ad-color-surface-page: #faf9f7;
+ --ad-color-surface-card: #fdfdfb;
+ --ad-color-surface-fill: #f1f0ed;
+ --ad-color-surface-fill-2: #e8e6e2;
+
+ /* Borders */
+ --ad-color-border-hairline: #dcdad7;
+ --ad-color-border-default: #c0bdb9;
+
+ /* Text */
+ --ad-color-text-primary: #100f0d;
+ --ad-color-text-secondary: #5f5d59;
+ --ad-color-text-tertiary: #888681;
+
+ /* Accent */
+ --ad-color-accent: #3a6a63;
+ --ad-color-accent-fg: #ffffff;
+ --ad-color-accent-ink: color-mix(in oklch, var(--ad-color-accent) 78%, black);
+ --ad-color-accent-soft: color-mix(in oklch, var(--ad-color-accent) 12%, white);
+
+ /* Status (fixed — never swapped like the accent) */
+ --ad-color-success: #3f7a4a;
+ --ad-color-success-bg: color-mix(in oklch, var(--ad-color-success) 12%, white);
+ --ad-color-danger: #ad3a2c;
+ --ad-color-danger-bg: color-mix(in oklch, var(--ad-color-danger) 10%, white);
+
+ /* Type families */
+ --ad-font-sans: -apple-system, "Helvetica Neue", Helvetica, Arial, system-ui, sans-serif;
+ --ad-font-mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, Consolas, "Courier New", monospace;
+ --ad-font-serif: Georgia, "Times New Roman", "Iowan Old Style", serif;
+
+ /* Type steps — fluid (Utopia scale), the design system's own body-text size */
+ --ad-step--2: clamp(0.7813rem, 0.7736rem + 0.0341vw, 0.8rem);
+ --ad-step--1: clamp(0.9375rem, 0.9119rem + 0.1136vw, 1rem);
+ --ad-step-0: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-step-1: clamp(1.35rem, 1.2631rem + 0.3864vw, 1.5625rem);
+ --ad-step-2: clamp(1.62rem, 1.4837rem + 0.6057vw, 1.9531rem);
+ --ad-step-3: clamp(1.944rem, 1.7405rem + 0.9044vw, 2.4414rem);
+ --ad-step-4: clamp(2.3328rem, 2.0387rem + 1.3072vw, 3.0518rem);
+ --ad-step-5: clamp(2.7994rem, 2.384rem + 1.8461vw, 3.8147rem);
+
+ /* Space */
+ --ad-space-3xs: clamp(0.3125rem, 0.3125rem + 0vw, 0.3125rem);
+ --ad-space-2xs: clamp(0.5625rem, 0.5369rem + 0.1136vw, 0.625rem);
+ --ad-space-xs: clamp(0.875rem, 0.8494rem + 0.1136vw, 0.9375rem);
+ --ad-space-s: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-space-m: clamp(1.6875rem, 1.6108rem + 0.3409vw, 1.875rem);
+ --ad-space-l: clamp(2.25rem, 2.1477rem + 0.4545vw, 2.5rem);
+ --ad-space-xl: clamp(3.375rem, 3.2216rem + 0.6818vw, 3.75rem);
+ --ad-space-2xl: clamp(4.5rem, 4.2955rem + 0.9091vw, 5rem);
+ --ad-space-3xl: clamp(6.75rem, 6.4432rem + 1.3636vw, 7.5rem);
+
+ /* Radius */
+ --ad-radius-sm: 4px;
+ --ad-radius-md: 6px;
+ --ad-radius-lg: 10px;
+ --ad-radius-full: 999px;
+ --ad-border-w: 1px;
+
+ /* Elevation */
+ --ad-shadow-sm: 0 1px 2px rgba(20, 18, 14, 0.05);
+ --ad-shadow-float: 0 2px 6px rgba(20, 18, 14, 0.06), 0 10px 24px -12px rgba(20, 18, 14, 0.18);
+
+ /* Motion */
+ --ad-dur-fast: 0.12s;
+ --ad-dur-base: 0.16s;
+ --ad-ease: cubic-bezier(0.2, 0, 0, 1);
+}
+
+@media (prefers-color-scheme: dark) {
+ :root:not([data-theme="light"]) {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+ }
+}
+
+:root[data-theme="dark"] {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ :root {
+ --ad-dur-fast: 0ms;
+ --ad-dur-base: 0ms;
+ }
+}
+
+/* A short reset. Everything it sets is something the system would otherwise
+ have to override in every component.
+
+ The rem values used for this documentation site's own chrome (measure,
+ content width, sidebar width, header height) belong to this site, not to
+ the design system it documents — the source design system defines no
+ container-width tokens at all, so there is nothing under `--ad-*` to reach
+ for here. See the header comment in site/shell.css. */
+
+*,
+*::before,
+*::after { box-sizing: border-box; }
+
+html {
+ -webkit-text-size-adjust: 100%;
+ scroll-behavior: smooth;
+ scroll-padding-top: calc(3.5rem + var(--ad-space-s));
+}
+
+@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;
+ }
+}
+
+body {
+ margin: 0;
+ background: var(--ad-color-surface-page);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ line-height: 1.55;
+ font-synthesis-weight: none;
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+}
+
+::selection {
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+}
+
+h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd { margin: 0; }
+ul[class], ol[class] { list-style: none; margin: 0; padding: 0; }
+
+img, svg, video { display: block; max-width: 100%; height: auto; }
+
+button, input, select, textarea {
+ font: inherit;
+ color: inherit;
+}
+
+hr {
+ height: 1px;
+ margin: var(--ad-space-l) 0;
+ border: 0;
+ background: var(--ad-color-border-hairline);
+}
+
+/* One focus ring, defined once. No component is allowed its own. */
+:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+ border-radius: var(--ad-radius-sm);
+}
+:focus:not(:focus-visible) { outline: none; }
+
+[hidden] { display: none !important; }
+
+.ad-visually-hidden {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip-path: inset(50%);
+ white-space: nowrap;
+ border: 0;
+}
+
+/* Every rule here reads a type token for family and size. Weight, line-height,
+ and letter-spacing are hardcoded to the exact numbers the source ships —
+ there is no `--ad-weight-*` / `--ad-leading-*` / `--ad-tracking-*` scale in
+ the real system, only eight fixed compound styles (see the Typography
+ foundation page, which this file implements verbatim). */
+
+.type-display { font: 600 var(--ad-step-4)/1.08 var(--ad-font-sans); letter-spacing: -0.015em; }
+.type-h1, h1 { font: 600 var(--ad-step-3)/1.12 var(--ad-font-sans); letter-spacing: -0.012em; }
+.type-h2, h2 { font: 600 var(--ad-step-2)/1.16 var(--ad-font-sans); letter-spacing: -0.01em; }
+.type-h3, h3 { font: 600 var(--ad-step-1)/1.25 var(--ad-font-sans); }
+.type-body { font: 400 var(--ad-step-0)/1.55 var(--ad-font-sans); }
+.type-body-sm { font: 400 var(--ad-step--1)/1.5 var(--ad-font-sans); }
+.type-label {
+ font: 500 var(--ad-step--2)/1 var(--ad-font-mono);
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: var(--ad-color-text-secondary);
+}
+.type-mono { font: 400 var(--ad-step--1)/1.5 var(--ad-font-mono); }
+
+/* Prose defaults for the documentation body copy. These read the same
+ tokens the compound classes above do; they are not a second scale. */
+h1, h2, h3, h4 { font-weight: 600; text-wrap: balance; }
+
+h4 { font: 600 var(--ad-step-0)/1.3 var(--ad-font-sans); }
+
+p, ul, ol { margin: 0 0 var(--ad-space-s); }
+li + li { margin-top: var(--ad-space-3xs); }
+
+a {
+ color: var(--ad-color-accent);
+ text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
+ text-underline-offset: 0.18em;
+ text-decoration-thickness: 1px;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ text-decoration-color var(--ad-dur-fast) var(--ad-ease);
+}
+a:hover {
+ color: var(--ad-color-accent-ink);
+ text-decoration-color: currentColor;
+}
+
+strong, b { font-weight: 600; }
+
+small { font-size: var(--ad-step--1); }
+
+.ad-lead {
+ font-size: var(--ad-step-1);
+ line-height: 1.5;
+ color: var(--ad-color-text-secondary);
+ max-width: 68ch;
+ text-wrap: pretty;
+}
+
+.ad-muted { color: var(--ad-color-text-secondary); }
+.ad-subtle { color: var(--ad-color-text-tertiary); }
+
+code, kbd, samp, pre {
+ font-family: var(--ad-font-mono);
+ font-feature-settings: "liga" 0;
+}
+
+:not(pre) > code {
+ padding: 0.15em 0.4em;
+ font-size: 0.875em;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-sm);
+ white-space: nowrap;
+}
+
+kbd {
+ display: inline-block;
+ padding: 0.1em 0.45em;
+ font-size: var(--ad-step--2);
+ line-height: 1.6;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-bottom-width: 2px;
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-secondary);
+}
+
+blockquote {
+ margin-block: var(--ad-space-s);
+ padding-left: var(--ad-space-s);
+ border-left: 2px solid var(--ad-color-accent);
+ color: var(--ad-color-text-secondary);
+}
+
+/* Button
+ A flat, quiet button that recedes rather than performs — no gradients, no
+ shine. Variants: default, primary, dark, ghost, danger. Sizes: sm, md
+ (default), lg — padding and font step only, weight and radius stay put.
+
+ The contract is the element itself: a bare <button> is styled directly, no
+ class required. An <a> that acts like a button (navigates rather than
+ changes something — see the Button page's "Links that act like buttons")
+ opts in with a single `.btn` class, since a bare `a` selector would catch
+ every ordinary prose link on the site. `:not(.icon-button)` keeps this
+ block from leaking onto IconButton, which shares the <button> tag but is
+ an entirely different shape. */
+
+button:not(.icon-button),
+a.btn {
+ --_bg: var(--ad-color-surface-card);
+ --_fg: var(--ad-color-text-primary);
+ --_border: var(--ad-color-border-default);
+ --_pad: var(--ad-space-xs) var(--ad-space-s);
+ --_font: var(--ad-step--1);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: var(--ad-space-2xs);
+ padding: var(--_pad);
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-sans);
+ font-size: var(--_font);
+ font-weight: 550;
+ letter-spacing: -0.01em;
+ line-height: 1.2;
+ text-align: center;
+ text-decoration: none;
+ white-space: nowrap;
+ cursor: pointer;
+ transition: filter var(--ad-dur-fast) var(--ad-ease),
+ border-color var(--ad-dur-fast) var(--ad-ease);
+}
+
+:is(button:not(.icon-button), a.btn):hover:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.96); }
+:is(button:not(.icon-button), a.btn):active:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.92); }
+
+button:not(.icon-button):disabled,
+:is(button:not(.icon-button), a.btn)[aria-disabled="true"] {
+ opacity: 0.5;
+ cursor: default;
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="primary"] {
+ --_bg: var(--ad-color-accent);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-accent);
+}
+
+/* Reserved for the single most-committing action in a view — a final Save,
+ never a whole row of them. */
+:is(button:not(.icon-button), a.btn)[data-variant="dark"] {
+ --_bg: var(--ad-color-text-primary);
+ --_fg: var(--ad-color-surface-page);
+ --_border: var(--ad-color-text-primary);
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="ghost"] {
+ --_bg: transparent;
+ --_fg: var(--ad-color-text-primary);
+ --_border: transparent;
+}
+
+/* The source hardcodes this label white rather than deriving it — danger
+ is not "on a filled accent surface", so --ad-color-accent-fg is reused
+ here only because its resolved value is the same literal white, not
+ because the two roles are the same thing. */
+:is(button:not(.icon-button), a.btn)[data-variant="danger"] {
+ --_bg: var(--ad-color-danger);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-danger);
+}
+
+:is(button:not(.icon-button), a.btn)[data-size="sm"] { --_pad: 0.375rem 0.75rem; --_font: 0.8125rem; }
+:is(button:not(.icon-button), a.btn)[data-size="lg"] { --_pad: 0.75rem 1.375rem; --_font: var(--ad-step-0); }
+
+:is(button:not(.icon-button), a.btn)[data-block] { width: 100%; }
+
+/* Button group — [role="group"] is already the correct ARIA role for a
+ toolbar of related buttons, so it doubles as the styling hook for free. */
+[role="group"] {
+ display: inline-flex;
+ isolation: isolate;
+}
+[role="group"] > button { border-radius: 0; }
+[role="group"] > button:first-child { border-start-start-radius: var(--ad-radius-md); border-end-start-radius: var(--ad-radius-md); }
+[role="group"] > button:last-child { border-start-end-radius: var(--ad-radius-md); border-end-end-radius: var(--ad-radius-md); }
+[role="group"] > button + button { margin-inline-start: -1px; }
+[role="group"] > button:hover,
+[role="group"] > button:focus-visible { z-index: 1; }
+
+/* IconButton — a square icon-only button. Always needs an aria-label; it
+ becomes both the accessible name and the native tooltip. There is no
+ attribute that distinguishes "a button that is only an icon" from a plain
+ Button, so this is the one sanctioned class in this file. */
+.icon-button {
+ --_size: 2.125rem;
+ --_bg: transparent;
+ --_border: transparent;
+ --_fg: var(--ad-color-text-secondary);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ padding: 0;
+ flex: none;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease),
+ color var(--ad-dur-fast) var(--ad-ease);
+}
+.icon-button:hover:not(:disabled) { background: var(--ad-color-surface-fill-2); }
+.icon-button:disabled { opacity: 0.5; cursor: default; }
+
+.icon-button[data-size="sm"] { --_size: 1.75rem; }
+.icon-button[data-size="lg"] { --_size: 2.5rem; }
+
+.icon-button[data-variant="outline"] { --_bg: var(--ad-color-surface-card); --_border: var(--ad-color-border-default); --_fg: var(--ad-color-text-primary); }
+.icon-button[data-variant="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); }
+
+/* Field
+ The labeled field wrapper — a <label> that wraps its own caption text
+ together with the control, so the association is native and needs no
+ `for`/`id` pair. The caption is mono, uppercase, tracked — the same
+ catalog-label convention as Badge and type-label.
+
+ A <label> is reused for three different shapes across this system (this
+ wrapper, Checkbox/Radio's row, Switch's row), so it cannot be styled bare
+ without one shape leaking into another. Field is the one of the three with
+ no distinguishing attribute of its own, so it gets the sanctioned class;
+ Checkbox/Radio tell themselves apart structurally instead (see below). */
+.field {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ min-width: 0;
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Hint and error read the native fine-print element. `role="alert"` is a
+ real live-region role for validation text, so it doubles as the selector
+ that turns a hint red. */
+.field > small {
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+.field > small[role="alert"] { color: var(--ad-color-danger); }
+
+/* Input / Select — bordered, matched to each other exactly. Both tags ARE
+ the component, so they are styled bare; nothing else on this site uses a
+ text input or a select. */
+input:not([type="checkbox"]):not([type="radio"]),
+select {
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ outline: none;
+ transition: border-color var(--ad-dur-fast) var(--ad-ease),
+ box-shadow var(--ad-dur-fast) var(--ad-ease);
+}
+input::placeholder { color: var(--ad-color-text-tertiary); }
+
+input:not([type="checkbox"]):not([type="radio"]):focus,
+select:focus {
+ border-color: var(--ad-color-accent);
+ box-shadow: 0 0 0 2px var(--ad-color-accent-soft);
+}
+
+input:not([type="checkbox"]):not([type="radio"]):disabled,
+select:disabled {
+ opacity: 0.5;
+ cursor: default;
+}
+
+/* The chevron is the one flagged color exception in this component set:
+ CSS cannot point a data-URI background-image at a custom property, so its
+ stroke is a literal hex rather than a var(--ad-*) reference. It is picked
+ to match --ad-color-text-secondary and commented here so it is not
+ mistaken for a stray color. */
+select {
+ appearance: none;
+ padding-inline-end: var(--ad-space-l);
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%235f5d59' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right var(--ad-space-xs) center;
+}
+
+input[aria-invalid="true"],
+select[aria-invalid="true"] {
+ border-color: var(--ad-color-danger);
+ background-color: var(--ad-color-danger-bg);
+}
+
+/* Checkbox / Radio — the real native controls, tinted with accent-color
+ instead of a hand-drawn box. This is not just a rename: it deletes the
+ custom SVG-checkmark markup entirely and lets the browser draw its own
+ (platform-appropriate) check and dot. `role="switch"` is excluded here so
+ Switch — a checkbox playing a different role — can style itself below
+ without this rule fighting it. A <label> wrapping a checkbox/radio is told
+ apart from Field's <label> structurally: it :has() one as a direct child,
+ no class required. */
+input[type="checkbox"]:not([role="switch"]),
+input[type="radio"] {
+ width: 1.125rem;
+ height: 1.125rem;
+ margin: 0;
+ accent-color: var(--ad-color-accent);
+ cursor: pointer;
+}
+input[type="checkbox"]:not([role="switch"]):focus-visible,
+input[type="radio"]:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+}
+
+label:has(> input[type="checkbox"]:not([role="switch"])),
+label:has(> input[type="radio"]) {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 400;
+ text-transform: none;
+ letter-spacing: normal;
+ color: var(--ad-color-text-primary);
+}
+label:has(> input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Switch — for on/off settings only, never a substitute for Checkbox.
+ `input[type="checkbox"][role="switch"]` is real ARIA (the correct role for
+ a toggle), not an invented hook, but the visual track+thumb needs a second
+ element for the thumb's independent position and transition — more than
+ `appearance: none` plus pseudo-elements on the input itself can cleanly
+ carry — so `.switch` is a deliberate, documented fallback class rather
+ than a first choice. */
+.switch {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ color: var(--ad-color-text-primary);
+}
+.switch input[role="switch"] { position: absolute; opacity: 0; width: 2.25rem; height: 1.25rem; margin: 0; }
+
+.switch span {
+ position: relative;
+ flex: none;
+ width: 2.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-border-default);
+ transition: background-color var(--ad-dur-base) var(--ad-ease);
+}
+.switch span::after {
+ content: "";
+ position: absolute;
+ inset-block-start: 2px;
+ inset-inline-start: 2px;
+ width: 1rem;
+ height: 1rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-fg);
+ box-shadow: var(--ad-shadow-sm);
+ transition: translate var(--ad-dur-base) var(--ad-ease);
+}
+.switch input[role="switch"]:checked + span { background: var(--ad-color-accent); }
+.switch input[role="switch"]:checked + span::after { translate: 1rem 0; }
+.switch input[role="switch"]:focus-visible + span { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+.switch:has(input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Rows of fields — used by the settings-form pattern. `<form>`/`<fieldset>`
+ are the elements that already mean this, so they are styled bare. A row
+ grouping two fields side by side has no element of its own, so it keeps
+ one plain class. */
+form {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-s);
+ max-width: 28rem;
+}
+.form-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
+ gap: var(--ad-space-xs);
+}
+fieldset {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ margin: 0;
+ padding: 0;
+ border: 0;
+}
+fieldset > legend {
+ padding: 0 0 var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Badge
+ Mono, uppercase, tracked — the catalog-label convention for status and
+ meta values (SAVED, OVERDUE, RECOMMENDED). A label, never a button.
+
+ A <span> is too generic to style globally — it is used all over running
+ prose for unrelated reasons — so Badge gets the one sanctioned class this
+ shape needs. Tone is an attribute, not a modifier class. */
+
+.badge {
+ --_bg: var(--ad-color-surface-fill);
+ --_fg: var(--ad-color-text-secondary);
+ --_border: var(--ad-color-border-hairline);
+
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.1875rem 0.5rem;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-sm);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ line-height: 1;
+ white-space: nowrap;
+}
+
+.badge[data-tone="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); --_border: transparent; }
+.badge[data-tone="success"] { --_bg: var(--ad-color-success-bg); --_fg: var(--ad-color-success); --_border: transparent; }
+.badge[data-tone="danger"] { --_bg: var(--ad-color-danger-bg); --_fg: var(--ad-color-danger); --_border: transparent; }
+
+.badge-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-2xs);
+}
+
+/* Tag
+ A removable mono chip for freeform tags — locations, categories.
+ `[data-dashed]` renders an "add tag" affordance instead of a value. Same
+ reasoning as Badge: <span> is too generic to select bare, so Tag gets one
+ class. The remove control is a real <button>, styled through the
+ descendant selector rather than a class of its own. */
+
+.tag {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.375rem;
+ padding: 0.25rem 0.625rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-default);
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ line-height: 1;
+}
+
+.tag[data-dashed] {
+ background: transparent;
+ border-style: dashed;
+ color: var(--ad-color-text-tertiary);
+ cursor: pointer;
+}
+
+.tag button {
+ all: unset;
+ cursor: pointer;
+ color: var(--ad-color-text-tertiary);
+ line-height: 1;
+ font-size: 0.75rem;
+}
+.tag button:hover { color: var(--ad-color-text-primary); }
+
+.tag-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.375rem;
+}
+
+/* Card
+ Flat surface, hairline border, radius-lg. `[data-elevated]` is opt-in and
+ reserved for the one focused/primary card in a view — a dialog, the
+ primary capture card — flat is the default everywhere else.
+
+ A <div>/<article> is too generic to select bare, so Card keeps one
+ sanctioned class. When Card's job is holding a list of rows — the common
+ case, entry lists, task lists, settings rows — it is a real <ul class="
+ card">, and CardRow is a bare <li> underneath it: no second class needed,
+ the row look comes from the `.card > li` descendant selector alone. The
+ first row's top border is removed automatically so callers never have to
+ override it by hand. */
+
+.card {
+ display: flex;
+ flex-direction: column;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: none;
+ overflow: hidden;
+}
+
+.card[data-elevated] { box-shadow: var(--ad-shadow-float); }
+
+.card > li {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+}
+.card > li:first-child { border-top: none; }
+
+/* A row whose ENTIRE content is wrapped in a real <button> or <a> — the
+ accessible way to make a row clickable — fills the row edge-to-edge and
+ picks up a hover fill and pointer cursor. `:only-child` is what tells this
+ apart from a row that merely contains a small button among other content
+ (a leading checkbox next to a title and a trailing value, say): only a
+ button/anchor that IS the row's one and only child means "the whole row
+ is the control." */
+.card > li:has(> button:only-child),
+.card > li:has(> a:only-child) {
+ padding: 0;
+}
+.card > li > button:only-child,
+.card > li > a:only-child {
+ all: unset;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ box-sizing: border-box;
+ color: inherit;
+ font: inherit;
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.card > li > button:only-child:hover,
+.card > li > a:only-child:hover { background: var(--ad-color-surface-fill); }
+
+/* Documentation-only extras: a body/footer pair and a responsive grid, used
+ by the prose pages (rationale.md, index.md) to show a Card as a plain
+ content tile rather than a data row. Neither is part of Card's props —
+ they exist because a content tile's inner layout has no element of its
+ own to hang a selector on. */
+.card-body {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-m);
+}
+.card-footer {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ margin-top: auto;
+ padding: var(--ad-space-xs) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+
+.card[data-link] { transition: border-color var(--ad-dur-fast) var(--ad-ease), box-shadow var(--ad-dur-fast) var(--ad-ease); }
+.card[data-link]:hover { border-color: var(--ad-color-border-default); box-shadow: var(--ad-shadow-sm); }
+.card[data-link]:focus-within { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+
+.card-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
+ gap: var(--ad-space-s);
+}
+
+/* Avatar — lives here because it almost never appears outside a card or a
+ row (the Loop dashboard's initials chip, Archive's file-type chip). A
+ <span> is too generic for a bare selector, so it keeps one class. */
+.avatar {
+ --_size: 1.875rem;
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ flex: none;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 600;
+ overflow: hidden;
+}
+.avatar[data-square] { border-radius: var(--ad-radius-md); background: var(--ad-color-surface-fill); color: var(--ad-color-text-secondary); border: 1px solid var(--ad-color-border-hairline); }
+.avatar[data-size="sm"] { --_size: 1.5rem; }
+.avatar[data-size="lg"] { --_size: 2.25rem; }
+
+/* Tooltip — a dark mono label on hover, positioned above the child. Pure CSS:
+ the trigger and bubble are siblings inside one relatively-positioned span,
+ shown on :hover/:focus-within so it works from the keyboard too.
+
+ `[role="tooltip"]` is the correct ARIA role for the bubble itself, so it
+ doubles as the styling hook — zero classes there. The wrapping span has no
+ attribute of its own, but it does not need one: `:has()` picks out "a span
+ that contains a [role=tooltip] bubble" precisely, without reaching for a
+ class on an otherwise-generic element. */
+span:has(> [role="tooltip"]) {
+ position: relative;
+ display: inline-flex;
+}
+
+[role="tooltip"] {
+ position: absolute;
+ bottom: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ margin-bottom: 0.375rem;
+ padding: 0.25rem 0.5rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-text-primary);
+ color: var(--ad-color-surface-page);
+ font-family: var(--ad-font-mono);
+ /* 11px is the source's own literal choice — smaller than --ad-step--2, the
+ smallest step on the scale, because a tooltip is meant to read as a
+ glance-only aside rather than body text. */
+ font-size: 11px;
+ white-space: nowrap;
+ line-height: 1.4;
+ z-index: 20;
+ opacity: 0;
+ visibility: hidden;
+ pointer-events: none;
+ transition: opacity var(--ad-dur-fast) var(--ad-ease);
+}
+span:has(> [role="tooltip"]):hover > [role="tooltip"],
+span:has(> [role="tooltip"]):focus-within > [role="tooltip"] {
+ opacity: 1;
+ visibility: visible;
+}
+
+/* Toast — the real pattern used for delete confirmations: a flat bar with an
+ optional destructive action and a dismiss. `[data-fixed]` pins it
+ bottom-right for real use; without it the toast stays in document flow for
+ documentation. `role="status"` is the correct live-region role for a
+ transient status message, so it is both the accessibility hook and the
+ style selector — no base class needed. The action and dismiss buttons are
+ plain <button>s told apart by `aria-label="Dismiss"`, an attribute they
+ need anyway for the icon-only close control. */
+[role="status"] {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ min-width: 20rem;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+}
+[role="status"][data-fixed] {
+ position: fixed;
+ inset-block-end: var(--ad-space-s);
+ inset-inline-end: var(--ad-space-s);
+ z-index: 60;
+}
+[role="status"] > span { flex: 1; }
+[role="status"] button {
+ all: unset;
+ cursor: pointer;
+ font-weight: 600;
+ color: var(--ad-color-danger);
+}
+[role="status"] button[aria-label="Dismiss"] {
+ color: var(--ad-color-text-tertiary);
+ font-size: 1rem;
+ font-weight: 400;
+ line-height: 1;
+}
+[role="status"] button[aria-label="Dismiss"]:hover { color: var(--ad-color-text-primary); }
+
+/* Dialog — the native <dialog> element: a centered modal, flat surface, no
+ colored header, hairline dividers between title/body/footer. A <header>
+ holds the title and a <footer> holds the actions, matching the elements
+ the tag already wants; everything else in between is the body, styled by
+ excluding those two rather than by a class of its own.
+
+ `.dialog-demo` scopes a static, always-open example to its own preview
+ container instead of the viewport, so it can sit safely inside a
+ documentation page — this is a doc-only affordance, not part of Dialog's
+ contract. It relies on the browser's own dialog centering: the UA
+ stylesheet already gives an open <dialog> `position: absolute; inset: 0;
+ margin: auto`, which centers it inside the nearest positioned ancestor —
+ here, `.dialog-demo` itself. A real `.showModal()` call gets the genuine
+ viewport-fixed overlay and `::backdrop` for free; a plain `open` attribute
+ (used here so the demo renders without JavaScript) does not paint a
+ backdrop at all, since `::backdrop` only exists for a dialog shown
+ modally — wire the real open/close state and call `.showModal()` in the
+ consuming app. */
+.dialog-demo {
+ position: relative;
+ min-height: 20rem;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-lg);
+}
+
+dialog {
+ width: 26rem;
+ max-width: calc(100% - (2 * var(--ad-space-s)));
+ height: fit-content;
+ padding: 0;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ overflow: hidden;
+}
+dialog::backdrop {
+ background: rgba(20, 18, 14, 0.35);
+}
+dialog > header {
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-sans);
+ font-weight: 600;
+ font-size: var(--ad-step-0);
+ color: var(--ad-color-text-primary);
+}
+dialog > footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+}
+dialog > *:not(header):not(footer) {
+ padding: var(--ad-space-m);
+ margin: 0;
+ color: var(--ad-color-text-primary);
+ font-size: var(--ad-step--1);
+}
+
+/* Tabs
+ Follows the real WAI-ARIA tabs pattern: a `[role="tablist"]` of
+ `[role="tab"]` buttons controlling `[role="tabpanel"]`s. Those roles are
+ required for the pattern to be accessible in the first place, so they
+ double as the entire styling contract — no classes at all for the base
+ shape. The keyboard wiring lives in assets/js/system.js and is
+ variant-agnostic — it only cares about `[data-tabs]` / `[role=tab]`.
+
+ Two real variants: `underline` (mono, accent underline — primary section
+ nav, the default) and `segmented` (pill track, active segment lifts to the
+ surface — filters like All · Overdue · Upcoming, never primary
+ navigation), chosen with `data-variant="segmented"` on the tablist since
+ neither has a natural attribute of its own. */
+
+/* Every rule below the list container pairs [role="tablist"] with
+ [role="tab"] rather than styling [role="tab"] alone — Tab buttons are
+ still literal <button> elements, so the compound selector's extra
+ specificity is what keeps Button's base styling (button.css) from
+ winning the cascade on shared properties like padding and background. */
+
+[role="tablist"] {
+ display: flex;
+ gap: var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+[role="tablist"] [role="tab"] {
+ padding: 0.5rem 0;
+ margin-bottom: -1px;
+ background: none;
+ border: 0;
+ border-bottom: 2px solid transparent;
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--1);
+ letter-spacing: 0.02em;
+ cursor: pointer;
+ transition: color var(--ad-dur-fast) var(--ad-ease), border-color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"] [role="tab"]:hover { color: var(--ad-color-text-primary); }
+[role="tablist"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ border-bottom-color: var(--ad-color-accent);
+}
+
+[role="tablist"][data-variant="segmented"] {
+ display: inline-flex;
+ gap: 0.125rem;
+ padding: 0.1875rem;
+ border-bottom: 0;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-md);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"] {
+ all: unset;
+ cursor: pointer;
+ padding: 0.375rem 0.875rem;
+ border-radius: var(--ad-radius-sm);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 550;
+ color: var(--ad-color-text-secondary);
+ transition: background-color var(--ad-dur-fast) var(--ad-ease), color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ background: var(--ad-color-surface-card);
+ box-shadow: var(--ad-shadow-sm);
+}
+
+[role="tabpanel"] { padding-top: var(--ad-space-s); }
+[role="tabpanel"][hidden] { display: none; }
+[role="tabpanel"]:focus-visible { outline-offset: 4px; }
+
+/* Documentation site chrome. Everything below the `.ad-doc-` prefix belongs
+ to this site rather than to the design system it documents — including the
+ width/height constants used directly below. The source design system
+ defines no container-width tokens at all, so there is nothing under
+ `--ad-*` for a doc shell to reach for; the Space foundation page's own
+ notes say to omit that group entirely rather than invent one. */
+
+.ad-doc-skip {
+ position: absolute;
+ inset-inline-start: var(--ad-space-xs);
+ inset-block-start: -4rem;
+ z-index: 40;
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ box-shadow: var(--ad-shadow-float);
+ transition: inset-block-start var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-skip:focus { inset-block-start: var(--ad-space-2xs); }
+
+.ad-doc-header {
+ position: sticky;
+ inset-block-start: 0;
+ z-index: 30;
+ height: 3.5rem;
+ background: color-mix(in srgb, var(--ad-color-surface-page) 92%, transparent);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-header__inner {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ height: 100%;
+ max-width: 72rem;
+ margin-inline: auto;
+ padding-inline: var(--ad-space-xs);
+}
+
+.ad-doc-wordmark {
+ display: flex;
+ align-items: baseline;
+ gap: var(--ad-space-2xs);
+ color: var(--ad-color-text-primary);
+ text-decoration: none;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ font-weight: 600;
+ letter-spacing: -0.01em;
+ white-space: nowrap;
+}
+.ad-doc-wordmark:hover { color: var(--ad-color-text-primary); }
+.ad-doc-wordmark__mark {
+ width: 1.375rem;
+ height: 1.375rem;
+ flex: none;
+ align-self: center;
+ color: var(--ad-color-accent);
+}
+.ad-doc-wordmark__version {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+
+.ad-doc-header__actions { margin-inline-start: auto; display: flex; align-items: center; gap: var(--ad-space-2xs); }
+
+.ad-doc-layout {
+ display: grid;
+ grid-template-columns: 16rem minmax(0, 1fr);
+ gap: var(--ad-space-l);
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-m) var(--ad-space-xs) var(--ad-space-2xl);
+}
+
+.ad-doc-sidebar {
+ position: sticky;
+ inset-block-start: calc(3.5rem + var(--ad-space-m));
+ align-self: start;
+ max-height: calc(100vh - 3.5rem - var(--ad-space-l));
+ overflow-y: auto;
+ padding-inline-end: var(--ad-space-2xs);
+}
+.ad-doc-nav__group + .ad-doc-nav__group { margin-top: var(--ad-space-s); }
+.ad-doc-nav__heading {
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-doc-nav__link {
+ display: block;
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ margin-inline-start: calc(var(--ad-space-xs) * -1);
+ border-inline-start: 2px solid transparent;
+ border-radius: 0 var(--ad-radius-sm) var(--ad-radius-sm) 0;
+ color: var(--ad-color-text-secondary);
+ font-size: var(--ad-step--1);
+ text-decoration: none;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-nav__link:hover { color: var(--ad-color-text-primary); background: var(--ad-color-surface-fill); }
+.ad-doc-nav__link[aria-current="page"] {
+ color: var(--ad-color-accent-ink);
+ border-inline-start-color: var(--ad-color-accent);
+ background: var(--ad-color-accent-soft);
+ font-weight: 500;
+}
+
+.ad-doc-main {
+ min-width: 0;
+ /* The design system's own body-text token (--ad-step-0, 18–20px) is right
+ for a product screen but too large for long-form documentation prose.
+ This site's own copy gets a plain, fixed 16px instead — the token above
+ is unchanged, and specimen tables/demos below still show its real
+ value. */
+ font-size: 1rem;
+}
+.ad-doc-main > * { max-width: 50rem; }
+
+.ad-doc-title { margin-bottom: var(--ad-space-xs); }
+.ad-doc-title__section {
+ display: block;
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-accent);
+}
+
+.ad-doc-main h2 {
+ margin-block: var(--ad-space-2xl) var(--ad-space-xs);
+ padding-bottom: var(--ad-space-2xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-main h3 { margin-block: var(--ad-space-l) var(--ad-space-2xs); }
+.ad-doc-main h4 { margin-block: var(--ad-space-s) var(--ad-space-2xs); }
+.ad-doc-main p,
+.ad-doc-main ul,
+.ad-doc-main ol { max-width: 68ch; }
+.ad-doc-main ul:not([class]),
+.ad-doc-main ol:not([class]) { padding-inline-start: var(--ad-space-s); }
+
+.ad-doc-footer {
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-s) var(--ad-space-xs) var(--ad-space-l);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-tertiary);
+ font-size: var(--ad-step--1);
+}
+.ad-doc-footer__inner { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); justify-content: space-between; }
+
+/* `button.ad-doc-nav-toggle` (element + class) rather than a bare class:
+ button.css's own base rule is `button:not(.icon-button)`, which is also
+ one element + one class in specificity and sets `display: inline-flex` —
+ a bare `.ad-doc-nav-toggle { display: none }` loses that fight and the
+ toggle stays visible at every width. Matching its specificity here (and
+ coming later in the concatenated stylesheet) settles it in source order. */
+button.ad-doc-nav-toggle { display: none; }
+
+@media (max-width: 60rem) {
+ .ad-doc-layout { grid-template-columns: minmax(0, 1fr); gap: var(--ad-space-s); }
+ .ad-doc-sidebar {
+ position: static;
+ max-height: none;
+ }
+ /* The toggle is a normal Button; the panel it opens gets its own card
+ treatment. Putting the same card styling on the <aside> wrapper too
+ nested a button-shaped box inside an outer box for no reason. */
+ .ad-doc-nav {
+ margin-top: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ }
+ .ad-doc-sidebar[data-collapsed="true"] > .ad-doc-nav { display: none; }
+ button.ad-doc-nav-toggle {
+ display: flex;
+ width: 100%;
+ justify-content: space-between;
+ }
+}
+
+@media (max-width: 34rem) {
+ .ad-doc-wordmark__version { display: none; }
+ .ad-doc-layout { padding-inline: var(--ad-space-2xs); }
+}
+
+/* The live-example figure used throughout the docs — see the "How the
+ example demos work" section on the Using the CSS page. One class on the
+ outer <figure>; everything inside is styled by plain element selectors,
+ since figcaption/div/details/summary/pre already say what they are.
+ `data-layout` and `data-surface` on the preview <div> distinguish the
+ handful of demos that need a non-default arrangement. */
+
+.example {
+ margin: var(--ad-space-s) 0 var(--ad-space-l);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+ background: var(--ad-color-surface-card);
+ overflow: hidden;
+}
+
+.example figcaption {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-secondary);
+}
+
+.example > div {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-s);
+}
+/* nowrap is load-bearing: a column-direction flex container with wrap enabled
+ sizes its single line against the container rather than the content, which
+ left every stacked example with a few hundred pixels of dead space. */
+.example > div[data-layout="stack"] { flex-direction: column; flex-wrap: nowrap; align-items: stretch; }
+.example > div[data-layout="grid"] {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
+ align-items: start;
+}
+.example > div[data-surface="sunken"] { background: var(--ad-color-surface-fill); }
+.example > div > * { min-width: 0; }
+
+.example details {
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+.example summary {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ color: var(--ad-color-text-secondary);
+ cursor: pointer;
+ list-style: none;
+ user-select: none;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+}
+.example summary::-webkit-details-marker { display: none; }
+.example summary::before {
+ content: "";
+ width: 0.5rem;
+ height: 0.5rem;
+ border-inline-end: 1.5px solid currentColor;
+ border-block-end: 1.5px solid currentColor;
+ rotate: -45deg;
+ transition: rotate var(--ad-dur-fast) var(--ad-ease);
+}
+.example details[open] > summary::before { rotate: 45deg; }
+.example summary:hover { color: var(--ad-color-text-primary); }
+
+.example pre {
+ margin: 0;
+ padding: var(--ad-space-xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+ tab-size: 2;
+}
+.example pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Standalone fenced code blocks in the prose (unhighlighted — there is no
+ syntax highlighter in this build any more, just Kramdown's plain
+ <pre><code class="language-*">). */
+.ad-doc-main > pre {
+ margin-block: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+}
+.ad-doc-main > pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Presentation for the token reference tables and swatch grids, plus a
+ generic data-table utility. All of this is documentation chrome — the
+ design system itself has no Table component (see the note on the removed
+ `table`/`progress`/`callout` pages in git history). */
+
+.ad-swatch-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs) var(--ad-space-s);
+ padding: 0;
+ list-style: none;
+}
+.ad-swatch {
+ display: flex;
+ flex-direction: column;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ overflow: hidden;
+ background: var(--ad-color-surface-card);
+}
+.ad-swatch__chip {
+ display: flex;
+ align-items: flex-end;
+ justify-content: flex-end;
+ height: 4rem;
+ padding: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+}
+.ad-swatch__meta {
+ display: flex;
+ flex-direction: column;
+ gap: 0.1rem;
+ padding: var(--ad-space-2xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+}
+.ad-swatch__hex { color: var(--ad-color-text-primary); text-transform: uppercase; }
+.ad-swatch__ratio { color: var(--ad-color-text-tertiary); }
+
+.ad-alias-row { display: flex; align-items: center; gap: var(--ad-space-2xs); }
+.ad-alias-chip {
+ width: 1.5rem;
+ height: 1.5rem;
+ flex: none;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+}
+.ad-alias-chip--pair { display: flex; }
+.ad-alias-chip--pair > span { flex: 1; }
+
+/* Type specimen rows. */
+.ad-specimen {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ gap: var(--ad-space-3xs);
+ padding-block: var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-specimen__meta {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-specimen__sample {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: var(--ad-color-text-primary);
+}
+
+/* Space and radius rulers. */
+.ad-ruler { display: flex; flex-direction: column; gap: var(--ad-space-2xs); }
+.ad-ruler__row { display: grid; grid-template-columns: 6.5rem 4.5rem 1fr; align-items: center; gap: var(--ad-space-xs); font-family: var(--ad-font-mono); font-size: 11px; }
+.ad-ruler__bar { height: 0.75rem; background: var(--ad-color-accent); border-radius: var(--ad-radius-sm); }
+.ad-ruler__num { color: var(--ad-color-text-tertiary); text-align: right; }
+
+.ad-radius-grid, .ad-elevation-grid, .ad-motion-grid, .ad-accent-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs);
+ padding: 0;
+ list-style: none;
+}
+.ad-radius-demo {
+ height: 4.5rem;
+ background: var(--ad-color-accent-soft);
+ border: 1px solid var(--ad-color-accent);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-elevation-demo {
+ height: 4.5rem;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-motion-demo {
+ height: 4.5rem;
+ display: grid;
+ place-items: center;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-md);
+ margin-bottom: var(--ad-space-2xs);
+ cursor: pointer;
+ overflow: hidden;
+}
+.ad-motion-demo__dot {
+ width: 1.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent);
+ translate: -3rem 0;
+ transition-property: translate;
+ transition-duration: var(--ad-dur-base);
+ transition-timing-function: var(--ad-ease);
+}
+.ad-motion-demo:hover .ad-motion-demo__dot,
+.ad-motion-demo:focus-visible .ad-motion-demo__dot { translate: 3rem 0; }
+
+.ad-accent-demo {
+ height: 4.5rem;
+ display: flex;
+ align-items: flex-end;
+ padding: var(--ad-space-2xs);
+ border-radius: var(--ad-radius-md);
+ color: #fff;
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ margin-bottom: var(--ad-space-2xs);
+}
+
+/* Token names read `<code>` sitewide, which already carries a mono chip
+ look for running prose (see :not(pre) > code in base/typography.css).
+ Inside these specimen/swatch/ruler rows the old `.ad-token-name` class was
+ plain mono text with no chip, so `<code>` is flattened back to that look
+ here the same way table cells are. */
+.ad-swatch code, .ad-specimen code, .ad-ruler code, .ad-alias-row code {
+ background: none;
+ border: 0;
+ padding: 0;
+ font-size: var(--ad-step--2);
+ color: var(--ad-color-text-primary);
+ white-space: nowrap;
+}
+
+/* A documentation aside. Like `<table>`, this is site chrome for the prose
+ itself, not a system component — the real system has no Callout. */
+.ad-callout {
+ margin-block: var(--ad-space-s);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-left: 3px solid var(--ad-color-accent);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+}
+/* Generic reference tables (component APIs, alias tables, the contrast
+ contract). `<table>` IS a table — no class needed to say so. Every table
+ on this site uses the one compact density, so there is no separate
+ modifier for it. `display: block` plus `overflow-x: auto` gives the table
+ its own horizontal scrollbar on narrow viewports without a wrapper div;
+ the anonymous table box the browser generates around `<thead>`/`<tbody>`
+ still does the row/column layout. `<code>` replaces the old
+ `.ad-token-name` / `.ad-table__code` cells — it is already styled
+ sitewide, just re-flattened here to a plain mono run instead of the
+ chip-with-border look prose code gets, to match this table's original
+ look. `.num` is the one sanctioned class left, for right-aligned numeric
+ columns — there is no attribute that means "this column is a number". */
+table { width: 100%; border-collapse: collapse; font-size: var(--ad-step--1); display: block; overflow-x: auto; }
+table th, table td {
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ text-align: left;
+ vertical-align: top;
+}
+table th { font-family: var(--ad-font-mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ad-color-text-secondary); font-weight: 500; }
+table code { background: none; border: 0; padding: 0; font-size: var(--ad-step--2); white-space: nowrap; }
+.num { text-align: right; font-variant-numeric: tabular-nums; }
+
+/* Home page. */
+.ad-hero { padding-block: var(--ad-space-m) var(--ad-space-l); }
+.ad-hero__title {
+ font-size: var(--ad-step-4);
+ margin-bottom: var(--ad-space-xs);
+}
+.ad-hero__actions { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); margin-top: var(--ad-space-s); }
+.ad-stat-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-l);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+}
+.ad-stat__value {
+ display: block;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-2);
+ font-weight: 600;
+ line-height: 1.16;
+ font-variant-numeric: tabular-nums;
+}
+.ad-stat__label {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+
+/* Card chrome. The pane supplies the frame; this just insets the page. */
+body { padding: var(--ad-space-l) var(--ad-space-m); }
+.ad-doc-main { max-width: 50rem; margin-inline: auto; }
+.ad-doc-main > * { max-width: none; }
+</style>
+</head>
+<body>
+<main class="ad-doc-main">
+<header class="ad-doc-title"><span class="ad-doc-title__section">Components</span>
+ <h1>Switch</h1>
+</header>
+<p class="ad-lead">Toggle switch for on/off settings. The one moving part in this system — everything else transitions color or border, never position.</p>
+<h2 id="basic">Basic</h2>
+
+<p><code>role="switch"</code> is the correct ARIA role for a toggle, so it doubles as the
+attribute that tells Switch’s <code>input[type="checkbox"]</code> apart from a plain
+Checkbox — real accessibility markup doing double duty as a style hook. The
+track and thumb still need <code>.switch</code>, one sanctioned fallback class: the
+thumb’s independent position and transition need a second element, more than
+<code>appearance: none</code> plus pseudo-elements on the input itself can cleanly
+carry.</p>
+
+<figure class="example">
+<figcaption>Switch</figcaption>
+<div>
+<label class="switch">
+ <input type="checkbox" role="switch" checked="" />
+ <span></span>
+ Dark mode
+</label>
+<label class="switch">
+ <input type="checkbox" role="switch" />
+ <span></span>
+ Weekly digest
+</label>
+</div>
+<details open=""><summary>Markup</summary>
+<pre><code><label class="switch">
+ <input type="checkbox" role="switch" checked="" />
+ <span></span>
+ Dark mode
+</label>
+<label class="switch">
+ <input type="checkbox" role="switch" />
+ <span></span>
+ Weekly digest
+</label></code></pre>
+</details>
+</figure>
+
+<h2 id="disabled">Disabled</h2>
+
+<figure class="example">
+<figcaption>Disabled</figcaption>
+<div>
+<label class="switch">
+ <input type="checkbox" role="switch" checked="" disabled="" />
+ <span></span>
+ Always on for this plan
+</label>
+</div>
+<details><summary>Markup</summary>
+<pre><code><label class="switch">
+ <input type="checkbox" role="switch" checked="" disabled="" />
+ <span></span>
+ Always on for this plan
+</label></code></pre>
+</details>
+</figure>
+
+<h2 id="api">API</h2>
+
+<table>
+ <thead><tr><th scope="col">Element / attribute</th><th scope="col">Values</th><th scope="col">Notes</th></tr></thead>
+ <tbody>
+ <tr><td><code>label.switch</code></td><td></td><td>Required wrapper.</td></tr>
+ <tr><td><code>input[type="checkbox"][role="switch"]</code></td><td></td><td>Required — the role is what makes it a Switch instead of a Checkbox.</td></tr>
+ <tr><td><code>> span</code></td><td></td><td>Required — the track and thumb. No class, just structural position.</td></tr>
+ <tr><td><code>checked</code></td><td>boolean</td><td></td></tr>
+ <tr><td><code>disabled</code></td><td>boolean</td><td></td></tr>
+ </tbody>
+</table>
+</main>
+<script>
+// Behavior for the pieces of the system that need it. Everything else is
+// CSS and semantic HTML on purpose — a design system that only works with
+// JavaScript running is a component library with extra steps.
+(function () {
+ "use strict";
+
+ var STORAGE_KEY = "ad-theme";
+
+ function readStored() {
+ try {
+ return localStorage.getItem(STORAGE_KEY);
+ } catch (error) {
+ return null;
+ }
+ }
+
+ function writeStored(value) {
+ try {
+ localStorage.setItem(STORAGE_KEY, value);
+ } catch (error) {
+ /* Nothing to do — the media query still gives a sensible default. */
+ }
+ }
+
+ function systemPrefersDark() {
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
+ }
+
+ function currentTheme() {
+ var explicit = document.documentElement.getAttribute("data-theme");
+ if (explicit) return explicit;
+ return systemPrefersDark() ? "dark" : "light";
+ }
+
+ function paintToggle(theme) {
+ document.querySelectorAll("[data-theme-toggle]").forEach(function (toggle) {
+ var next = theme === "dark" ? "light" : "dark";
+ toggle.querySelectorAll("[data-theme-icon]").forEach(function (icon) {
+ icon.hidden = icon.dataset.themeIcon !== theme;
+ });
+ toggle.setAttribute("aria-label", "Switch to the " + next + " theme");
+ toggle.setAttribute("title", "Switch to the " + next + " theme");
+ var label = toggle.querySelector("[data-theme-label]");
+ if (label) label.textContent = "Switch to the " + next + " theme";
+ });
+ }
+
+ function setUpTheme() {
+ paintToggle(currentTheme());
+
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-theme-toggle]");
+ if (!toggle) return;
+
+ var next = currentTheme() === "dark" ? "light" : "dark";
+ document.documentElement.setAttribute("data-theme", next);
+ writeStored(next);
+ paintToggle(next);
+ });
+
+ // Follow the OS while the reader has not made an explicit choice.
+ window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", function () {
+ if (!readStored()) paintToggle(currentTheme());
+ });
+ }
+
+ // Tabs, wired to the WAI-ARIA authoring practices: arrows move, Home/End
+ // jump, and only the selected tab stays in the tab order.
+ function setUpTabs() {
+ document.querySelectorAll("[data-tabs]").forEach(function (root) {
+ var tabs = Array.prototype.slice.call(root.querySelectorAll('[role="tab"]'));
+ if (!tabs.length) return;
+
+ function select(tab, focus) {
+ tabs.forEach(function (candidate) {
+ var selected = candidate === tab;
+ candidate.setAttribute("aria-selected", String(selected));
+ candidate.tabIndex = selected ? 0 : -1;
+ var panel = document.getElementById(candidate.getAttribute("aria-controls"));
+ if (panel) panel.hidden = !selected;
+ });
+ if (focus) tab.focus();
+ }
+
+ root.addEventListener("click", function (event) {
+ var tab = event.target.closest('[role="tab"]');
+ if (tab) select(tab, false);
+ });
+
+ root.addEventListener("keydown", function (event) {
+ var index = tabs.indexOf(document.activeElement);
+ if (index === -1) return;
+
+ var next = null;
+ if (event.key === "ArrowRight") next = tabs[(index + 1) % tabs.length];
+ else if (event.key === "ArrowLeft") next = tabs[(index - 1 + tabs.length) % tabs.length];
+ else if (event.key === "Home") next = tabs[0];
+ else if (event.key === "End") next = tabs[tabs.length - 1];
+
+ if (next) {
+ event.preventDefault();
+ select(next, true);
+ }
+ });
+
+ select(tabs.filter(function (tab) {
+ return tab.getAttribute("aria-selected") === "true";
+ })[0] || tabs[0], false);
+ });
+ }
+
+ function setUpNavToggle() {
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-nav-toggle]");
+ if (!toggle) return;
+
+ var sidebar = toggle.closest("[data-sidebar]");
+ var collapsed = sidebar.dataset.collapsed !== "false";
+ sidebar.dataset.collapsed = String(!collapsed);
+ toggle.setAttribute("aria-expanded", String(collapsed));
+ });
+ }
+
+ setUpTheme();
+ setUpTabs();
+ setUpNavToggle();
+})();
+
+</script>
+</body>
+</html>
diff --git a/design-bundle/components/tabs.html b/design-bundle/components/tabs.html
new file mode 100644
index 0000000..1b2a5ca
--- /dev/null
+++ b/design-bundle/components/tabs.html
@@ -0,0 +1,1848 @@
+<!-- @dsCard group="Navigation" -->
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Tabs</title>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&display=swap">
+<style>
+/* Arbitrary Definitions — design tokens. Hand-maintained; edit values directly. */
+
+:root {
+ /* Palette ramps — reference only. Components use the aliases below. */
+ --ad-neutral-0: #faf9f7;
+ --ad-neutral-50: #fdfdfb;
+ --ad-neutral-100: #f1f0ed;
+ --ad-neutral-150: #e8e6e2;
+ --ad-neutral-200: #dcdad7;
+ --ad-neutral-300: #c0bdb9;
+ --ad-neutral-500: #888681;
+ --ad-neutral-600: #5f5d59;
+ --ad-neutral-800: #2a2926;
+ --ad-neutral-900: #100f0d;
+ --ad-neutral-dark-0: #0e0d0b;
+ --ad-neutral-dark-50: #151412;
+ --ad-neutral-dark-100: #1e1d1a;
+ --ad-neutral-dark-150: #252421;
+ --ad-neutral-dark-200: #2c2b29;
+ --ad-neutral-dark-300: #44423f;
+ --ad-neutral-dark-500: #7c7a76;
+ --ad-neutral-dark-600: #a09e9a;
+ --ad-neutral-dark-800: #d9d7d4;
+ --ad-neutral-dark-900: #e9e8e6;
+ --ad-accent-teal: #3a6a63;
+ --ad-accent-clay: #9a5a3c;
+ --ad-accent-ink-blue: #2b4a78;
+ --ad-accent-ochre: #93701f;
+ --ad-accent-plum: #6a3a5c;
+
+ /* Surfaces */
+ --ad-color-surface-page: #faf9f7;
+ --ad-color-surface-card: #fdfdfb;
+ --ad-color-surface-fill: #f1f0ed;
+ --ad-color-surface-fill-2: #e8e6e2;
+
+ /* Borders */
+ --ad-color-border-hairline: #dcdad7;
+ --ad-color-border-default: #c0bdb9;
+
+ /* Text */
+ --ad-color-text-primary: #100f0d;
+ --ad-color-text-secondary: #5f5d59;
+ --ad-color-text-tertiary: #888681;
+
+ /* Accent */
+ --ad-color-accent: #3a6a63;
+ --ad-color-accent-fg: #ffffff;
+ --ad-color-accent-ink: color-mix(in oklch, var(--ad-color-accent) 78%, black);
+ --ad-color-accent-soft: color-mix(in oklch, var(--ad-color-accent) 12%, white);
+
+ /* Status (fixed — never swapped like the accent) */
+ --ad-color-success: #3f7a4a;
+ --ad-color-success-bg: color-mix(in oklch, var(--ad-color-success) 12%, white);
+ --ad-color-danger: #ad3a2c;
+ --ad-color-danger-bg: color-mix(in oklch, var(--ad-color-danger) 10%, white);
+
+ /* Type families */
+ --ad-font-sans: -apple-system, "Helvetica Neue", Helvetica, Arial, system-ui, sans-serif;
+ --ad-font-mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, Consolas, "Courier New", monospace;
+ --ad-font-serif: Georgia, "Times New Roman", "Iowan Old Style", serif;
+
+ /* Type steps — fluid (Utopia scale), the design system's own body-text size */
+ --ad-step--2: clamp(0.7813rem, 0.7736rem + 0.0341vw, 0.8rem);
+ --ad-step--1: clamp(0.9375rem, 0.9119rem + 0.1136vw, 1rem);
+ --ad-step-0: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-step-1: clamp(1.35rem, 1.2631rem + 0.3864vw, 1.5625rem);
+ --ad-step-2: clamp(1.62rem, 1.4837rem + 0.6057vw, 1.9531rem);
+ --ad-step-3: clamp(1.944rem, 1.7405rem + 0.9044vw, 2.4414rem);
+ --ad-step-4: clamp(2.3328rem, 2.0387rem + 1.3072vw, 3.0518rem);
+ --ad-step-5: clamp(2.7994rem, 2.384rem + 1.8461vw, 3.8147rem);
+
+ /* Space */
+ --ad-space-3xs: clamp(0.3125rem, 0.3125rem + 0vw, 0.3125rem);
+ --ad-space-2xs: clamp(0.5625rem, 0.5369rem + 0.1136vw, 0.625rem);
+ --ad-space-xs: clamp(0.875rem, 0.8494rem + 0.1136vw, 0.9375rem);
+ --ad-space-s: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-space-m: clamp(1.6875rem, 1.6108rem + 0.3409vw, 1.875rem);
+ --ad-space-l: clamp(2.25rem, 2.1477rem + 0.4545vw, 2.5rem);
+ --ad-space-xl: clamp(3.375rem, 3.2216rem + 0.6818vw, 3.75rem);
+ --ad-space-2xl: clamp(4.5rem, 4.2955rem + 0.9091vw, 5rem);
+ --ad-space-3xl: clamp(6.75rem, 6.4432rem + 1.3636vw, 7.5rem);
+
+ /* Radius */
+ --ad-radius-sm: 4px;
+ --ad-radius-md: 6px;
+ --ad-radius-lg: 10px;
+ --ad-radius-full: 999px;
+ --ad-border-w: 1px;
+
+ /* Elevation */
+ --ad-shadow-sm: 0 1px 2px rgba(20, 18, 14, 0.05);
+ --ad-shadow-float: 0 2px 6px rgba(20, 18, 14, 0.06), 0 10px 24px -12px rgba(20, 18, 14, 0.18);
+
+ /* Motion */
+ --ad-dur-fast: 0.12s;
+ --ad-dur-base: 0.16s;
+ --ad-ease: cubic-bezier(0.2, 0, 0, 1);
+}
+
+@media (prefers-color-scheme: dark) {
+ :root:not([data-theme="light"]) {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+ }
+}
+
+:root[data-theme="dark"] {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ :root {
+ --ad-dur-fast: 0ms;
+ --ad-dur-base: 0ms;
+ }
+}
+
+/* A short reset. Everything it sets is something the system would otherwise
+ have to override in every component.
+
+ The rem values used for this documentation site's own chrome (measure,
+ content width, sidebar width, header height) belong to this site, not to
+ the design system it documents — the source design system defines no
+ container-width tokens at all, so there is nothing under `--ad-*` to reach
+ for here. See the header comment in site/shell.css. */
+
+*,
+*::before,
+*::after { box-sizing: border-box; }
+
+html {
+ -webkit-text-size-adjust: 100%;
+ scroll-behavior: smooth;
+ scroll-padding-top: calc(3.5rem + var(--ad-space-s));
+}
+
+@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;
+ }
+}
+
+body {
+ margin: 0;
+ background: var(--ad-color-surface-page);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ line-height: 1.55;
+ font-synthesis-weight: none;
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+}
+
+::selection {
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+}
+
+h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd { margin: 0; }
+ul[class], ol[class] { list-style: none; margin: 0; padding: 0; }
+
+img, svg, video { display: block; max-width: 100%; height: auto; }
+
+button, input, select, textarea {
+ font: inherit;
+ color: inherit;
+}
+
+hr {
+ height: 1px;
+ margin: var(--ad-space-l) 0;
+ border: 0;
+ background: var(--ad-color-border-hairline);
+}
+
+/* One focus ring, defined once. No component is allowed its own. */
+:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+ border-radius: var(--ad-radius-sm);
+}
+:focus:not(:focus-visible) { outline: none; }
+
+[hidden] { display: none !important; }
+
+.ad-visually-hidden {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip-path: inset(50%);
+ white-space: nowrap;
+ border: 0;
+}
+
+/* Every rule here reads a type token for family and size. Weight, line-height,
+ and letter-spacing are hardcoded to the exact numbers the source ships —
+ there is no `--ad-weight-*` / `--ad-leading-*` / `--ad-tracking-*` scale in
+ the real system, only eight fixed compound styles (see the Typography
+ foundation page, which this file implements verbatim). */
+
+.type-display { font: 600 var(--ad-step-4)/1.08 var(--ad-font-sans); letter-spacing: -0.015em; }
+.type-h1, h1 { font: 600 var(--ad-step-3)/1.12 var(--ad-font-sans); letter-spacing: -0.012em; }
+.type-h2, h2 { font: 600 var(--ad-step-2)/1.16 var(--ad-font-sans); letter-spacing: -0.01em; }
+.type-h3, h3 { font: 600 var(--ad-step-1)/1.25 var(--ad-font-sans); }
+.type-body { font: 400 var(--ad-step-0)/1.55 var(--ad-font-sans); }
+.type-body-sm { font: 400 var(--ad-step--1)/1.5 var(--ad-font-sans); }
+.type-label {
+ font: 500 var(--ad-step--2)/1 var(--ad-font-mono);
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: var(--ad-color-text-secondary);
+}
+.type-mono { font: 400 var(--ad-step--1)/1.5 var(--ad-font-mono); }
+
+/* Prose defaults for the documentation body copy. These read the same
+ tokens the compound classes above do; they are not a second scale. */
+h1, h2, h3, h4 { font-weight: 600; text-wrap: balance; }
+
+h4 { font: 600 var(--ad-step-0)/1.3 var(--ad-font-sans); }
+
+p, ul, ol { margin: 0 0 var(--ad-space-s); }
+li + li { margin-top: var(--ad-space-3xs); }
+
+a {
+ color: var(--ad-color-accent);
+ text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
+ text-underline-offset: 0.18em;
+ text-decoration-thickness: 1px;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ text-decoration-color var(--ad-dur-fast) var(--ad-ease);
+}
+a:hover {
+ color: var(--ad-color-accent-ink);
+ text-decoration-color: currentColor;
+}
+
+strong, b { font-weight: 600; }
+
+small { font-size: var(--ad-step--1); }
+
+.ad-lead {
+ font-size: var(--ad-step-1);
+ line-height: 1.5;
+ color: var(--ad-color-text-secondary);
+ max-width: 68ch;
+ text-wrap: pretty;
+}
+
+.ad-muted { color: var(--ad-color-text-secondary); }
+.ad-subtle { color: var(--ad-color-text-tertiary); }
+
+code, kbd, samp, pre {
+ font-family: var(--ad-font-mono);
+ font-feature-settings: "liga" 0;
+}
+
+:not(pre) > code {
+ padding: 0.15em 0.4em;
+ font-size: 0.875em;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-sm);
+ white-space: nowrap;
+}
+
+kbd {
+ display: inline-block;
+ padding: 0.1em 0.45em;
+ font-size: var(--ad-step--2);
+ line-height: 1.6;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-bottom-width: 2px;
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-secondary);
+}
+
+blockquote {
+ margin-block: var(--ad-space-s);
+ padding-left: var(--ad-space-s);
+ border-left: 2px solid var(--ad-color-accent);
+ color: var(--ad-color-text-secondary);
+}
+
+/* Button
+ A flat, quiet button that recedes rather than performs — no gradients, no
+ shine. Variants: default, primary, dark, ghost, danger. Sizes: sm, md
+ (default), lg — padding and font step only, weight and radius stay put.
+
+ The contract is the element itself: a bare <button> is styled directly, no
+ class required. An <a> that acts like a button (navigates rather than
+ changes something — see the Button page's "Links that act like buttons")
+ opts in with a single `.btn` class, since a bare `a` selector would catch
+ every ordinary prose link on the site. `:not(.icon-button)` keeps this
+ block from leaking onto IconButton, which shares the <button> tag but is
+ an entirely different shape. */
+
+button:not(.icon-button),
+a.btn {
+ --_bg: var(--ad-color-surface-card);
+ --_fg: var(--ad-color-text-primary);
+ --_border: var(--ad-color-border-default);
+ --_pad: var(--ad-space-xs) var(--ad-space-s);
+ --_font: var(--ad-step--1);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: var(--ad-space-2xs);
+ padding: var(--_pad);
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-sans);
+ font-size: var(--_font);
+ font-weight: 550;
+ letter-spacing: -0.01em;
+ line-height: 1.2;
+ text-align: center;
+ text-decoration: none;
+ white-space: nowrap;
+ cursor: pointer;
+ transition: filter var(--ad-dur-fast) var(--ad-ease),
+ border-color var(--ad-dur-fast) var(--ad-ease);
+}
+
+:is(button:not(.icon-button), a.btn):hover:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.96); }
+:is(button:not(.icon-button), a.btn):active:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.92); }
+
+button:not(.icon-button):disabled,
+:is(button:not(.icon-button), a.btn)[aria-disabled="true"] {
+ opacity: 0.5;
+ cursor: default;
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="primary"] {
+ --_bg: var(--ad-color-accent);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-accent);
+}
+
+/* Reserved for the single most-committing action in a view — a final Save,
+ never a whole row of them. */
+:is(button:not(.icon-button), a.btn)[data-variant="dark"] {
+ --_bg: var(--ad-color-text-primary);
+ --_fg: var(--ad-color-surface-page);
+ --_border: var(--ad-color-text-primary);
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="ghost"] {
+ --_bg: transparent;
+ --_fg: var(--ad-color-text-primary);
+ --_border: transparent;
+}
+
+/* The source hardcodes this label white rather than deriving it — danger
+ is not "on a filled accent surface", so --ad-color-accent-fg is reused
+ here only because its resolved value is the same literal white, not
+ because the two roles are the same thing. */
+:is(button:not(.icon-button), a.btn)[data-variant="danger"] {
+ --_bg: var(--ad-color-danger);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-danger);
+}
+
+:is(button:not(.icon-button), a.btn)[data-size="sm"] { --_pad: 0.375rem 0.75rem; --_font: 0.8125rem; }
+:is(button:not(.icon-button), a.btn)[data-size="lg"] { --_pad: 0.75rem 1.375rem; --_font: var(--ad-step-0); }
+
+:is(button:not(.icon-button), a.btn)[data-block] { width: 100%; }
+
+/* Button group — [role="group"] is already the correct ARIA role for a
+ toolbar of related buttons, so it doubles as the styling hook for free. */
+[role="group"] {
+ display: inline-flex;
+ isolation: isolate;
+}
+[role="group"] > button { border-radius: 0; }
+[role="group"] > button:first-child { border-start-start-radius: var(--ad-radius-md); border-end-start-radius: var(--ad-radius-md); }
+[role="group"] > button:last-child { border-start-end-radius: var(--ad-radius-md); border-end-end-radius: var(--ad-radius-md); }
+[role="group"] > button + button { margin-inline-start: -1px; }
+[role="group"] > button:hover,
+[role="group"] > button:focus-visible { z-index: 1; }
+
+/* IconButton — a square icon-only button. Always needs an aria-label; it
+ becomes both the accessible name and the native tooltip. There is no
+ attribute that distinguishes "a button that is only an icon" from a plain
+ Button, so this is the one sanctioned class in this file. */
+.icon-button {
+ --_size: 2.125rem;
+ --_bg: transparent;
+ --_border: transparent;
+ --_fg: var(--ad-color-text-secondary);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ padding: 0;
+ flex: none;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease),
+ color var(--ad-dur-fast) var(--ad-ease);
+}
+.icon-button:hover:not(:disabled) { background: var(--ad-color-surface-fill-2); }
+.icon-button:disabled { opacity: 0.5; cursor: default; }
+
+.icon-button[data-size="sm"] { --_size: 1.75rem; }
+.icon-button[data-size="lg"] { --_size: 2.5rem; }
+
+.icon-button[data-variant="outline"] { --_bg: var(--ad-color-surface-card); --_border: var(--ad-color-border-default); --_fg: var(--ad-color-text-primary); }
+.icon-button[data-variant="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); }
+
+/* Field
+ The labeled field wrapper — a <label> that wraps its own caption text
+ together with the control, so the association is native and needs no
+ `for`/`id` pair. The caption is mono, uppercase, tracked — the same
+ catalog-label convention as Badge and type-label.
+
+ A <label> is reused for three different shapes across this system (this
+ wrapper, Checkbox/Radio's row, Switch's row), so it cannot be styled bare
+ without one shape leaking into another. Field is the one of the three with
+ no distinguishing attribute of its own, so it gets the sanctioned class;
+ Checkbox/Radio tell themselves apart structurally instead (see below). */
+.field {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ min-width: 0;
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Hint and error read the native fine-print element. `role="alert"` is a
+ real live-region role for validation text, so it doubles as the selector
+ that turns a hint red. */
+.field > small {
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+.field > small[role="alert"] { color: var(--ad-color-danger); }
+
+/* Input / Select — bordered, matched to each other exactly. Both tags ARE
+ the component, so they are styled bare; nothing else on this site uses a
+ text input or a select. */
+input:not([type="checkbox"]):not([type="radio"]),
+select {
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ outline: none;
+ transition: border-color var(--ad-dur-fast) var(--ad-ease),
+ box-shadow var(--ad-dur-fast) var(--ad-ease);
+}
+input::placeholder { color: var(--ad-color-text-tertiary); }
+
+input:not([type="checkbox"]):not([type="radio"]):focus,
+select:focus {
+ border-color: var(--ad-color-accent);
+ box-shadow: 0 0 0 2px var(--ad-color-accent-soft);
+}
+
+input:not([type="checkbox"]):not([type="radio"]):disabled,
+select:disabled {
+ opacity: 0.5;
+ cursor: default;
+}
+
+/* The chevron is the one flagged color exception in this component set:
+ CSS cannot point a data-URI background-image at a custom property, so its
+ stroke is a literal hex rather than a var(--ad-*) reference. It is picked
+ to match --ad-color-text-secondary and commented here so it is not
+ mistaken for a stray color. */
+select {
+ appearance: none;
+ padding-inline-end: var(--ad-space-l);
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%235f5d59' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right var(--ad-space-xs) center;
+}
+
+input[aria-invalid="true"],
+select[aria-invalid="true"] {
+ border-color: var(--ad-color-danger);
+ background-color: var(--ad-color-danger-bg);
+}
+
+/* Checkbox / Radio — the real native controls, tinted with accent-color
+ instead of a hand-drawn box. This is not just a rename: it deletes the
+ custom SVG-checkmark markup entirely and lets the browser draw its own
+ (platform-appropriate) check and dot. `role="switch"` is excluded here so
+ Switch — a checkbox playing a different role — can style itself below
+ without this rule fighting it. A <label> wrapping a checkbox/radio is told
+ apart from Field's <label> structurally: it :has() one as a direct child,
+ no class required. */
+input[type="checkbox"]:not([role="switch"]),
+input[type="radio"] {
+ width: 1.125rem;
+ height: 1.125rem;
+ margin: 0;
+ accent-color: var(--ad-color-accent);
+ cursor: pointer;
+}
+input[type="checkbox"]:not([role="switch"]):focus-visible,
+input[type="radio"]:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+}
+
+label:has(> input[type="checkbox"]:not([role="switch"])),
+label:has(> input[type="radio"]) {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 400;
+ text-transform: none;
+ letter-spacing: normal;
+ color: var(--ad-color-text-primary);
+}
+label:has(> input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Switch — for on/off settings only, never a substitute for Checkbox.
+ `input[type="checkbox"][role="switch"]` is real ARIA (the correct role for
+ a toggle), not an invented hook, but the visual track+thumb needs a second
+ element for the thumb's independent position and transition — more than
+ `appearance: none` plus pseudo-elements on the input itself can cleanly
+ carry — so `.switch` is a deliberate, documented fallback class rather
+ than a first choice. */
+.switch {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ color: var(--ad-color-text-primary);
+}
+.switch input[role="switch"] { position: absolute; opacity: 0; width: 2.25rem; height: 1.25rem; margin: 0; }
+
+.switch span {
+ position: relative;
+ flex: none;
+ width: 2.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-border-default);
+ transition: background-color var(--ad-dur-base) var(--ad-ease);
+}
+.switch span::after {
+ content: "";
+ position: absolute;
+ inset-block-start: 2px;
+ inset-inline-start: 2px;
+ width: 1rem;
+ height: 1rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-fg);
+ box-shadow: var(--ad-shadow-sm);
+ transition: translate var(--ad-dur-base) var(--ad-ease);
+}
+.switch input[role="switch"]:checked + span { background: var(--ad-color-accent); }
+.switch input[role="switch"]:checked + span::after { translate: 1rem 0; }
+.switch input[role="switch"]:focus-visible + span { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+.switch:has(input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Rows of fields — used by the settings-form pattern. `<form>`/`<fieldset>`
+ are the elements that already mean this, so they are styled bare. A row
+ grouping two fields side by side has no element of its own, so it keeps
+ one plain class. */
+form {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-s);
+ max-width: 28rem;
+}
+.form-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
+ gap: var(--ad-space-xs);
+}
+fieldset {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ margin: 0;
+ padding: 0;
+ border: 0;
+}
+fieldset > legend {
+ padding: 0 0 var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Badge
+ Mono, uppercase, tracked — the catalog-label convention for status and
+ meta values (SAVED, OVERDUE, RECOMMENDED). A label, never a button.
+
+ A <span> is too generic to style globally — it is used all over running
+ prose for unrelated reasons — so Badge gets the one sanctioned class this
+ shape needs. Tone is an attribute, not a modifier class. */
+
+.badge {
+ --_bg: var(--ad-color-surface-fill);
+ --_fg: var(--ad-color-text-secondary);
+ --_border: var(--ad-color-border-hairline);
+
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.1875rem 0.5rem;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-sm);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ line-height: 1;
+ white-space: nowrap;
+}
+
+.badge[data-tone="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); --_border: transparent; }
+.badge[data-tone="success"] { --_bg: var(--ad-color-success-bg); --_fg: var(--ad-color-success); --_border: transparent; }
+.badge[data-tone="danger"] { --_bg: var(--ad-color-danger-bg); --_fg: var(--ad-color-danger); --_border: transparent; }
+
+.badge-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-2xs);
+}
+
+/* Tag
+ A removable mono chip for freeform tags — locations, categories.
+ `[data-dashed]` renders an "add tag" affordance instead of a value. Same
+ reasoning as Badge: <span> is too generic to select bare, so Tag gets one
+ class. The remove control is a real <button>, styled through the
+ descendant selector rather than a class of its own. */
+
+.tag {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.375rem;
+ padding: 0.25rem 0.625rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-default);
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ line-height: 1;
+}
+
+.tag[data-dashed] {
+ background: transparent;
+ border-style: dashed;
+ color: var(--ad-color-text-tertiary);
+ cursor: pointer;
+}
+
+.tag button {
+ all: unset;
+ cursor: pointer;
+ color: var(--ad-color-text-tertiary);
+ line-height: 1;
+ font-size: 0.75rem;
+}
+.tag button:hover { color: var(--ad-color-text-primary); }
+
+.tag-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.375rem;
+}
+
+/* Card
+ Flat surface, hairline border, radius-lg. `[data-elevated]` is opt-in and
+ reserved for the one focused/primary card in a view — a dialog, the
+ primary capture card — flat is the default everywhere else.
+
+ A <div>/<article> is too generic to select bare, so Card keeps one
+ sanctioned class. When Card's job is holding a list of rows — the common
+ case, entry lists, task lists, settings rows — it is a real <ul class="
+ card">, and CardRow is a bare <li> underneath it: no second class needed,
+ the row look comes from the `.card > li` descendant selector alone. The
+ first row's top border is removed automatically so callers never have to
+ override it by hand. */
+
+.card {
+ display: flex;
+ flex-direction: column;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: none;
+ overflow: hidden;
+}
+
+.card[data-elevated] { box-shadow: var(--ad-shadow-float); }
+
+.card > li {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+}
+.card > li:first-child { border-top: none; }
+
+/* A row whose ENTIRE content is wrapped in a real <button> or <a> — the
+ accessible way to make a row clickable — fills the row edge-to-edge and
+ picks up a hover fill and pointer cursor. `:only-child` is what tells this
+ apart from a row that merely contains a small button among other content
+ (a leading checkbox next to a title and a trailing value, say): only a
+ button/anchor that IS the row's one and only child means "the whole row
+ is the control." */
+.card > li:has(> button:only-child),
+.card > li:has(> a:only-child) {
+ padding: 0;
+}
+.card > li > button:only-child,
+.card > li > a:only-child {
+ all: unset;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ box-sizing: border-box;
+ color: inherit;
+ font: inherit;
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.card > li > button:only-child:hover,
+.card > li > a:only-child:hover { background: var(--ad-color-surface-fill); }
+
+/* Documentation-only extras: a body/footer pair and a responsive grid, used
+ by the prose pages (rationale.md, index.md) to show a Card as a plain
+ content tile rather than a data row. Neither is part of Card's props —
+ they exist because a content tile's inner layout has no element of its
+ own to hang a selector on. */
+.card-body {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-m);
+}
+.card-footer {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ margin-top: auto;
+ padding: var(--ad-space-xs) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+
+.card[data-link] { transition: border-color var(--ad-dur-fast) var(--ad-ease), box-shadow var(--ad-dur-fast) var(--ad-ease); }
+.card[data-link]:hover { border-color: var(--ad-color-border-default); box-shadow: var(--ad-shadow-sm); }
+.card[data-link]:focus-within { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+
+.card-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
+ gap: var(--ad-space-s);
+}
+
+/* Avatar — lives here because it almost never appears outside a card or a
+ row (the Loop dashboard's initials chip, Archive's file-type chip). A
+ <span> is too generic for a bare selector, so it keeps one class. */
+.avatar {
+ --_size: 1.875rem;
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ flex: none;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 600;
+ overflow: hidden;
+}
+.avatar[data-square] { border-radius: var(--ad-radius-md); background: var(--ad-color-surface-fill); color: var(--ad-color-text-secondary); border: 1px solid var(--ad-color-border-hairline); }
+.avatar[data-size="sm"] { --_size: 1.5rem; }
+.avatar[data-size="lg"] { --_size: 2.25rem; }
+
+/* Tooltip — a dark mono label on hover, positioned above the child. Pure CSS:
+ the trigger and bubble are siblings inside one relatively-positioned span,
+ shown on :hover/:focus-within so it works from the keyboard too.
+
+ `[role="tooltip"]` is the correct ARIA role for the bubble itself, so it
+ doubles as the styling hook — zero classes there. The wrapping span has no
+ attribute of its own, but it does not need one: `:has()` picks out "a span
+ that contains a [role=tooltip] bubble" precisely, without reaching for a
+ class on an otherwise-generic element. */
+span:has(> [role="tooltip"]) {
+ position: relative;
+ display: inline-flex;
+}
+
+[role="tooltip"] {
+ position: absolute;
+ bottom: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ margin-bottom: 0.375rem;
+ padding: 0.25rem 0.5rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-text-primary);
+ color: var(--ad-color-surface-page);
+ font-family: var(--ad-font-mono);
+ /* 11px is the source's own literal choice — smaller than --ad-step--2, the
+ smallest step on the scale, because a tooltip is meant to read as a
+ glance-only aside rather than body text. */
+ font-size: 11px;
+ white-space: nowrap;
+ line-height: 1.4;
+ z-index: 20;
+ opacity: 0;
+ visibility: hidden;
+ pointer-events: none;
+ transition: opacity var(--ad-dur-fast) var(--ad-ease);
+}
+span:has(> [role="tooltip"]):hover > [role="tooltip"],
+span:has(> [role="tooltip"]):focus-within > [role="tooltip"] {
+ opacity: 1;
+ visibility: visible;
+}
+
+/* Toast — the real pattern used for delete confirmations: a flat bar with an
+ optional destructive action and a dismiss. `[data-fixed]` pins it
+ bottom-right for real use; without it the toast stays in document flow for
+ documentation. `role="status"` is the correct live-region role for a
+ transient status message, so it is both the accessibility hook and the
+ style selector — no base class needed. The action and dismiss buttons are
+ plain <button>s told apart by `aria-label="Dismiss"`, an attribute they
+ need anyway for the icon-only close control. */
+[role="status"] {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ min-width: 20rem;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+}
+[role="status"][data-fixed] {
+ position: fixed;
+ inset-block-end: var(--ad-space-s);
+ inset-inline-end: var(--ad-space-s);
+ z-index: 60;
+}
+[role="status"] > span { flex: 1; }
+[role="status"] button {
+ all: unset;
+ cursor: pointer;
+ font-weight: 600;
+ color: var(--ad-color-danger);
+}
+[role="status"] button[aria-label="Dismiss"] {
+ color: var(--ad-color-text-tertiary);
+ font-size: 1rem;
+ font-weight: 400;
+ line-height: 1;
+}
+[role="status"] button[aria-label="Dismiss"]:hover { color: var(--ad-color-text-primary); }
+
+/* Dialog — the native <dialog> element: a centered modal, flat surface, no
+ colored header, hairline dividers between title/body/footer. A <header>
+ holds the title and a <footer> holds the actions, matching the elements
+ the tag already wants; everything else in between is the body, styled by
+ excluding those two rather than by a class of its own.
+
+ `.dialog-demo` scopes a static, always-open example to its own preview
+ container instead of the viewport, so it can sit safely inside a
+ documentation page — this is a doc-only affordance, not part of Dialog's
+ contract. It relies on the browser's own dialog centering: the UA
+ stylesheet already gives an open <dialog> `position: absolute; inset: 0;
+ margin: auto`, which centers it inside the nearest positioned ancestor —
+ here, `.dialog-demo` itself. A real `.showModal()` call gets the genuine
+ viewport-fixed overlay and `::backdrop` for free; a plain `open` attribute
+ (used here so the demo renders without JavaScript) does not paint a
+ backdrop at all, since `::backdrop` only exists for a dialog shown
+ modally — wire the real open/close state and call `.showModal()` in the
+ consuming app. */
+.dialog-demo {
+ position: relative;
+ min-height: 20rem;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-lg);
+}
+
+dialog {
+ width: 26rem;
+ max-width: calc(100% - (2 * var(--ad-space-s)));
+ height: fit-content;
+ padding: 0;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ overflow: hidden;
+}
+dialog::backdrop {
+ background: rgba(20, 18, 14, 0.35);
+}
+dialog > header {
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-sans);
+ font-weight: 600;
+ font-size: var(--ad-step-0);
+ color: var(--ad-color-text-primary);
+}
+dialog > footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+}
+dialog > *:not(header):not(footer) {
+ padding: var(--ad-space-m);
+ margin: 0;
+ color: var(--ad-color-text-primary);
+ font-size: var(--ad-step--1);
+}
+
+/* Tabs
+ Follows the real WAI-ARIA tabs pattern: a `[role="tablist"]` of
+ `[role="tab"]` buttons controlling `[role="tabpanel"]`s. Those roles are
+ required for the pattern to be accessible in the first place, so they
+ double as the entire styling contract — no classes at all for the base
+ shape. The keyboard wiring lives in assets/js/system.js and is
+ variant-agnostic — it only cares about `[data-tabs]` / `[role=tab]`.
+
+ Two real variants: `underline` (mono, accent underline — primary section
+ nav, the default) and `segmented` (pill track, active segment lifts to the
+ surface — filters like All · Overdue · Upcoming, never primary
+ navigation), chosen with `data-variant="segmented"` on the tablist since
+ neither has a natural attribute of its own. */
+
+/* Every rule below the list container pairs [role="tablist"] with
+ [role="tab"] rather than styling [role="tab"] alone — Tab buttons are
+ still literal <button> elements, so the compound selector's extra
+ specificity is what keeps Button's base styling (button.css) from
+ winning the cascade on shared properties like padding and background. */
+
+[role="tablist"] {
+ display: flex;
+ gap: var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+[role="tablist"] [role="tab"] {
+ padding: 0.5rem 0;
+ margin-bottom: -1px;
+ background: none;
+ border: 0;
+ border-bottom: 2px solid transparent;
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--1);
+ letter-spacing: 0.02em;
+ cursor: pointer;
+ transition: color var(--ad-dur-fast) var(--ad-ease), border-color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"] [role="tab"]:hover { color: var(--ad-color-text-primary); }
+[role="tablist"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ border-bottom-color: var(--ad-color-accent);
+}
+
+[role="tablist"][data-variant="segmented"] {
+ display: inline-flex;
+ gap: 0.125rem;
+ padding: 0.1875rem;
+ border-bottom: 0;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-md);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"] {
+ all: unset;
+ cursor: pointer;
+ padding: 0.375rem 0.875rem;
+ border-radius: var(--ad-radius-sm);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 550;
+ color: var(--ad-color-text-secondary);
+ transition: background-color var(--ad-dur-fast) var(--ad-ease), color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ background: var(--ad-color-surface-card);
+ box-shadow: var(--ad-shadow-sm);
+}
+
+[role="tabpanel"] { padding-top: var(--ad-space-s); }
+[role="tabpanel"][hidden] { display: none; }
+[role="tabpanel"]:focus-visible { outline-offset: 4px; }
+
+/* Documentation site chrome. Everything below the `.ad-doc-` prefix belongs
+ to this site rather than to the design system it documents — including the
+ width/height constants used directly below. The source design system
+ defines no container-width tokens at all, so there is nothing under
+ `--ad-*` for a doc shell to reach for; the Space foundation page's own
+ notes say to omit that group entirely rather than invent one. */
+
+.ad-doc-skip {
+ position: absolute;
+ inset-inline-start: var(--ad-space-xs);
+ inset-block-start: -4rem;
+ z-index: 40;
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ box-shadow: var(--ad-shadow-float);
+ transition: inset-block-start var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-skip:focus { inset-block-start: var(--ad-space-2xs); }
+
+.ad-doc-header {
+ position: sticky;
+ inset-block-start: 0;
+ z-index: 30;
+ height: 3.5rem;
+ background: color-mix(in srgb, var(--ad-color-surface-page) 92%, transparent);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-header__inner {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ height: 100%;
+ max-width: 72rem;
+ margin-inline: auto;
+ padding-inline: var(--ad-space-xs);
+}
+
+.ad-doc-wordmark {
+ display: flex;
+ align-items: baseline;
+ gap: var(--ad-space-2xs);
+ color: var(--ad-color-text-primary);
+ text-decoration: none;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ font-weight: 600;
+ letter-spacing: -0.01em;
+ white-space: nowrap;
+}
+.ad-doc-wordmark:hover { color: var(--ad-color-text-primary); }
+.ad-doc-wordmark__mark {
+ width: 1.375rem;
+ height: 1.375rem;
+ flex: none;
+ align-self: center;
+ color: var(--ad-color-accent);
+}
+.ad-doc-wordmark__version {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+
+.ad-doc-header__actions { margin-inline-start: auto; display: flex; align-items: center; gap: var(--ad-space-2xs); }
+
+.ad-doc-layout {
+ display: grid;
+ grid-template-columns: 16rem minmax(0, 1fr);
+ gap: var(--ad-space-l);
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-m) var(--ad-space-xs) var(--ad-space-2xl);
+}
+
+.ad-doc-sidebar {
+ position: sticky;
+ inset-block-start: calc(3.5rem + var(--ad-space-m));
+ align-self: start;
+ max-height: calc(100vh - 3.5rem - var(--ad-space-l));
+ overflow-y: auto;
+ padding-inline-end: var(--ad-space-2xs);
+}
+.ad-doc-nav__group + .ad-doc-nav__group { margin-top: var(--ad-space-s); }
+.ad-doc-nav__heading {
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-doc-nav__link {
+ display: block;
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ margin-inline-start: calc(var(--ad-space-xs) * -1);
+ border-inline-start: 2px solid transparent;
+ border-radius: 0 var(--ad-radius-sm) var(--ad-radius-sm) 0;
+ color: var(--ad-color-text-secondary);
+ font-size: var(--ad-step--1);
+ text-decoration: none;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-nav__link:hover { color: var(--ad-color-text-primary); background: var(--ad-color-surface-fill); }
+.ad-doc-nav__link[aria-current="page"] {
+ color: var(--ad-color-accent-ink);
+ border-inline-start-color: var(--ad-color-accent);
+ background: var(--ad-color-accent-soft);
+ font-weight: 500;
+}
+
+.ad-doc-main {
+ min-width: 0;
+ /* The design system's own body-text token (--ad-step-0, 18–20px) is right
+ for a product screen but too large for long-form documentation prose.
+ This site's own copy gets a plain, fixed 16px instead — the token above
+ is unchanged, and specimen tables/demos below still show its real
+ value. */
+ font-size: 1rem;
+}
+.ad-doc-main > * { max-width: 50rem; }
+
+.ad-doc-title { margin-bottom: var(--ad-space-xs); }
+.ad-doc-title__section {
+ display: block;
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-accent);
+}
+
+.ad-doc-main h2 {
+ margin-block: var(--ad-space-2xl) var(--ad-space-xs);
+ padding-bottom: var(--ad-space-2xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-main h3 { margin-block: var(--ad-space-l) var(--ad-space-2xs); }
+.ad-doc-main h4 { margin-block: var(--ad-space-s) var(--ad-space-2xs); }
+.ad-doc-main p,
+.ad-doc-main ul,
+.ad-doc-main ol { max-width: 68ch; }
+.ad-doc-main ul:not([class]),
+.ad-doc-main ol:not([class]) { padding-inline-start: var(--ad-space-s); }
+
+.ad-doc-footer {
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-s) var(--ad-space-xs) var(--ad-space-l);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-tertiary);
+ font-size: var(--ad-step--1);
+}
+.ad-doc-footer__inner { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); justify-content: space-between; }
+
+/* `button.ad-doc-nav-toggle` (element + class) rather than a bare class:
+ button.css's own base rule is `button:not(.icon-button)`, which is also
+ one element + one class in specificity and sets `display: inline-flex` —
+ a bare `.ad-doc-nav-toggle { display: none }` loses that fight and the
+ toggle stays visible at every width. Matching its specificity here (and
+ coming later in the concatenated stylesheet) settles it in source order. */
+button.ad-doc-nav-toggle { display: none; }
+
+@media (max-width: 60rem) {
+ .ad-doc-layout { grid-template-columns: minmax(0, 1fr); gap: var(--ad-space-s); }
+ .ad-doc-sidebar {
+ position: static;
+ max-height: none;
+ }
+ /* The toggle is a normal Button; the panel it opens gets its own card
+ treatment. Putting the same card styling on the <aside> wrapper too
+ nested a button-shaped box inside an outer box for no reason. */
+ .ad-doc-nav {
+ margin-top: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ }
+ .ad-doc-sidebar[data-collapsed="true"] > .ad-doc-nav { display: none; }
+ button.ad-doc-nav-toggle {
+ display: flex;
+ width: 100%;
+ justify-content: space-between;
+ }
+}
+
+@media (max-width: 34rem) {
+ .ad-doc-wordmark__version { display: none; }
+ .ad-doc-layout { padding-inline: var(--ad-space-2xs); }
+}
+
+/* The live-example figure used throughout the docs — see the "How the
+ example demos work" section on the Using the CSS page. One class on the
+ outer <figure>; everything inside is styled by plain element selectors,
+ since figcaption/div/details/summary/pre already say what they are.
+ `data-layout` and `data-surface` on the preview <div> distinguish the
+ handful of demos that need a non-default arrangement. */
+
+.example {
+ margin: var(--ad-space-s) 0 var(--ad-space-l);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+ background: var(--ad-color-surface-card);
+ overflow: hidden;
+}
+
+.example figcaption {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-secondary);
+}
+
+.example > div {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-s);
+}
+/* nowrap is load-bearing: a column-direction flex container with wrap enabled
+ sizes its single line against the container rather than the content, which
+ left every stacked example with a few hundred pixels of dead space. */
+.example > div[data-layout="stack"] { flex-direction: column; flex-wrap: nowrap; align-items: stretch; }
+.example > div[data-layout="grid"] {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
+ align-items: start;
+}
+.example > div[data-surface="sunken"] { background: var(--ad-color-surface-fill); }
+.example > div > * { min-width: 0; }
+
+.example details {
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+.example summary {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ color: var(--ad-color-text-secondary);
+ cursor: pointer;
+ list-style: none;
+ user-select: none;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+}
+.example summary::-webkit-details-marker { display: none; }
+.example summary::before {
+ content: "";
+ width: 0.5rem;
+ height: 0.5rem;
+ border-inline-end: 1.5px solid currentColor;
+ border-block-end: 1.5px solid currentColor;
+ rotate: -45deg;
+ transition: rotate var(--ad-dur-fast) var(--ad-ease);
+}
+.example details[open] > summary::before { rotate: 45deg; }
+.example summary:hover { color: var(--ad-color-text-primary); }
+
+.example pre {
+ margin: 0;
+ padding: var(--ad-space-xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+ tab-size: 2;
+}
+.example pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Standalone fenced code blocks in the prose (unhighlighted — there is no
+ syntax highlighter in this build any more, just Kramdown's plain
+ <pre><code class="language-*">). */
+.ad-doc-main > pre {
+ margin-block: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+}
+.ad-doc-main > pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Presentation for the token reference tables and swatch grids, plus a
+ generic data-table utility. All of this is documentation chrome — the
+ design system itself has no Table component (see the note on the removed
+ `table`/`progress`/`callout` pages in git history). */
+
+.ad-swatch-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs) var(--ad-space-s);
+ padding: 0;
+ list-style: none;
+}
+.ad-swatch {
+ display: flex;
+ flex-direction: column;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ overflow: hidden;
+ background: var(--ad-color-surface-card);
+}
+.ad-swatch__chip {
+ display: flex;
+ align-items: flex-end;
+ justify-content: flex-end;
+ height: 4rem;
+ padding: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+}
+.ad-swatch__meta {
+ display: flex;
+ flex-direction: column;
+ gap: 0.1rem;
+ padding: var(--ad-space-2xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+}
+.ad-swatch__hex { color: var(--ad-color-text-primary); text-transform: uppercase; }
+.ad-swatch__ratio { color: var(--ad-color-text-tertiary); }
+
+.ad-alias-row { display: flex; align-items: center; gap: var(--ad-space-2xs); }
+.ad-alias-chip {
+ width: 1.5rem;
+ height: 1.5rem;
+ flex: none;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+}
+.ad-alias-chip--pair { display: flex; }
+.ad-alias-chip--pair > span { flex: 1; }
+
+/* Type specimen rows. */
+.ad-specimen {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ gap: var(--ad-space-3xs);
+ padding-block: var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-specimen__meta {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-specimen__sample {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: var(--ad-color-text-primary);
+}
+
+/* Space and radius rulers. */
+.ad-ruler { display: flex; flex-direction: column; gap: var(--ad-space-2xs); }
+.ad-ruler__row { display: grid; grid-template-columns: 6.5rem 4.5rem 1fr; align-items: center; gap: var(--ad-space-xs); font-family: var(--ad-font-mono); font-size: 11px; }
+.ad-ruler__bar { height: 0.75rem; background: var(--ad-color-accent); border-radius: var(--ad-radius-sm); }
+.ad-ruler__num { color: var(--ad-color-text-tertiary); text-align: right; }
+
+.ad-radius-grid, .ad-elevation-grid, .ad-motion-grid, .ad-accent-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs);
+ padding: 0;
+ list-style: none;
+}
+.ad-radius-demo {
+ height: 4.5rem;
+ background: var(--ad-color-accent-soft);
+ border: 1px solid var(--ad-color-accent);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-elevation-demo {
+ height: 4.5rem;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-motion-demo {
+ height: 4.5rem;
+ display: grid;
+ place-items: center;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-md);
+ margin-bottom: var(--ad-space-2xs);
+ cursor: pointer;
+ overflow: hidden;
+}
+.ad-motion-demo__dot {
+ width: 1.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent);
+ translate: -3rem 0;
+ transition-property: translate;
+ transition-duration: var(--ad-dur-base);
+ transition-timing-function: var(--ad-ease);
+}
+.ad-motion-demo:hover .ad-motion-demo__dot,
+.ad-motion-demo:focus-visible .ad-motion-demo__dot { translate: 3rem 0; }
+
+.ad-accent-demo {
+ height: 4.5rem;
+ display: flex;
+ align-items: flex-end;
+ padding: var(--ad-space-2xs);
+ border-radius: var(--ad-radius-md);
+ color: #fff;
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ margin-bottom: var(--ad-space-2xs);
+}
+
+/* Token names read `<code>` sitewide, which already carries a mono chip
+ look for running prose (see :not(pre) > code in base/typography.css).
+ Inside these specimen/swatch/ruler rows the old `.ad-token-name` class was
+ plain mono text with no chip, so `<code>` is flattened back to that look
+ here the same way table cells are. */
+.ad-swatch code, .ad-specimen code, .ad-ruler code, .ad-alias-row code {
+ background: none;
+ border: 0;
+ padding: 0;
+ font-size: var(--ad-step--2);
+ color: var(--ad-color-text-primary);
+ white-space: nowrap;
+}
+
+/* A documentation aside. Like `<table>`, this is site chrome for the prose
+ itself, not a system component — the real system has no Callout. */
+.ad-callout {
+ margin-block: var(--ad-space-s);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-left: 3px solid var(--ad-color-accent);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+}
+/* Generic reference tables (component APIs, alias tables, the contrast
+ contract). `<table>` IS a table — no class needed to say so. Every table
+ on this site uses the one compact density, so there is no separate
+ modifier for it. `display: block` plus `overflow-x: auto` gives the table
+ its own horizontal scrollbar on narrow viewports without a wrapper div;
+ the anonymous table box the browser generates around `<thead>`/`<tbody>`
+ still does the row/column layout. `<code>` replaces the old
+ `.ad-token-name` / `.ad-table__code` cells — it is already styled
+ sitewide, just re-flattened here to a plain mono run instead of the
+ chip-with-border look prose code gets, to match this table's original
+ look. `.num` is the one sanctioned class left, for right-aligned numeric
+ columns — there is no attribute that means "this column is a number". */
+table { width: 100%; border-collapse: collapse; font-size: var(--ad-step--1); display: block; overflow-x: auto; }
+table th, table td {
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ text-align: left;
+ vertical-align: top;
+}
+table th { font-family: var(--ad-font-mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ad-color-text-secondary); font-weight: 500; }
+table code { background: none; border: 0; padding: 0; font-size: var(--ad-step--2); white-space: nowrap; }
+.num { text-align: right; font-variant-numeric: tabular-nums; }
+
+/* Home page. */
+.ad-hero { padding-block: var(--ad-space-m) var(--ad-space-l); }
+.ad-hero__title {
+ font-size: var(--ad-step-4);
+ margin-bottom: var(--ad-space-xs);
+}
+.ad-hero__actions { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); margin-top: var(--ad-space-s); }
+.ad-stat-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-l);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+}
+.ad-stat__value {
+ display: block;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-2);
+ font-weight: 600;
+ line-height: 1.16;
+ font-variant-numeric: tabular-nums;
+}
+.ad-stat__label {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+
+/* Card chrome. The pane supplies the frame; this just insets the page. */
+body { padding: var(--ad-space-l) var(--ad-space-m); }
+.ad-doc-main { max-width: 50rem; margin-inline: auto; }
+.ad-doc-main > * { max-width: none; }
+</style>
+</head>
+<body>
+<main class="ad-doc-main">
+<header class="ad-doc-title"><span class="ad-doc-title__section">Components</span>
+ <h1>Tabs</h1>
+</header>
+<p class="ad-lead">Two real variants: underline for primary section navigation, segmented for filters. The WAI-ARIA tabs pattern, wired up in vanilla JavaScript — arrows move, Home/End jump to the ends.</p>
+<p>Tabs follows the real WAI-ARIA tabs pattern: <code>[role="tablist"]</code> contains
+<code>[role="tab"]</code> buttons controlling <code>[role="tabpanel"]</code>s. Those roles are
+required for the pattern to be accessible in the first place, so they are the
+entire styling contract too — no classes anywhere in this component. The
+keyboard wiring (<code>assets/js/system.js</code>) only looks for <code>[data-tabs]</code> /
+<code>[role=tab]</code>, so the root wrapper keeps that one data attribute and needs
+nothing else.</p>
+
+<h2 id="underline">Underline</h2>
+
+<p>Mono, with an accent underline. For primary section navigation — a project’s
+own tabs, never a filter. This is the default; no <code>data-variant</code> needed.</p>
+
+<figure class="example">
+<figcaption>Underline</figcaption>
+<div data-layout="stack">
+<div data-tabs="">
+ <div role="tablist" aria-label="Repository">
+ <button role="tab" id="tabs-code-tab" aria-controls="tabs-code-panel" aria-selected="true" tabindex="0">Code</button>
+ <button role="tab" id="tabs-ci-tab" aria-controls="tabs-ci-panel" aria-selected="false" tabindex="-1">CI</button>
+ <button role="tab" id="tabs-issues-tab" aria-controls="tabs-issues-panel" aria-selected="false" tabindex="-1">Issues</button>
+ </div>
+ <div role="tabpanel" id="tabs-code-panel" aria-labelledby="tabs-code-tab" tabindex="0">Repository contents.</div>
+ <div role="tabpanel" id="tabs-ci-panel" aria-labelledby="tabs-ci-tab" tabindex="0" hidden="">Build history.</div>
+ <div role="tabpanel" id="tabs-issues-panel" aria-labelledby="tabs-issues-tab" tabindex="0" hidden="">Open issues.</div>
+</div>
+</div>
+<details open=""><summary>Markup</summary>
+<pre><code><div data-tabs="">
+ <div role="tablist" aria-label="Repository">
+ <button role="tab" id="tabs-code-tab" aria-controls="tabs-code-panel" aria-selected="true" tabindex="0">Code</button>
+ <button role="tab" id="tabs-ci-tab" aria-controls="tabs-ci-panel" aria-selected="false" tabindex="-1">CI</button>
+ <button role="tab" id="tabs-issues-tab" aria-controls="tabs-issues-panel" aria-selected="false" tabindex="-1">Issues</button>
+ </div>
+ <div role="tabpanel" id="tabs-code-panel" aria-labelledby="tabs-code-tab" tabindex="0">Repository contents.</div>
+ <div role="tabpanel" id="tabs-ci-panel" aria-labelledby="tabs-ci-tab" tabindex="0" hidden="">Build history.</div>
+ <div role="tabpanel" id="tabs-issues-panel" aria-labelledby="tabs-issues-tab" tabindex="0" hidden="">Open issues.</div>
+</div></code></pre>
+</details>
+</figure>
+
+<h2 id="segmented">Segmented</h2>
+
+<p>A pill track; the active segment lifts to the surface. For filters — <em>All ·
+Overdue · Upcoming</em> — never primary navigation. <code>data-variant="segmented"</code> on
+the tablist is the only attribute either variant needs, since neither has a
+natural one of its own.</p>
+
+<figure class="example">
+<figcaption>Segmented</figcaption>
+<div data-layout="stack">
+<div data-tabs="">
+ <div role="tablist" data-variant="segmented" aria-label="Filter">
+ <button role="tab" id="tabs-all-tab" aria-controls="tabs-all-panel" aria-selected="true" tabindex="0">All</button>
+ <button role="tab" id="tabs-overdue-tab" aria-controls="tabs-overdue-panel" aria-selected="false" tabindex="-1">Overdue</button>
+ <button role="tab" id="tabs-upcoming-tab" aria-controls="tabs-upcoming-panel" aria-selected="false" tabindex="-1">Upcoming</button>
+ </div>
+ <div role="tabpanel" id="tabs-all-panel" aria-labelledby="tabs-all-tab" tabindex="0">7 series.</div>
+ <div role="tabpanel" id="tabs-overdue-panel" aria-labelledby="tabs-overdue-tab" tabindex="0" hidden="">2 series.</div>
+ <div role="tabpanel" id="tabs-upcoming-panel" aria-labelledby="tabs-upcoming-tab" tabindex="0" hidden="">5 series.</div>
+</div>
+</div>
+<details><summary>Markup</summary>
+<pre><code><div data-tabs="">
+ <div role="tablist" data-variant="segmented" aria-label="Filter">
+ <button role="tab" id="tabs-all-tab" aria-controls="tabs-all-panel" aria-selected="true" tabindex="0">All</button>
+ <button role="tab" id="tabs-overdue-tab" aria-controls="tabs-overdue-panel" aria-selected="false" tabindex="-1">Overdue</button>
+ <button role="tab" id="tabs-upcoming-tab" aria-controls="tabs-upcoming-panel" aria-selected="false" tabindex="-1">Upcoming</button>
+ </div>
+ <div role="tabpanel" id="tabs-all-panel" aria-labelledby="tabs-all-tab" tabindex="0">7 series.</div>
+ <div role="tabpanel" id="tabs-overdue-panel" aria-labelledby="tabs-overdue-tab" tabindex="0" hidden="">2 series.</div>
+ <div role="tabpanel" id="tabs-upcoming-panel" aria-labelledby="tabs-upcoming-tab" tabindex="0" hidden="">5 series.</div>
+</div></code></pre>
+</details>
+</figure>
+
+<h2 id="rules">Rules</h2>
+
+<ul>
+ <li><strong>Never both variants in one view.</strong> Underline is for the page’s own
+sections; segmented is for filtering what is already on screen.</li>
+ <li><strong>Segmented is not primary navigation.</strong> If it decides which screen the
+reader is on, it should be underline.</li>
+</ul>
+
+<h2 id="api">API</h2>
+
+<table>
+ <thead><tr><th scope="col">Element / attribute</th><th scope="col">Values</th><th scope="col">Notes</th></tr></thead>
+ <tbody>
+ <tr><td><code>[data-tabs]</code></td><td></td><td>Required on the root wrapper — what the keyboard wiring in <code>system.js</code> looks for.</td></tr>
+ <tr><td><code>[role="tablist"]</code></td><td></td><td>Required. Contains the tab buttons.</td></tr>
+ <tr><td><code>[role="tab"]</code></td><td></td><td>Required, one per item. <code>aria-selected</code> and <code>tabindex</code> are managed by the JS.</td></tr>
+ <tr><td><code>[role="tabpanel"]</code></td><td></td><td>Required, one per item, referenced by the tab's <code>aria-controls</code>.</td></tr>
+ <tr><td><code>data-variant</code></td><td><code>segmented</code></td><td>On the tablist. Omit for the default underline look.</td></tr>
+ </tbody>
+</table>
+</main>
+<script>
+// Behavior for the pieces of the system that need it. Everything else is
+// CSS and semantic HTML on purpose — a design system that only works with
+// JavaScript running is a component library with extra steps.
+(function () {
+ "use strict";
+
+ var STORAGE_KEY = "ad-theme";
+
+ function readStored() {
+ try {
+ return localStorage.getItem(STORAGE_KEY);
+ } catch (error) {
+ return null;
+ }
+ }
+
+ function writeStored(value) {
+ try {
+ localStorage.setItem(STORAGE_KEY, value);
+ } catch (error) {
+ /* Nothing to do — the media query still gives a sensible default. */
+ }
+ }
+
+ function systemPrefersDark() {
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
+ }
+
+ function currentTheme() {
+ var explicit = document.documentElement.getAttribute("data-theme");
+ if (explicit) return explicit;
+ return systemPrefersDark() ? "dark" : "light";
+ }
+
+ function paintToggle(theme) {
+ document.querySelectorAll("[data-theme-toggle]").forEach(function (toggle) {
+ var next = theme === "dark" ? "light" : "dark";
+ toggle.querySelectorAll("[data-theme-icon]").forEach(function (icon) {
+ icon.hidden = icon.dataset.themeIcon !== theme;
+ });
+ toggle.setAttribute("aria-label", "Switch to the " + next + " theme");
+ toggle.setAttribute("title", "Switch to the " + next + " theme");
+ var label = toggle.querySelector("[data-theme-label]");
+ if (label) label.textContent = "Switch to the " + next + " theme";
+ });
+ }
+
+ function setUpTheme() {
+ paintToggle(currentTheme());
+
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-theme-toggle]");
+ if (!toggle) return;
+
+ var next = currentTheme() === "dark" ? "light" : "dark";
+ document.documentElement.setAttribute("data-theme", next);
+ writeStored(next);
+ paintToggle(next);
+ });
+
+ // Follow the OS while the reader has not made an explicit choice.
+ window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", function () {
+ if (!readStored()) paintToggle(currentTheme());
+ });
+ }
+
+ // Tabs, wired to the WAI-ARIA authoring practices: arrows move, Home/End
+ // jump, and only the selected tab stays in the tab order.
+ function setUpTabs() {
+ document.querySelectorAll("[data-tabs]").forEach(function (root) {
+ var tabs = Array.prototype.slice.call(root.querySelectorAll('[role="tab"]'));
+ if (!tabs.length) return;
+
+ function select(tab, focus) {
+ tabs.forEach(function (candidate) {
+ var selected = candidate === tab;
+ candidate.setAttribute("aria-selected", String(selected));
+ candidate.tabIndex = selected ? 0 : -1;
+ var panel = document.getElementById(candidate.getAttribute("aria-controls"));
+ if (panel) panel.hidden = !selected;
+ });
+ if (focus) tab.focus();
+ }
+
+ root.addEventListener("click", function (event) {
+ var tab = event.target.closest('[role="tab"]');
+ if (tab) select(tab, false);
+ });
+
+ root.addEventListener("keydown", function (event) {
+ var index = tabs.indexOf(document.activeElement);
+ if (index === -1) return;
+
+ var next = null;
+ if (event.key === "ArrowRight") next = tabs[(index + 1) % tabs.length];
+ else if (event.key === "ArrowLeft") next = tabs[(index - 1 + tabs.length) % tabs.length];
+ else if (event.key === "Home") next = tabs[0];
+ else if (event.key === "End") next = tabs[tabs.length - 1];
+
+ if (next) {
+ event.preventDefault();
+ select(next, true);
+ }
+ });
+
+ select(tabs.filter(function (tab) {
+ return tab.getAttribute("aria-selected") === "true";
+ })[0] || tabs[0], false);
+ });
+ }
+
+ function setUpNavToggle() {
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-nav-toggle]");
+ if (!toggle) return;
+
+ var sidebar = toggle.closest("[data-sidebar]");
+ var collapsed = sidebar.dataset.collapsed !== "false";
+ sidebar.dataset.collapsed = String(!collapsed);
+ toggle.setAttribute("aria-expanded", String(collapsed));
+ });
+ }
+
+ setUpTheme();
+ setUpTabs();
+ setUpNavToggle();
+})();
+
+</script>
+</body>
+</html>
diff --git a/design-bundle/components/tag.html b/design-bundle/components/tag.html
new file mode 100644
index 0000000..dc4dee4
--- /dev/null
+++ b/design-bundle/components/tag.html
@@ -0,0 +1,1805 @@
+<!-- @dsCard group="Core" -->
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Tag</title>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&display=swap">
+<style>
+/* Arbitrary Definitions — design tokens. Hand-maintained; edit values directly. */
+
+:root {
+ /* Palette ramps — reference only. Components use the aliases below. */
+ --ad-neutral-0: #faf9f7;
+ --ad-neutral-50: #fdfdfb;
+ --ad-neutral-100: #f1f0ed;
+ --ad-neutral-150: #e8e6e2;
+ --ad-neutral-200: #dcdad7;
+ --ad-neutral-300: #c0bdb9;
+ --ad-neutral-500: #888681;
+ --ad-neutral-600: #5f5d59;
+ --ad-neutral-800: #2a2926;
+ --ad-neutral-900: #100f0d;
+ --ad-neutral-dark-0: #0e0d0b;
+ --ad-neutral-dark-50: #151412;
+ --ad-neutral-dark-100: #1e1d1a;
+ --ad-neutral-dark-150: #252421;
+ --ad-neutral-dark-200: #2c2b29;
+ --ad-neutral-dark-300: #44423f;
+ --ad-neutral-dark-500: #7c7a76;
+ --ad-neutral-dark-600: #a09e9a;
+ --ad-neutral-dark-800: #d9d7d4;
+ --ad-neutral-dark-900: #e9e8e6;
+ --ad-accent-teal: #3a6a63;
+ --ad-accent-clay: #9a5a3c;
+ --ad-accent-ink-blue: #2b4a78;
+ --ad-accent-ochre: #93701f;
+ --ad-accent-plum: #6a3a5c;
+
+ /* Surfaces */
+ --ad-color-surface-page: #faf9f7;
+ --ad-color-surface-card: #fdfdfb;
+ --ad-color-surface-fill: #f1f0ed;
+ --ad-color-surface-fill-2: #e8e6e2;
+
+ /* Borders */
+ --ad-color-border-hairline: #dcdad7;
+ --ad-color-border-default: #c0bdb9;
+
+ /* Text */
+ --ad-color-text-primary: #100f0d;
+ --ad-color-text-secondary: #5f5d59;
+ --ad-color-text-tertiary: #888681;
+
+ /* Accent */
+ --ad-color-accent: #3a6a63;
+ --ad-color-accent-fg: #ffffff;
+ --ad-color-accent-ink: color-mix(in oklch, var(--ad-color-accent) 78%, black);
+ --ad-color-accent-soft: color-mix(in oklch, var(--ad-color-accent) 12%, white);
+
+ /* Status (fixed — never swapped like the accent) */
+ --ad-color-success: #3f7a4a;
+ --ad-color-success-bg: color-mix(in oklch, var(--ad-color-success) 12%, white);
+ --ad-color-danger: #ad3a2c;
+ --ad-color-danger-bg: color-mix(in oklch, var(--ad-color-danger) 10%, white);
+
+ /* Type families */
+ --ad-font-sans: -apple-system, "Helvetica Neue", Helvetica, Arial, system-ui, sans-serif;
+ --ad-font-mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, Consolas, "Courier New", monospace;
+ --ad-font-serif: Georgia, "Times New Roman", "Iowan Old Style", serif;
+
+ /* Type steps — fluid (Utopia scale), the design system's own body-text size */
+ --ad-step--2: clamp(0.7813rem, 0.7736rem + 0.0341vw, 0.8rem);
+ --ad-step--1: clamp(0.9375rem, 0.9119rem + 0.1136vw, 1rem);
+ --ad-step-0: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-step-1: clamp(1.35rem, 1.2631rem + 0.3864vw, 1.5625rem);
+ --ad-step-2: clamp(1.62rem, 1.4837rem + 0.6057vw, 1.9531rem);
+ --ad-step-3: clamp(1.944rem, 1.7405rem + 0.9044vw, 2.4414rem);
+ --ad-step-4: clamp(2.3328rem, 2.0387rem + 1.3072vw, 3.0518rem);
+ --ad-step-5: clamp(2.7994rem, 2.384rem + 1.8461vw, 3.8147rem);
+
+ /* Space */
+ --ad-space-3xs: clamp(0.3125rem, 0.3125rem + 0vw, 0.3125rem);
+ --ad-space-2xs: clamp(0.5625rem, 0.5369rem + 0.1136vw, 0.625rem);
+ --ad-space-xs: clamp(0.875rem, 0.8494rem + 0.1136vw, 0.9375rem);
+ --ad-space-s: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-space-m: clamp(1.6875rem, 1.6108rem + 0.3409vw, 1.875rem);
+ --ad-space-l: clamp(2.25rem, 2.1477rem + 0.4545vw, 2.5rem);
+ --ad-space-xl: clamp(3.375rem, 3.2216rem + 0.6818vw, 3.75rem);
+ --ad-space-2xl: clamp(4.5rem, 4.2955rem + 0.9091vw, 5rem);
+ --ad-space-3xl: clamp(6.75rem, 6.4432rem + 1.3636vw, 7.5rem);
+
+ /* Radius */
+ --ad-radius-sm: 4px;
+ --ad-radius-md: 6px;
+ --ad-radius-lg: 10px;
+ --ad-radius-full: 999px;
+ --ad-border-w: 1px;
+
+ /* Elevation */
+ --ad-shadow-sm: 0 1px 2px rgba(20, 18, 14, 0.05);
+ --ad-shadow-float: 0 2px 6px rgba(20, 18, 14, 0.06), 0 10px 24px -12px rgba(20, 18, 14, 0.18);
+
+ /* Motion */
+ --ad-dur-fast: 0.12s;
+ --ad-dur-base: 0.16s;
+ --ad-ease: cubic-bezier(0.2, 0, 0, 1);
+}
+
+@media (prefers-color-scheme: dark) {
+ :root:not([data-theme="light"]) {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+ }
+}
+
+:root[data-theme="dark"] {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ :root {
+ --ad-dur-fast: 0ms;
+ --ad-dur-base: 0ms;
+ }
+}
+
+/* A short reset. Everything it sets is something the system would otherwise
+ have to override in every component.
+
+ The rem values used for this documentation site's own chrome (measure,
+ content width, sidebar width, header height) belong to this site, not to
+ the design system it documents — the source design system defines no
+ container-width tokens at all, so there is nothing under `--ad-*` to reach
+ for here. See the header comment in site/shell.css. */
+
+*,
+*::before,
+*::after { box-sizing: border-box; }
+
+html {
+ -webkit-text-size-adjust: 100%;
+ scroll-behavior: smooth;
+ scroll-padding-top: calc(3.5rem + var(--ad-space-s));
+}
+
+@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;
+ }
+}
+
+body {
+ margin: 0;
+ background: var(--ad-color-surface-page);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ line-height: 1.55;
+ font-synthesis-weight: none;
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+}
+
+::selection {
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+}
+
+h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd { margin: 0; }
+ul[class], ol[class] { list-style: none; margin: 0; padding: 0; }
+
+img, svg, video { display: block; max-width: 100%; height: auto; }
+
+button, input, select, textarea {
+ font: inherit;
+ color: inherit;
+}
+
+hr {
+ height: 1px;
+ margin: var(--ad-space-l) 0;
+ border: 0;
+ background: var(--ad-color-border-hairline);
+}
+
+/* One focus ring, defined once. No component is allowed its own. */
+:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+ border-radius: var(--ad-radius-sm);
+}
+:focus:not(:focus-visible) { outline: none; }
+
+[hidden] { display: none !important; }
+
+.ad-visually-hidden {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip-path: inset(50%);
+ white-space: nowrap;
+ border: 0;
+}
+
+/* Every rule here reads a type token for family and size. Weight, line-height,
+ and letter-spacing are hardcoded to the exact numbers the source ships —
+ there is no `--ad-weight-*` / `--ad-leading-*` / `--ad-tracking-*` scale in
+ the real system, only eight fixed compound styles (see the Typography
+ foundation page, which this file implements verbatim). */
+
+.type-display { font: 600 var(--ad-step-4)/1.08 var(--ad-font-sans); letter-spacing: -0.015em; }
+.type-h1, h1 { font: 600 var(--ad-step-3)/1.12 var(--ad-font-sans); letter-spacing: -0.012em; }
+.type-h2, h2 { font: 600 var(--ad-step-2)/1.16 var(--ad-font-sans); letter-spacing: -0.01em; }
+.type-h3, h3 { font: 600 var(--ad-step-1)/1.25 var(--ad-font-sans); }
+.type-body { font: 400 var(--ad-step-0)/1.55 var(--ad-font-sans); }
+.type-body-sm { font: 400 var(--ad-step--1)/1.5 var(--ad-font-sans); }
+.type-label {
+ font: 500 var(--ad-step--2)/1 var(--ad-font-mono);
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: var(--ad-color-text-secondary);
+}
+.type-mono { font: 400 var(--ad-step--1)/1.5 var(--ad-font-mono); }
+
+/* Prose defaults for the documentation body copy. These read the same
+ tokens the compound classes above do; they are not a second scale. */
+h1, h2, h3, h4 { font-weight: 600; text-wrap: balance; }
+
+h4 { font: 600 var(--ad-step-0)/1.3 var(--ad-font-sans); }
+
+p, ul, ol { margin: 0 0 var(--ad-space-s); }
+li + li { margin-top: var(--ad-space-3xs); }
+
+a {
+ color: var(--ad-color-accent);
+ text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
+ text-underline-offset: 0.18em;
+ text-decoration-thickness: 1px;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ text-decoration-color var(--ad-dur-fast) var(--ad-ease);
+}
+a:hover {
+ color: var(--ad-color-accent-ink);
+ text-decoration-color: currentColor;
+}
+
+strong, b { font-weight: 600; }
+
+small { font-size: var(--ad-step--1); }
+
+.ad-lead {
+ font-size: var(--ad-step-1);
+ line-height: 1.5;
+ color: var(--ad-color-text-secondary);
+ max-width: 68ch;
+ text-wrap: pretty;
+}
+
+.ad-muted { color: var(--ad-color-text-secondary); }
+.ad-subtle { color: var(--ad-color-text-tertiary); }
+
+code, kbd, samp, pre {
+ font-family: var(--ad-font-mono);
+ font-feature-settings: "liga" 0;
+}
+
+:not(pre) > code {
+ padding: 0.15em 0.4em;
+ font-size: 0.875em;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-sm);
+ white-space: nowrap;
+}
+
+kbd {
+ display: inline-block;
+ padding: 0.1em 0.45em;
+ font-size: var(--ad-step--2);
+ line-height: 1.6;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-bottom-width: 2px;
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-secondary);
+}
+
+blockquote {
+ margin-block: var(--ad-space-s);
+ padding-left: var(--ad-space-s);
+ border-left: 2px solid var(--ad-color-accent);
+ color: var(--ad-color-text-secondary);
+}
+
+/* Button
+ A flat, quiet button that recedes rather than performs — no gradients, no
+ shine. Variants: default, primary, dark, ghost, danger. Sizes: sm, md
+ (default), lg — padding and font step only, weight and radius stay put.
+
+ The contract is the element itself: a bare <button> is styled directly, no
+ class required. An <a> that acts like a button (navigates rather than
+ changes something — see the Button page's "Links that act like buttons")
+ opts in with a single `.btn` class, since a bare `a` selector would catch
+ every ordinary prose link on the site. `:not(.icon-button)` keeps this
+ block from leaking onto IconButton, which shares the <button> tag but is
+ an entirely different shape. */
+
+button:not(.icon-button),
+a.btn {
+ --_bg: var(--ad-color-surface-card);
+ --_fg: var(--ad-color-text-primary);
+ --_border: var(--ad-color-border-default);
+ --_pad: var(--ad-space-xs) var(--ad-space-s);
+ --_font: var(--ad-step--1);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: var(--ad-space-2xs);
+ padding: var(--_pad);
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-sans);
+ font-size: var(--_font);
+ font-weight: 550;
+ letter-spacing: -0.01em;
+ line-height: 1.2;
+ text-align: center;
+ text-decoration: none;
+ white-space: nowrap;
+ cursor: pointer;
+ transition: filter var(--ad-dur-fast) var(--ad-ease),
+ border-color var(--ad-dur-fast) var(--ad-ease);
+}
+
+:is(button:not(.icon-button), a.btn):hover:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.96); }
+:is(button:not(.icon-button), a.btn):active:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.92); }
+
+button:not(.icon-button):disabled,
+:is(button:not(.icon-button), a.btn)[aria-disabled="true"] {
+ opacity: 0.5;
+ cursor: default;
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="primary"] {
+ --_bg: var(--ad-color-accent);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-accent);
+}
+
+/* Reserved for the single most-committing action in a view — a final Save,
+ never a whole row of them. */
+:is(button:not(.icon-button), a.btn)[data-variant="dark"] {
+ --_bg: var(--ad-color-text-primary);
+ --_fg: var(--ad-color-surface-page);
+ --_border: var(--ad-color-text-primary);
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="ghost"] {
+ --_bg: transparent;
+ --_fg: var(--ad-color-text-primary);
+ --_border: transparent;
+}
+
+/* The source hardcodes this label white rather than deriving it — danger
+ is not "on a filled accent surface", so --ad-color-accent-fg is reused
+ here only because its resolved value is the same literal white, not
+ because the two roles are the same thing. */
+:is(button:not(.icon-button), a.btn)[data-variant="danger"] {
+ --_bg: var(--ad-color-danger);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-danger);
+}
+
+:is(button:not(.icon-button), a.btn)[data-size="sm"] { --_pad: 0.375rem 0.75rem; --_font: 0.8125rem; }
+:is(button:not(.icon-button), a.btn)[data-size="lg"] { --_pad: 0.75rem 1.375rem; --_font: var(--ad-step-0); }
+
+:is(button:not(.icon-button), a.btn)[data-block] { width: 100%; }
+
+/* Button group — [role="group"] is already the correct ARIA role for a
+ toolbar of related buttons, so it doubles as the styling hook for free. */
+[role="group"] {
+ display: inline-flex;
+ isolation: isolate;
+}
+[role="group"] > button { border-radius: 0; }
+[role="group"] > button:first-child { border-start-start-radius: var(--ad-radius-md); border-end-start-radius: var(--ad-radius-md); }
+[role="group"] > button:last-child { border-start-end-radius: var(--ad-radius-md); border-end-end-radius: var(--ad-radius-md); }
+[role="group"] > button + button { margin-inline-start: -1px; }
+[role="group"] > button:hover,
+[role="group"] > button:focus-visible { z-index: 1; }
+
+/* IconButton — a square icon-only button. Always needs an aria-label; it
+ becomes both the accessible name and the native tooltip. There is no
+ attribute that distinguishes "a button that is only an icon" from a plain
+ Button, so this is the one sanctioned class in this file. */
+.icon-button {
+ --_size: 2.125rem;
+ --_bg: transparent;
+ --_border: transparent;
+ --_fg: var(--ad-color-text-secondary);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ padding: 0;
+ flex: none;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease),
+ color var(--ad-dur-fast) var(--ad-ease);
+}
+.icon-button:hover:not(:disabled) { background: var(--ad-color-surface-fill-2); }
+.icon-button:disabled { opacity: 0.5; cursor: default; }
+
+.icon-button[data-size="sm"] { --_size: 1.75rem; }
+.icon-button[data-size="lg"] { --_size: 2.5rem; }
+
+.icon-button[data-variant="outline"] { --_bg: var(--ad-color-surface-card); --_border: var(--ad-color-border-default); --_fg: var(--ad-color-text-primary); }
+.icon-button[data-variant="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); }
+
+/* Field
+ The labeled field wrapper — a <label> that wraps its own caption text
+ together with the control, so the association is native and needs no
+ `for`/`id` pair. The caption is mono, uppercase, tracked — the same
+ catalog-label convention as Badge and type-label.
+
+ A <label> is reused for three different shapes across this system (this
+ wrapper, Checkbox/Radio's row, Switch's row), so it cannot be styled bare
+ without one shape leaking into another. Field is the one of the three with
+ no distinguishing attribute of its own, so it gets the sanctioned class;
+ Checkbox/Radio tell themselves apart structurally instead (see below). */
+.field {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ min-width: 0;
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Hint and error read the native fine-print element. `role="alert"` is a
+ real live-region role for validation text, so it doubles as the selector
+ that turns a hint red. */
+.field > small {
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+.field > small[role="alert"] { color: var(--ad-color-danger); }
+
+/* Input / Select — bordered, matched to each other exactly. Both tags ARE
+ the component, so they are styled bare; nothing else on this site uses a
+ text input or a select. */
+input:not([type="checkbox"]):not([type="radio"]),
+select {
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ outline: none;
+ transition: border-color var(--ad-dur-fast) var(--ad-ease),
+ box-shadow var(--ad-dur-fast) var(--ad-ease);
+}
+input::placeholder { color: var(--ad-color-text-tertiary); }
+
+input:not([type="checkbox"]):not([type="radio"]):focus,
+select:focus {
+ border-color: var(--ad-color-accent);
+ box-shadow: 0 0 0 2px var(--ad-color-accent-soft);
+}
+
+input:not([type="checkbox"]):not([type="radio"]):disabled,
+select:disabled {
+ opacity: 0.5;
+ cursor: default;
+}
+
+/* The chevron is the one flagged color exception in this component set:
+ CSS cannot point a data-URI background-image at a custom property, so its
+ stroke is a literal hex rather than a var(--ad-*) reference. It is picked
+ to match --ad-color-text-secondary and commented here so it is not
+ mistaken for a stray color. */
+select {
+ appearance: none;
+ padding-inline-end: var(--ad-space-l);
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%235f5d59' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right var(--ad-space-xs) center;
+}
+
+input[aria-invalid="true"],
+select[aria-invalid="true"] {
+ border-color: var(--ad-color-danger);
+ background-color: var(--ad-color-danger-bg);
+}
+
+/* Checkbox / Radio — the real native controls, tinted with accent-color
+ instead of a hand-drawn box. This is not just a rename: it deletes the
+ custom SVG-checkmark markup entirely and lets the browser draw its own
+ (platform-appropriate) check and dot. `role="switch"` is excluded here so
+ Switch — a checkbox playing a different role — can style itself below
+ without this rule fighting it. A <label> wrapping a checkbox/radio is told
+ apart from Field's <label> structurally: it :has() one as a direct child,
+ no class required. */
+input[type="checkbox"]:not([role="switch"]),
+input[type="radio"] {
+ width: 1.125rem;
+ height: 1.125rem;
+ margin: 0;
+ accent-color: var(--ad-color-accent);
+ cursor: pointer;
+}
+input[type="checkbox"]:not([role="switch"]):focus-visible,
+input[type="radio"]:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+}
+
+label:has(> input[type="checkbox"]:not([role="switch"])),
+label:has(> input[type="radio"]) {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 400;
+ text-transform: none;
+ letter-spacing: normal;
+ color: var(--ad-color-text-primary);
+}
+label:has(> input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Switch — for on/off settings only, never a substitute for Checkbox.
+ `input[type="checkbox"][role="switch"]` is real ARIA (the correct role for
+ a toggle), not an invented hook, but the visual track+thumb needs a second
+ element for the thumb's independent position and transition — more than
+ `appearance: none` plus pseudo-elements on the input itself can cleanly
+ carry — so `.switch` is a deliberate, documented fallback class rather
+ than a first choice. */
+.switch {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ color: var(--ad-color-text-primary);
+}
+.switch input[role="switch"] { position: absolute; opacity: 0; width: 2.25rem; height: 1.25rem; margin: 0; }
+
+.switch span {
+ position: relative;
+ flex: none;
+ width: 2.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-border-default);
+ transition: background-color var(--ad-dur-base) var(--ad-ease);
+}
+.switch span::after {
+ content: "";
+ position: absolute;
+ inset-block-start: 2px;
+ inset-inline-start: 2px;
+ width: 1rem;
+ height: 1rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-fg);
+ box-shadow: var(--ad-shadow-sm);
+ transition: translate var(--ad-dur-base) var(--ad-ease);
+}
+.switch input[role="switch"]:checked + span { background: var(--ad-color-accent); }
+.switch input[role="switch"]:checked + span::after { translate: 1rem 0; }
+.switch input[role="switch"]:focus-visible + span { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+.switch:has(input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Rows of fields — used by the settings-form pattern. `<form>`/`<fieldset>`
+ are the elements that already mean this, so they are styled bare. A row
+ grouping two fields side by side has no element of its own, so it keeps
+ one plain class. */
+form {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-s);
+ max-width: 28rem;
+}
+.form-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
+ gap: var(--ad-space-xs);
+}
+fieldset {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ margin: 0;
+ padding: 0;
+ border: 0;
+}
+fieldset > legend {
+ padding: 0 0 var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Badge
+ Mono, uppercase, tracked — the catalog-label convention for status and
+ meta values (SAVED, OVERDUE, RECOMMENDED). A label, never a button.
+
+ A <span> is too generic to style globally — it is used all over running
+ prose for unrelated reasons — so Badge gets the one sanctioned class this
+ shape needs. Tone is an attribute, not a modifier class. */
+
+.badge {
+ --_bg: var(--ad-color-surface-fill);
+ --_fg: var(--ad-color-text-secondary);
+ --_border: var(--ad-color-border-hairline);
+
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.1875rem 0.5rem;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-sm);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ line-height: 1;
+ white-space: nowrap;
+}
+
+.badge[data-tone="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); --_border: transparent; }
+.badge[data-tone="success"] { --_bg: var(--ad-color-success-bg); --_fg: var(--ad-color-success); --_border: transparent; }
+.badge[data-tone="danger"] { --_bg: var(--ad-color-danger-bg); --_fg: var(--ad-color-danger); --_border: transparent; }
+
+.badge-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-2xs);
+}
+
+/* Tag
+ A removable mono chip for freeform tags — locations, categories.
+ `[data-dashed]` renders an "add tag" affordance instead of a value. Same
+ reasoning as Badge: <span> is too generic to select bare, so Tag gets one
+ class. The remove control is a real <button>, styled through the
+ descendant selector rather than a class of its own. */
+
+.tag {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.375rem;
+ padding: 0.25rem 0.625rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-default);
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ line-height: 1;
+}
+
+.tag[data-dashed] {
+ background: transparent;
+ border-style: dashed;
+ color: var(--ad-color-text-tertiary);
+ cursor: pointer;
+}
+
+.tag button {
+ all: unset;
+ cursor: pointer;
+ color: var(--ad-color-text-tertiary);
+ line-height: 1;
+ font-size: 0.75rem;
+}
+.tag button:hover { color: var(--ad-color-text-primary); }
+
+.tag-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.375rem;
+}
+
+/* Card
+ Flat surface, hairline border, radius-lg. `[data-elevated]` is opt-in and
+ reserved for the one focused/primary card in a view — a dialog, the
+ primary capture card — flat is the default everywhere else.
+
+ A <div>/<article> is too generic to select bare, so Card keeps one
+ sanctioned class. When Card's job is holding a list of rows — the common
+ case, entry lists, task lists, settings rows — it is a real <ul class="
+ card">, and CardRow is a bare <li> underneath it: no second class needed,
+ the row look comes from the `.card > li` descendant selector alone. The
+ first row's top border is removed automatically so callers never have to
+ override it by hand. */
+
+.card {
+ display: flex;
+ flex-direction: column;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: none;
+ overflow: hidden;
+}
+
+.card[data-elevated] { box-shadow: var(--ad-shadow-float); }
+
+.card > li {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+}
+.card > li:first-child { border-top: none; }
+
+/* A row whose ENTIRE content is wrapped in a real <button> or <a> — the
+ accessible way to make a row clickable — fills the row edge-to-edge and
+ picks up a hover fill and pointer cursor. `:only-child` is what tells this
+ apart from a row that merely contains a small button among other content
+ (a leading checkbox next to a title and a trailing value, say): only a
+ button/anchor that IS the row's one and only child means "the whole row
+ is the control." */
+.card > li:has(> button:only-child),
+.card > li:has(> a:only-child) {
+ padding: 0;
+}
+.card > li > button:only-child,
+.card > li > a:only-child {
+ all: unset;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ box-sizing: border-box;
+ color: inherit;
+ font: inherit;
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.card > li > button:only-child:hover,
+.card > li > a:only-child:hover { background: var(--ad-color-surface-fill); }
+
+/* Documentation-only extras: a body/footer pair and a responsive grid, used
+ by the prose pages (rationale.md, index.md) to show a Card as a plain
+ content tile rather than a data row. Neither is part of Card's props —
+ they exist because a content tile's inner layout has no element of its
+ own to hang a selector on. */
+.card-body {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-m);
+}
+.card-footer {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ margin-top: auto;
+ padding: var(--ad-space-xs) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+
+.card[data-link] { transition: border-color var(--ad-dur-fast) var(--ad-ease), box-shadow var(--ad-dur-fast) var(--ad-ease); }
+.card[data-link]:hover { border-color: var(--ad-color-border-default); box-shadow: var(--ad-shadow-sm); }
+.card[data-link]:focus-within { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+
+.card-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
+ gap: var(--ad-space-s);
+}
+
+/* Avatar — lives here because it almost never appears outside a card or a
+ row (the Loop dashboard's initials chip, Archive's file-type chip). A
+ <span> is too generic for a bare selector, so it keeps one class. */
+.avatar {
+ --_size: 1.875rem;
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ flex: none;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 600;
+ overflow: hidden;
+}
+.avatar[data-square] { border-radius: var(--ad-radius-md); background: var(--ad-color-surface-fill); color: var(--ad-color-text-secondary); border: 1px solid var(--ad-color-border-hairline); }
+.avatar[data-size="sm"] { --_size: 1.5rem; }
+.avatar[data-size="lg"] { --_size: 2.25rem; }
+
+/* Tooltip — a dark mono label on hover, positioned above the child. Pure CSS:
+ the trigger and bubble are siblings inside one relatively-positioned span,
+ shown on :hover/:focus-within so it works from the keyboard too.
+
+ `[role="tooltip"]` is the correct ARIA role for the bubble itself, so it
+ doubles as the styling hook — zero classes there. The wrapping span has no
+ attribute of its own, but it does not need one: `:has()` picks out "a span
+ that contains a [role=tooltip] bubble" precisely, without reaching for a
+ class on an otherwise-generic element. */
+span:has(> [role="tooltip"]) {
+ position: relative;
+ display: inline-flex;
+}
+
+[role="tooltip"] {
+ position: absolute;
+ bottom: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ margin-bottom: 0.375rem;
+ padding: 0.25rem 0.5rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-text-primary);
+ color: var(--ad-color-surface-page);
+ font-family: var(--ad-font-mono);
+ /* 11px is the source's own literal choice — smaller than --ad-step--2, the
+ smallest step on the scale, because a tooltip is meant to read as a
+ glance-only aside rather than body text. */
+ font-size: 11px;
+ white-space: nowrap;
+ line-height: 1.4;
+ z-index: 20;
+ opacity: 0;
+ visibility: hidden;
+ pointer-events: none;
+ transition: opacity var(--ad-dur-fast) var(--ad-ease);
+}
+span:has(> [role="tooltip"]):hover > [role="tooltip"],
+span:has(> [role="tooltip"]):focus-within > [role="tooltip"] {
+ opacity: 1;
+ visibility: visible;
+}
+
+/* Toast — the real pattern used for delete confirmations: a flat bar with an
+ optional destructive action and a dismiss. `[data-fixed]` pins it
+ bottom-right for real use; without it the toast stays in document flow for
+ documentation. `role="status"` is the correct live-region role for a
+ transient status message, so it is both the accessibility hook and the
+ style selector — no base class needed. The action and dismiss buttons are
+ plain <button>s told apart by `aria-label="Dismiss"`, an attribute they
+ need anyway for the icon-only close control. */
+[role="status"] {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ min-width: 20rem;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+}
+[role="status"][data-fixed] {
+ position: fixed;
+ inset-block-end: var(--ad-space-s);
+ inset-inline-end: var(--ad-space-s);
+ z-index: 60;
+}
+[role="status"] > span { flex: 1; }
+[role="status"] button {
+ all: unset;
+ cursor: pointer;
+ font-weight: 600;
+ color: var(--ad-color-danger);
+}
+[role="status"] button[aria-label="Dismiss"] {
+ color: var(--ad-color-text-tertiary);
+ font-size: 1rem;
+ font-weight: 400;
+ line-height: 1;
+}
+[role="status"] button[aria-label="Dismiss"]:hover { color: var(--ad-color-text-primary); }
+
+/* Dialog — the native <dialog> element: a centered modal, flat surface, no
+ colored header, hairline dividers between title/body/footer. A <header>
+ holds the title and a <footer> holds the actions, matching the elements
+ the tag already wants; everything else in between is the body, styled by
+ excluding those two rather than by a class of its own.
+
+ `.dialog-demo` scopes a static, always-open example to its own preview
+ container instead of the viewport, so it can sit safely inside a
+ documentation page — this is a doc-only affordance, not part of Dialog's
+ contract. It relies on the browser's own dialog centering: the UA
+ stylesheet already gives an open <dialog> `position: absolute; inset: 0;
+ margin: auto`, which centers it inside the nearest positioned ancestor —
+ here, `.dialog-demo` itself. A real `.showModal()` call gets the genuine
+ viewport-fixed overlay and `::backdrop` for free; a plain `open` attribute
+ (used here so the demo renders without JavaScript) does not paint a
+ backdrop at all, since `::backdrop` only exists for a dialog shown
+ modally — wire the real open/close state and call `.showModal()` in the
+ consuming app. */
+.dialog-demo {
+ position: relative;
+ min-height: 20rem;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-lg);
+}
+
+dialog {
+ width: 26rem;
+ max-width: calc(100% - (2 * var(--ad-space-s)));
+ height: fit-content;
+ padding: 0;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ overflow: hidden;
+}
+dialog::backdrop {
+ background: rgba(20, 18, 14, 0.35);
+}
+dialog > header {
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-sans);
+ font-weight: 600;
+ font-size: var(--ad-step-0);
+ color: var(--ad-color-text-primary);
+}
+dialog > footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+}
+dialog > *:not(header):not(footer) {
+ padding: var(--ad-space-m);
+ margin: 0;
+ color: var(--ad-color-text-primary);
+ font-size: var(--ad-step--1);
+}
+
+/* Tabs
+ Follows the real WAI-ARIA tabs pattern: a `[role="tablist"]` of
+ `[role="tab"]` buttons controlling `[role="tabpanel"]`s. Those roles are
+ required for the pattern to be accessible in the first place, so they
+ double as the entire styling contract — no classes at all for the base
+ shape. The keyboard wiring lives in assets/js/system.js and is
+ variant-agnostic — it only cares about `[data-tabs]` / `[role=tab]`.
+
+ Two real variants: `underline` (mono, accent underline — primary section
+ nav, the default) and `segmented` (pill track, active segment lifts to the
+ surface — filters like All · Overdue · Upcoming, never primary
+ navigation), chosen with `data-variant="segmented"` on the tablist since
+ neither has a natural attribute of its own. */
+
+/* Every rule below the list container pairs [role="tablist"] with
+ [role="tab"] rather than styling [role="tab"] alone — Tab buttons are
+ still literal <button> elements, so the compound selector's extra
+ specificity is what keeps Button's base styling (button.css) from
+ winning the cascade on shared properties like padding and background. */
+
+[role="tablist"] {
+ display: flex;
+ gap: var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+[role="tablist"] [role="tab"] {
+ padding: 0.5rem 0;
+ margin-bottom: -1px;
+ background: none;
+ border: 0;
+ border-bottom: 2px solid transparent;
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--1);
+ letter-spacing: 0.02em;
+ cursor: pointer;
+ transition: color var(--ad-dur-fast) var(--ad-ease), border-color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"] [role="tab"]:hover { color: var(--ad-color-text-primary); }
+[role="tablist"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ border-bottom-color: var(--ad-color-accent);
+}
+
+[role="tablist"][data-variant="segmented"] {
+ display: inline-flex;
+ gap: 0.125rem;
+ padding: 0.1875rem;
+ border-bottom: 0;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-md);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"] {
+ all: unset;
+ cursor: pointer;
+ padding: 0.375rem 0.875rem;
+ border-radius: var(--ad-radius-sm);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 550;
+ color: var(--ad-color-text-secondary);
+ transition: background-color var(--ad-dur-fast) var(--ad-ease), color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ background: var(--ad-color-surface-card);
+ box-shadow: var(--ad-shadow-sm);
+}
+
+[role="tabpanel"] { padding-top: var(--ad-space-s); }
+[role="tabpanel"][hidden] { display: none; }
+[role="tabpanel"]:focus-visible { outline-offset: 4px; }
+
+/* Documentation site chrome. Everything below the `.ad-doc-` prefix belongs
+ to this site rather than to the design system it documents — including the
+ width/height constants used directly below. The source design system
+ defines no container-width tokens at all, so there is nothing under
+ `--ad-*` for a doc shell to reach for; the Space foundation page's own
+ notes say to omit that group entirely rather than invent one. */
+
+.ad-doc-skip {
+ position: absolute;
+ inset-inline-start: var(--ad-space-xs);
+ inset-block-start: -4rem;
+ z-index: 40;
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ box-shadow: var(--ad-shadow-float);
+ transition: inset-block-start var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-skip:focus { inset-block-start: var(--ad-space-2xs); }
+
+.ad-doc-header {
+ position: sticky;
+ inset-block-start: 0;
+ z-index: 30;
+ height: 3.5rem;
+ background: color-mix(in srgb, var(--ad-color-surface-page) 92%, transparent);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-header__inner {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ height: 100%;
+ max-width: 72rem;
+ margin-inline: auto;
+ padding-inline: var(--ad-space-xs);
+}
+
+.ad-doc-wordmark {
+ display: flex;
+ align-items: baseline;
+ gap: var(--ad-space-2xs);
+ color: var(--ad-color-text-primary);
+ text-decoration: none;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ font-weight: 600;
+ letter-spacing: -0.01em;
+ white-space: nowrap;
+}
+.ad-doc-wordmark:hover { color: var(--ad-color-text-primary); }
+.ad-doc-wordmark__mark {
+ width: 1.375rem;
+ height: 1.375rem;
+ flex: none;
+ align-self: center;
+ color: var(--ad-color-accent);
+}
+.ad-doc-wordmark__version {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+
+.ad-doc-header__actions { margin-inline-start: auto; display: flex; align-items: center; gap: var(--ad-space-2xs); }
+
+.ad-doc-layout {
+ display: grid;
+ grid-template-columns: 16rem minmax(0, 1fr);
+ gap: var(--ad-space-l);
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-m) var(--ad-space-xs) var(--ad-space-2xl);
+}
+
+.ad-doc-sidebar {
+ position: sticky;
+ inset-block-start: calc(3.5rem + var(--ad-space-m));
+ align-self: start;
+ max-height: calc(100vh - 3.5rem - var(--ad-space-l));
+ overflow-y: auto;
+ padding-inline-end: var(--ad-space-2xs);
+}
+.ad-doc-nav__group + .ad-doc-nav__group { margin-top: var(--ad-space-s); }
+.ad-doc-nav__heading {
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-doc-nav__link {
+ display: block;
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ margin-inline-start: calc(var(--ad-space-xs) * -1);
+ border-inline-start: 2px solid transparent;
+ border-radius: 0 var(--ad-radius-sm) var(--ad-radius-sm) 0;
+ color: var(--ad-color-text-secondary);
+ font-size: var(--ad-step--1);
+ text-decoration: none;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-nav__link:hover { color: var(--ad-color-text-primary); background: var(--ad-color-surface-fill); }
+.ad-doc-nav__link[aria-current="page"] {
+ color: var(--ad-color-accent-ink);
+ border-inline-start-color: var(--ad-color-accent);
+ background: var(--ad-color-accent-soft);
+ font-weight: 500;
+}
+
+.ad-doc-main {
+ min-width: 0;
+ /* The design system's own body-text token (--ad-step-0, 18–20px) is right
+ for a product screen but too large for long-form documentation prose.
+ This site's own copy gets a plain, fixed 16px instead — the token above
+ is unchanged, and specimen tables/demos below still show its real
+ value. */
+ font-size: 1rem;
+}
+.ad-doc-main > * { max-width: 50rem; }
+
+.ad-doc-title { margin-bottom: var(--ad-space-xs); }
+.ad-doc-title__section {
+ display: block;
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-accent);
+}
+
+.ad-doc-main h2 {
+ margin-block: var(--ad-space-2xl) var(--ad-space-xs);
+ padding-bottom: var(--ad-space-2xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-main h3 { margin-block: var(--ad-space-l) var(--ad-space-2xs); }
+.ad-doc-main h4 { margin-block: var(--ad-space-s) var(--ad-space-2xs); }
+.ad-doc-main p,
+.ad-doc-main ul,
+.ad-doc-main ol { max-width: 68ch; }
+.ad-doc-main ul:not([class]),
+.ad-doc-main ol:not([class]) { padding-inline-start: var(--ad-space-s); }
+
+.ad-doc-footer {
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-s) var(--ad-space-xs) var(--ad-space-l);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-tertiary);
+ font-size: var(--ad-step--1);
+}
+.ad-doc-footer__inner { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); justify-content: space-between; }
+
+/* `button.ad-doc-nav-toggle` (element + class) rather than a bare class:
+ button.css's own base rule is `button:not(.icon-button)`, which is also
+ one element + one class in specificity and sets `display: inline-flex` —
+ a bare `.ad-doc-nav-toggle { display: none }` loses that fight and the
+ toggle stays visible at every width. Matching its specificity here (and
+ coming later in the concatenated stylesheet) settles it in source order. */
+button.ad-doc-nav-toggle { display: none; }
+
+@media (max-width: 60rem) {
+ .ad-doc-layout { grid-template-columns: minmax(0, 1fr); gap: var(--ad-space-s); }
+ .ad-doc-sidebar {
+ position: static;
+ max-height: none;
+ }
+ /* The toggle is a normal Button; the panel it opens gets its own card
+ treatment. Putting the same card styling on the <aside> wrapper too
+ nested a button-shaped box inside an outer box for no reason. */
+ .ad-doc-nav {
+ margin-top: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ }
+ .ad-doc-sidebar[data-collapsed="true"] > .ad-doc-nav { display: none; }
+ button.ad-doc-nav-toggle {
+ display: flex;
+ width: 100%;
+ justify-content: space-between;
+ }
+}
+
+@media (max-width: 34rem) {
+ .ad-doc-wordmark__version { display: none; }
+ .ad-doc-layout { padding-inline: var(--ad-space-2xs); }
+}
+
+/* The live-example figure used throughout the docs — see the "How the
+ example demos work" section on the Using the CSS page. One class on the
+ outer <figure>; everything inside is styled by plain element selectors,
+ since figcaption/div/details/summary/pre already say what they are.
+ `data-layout` and `data-surface` on the preview <div> distinguish the
+ handful of demos that need a non-default arrangement. */
+
+.example {
+ margin: var(--ad-space-s) 0 var(--ad-space-l);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+ background: var(--ad-color-surface-card);
+ overflow: hidden;
+}
+
+.example figcaption {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-secondary);
+}
+
+.example > div {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-s);
+}
+/* nowrap is load-bearing: a column-direction flex container with wrap enabled
+ sizes its single line against the container rather than the content, which
+ left every stacked example with a few hundred pixels of dead space. */
+.example > div[data-layout="stack"] { flex-direction: column; flex-wrap: nowrap; align-items: stretch; }
+.example > div[data-layout="grid"] {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
+ align-items: start;
+}
+.example > div[data-surface="sunken"] { background: var(--ad-color-surface-fill); }
+.example > div > * { min-width: 0; }
+
+.example details {
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+.example summary {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ color: var(--ad-color-text-secondary);
+ cursor: pointer;
+ list-style: none;
+ user-select: none;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+}
+.example summary::-webkit-details-marker { display: none; }
+.example summary::before {
+ content: "";
+ width: 0.5rem;
+ height: 0.5rem;
+ border-inline-end: 1.5px solid currentColor;
+ border-block-end: 1.5px solid currentColor;
+ rotate: -45deg;
+ transition: rotate var(--ad-dur-fast) var(--ad-ease);
+}
+.example details[open] > summary::before { rotate: 45deg; }
+.example summary:hover { color: var(--ad-color-text-primary); }
+
+.example pre {
+ margin: 0;
+ padding: var(--ad-space-xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+ tab-size: 2;
+}
+.example pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Standalone fenced code blocks in the prose (unhighlighted — there is no
+ syntax highlighter in this build any more, just Kramdown's plain
+ <pre><code class="language-*">). */
+.ad-doc-main > pre {
+ margin-block: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+}
+.ad-doc-main > pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Presentation for the token reference tables and swatch grids, plus a
+ generic data-table utility. All of this is documentation chrome — the
+ design system itself has no Table component (see the note on the removed
+ `table`/`progress`/`callout` pages in git history). */
+
+.ad-swatch-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs) var(--ad-space-s);
+ padding: 0;
+ list-style: none;
+}
+.ad-swatch {
+ display: flex;
+ flex-direction: column;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ overflow: hidden;
+ background: var(--ad-color-surface-card);
+}
+.ad-swatch__chip {
+ display: flex;
+ align-items: flex-end;
+ justify-content: flex-end;
+ height: 4rem;
+ padding: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+}
+.ad-swatch__meta {
+ display: flex;
+ flex-direction: column;
+ gap: 0.1rem;
+ padding: var(--ad-space-2xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+}
+.ad-swatch__hex { color: var(--ad-color-text-primary); text-transform: uppercase; }
+.ad-swatch__ratio { color: var(--ad-color-text-tertiary); }
+
+.ad-alias-row { display: flex; align-items: center; gap: var(--ad-space-2xs); }
+.ad-alias-chip {
+ width: 1.5rem;
+ height: 1.5rem;
+ flex: none;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+}
+.ad-alias-chip--pair { display: flex; }
+.ad-alias-chip--pair > span { flex: 1; }
+
+/* Type specimen rows. */
+.ad-specimen {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ gap: var(--ad-space-3xs);
+ padding-block: var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-specimen__meta {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-specimen__sample {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: var(--ad-color-text-primary);
+}
+
+/* Space and radius rulers. */
+.ad-ruler { display: flex; flex-direction: column; gap: var(--ad-space-2xs); }
+.ad-ruler__row { display: grid; grid-template-columns: 6.5rem 4.5rem 1fr; align-items: center; gap: var(--ad-space-xs); font-family: var(--ad-font-mono); font-size: 11px; }
+.ad-ruler__bar { height: 0.75rem; background: var(--ad-color-accent); border-radius: var(--ad-radius-sm); }
+.ad-ruler__num { color: var(--ad-color-text-tertiary); text-align: right; }
+
+.ad-radius-grid, .ad-elevation-grid, .ad-motion-grid, .ad-accent-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs);
+ padding: 0;
+ list-style: none;
+}
+.ad-radius-demo {
+ height: 4.5rem;
+ background: var(--ad-color-accent-soft);
+ border: 1px solid var(--ad-color-accent);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-elevation-demo {
+ height: 4.5rem;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-motion-demo {
+ height: 4.5rem;
+ display: grid;
+ place-items: center;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-md);
+ margin-bottom: var(--ad-space-2xs);
+ cursor: pointer;
+ overflow: hidden;
+}
+.ad-motion-demo__dot {
+ width: 1.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent);
+ translate: -3rem 0;
+ transition-property: translate;
+ transition-duration: var(--ad-dur-base);
+ transition-timing-function: var(--ad-ease);
+}
+.ad-motion-demo:hover .ad-motion-demo__dot,
+.ad-motion-demo:focus-visible .ad-motion-demo__dot { translate: 3rem 0; }
+
+.ad-accent-demo {
+ height: 4.5rem;
+ display: flex;
+ align-items: flex-end;
+ padding: var(--ad-space-2xs);
+ border-radius: var(--ad-radius-md);
+ color: #fff;
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ margin-bottom: var(--ad-space-2xs);
+}
+
+/* Token names read `<code>` sitewide, which already carries a mono chip
+ look for running prose (see :not(pre) > code in base/typography.css).
+ Inside these specimen/swatch/ruler rows the old `.ad-token-name` class was
+ plain mono text with no chip, so `<code>` is flattened back to that look
+ here the same way table cells are. */
+.ad-swatch code, .ad-specimen code, .ad-ruler code, .ad-alias-row code {
+ background: none;
+ border: 0;
+ padding: 0;
+ font-size: var(--ad-step--2);
+ color: var(--ad-color-text-primary);
+ white-space: nowrap;
+}
+
+/* A documentation aside. Like `<table>`, this is site chrome for the prose
+ itself, not a system component — the real system has no Callout. */
+.ad-callout {
+ margin-block: var(--ad-space-s);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-left: 3px solid var(--ad-color-accent);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+}
+/* Generic reference tables (component APIs, alias tables, the contrast
+ contract). `<table>` IS a table — no class needed to say so. Every table
+ on this site uses the one compact density, so there is no separate
+ modifier for it. `display: block` plus `overflow-x: auto` gives the table
+ its own horizontal scrollbar on narrow viewports without a wrapper div;
+ the anonymous table box the browser generates around `<thead>`/`<tbody>`
+ still does the row/column layout. `<code>` replaces the old
+ `.ad-token-name` / `.ad-table__code` cells — it is already styled
+ sitewide, just re-flattened here to a plain mono run instead of the
+ chip-with-border look prose code gets, to match this table's original
+ look. `.num` is the one sanctioned class left, for right-aligned numeric
+ columns — there is no attribute that means "this column is a number". */
+table { width: 100%; border-collapse: collapse; font-size: var(--ad-step--1); display: block; overflow-x: auto; }
+table th, table td {
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ text-align: left;
+ vertical-align: top;
+}
+table th { font-family: var(--ad-font-mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ad-color-text-secondary); font-weight: 500; }
+table code { background: none; border: 0; padding: 0; font-size: var(--ad-step--2); white-space: nowrap; }
+.num { text-align: right; font-variant-numeric: tabular-nums; }
+
+/* Home page. */
+.ad-hero { padding-block: var(--ad-space-m) var(--ad-space-l); }
+.ad-hero__title {
+ font-size: var(--ad-step-4);
+ margin-bottom: var(--ad-space-xs);
+}
+.ad-hero__actions { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); margin-top: var(--ad-space-s); }
+.ad-stat-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-l);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+}
+.ad-stat__value {
+ display: block;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-2);
+ font-weight: 600;
+ line-height: 1.16;
+ font-variant-numeric: tabular-nums;
+}
+.ad-stat__label {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+
+/* Card chrome. The pane supplies the frame; this just insets the page. */
+body { padding: var(--ad-space-l) var(--ad-space-m); }
+.ad-doc-main { max-width: 50rem; margin-inline: auto; }
+.ad-doc-main > * { max-width: none; }
+</style>
+</head>
+<body>
+<main class="ad-doc-main">
+<header class="ad-doc-title"><span class="ad-doc-title__section">Components</span>
+ <h1>Tag</h1>
+</header>
+<p class="ad-lead">A removable mono chip for freeform tags — locations, categories. `[data-dashed]` renders an "add tag" affordance instead of a value.</p>
+<h2 id="default-and-removable">Default and removable</h2>
+
+<p>Same reasoning as Badge: a <code><span></code> is too generic to select bare, so Tag
+keeps one class. The remove control is a real <code><button></code>, styled through the
+descendant selector <code>.tag button</code> — no class of its own.</p>
+
+<figure class="example">
+<figcaption>Tags</figcaption>
+<div>
+<span class="tag">location:garage</span>
+<span class="tag">category:appliance</span>
+<span class="tag">
+ category:receipt
+ <button aria-label="Remove tag">×</button>
+</span>
+</div>
+<details open=""><summary>Markup</summary>
+<pre><code><span class="tag">location:garage</span>
+<span class="tag">category:appliance</span>
+<span class="tag">
+ category:receipt
+ <button aria-label="Remove tag">×</button>
+</span></code></pre>
+</details>
+</figure>
+
+<h2 id="dashed--the-add-tag-affordance">Dashed — the “add tag” affordance</h2>
+
+<figure class="example">
+<figcaption>Add tag</figcaption>
+<div>
+<div class="tag-set">
+ <span class="tag">location:garage</span>
+ <span class="tag" data-dashed="">+ add tag</span>
+</div>
+</div>
+<details><summary>Markup</summary>
+<pre><code><div class="tag-set">
+ <span class="tag">location:garage</span>
+ <span class="tag" data-dashed>+ add tag</span>
+</div></code></pre>
+</details>
+</figure>
+
+<h2 id="api">API</h2>
+
+<table>
+ <thead><tr><th scope="col">Element / attribute</th><th scope="col">Values</th><th scope="col">Notes</th></tr></thead>
+ <tbody>
+ <tr><td><code>span.tag</code></td><td></td><td>Required base.</td></tr>
+ <tr><td><code>button</code></td><td></td><td>Nest a real <code><button aria-label="Remove tag"></code> to render the × affordance — styled via <code>.tag button</code>, no class needed.</td></tr>
+ <tr><td><code>data-dashed</code></td><td>boolean</td><td>Renders an "add tag" affordance instead of a value.</td></tr>
+ </tbody>
+</table>
+</main>
+<script>
+// Behavior for the pieces of the system that need it. Everything else is
+// CSS and semantic HTML on purpose — a design system that only works with
+// JavaScript running is a component library with extra steps.
+(function () {
+ "use strict";
+
+ var STORAGE_KEY = "ad-theme";
+
+ function readStored() {
+ try {
+ return localStorage.getItem(STORAGE_KEY);
+ } catch (error) {
+ return null;
+ }
+ }
+
+ function writeStored(value) {
+ try {
+ localStorage.setItem(STORAGE_KEY, value);
+ } catch (error) {
+ /* Nothing to do — the media query still gives a sensible default. */
+ }
+ }
+
+ function systemPrefersDark() {
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
+ }
+
+ function currentTheme() {
+ var explicit = document.documentElement.getAttribute("data-theme");
+ if (explicit) return explicit;
+ return systemPrefersDark() ? "dark" : "light";
+ }
+
+ function paintToggle(theme) {
+ document.querySelectorAll("[data-theme-toggle]").forEach(function (toggle) {
+ var next = theme === "dark" ? "light" : "dark";
+ toggle.querySelectorAll("[data-theme-icon]").forEach(function (icon) {
+ icon.hidden = icon.dataset.themeIcon !== theme;
+ });
+ toggle.setAttribute("aria-label", "Switch to the " + next + " theme");
+ toggle.setAttribute("title", "Switch to the " + next + " theme");
+ var label = toggle.querySelector("[data-theme-label]");
+ if (label) label.textContent = "Switch to the " + next + " theme";
+ });
+ }
+
+ function setUpTheme() {
+ paintToggle(currentTheme());
+
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-theme-toggle]");
+ if (!toggle) return;
+
+ var next = currentTheme() === "dark" ? "light" : "dark";
+ document.documentElement.setAttribute("data-theme", next);
+ writeStored(next);
+ paintToggle(next);
+ });
+
+ // Follow the OS while the reader has not made an explicit choice.
+ window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", function () {
+ if (!readStored()) paintToggle(currentTheme());
+ });
+ }
+
+ // Tabs, wired to the WAI-ARIA authoring practices: arrows move, Home/End
+ // jump, and only the selected tab stays in the tab order.
+ function setUpTabs() {
+ document.querySelectorAll("[data-tabs]").forEach(function (root) {
+ var tabs = Array.prototype.slice.call(root.querySelectorAll('[role="tab"]'));
+ if (!tabs.length) return;
+
+ function select(tab, focus) {
+ tabs.forEach(function (candidate) {
+ var selected = candidate === tab;
+ candidate.setAttribute("aria-selected", String(selected));
+ candidate.tabIndex = selected ? 0 : -1;
+ var panel = document.getElementById(candidate.getAttribute("aria-controls"));
+ if (panel) panel.hidden = !selected;
+ });
+ if (focus) tab.focus();
+ }
+
+ root.addEventListener("click", function (event) {
+ var tab = event.target.closest('[role="tab"]');
+ if (tab) select(tab, false);
+ });
+
+ root.addEventListener("keydown", function (event) {
+ var index = tabs.indexOf(document.activeElement);
+ if (index === -1) return;
+
+ var next = null;
+ if (event.key === "ArrowRight") next = tabs[(index + 1) % tabs.length];
+ else if (event.key === "ArrowLeft") next = tabs[(index - 1 + tabs.length) % tabs.length];
+ else if (event.key === "Home") next = tabs[0];
+ else if (event.key === "End") next = tabs[tabs.length - 1];
+
+ if (next) {
+ event.preventDefault();
+ select(next, true);
+ }
+ });
+
+ select(tabs.filter(function (tab) {
+ return tab.getAttribute("aria-selected") === "true";
+ })[0] || tabs[0], false);
+ });
+ }
+
+ function setUpNavToggle() {
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-nav-toggle]");
+ if (!toggle) return;
+
+ var sidebar = toggle.closest("[data-sidebar]");
+ var collapsed = sidebar.dataset.collapsed !== "false";
+ sidebar.dataset.collapsed = String(!collapsed);
+ toggle.setAttribute("aria-expanded", String(collapsed));
+ });
+ }
+
+ setUpTheme();
+ setUpTabs();
+ setUpNavToggle();
+})();
+
+</script>
+</body>
+</html>
diff --git a/design-bundle/components/toast.html b/design-bundle/components/toast.html
new file mode 100644
index 0000000..ac1e35c
--- /dev/null
+++ b/design-bundle/components/toast.html
@@ -0,0 +1,1809 @@
+<!-- @dsCard group="Feedback" -->
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Toast</title>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&display=swap">
+<style>
+/* Arbitrary Definitions — design tokens. Hand-maintained; edit values directly. */
+
+:root {
+ /* Palette ramps — reference only. Components use the aliases below. */
+ --ad-neutral-0: #faf9f7;
+ --ad-neutral-50: #fdfdfb;
+ --ad-neutral-100: #f1f0ed;
+ --ad-neutral-150: #e8e6e2;
+ --ad-neutral-200: #dcdad7;
+ --ad-neutral-300: #c0bdb9;
+ --ad-neutral-500: #888681;
+ --ad-neutral-600: #5f5d59;
+ --ad-neutral-800: #2a2926;
+ --ad-neutral-900: #100f0d;
+ --ad-neutral-dark-0: #0e0d0b;
+ --ad-neutral-dark-50: #151412;
+ --ad-neutral-dark-100: #1e1d1a;
+ --ad-neutral-dark-150: #252421;
+ --ad-neutral-dark-200: #2c2b29;
+ --ad-neutral-dark-300: #44423f;
+ --ad-neutral-dark-500: #7c7a76;
+ --ad-neutral-dark-600: #a09e9a;
+ --ad-neutral-dark-800: #d9d7d4;
+ --ad-neutral-dark-900: #e9e8e6;
+ --ad-accent-teal: #3a6a63;
+ --ad-accent-clay: #9a5a3c;
+ --ad-accent-ink-blue: #2b4a78;
+ --ad-accent-ochre: #93701f;
+ --ad-accent-plum: #6a3a5c;
+
+ /* Surfaces */
+ --ad-color-surface-page: #faf9f7;
+ --ad-color-surface-card: #fdfdfb;
+ --ad-color-surface-fill: #f1f0ed;
+ --ad-color-surface-fill-2: #e8e6e2;
+
+ /* Borders */
+ --ad-color-border-hairline: #dcdad7;
+ --ad-color-border-default: #c0bdb9;
+
+ /* Text */
+ --ad-color-text-primary: #100f0d;
+ --ad-color-text-secondary: #5f5d59;
+ --ad-color-text-tertiary: #888681;
+
+ /* Accent */
+ --ad-color-accent: #3a6a63;
+ --ad-color-accent-fg: #ffffff;
+ --ad-color-accent-ink: color-mix(in oklch, var(--ad-color-accent) 78%, black);
+ --ad-color-accent-soft: color-mix(in oklch, var(--ad-color-accent) 12%, white);
+
+ /* Status (fixed — never swapped like the accent) */
+ --ad-color-success: #3f7a4a;
+ --ad-color-success-bg: color-mix(in oklch, var(--ad-color-success) 12%, white);
+ --ad-color-danger: #ad3a2c;
+ --ad-color-danger-bg: color-mix(in oklch, var(--ad-color-danger) 10%, white);
+
+ /* Type families */
+ --ad-font-sans: -apple-system, "Helvetica Neue", Helvetica, Arial, system-ui, sans-serif;
+ --ad-font-mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, Consolas, "Courier New", monospace;
+ --ad-font-serif: Georgia, "Times New Roman", "Iowan Old Style", serif;
+
+ /* Type steps — fluid (Utopia scale), the design system's own body-text size */
+ --ad-step--2: clamp(0.7813rem, 0.7736rem + 0.0341vw, 0.8rem);
+ --ad-step--1: clamp(0.9375rem, 0.9119rem + 0.1136vw, 1rem);
+ --ad-step-0: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-step-1: clamp(1.35rem, 1.2631rem + 0.3864vw, 1.5625rem);
+ --ad-step-2: clamp(1.62rem, 1.4837rem + 0.6057vw, 1.9531rem);
+ --ad-step-3: clamp(1.944rem, 1.7405rem + 0.9044vw, 2.4414rem);
+ --ad-step-4: clamp(2.3328rem, 2.0387rem + 1.3072vw, 3.0518rem);
+ --ad-step-5: clamp(2.7994rem, 2.384rem + 1.8461vw, 3.8147rem);
+
+ /* Space */
+ --ad-space-3xs: clamp(0.3125rem, 0.3125rem + 0vw, 0.3125rem);
+ --ad-space-2xs: clamp(0.5625rem, 0.5369rem + 0.1136vw, 0.625rem);
+ --ad-space-xs: clamp(0.875rem, 0.8494rem + 0.1136vw, 0.9375rem);
+ --ad-space-s: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-space-m: clamp(1.6875rem, 1.6108rem + 0.3409vw, 1.875rem);
+ --ad-space-l: clamp(2.25rem, 2.1477rem + 0.4545vw, 2.5rem);
+ --ad-space-xl: clamp(3.375rem, 3.2216rem + 0.6818vw, 3.75rem);
+ --ad-space-2xl: clamp(4.5rem, 4.2955rem + 0.9091vw, 5rem);
+ --ad-space-3xl: clamp(6.75rem, 6.4432rem + 1.3636vw, 7.5rem);
+
+ /* Radius */
+ --ad-radius-sm: 4px;
+ --ad-radius-md: 6px;
+ --ad-radius-lg: 10px;
+ --ad-radius-full: 999px;
+ --ad-border-w: 1px;
+
+ /* Elevation */
+ --ad-shadow-sm: 0 1px 2px rgba(20, 18, 14, 0.05);
+ --ad-shadow-float: 0 2px 6px rgba(20, 18, 14, 0.06), 0 10px 24px -12px rgba(20, 18, 14, 0.18);
+
+ /* Motion */
+ --ad-dur-fast: 0.12s;
+ --ad-dur-base: 0.16s;
+ --ad-ease: cubic-bezier(0.2, 0, 0, 1);
+}
+
+@media (prefers-color-scheme: dark) {
+ :root:not([data-theme="light"]) {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+ }
+}
+
+:root[data-theme="dark"] {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ :root {
+ --ad-dur-fast: 0ms;
+ --ad-dur-base: 0ms;
+ }
+}
+
+/* A short reset. Everything it sets is something the system would otherwise
+ have to override in every component.
+
+ The rem values used for this documentation site's own chrome (measure,
+ content width, sidebar width, header height) belong to this site, not to
+ the design system it documents — the source design system defines no
+ container-width tokens at all, so there is nothing under `--ad-*` to reach
+ for here. See the header comment in site/shell.css. */
+
+*,
+*::before,
+*::after { box-sizing: border-box; }
+
+html {
+ -webkit-text-size-adjust: 100%;
+ scroll-behavior: smooth;
+ scroll-padding-top: calc(3.5rem + var(--ad-space-s));
+}
+
+@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;
+ }
+}
+
+body {
+ margin: 0;
+ background: var(--ad-color-surface-page);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ line-height: 1.55;
+ font-synthesis-weight: none;
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+}
+
+::selection {
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+}
+
+h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd { margin: 0; }
+ul[class], ol[class] { list-style: none; margin: 0; padding: 0; }
+
+img, svg, video { display: block; max-width: 100%; height: auto; }
+
+button, input, select, textarea {
+ font: inherit;
+ color: inherit;
+}
+
+hr {
+ height: 1px;
+ margin: var(--ad-space-l) 0;
+ border: 0;
+ background: var(--ad-color-border-hairline);
+}
+
+/* One focus ring, defined once. No component is allowed its own. */
+:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+ border-radius: var(--ad-radius-sm);
+}
+:focus:not(:focus-visible) { outline: none; }
+
+[hidden] { display: none !important; }
+
+.ad-visually-hidden {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip-path: inset(50%);
+ white-space: nowrap;
+ border: 0;
+}
+
+/* Every rule here reads a type token for family and size. Weight, line-height,
+ and letter-spacing are hardcoded to the exact numbers the source ships —
+ there is no `--ad-weight-*` / `--ad-leading-*` / `--ad-tracking-*` scale in
+ the real system, only eight fixed compound styles (see the Typography
+ foundation page, which this file implements verbatim). */
+
+.type-display { font: 600 var(--ad-step-4)/1.08 var(--ad-font-sans); letter-spacing: -0.015em; }
+.type-h1, h1 { font: 600 var(--ad-step-3)/1.12 var(--ad-font-sans); letter-spacing: -0.012em; }
+.type-h2, h2 { font: 600 var(--ad-step-2)/1.16 var(--ad-font-sans); letter-spacing: -0.01em; }
+.type-h3, h3 { font: 600 var(--ad-step-1)/1.25 var(--ad-font-sans); }
+.type-body { font: 400 var(--ad-step-0)/1.55 var(--ad-font-sans); }
+.type-body-sm { font: 400 var(--ad-step--1)/1.5 var(--ad-font-sans); }
+.type-label {
+ font: 500 var(--ad-step--2)/1 var(--ad-font-mono);
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: var(--ad-color-text-secondary);
+}
+.type-mono { font: 400 var(--ad-step--1)/1.5 var(--ad-font-mono); }
+
+/* Prose defaults for the documentation body copy. These read the same
+ tokens the compound classes above do; they are not a second scale. */
+h1, h2, h3, h4 { font-weight: 600; text-wrap: balance; }
+
+h4 { font: 600 var(--ad-step-0)/1.3 var(--ad-font-sans); }
+
+p, ul, ol { margin: 0 0 var(--ad-space-s); }
+li + li { margin-top: var(--ad-space-3xs); }
+
+a {
+ color: var(--ad-color-accent);
+ text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
+ text-underline-offset: 0.18em;
+ text-decoration-thickness: 1px;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ text-decoration-color var(--ad-dur-fast) var(--ad-ease);
+}
+a:hover {
+ color: var(--ad-color-accent-ink);
+ text-decoration-color: currentColor;
+}
+
+strong, b { font-weight: 600; }
+
+small { font-size: var(--ad-step--1); }
+
+.ad-lead {
+ font-size: var(--ad-step-1);
+ line-height: 1.5;
+ color: var(--ad-color-text-secondary);
+ max-width: 68ch;
+ text-wrap: pretty;
+}
+
+.ad-muted { color: var(--ad-color-text-secondary); }
+.ad-subtle { color: var(--ad-color-text-tertiary); }
+
+code, kbd, samp, pre {
+ font-family: var(--ad-font-mono);
+ font-feature-settings: "liga" 0;
+}
+
+:not(pre) > code {
+ padding: 0.15em 0.4em;
+ font-size: 0.875em;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-sm);
+ white-space: nowrap;
+}
+
+kbd {
+ display: inline-block;
+ padding: 0.1em 0.45em;
+ font-size: var(--ad-step--2);
+ line-height: 1.6;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-bottom-width: 2px;
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-secondary);
+}
+
+blockquote {
+ margin-block: var(--ad-space-s);
+ padding-left: var(--ad-space-s);
+ border-left: 2px solid var(--ad-color-accent);
+ color: var(--ad-color-text-secondary);
+}
+
+/* Button
+ A flat, quiet button that recedes rather than performs — no gradients, no
+ shine. Variants: default, primary, dark, ghost, danger. Sizes: sm, md
+ (default), lg — padding and font step only, weight and radius stay put.
+
+ The contract is the element itself: a bare <button> is styled directly, no
+ class required. An <a> that acts like a button (navigates rather than
+ changes something — see the Button page's "Links that act like buttons")
+ opts in with a single `.btn` class, since a bare `a` selector would catch
+ every ordinary prose link on the site. `:not(.icon-button)` keeps this
+ block from leaking onto IconButton, which shares the <button> tag but is
+ an entirely different shape. */
+
+button:not(.icon-button),
+a.btn {
+ --_bg: var(--ad-color-surface-card);
+ --_fg: var(--ad-color-text-primary);
+ --_border: var(--ad-color-border-default);
+ --_pad: var(--ad-space-xs) var(--ad-space-s);
+ --_font: var(--ad-step--1);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: var(--ad-space-2xs);
+ padding: var(--_pad);
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-sans);
+ font-size: var(--_font);
+ font-weight: 550;
+ letter-spacing: -0.01em;
+ line-height: 1.2;
+ text-align: center;
+ text-decoration: none;
+ white-space: nowrap;
+ cursor: pointer;
+ transition: filter var(--ad-dur-fast) var(--ad-ease),
+ border-color var(--ad-dur-fast) var(--ad-ease);
+}
+
+:is(button:not(.icon-button), a.btn):hover:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.96); }
+:is(button:not(.icon-button), a.btn):active:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.92); }
+
+button:not(.icon-button):disabled,
+:is(button:not(.icon-button), a.btn)[aria-disabled="true"] {
+ opacity: 0.5;
+ cursor: default;
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="primary"] {
+ --_bg: var(--ad-color-accent);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-accent);
+}
+
+/* Reserved for the single most-committing action in a view — a final Save,
+ never a whole row of them. */
+:is(button:not(.icon-button), a.btn)[data-variant="dark"] {
+ --_bg: var(--ad-color-text-primary);
+ --_fg: var(--ad-color-surface-page);
+ --_border: var(--ad-color-text-primary);
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="ghost"] {
+ --_bg: transparent;
+ --_fg: var(--ad-color-text-primary);
+ --_border: transparent;
+}
+
+/* The source hardcodes this label white rather than deriving it — danger
+ is not "on a filled accent surface", so --ad-color-accent-fg is reused
+ here only because its resolved value is the same literal white, not
+ because the two roles are the same thing. */
+:is(button:not(.icon-button), a.btn)[data-variant="danger"] {
+ --_bg: var(--ad-color-danger);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-danger);
+}
+
+:is(button:not(.icon-button), a.btn)[data-size="sm"] { --_pad: 0.375rem 0.75rem; --_font: 0.8125rem; }
+:is(button:not(.icon-button), a.btn)[data-size="lg"] { --_pad: 0.75rem 1.375rem; --_font: var(--ad-step-0); }
+
+:is(button:not(.icon-button), a.btn)[data-block] { width: 100%; }
+
+/* Button group — [role="group"] is already the correct ARIA role for a
+ toolbar of related buttons, so it doubles as the styling hook for free. */
+[role="group"] {
+ display: inline-flex;
+ isolation: isolate;
+}
+[role="group"] > button { border-radius: 0; }
+[role="group"] > button:first-child { border-start-start-radius: var(--ad-radius-md); border-end-start-radius: var(--ad-radius-md); }
+[role="group"] > button:last-child { border-start-end-radius: var(--ad-radius-md); border-end-end-radius: var(--ad-radius-md); }
+[role="group"] > button + button { margin-inline-start: -1px; }
+[role="group"] > button:hover,
+[role="group"] > button:focus-visible { z-index: 1; }
+
+/* IconButton — a square icon-only button. Always needs an aria-label; it
+ becomes both the accessible name and the native tooltip. There is no
+ attribute that distinguishes "a button that is only an icon" from a plain
+ Button, so this is the one sanctioned class in this file. */
+.icon-button {
+ --_size: 2.125rem;
+ --_bg: transparent;
+ --_border: transparent;
+ --_fg: var(--ad-color-text-secondary);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ padding: 0;
+ flex: none;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease),
+ color var(--ad-dur-fast) var(--ad-ease);
+}
+.icon-button:hover:not(:disabled) { background: var(--ad-color-surface-fill-2); }
+.icon-button:disabled { opacity: 0.5; cursor: default; }
+
+.icon-button[data-size="sm"] { --_size: 1.75rem; }
+.icon-button[data-size="lg"] { --_size: 2.5rem; }
+
+.icon-button[data-variant="outline"] { --_bg: var(--ad-color-surface-card); --_border: var(--ad-color-border-default); --_fg: var(--ad-color-text-primary); }
+.icon-button[data-variant="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); }
+
+/* Field
+ The labeled field wrapper — a <label> that wraps its own caption text
+ together with the control, so the association is native and needs no
+ `for`/`id` pair. The caption is mono, uppercase, tracked — the same
+ catalog-label convention as Badge and type-label.
+
+ A <label> is reused for three different shapes across this system (this
+ wrapper, Checkbox/Radio's row, Switch's row), so it cannot be styled bare
+ without one shape leaking into another. Field is the one of the three with
+ no distinguishing attribute of its own, so it gets the sanctioned class;
+ Checkbox/Radio tell themselves apart structurally instead (see below). */
+.field {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ min-width: 0;
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Hint and error read the native fine-print element. `role="alert"` is a
+ real live-region role for validation text, so it doubles as the selector
+ that turns a hint red. */
+.field > small {
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+.field > small[role="alert"] { color: var(--ad-color-danger); }
+
+/* Input / Select — bordered, matched to each other exactly. Both tags ARE
+ the component, so they are styled bare; nothing else on this site uses a
+ text input or a select. */
+input:not([type="checkbox"]):not([type="radio"]),
+select {
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ outline: none;
+ transition: border-color var(--ad-dur-fast) var(--ad-ease),
+ box-shadow var(--ad-dur-fast) var(--ad-ease);
+}
+input::placeholder { color: var(--ad-color-text-tertiary); }
+
+input:not([type="checkbox"]):not([type="radio"]):focus,
+select:focus {
+ border-color: var(--ad-color-accent);
+ box-shadow: 0 0 0 2px var(--ad-color-accent-soft);
+}
+
+input:not([type="checkbox"]):not([type="radio"]):disabled,
+select:disabled {
+ opacity: 0.5;
+ cursor: default;
+}
+
+/* The chevron is the one flagged color exception in this component set:
+ CSS cannot point a data-URI background-image at a custom property, so its
+ stroke is a literal hex rather than a var(--ad-*) reference. It is picked
+ to match --ad-color-text-secondary and commented here so it is not
+ mistaken for a stray color. */
+select {
+ appearance: none;
+ padding-inline-end: var(--ad-space-l);
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%235f5d59' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right var(--ad-space-xs) center;
+}
+
+input[aria-invalid="true"],
+select[aria-invalid="true"] {
+ border-color: var(--ad-color-danger);
+ background-color: var(--ad-color-danger-bg);
+}
+
+/* Checkbox / Radio — the real native controls, tinted with accent-color
+ instead of a hand-drawn box. This is not just a rename: it deletes the
+ custom SVG-checkmark markup entirely and lets the browser draw its own
+ (platform-appropriate) check and dot. `role="switch"` is excluded here so
+ Switch — a checkbox playing a different role — can style itself below
+ without this rule fighting it. A <label> wrapping a checkbox/radio is told
+ apart from Field's <label> structurally: it :has() one as a direct child,
+ no class required. */
+input[type="checkbox"]:not([role="switch"]),
+input[type="radio"] {
+ width: 1.125rem;
+ height: 1.125rem;
+ margin: 0;
+ accent-color: var(--ad-color-accent);
+ cursor: pointer;
+}
+input[type="checkbox"]:not([role="switch"]):focus-visible,
+input[type="radio"]:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+}
+
+label:has(> input[type="checkbox"]:not([role="switch"])),
+label:has(> input[type="radio"]) {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 400;
+ text-transform: none;
+ letter-spacing: normal;
+ color: var(--ad-color-text-primary);
+}
+label:has(> input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Switch — for on/off settings only, never a substitute for Checkbox.
+ `input[type="checkbox"][role="switch"]` is real ARIA (the correct role for
+ a toggle), not an invented hook, but the visual track+thumb needs a second
+ element for the thumb's independent position and transition — more than
+ `appearance: none` plus pseudo-elements on the input itself can cleanly
+ carry — so `.switch` is a deliberate, documented fallback class rather
+ than a first choice. */
+.switch {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ color: var(--ad-color-text-primary);
+}
+.switch input[role="switch"] { position: absolute; opacity: 0; width: 2.25rem; height: 1.25rem; margin: 0; }
+
+.switch span {
+ position: relative;
+ flex: none;
+ width: 2.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-border-default);
+ transition: background-color var(--ad-dur-base) var(--ad-ease);
+}
+.switch span::after {
+ content: "";
+ position: absolute;
+ inset-block-start: 2px;
+ inset-inline-start: 2px;
+ width: 1rem;
+ height: 1rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-fg);
+ box-shadow: var(--ad-shadow-sm);
+ transition: translate var(--ad-dur-base) var(--ad-ease);
+}
+.switch input[role="switch"]:checked + span { background: var(--ad-color-accent); }
+.switch input[role="switch"]:checked + span::after { translate: 1rem 0; }
+.switch input[role="switch"]:focus-visible + span { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+.switch:has(input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Rows of fields — used by the settings-form pattern. `<form>`/`<fieldset>`
+ are the elements that already mean this, so they are styled bare. A row
+ grouping two fields side by side has no element of its own, so it keeps
+ one plain class. */
+form {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-s);
+ max-width: 28rem;
+}
+.form-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
+ gap: var(--ad-space-xs);
+}
+fieldset {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ margin: 0;
+ padding: 0;
+ border: 0;
+}
+fieldset > legend {
+ padding: 0 0 var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Badge
+ Mono, uppercase, tracked — the catalog-label convention for status and
+ meta values (SAVED, OVERDUE, RECOMMENDED). A label, never a button.
+
+ A <span> is too generic to style globally — it is used all over running
+ prose for unrelated reasons — so Badge gets the one sanctioned class this
+ shape needs. Tone is an attribute, not a modifier class. */
+
+.badge {
+ --_bg: var(--ad-color-surface-fill);
+ --_fg: var(--ad-color-text-secondary);
+ --_border: var(--ad-color-border-hairline);
+
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.1875rem 0.5rem;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-sm);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ line-height: 1;
+ white-space: nowrap;
+}
+
+.badge[data-tone="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); --_border: transparent; }
+.badge[data-tone="success"] { --_bg: var(--ad-color-success-bg); --_fg: var(--ad-color-success); --_border: transparent; }
+.badge[data-tone="danger"] { --_bg: var(--ad-color-danger-bg); --_fg: var(--ad-color-danger); --_border: transparent; }
+
+.badge-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-2xs);
+}
+
+/* Tag
+ A removable mono chip for freeform tags — locations, categories.
+ `[data-dashed]` renders an "add tag" affordance instead of a value. Same
+ reasoning as Badge: <span> is too generic to select bare, so Tag gets one
+ class. The remove control is a real <button>, styled through the
+ descendant selector rather than a class of its own. */
+
+.tag {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.375rem;
+ padding: 0.25rem 0.625rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-default);
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ line-height: 1;
+}
+
+.tag[data-dashed] {
+ background: transparent;
+ border-style: dashed;
+ color: var(--ad-color-text-tertiary);
+ cursor: pointer;
+}
+
+.tag button {
+ all: unset;
+ cursor: pointer;
+ color: var(--ad-color-text-tertiary);
+ line-height: 1;
+ font-size: 0.75rem;
+}
+.tag button:hover { color: var(--ad-color-text-primary); }
+
+.tag-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.375rem;
+}
+
+/* Card
+ Flat surface, hairline border, radius-lg. `[data-elevated]` is opt-in and
+ reserved for the one focused/primary card in a view — a dialog, the
+ primary capture card — flat is the default everywhere else.
+
+ A <div>/<article> is too generic to select bare, so Card keeps one
+ sanctioned class. When Card's job is holding a list of rows — the common
+ case, entry lists, task lists, settings rows — it is a real <ul class="
+ card">, and CardRow is a bare <li> underneath it: no second class needed,
+ the row look comes from the `.card > li` descendant selector alone. The
+ first row's top border is removed automatically so callers never have to
+ override it by hand. */
+
+.card {
+ display: flex;
+ flex-direction: column;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: none;
+ overflow: hidden;
+}
+
+.card[data-elevated] { box-shadow: var(--ad-shadow-float); }
+
+.card > li {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+}
+.card > li:first-child { border-top: none; }
+
+/* A row whose ENTIRE content is wrapped in a real <button> or <a> — the
+ accessible way to make a row clickable — fills the row edge-to-edge and
+ picks up a hover fill and pointer cursor. `:only-child` is what tells this
+ apart from a row that merely contains a small button among other content
+ (a leading checkbox next to a title and a trailing value, say): only a
+ button/anchor that IS the row's one and only child means "the whole row
+ is the control." */
+.card > li:has(> button:only-child),
+.card > li:has(> a:only-child) {
+ padding: 0;
+}
+.card > li > button:only-child,
+.card > li > a:only-child {
+ all: unset;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ box-sizing: border-box;
+ color: inherit;
+ font: inherit;
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.card > li > button:only-child:hover,
+.card > li > a:only-child:hover { background: var(--ad-color-surface-fill); }
+
+/* Documentation-only extras: a body/footer pair and a responsive grid, used
+ by the prose pages (rationale.md, index.md) to show a Card as a plain
+ content tile rather than a data row. Neither is part of Card's props —
+ they exist because a content tile's inner layout has no element of its
+ own to hang a selector on. */
+.card-body {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-m);
+}
+.card-footer {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ margin-top: auto;
+ padding: var(--ad-space-xs) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+
+.card[data-link] { transition: border-color var(--ad-dur-fast) var(--ad-ease), box-shadow var(--ad-dur-fast) var(--ad-ease); }
+.card[data-link]:hover { border-color: var(--ad-color-border-default); box-shadow: var(--ad-shadow-sm); }
+.card[data-link]:focus-within { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+
+.card-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
+ gap: var(--ad-space-s);
+}
+
+/* Avatar — lives here because it almost never appears outside a card or a
+ row (the Loop dashboard's initials chip, Archive's file-type chip). A
+ <span> is too generic for a bare selector, so it keeps one class. */
+.avatar {
+ --_size: 1.875rem;
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ flex: none;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 600;
+ overflow: hidden;
+}
+.avatar[data-square] { border-radius: var(--ad-radius-md); background: var(--ad-color-surface-fill); color: var(--ad-color-text-secondary); border: 1px solid var(--ad-color-border-hairline); }
+.avatar[data-size="sm"] { --_size: 1.5rem; }
+.avatar[data-size="lg"] { --_size: 2.25rem; }
+
+/* Tooltip — a dark mono label on hover, positioned above the child. Pure CSS:
+ the trigger and bubble are siblings inside one relatively-positioned span,
+ shown on :hover/:focus-within so it works from the keyboard too.
+
+ `[role="tooltip"]` is the correct ARIA role for the bubble itself, so it
+ doubles as the styling hook — zero classes there. The wrapping span has no
+ attribute of its own, but it does not need one: `:has()` picks out "a span
+ that contains a [role=tooltip] bubble" precisely, without reaching for a
+ class on an otherwise-generic element. */
+span:has(> [role="tooltip"]) {
+ position: relative;
+ display: inline-flex;
+}
+
+[role="tooltip"] {
+ position: absolute;
+ bottom: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ margin-bottom: 0.375rem;
+ padding: 0.25rem 0.5rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-text-primary);
+ color: var(--ad-color-surface-page);
+ font-family: var(--ad-font-mono);
+ /* 11px is the source's own literal choice — smaller than --ad-step--2, the
+ smallest step on the scale, because a tooltip is meant to read as a
+ glance-only aside rather than body text. */
+ font-size: 11px;
+ white-space: nowrap;
+ line-height: 1.4;
+ z-index: 20;
+ opacity: 0;
+ visibility: hidden;
+ pointer-events: none;
+ transition: opacity var(--ad-dur-fast) var(--ad-ease);
+}
+span:has(> [role="tooltip"]):hover > [role="tooltip"],
+span:has(> [role="tooltip"]):focus-within > [role="tooltip"] {
+ opacity: 1;
+ visibility: visible;
+}
+
+/* Toast — the real pattern used for delete confirmations: a flat bar with an
+ optional destructive action and a dismiss. `[data-fixed]` pins it
+ bottom-right for real use; without it the toast stays in document flow for
+ documentation. `role="status"` is the correct live-region role for a
+ transient status message, so it is both the accessibility hook and the
+ style selector — no base class needed. The action and dismiss buttons are
+ plain <button>s told apart by `aria-label="Dismiss"`, an attribute they
+ need anyway for the icon-only close control. */
+[role="status"] {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ min-width: 20rem;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+}
+[role="status"][data-fixed] {
+ position: fixed;
+ inset-block-end: var(--ad-space-s);
+ inset-inline-end: var(--ad-space-s);
+ z-index: 60;
+}
+[role="status"] > span { flex: 1; }
+[role="status"] button {
+ all: unset;
+ cursor: pointer;
+ font-weight: 600;
+ color: var(--ad-color-danger);
+}
+[role="status"] button[aria-label="Dismiss"] {
+ color: var(--ad-color-text-tertiary);
+ font-size: 1rem;
+ font-weight: 400;
+ line-height: 1;
+}
+[role="status"] button[aria-label="Dismiss"]:hover { color: var(--ad-color-text-primary); }
+
+/* Dialog — the native <dialog> element: a centered modal, flat surface, no
+ colored header, hairline dividers between title/body/footer. A <header>
+ holds the title and a <footer> holds the actions, matching the elements
+ the tag already wants; everything else in between is the body, styled by
+ excluding those two rather than by a class of its own.
+
+ `.dialog-demo` scopes a static, always-open example to its own preview
+ container instead of the viewport, so it can sit safely inside a
+ documentation page — this is a doc-only affordance, not part of Dialog's
+ contract. It relies on the browser's own dialog centering: the UA
+ stylesheet already gives an open <dialog> `position: absolute; inset: 0;
+ margin: auto`, which centers it inside the nearest positioned ancestor —
+ here, `.dialog-demo` itself. A real `.showModal()` call gets the genuine
+ viewport-fixed overlay and `::backdrop` for free; a plain `open` attribute
+ (used here so the demo renders without JavaScript) does not paint a
+ backdrop at all, since `::backdrop` only exists for a dialog shown
+ modally — wire the real open/close state and call `.showModal()` in the
+ consuming app. */
+.dialog-demo {
+ position: relative;
+ min-height: 20rem;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-lg);
+}
+
+dialog {
+ width: 26rem;
+ max-width: calc(100% - (2 * var(--ad-space-s)));
+ height: fit-content;
+ padding: 0;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ overflow: hidden;
+}
+dialog::backdrop {
+ background: rgba(20, 18, 14, 0.35);
+}
+dialog > header {
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-sans);
+ font-weight: 600;
+ font-size: var(--ad-step-0);
+ color: var(--ad-color-text-primary);
+}
+dialog > footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+}
+dialog > *:not(header):not(footer) {
+ padding: var(--ad-space-m);
+ margin: 0;
+ color: var(--ad-color-text-primary);
+ font-size: var(--ad-step--1);
+}
+
+/* Tabs
+ Follows the real WAI-ARIA tabs pattern: a `[role="tablist"]` of
+ `[role="tab"]` buttons controlling `[role="tabpanel"]`s. Those roles are
+ required for the pattern to be accessible in the first place, so they
+ double as the entire styling contract — no classes at all for the base
+ shape. The keyboard wiring lives in assets/js/system.js and is
+ variant-agnostic — it only cares about `[data-tabs]` / `[role=tab]`.
+
+ Two real variants: `underline` (mono, accent underline — primary section
+ nav, the default) and `segmented` (pill track, active segment lifts to the
+ surface — filters like All · Overdue · Upcoming, never primary
+ navigation), chosen with `data-variant="segmented"` on the tablist since
+ neither has a natural attribute of its own. */
+
+/* Every rule below the list container pairs [role="tablist"] with
+ [role="tab"] rather than styling [role="tab"] alone — Tab buttons are
+ still literal <button> elements, so the compound selector's extra
+ specificity is what keeps Button's base styling (button.css) from
+ winning the cascade on shared properties like padding and background. */
+
+[role="tablist"] {
+ display: flex;
+ gap: var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+[role="tablist"] [role="tab"] {
+ padding: 0.5rem 0;
+ margin-bottom: -1px;
+ background: none;
+ border: 0;
+ border-bottom: 2px solid transparent;
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--1);
+ letter-spacing: 0.02em;
+ cursor: pointer;
+ transition: color var(--ad-dur-fast) var(--ad-ease), border-color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"] [role="tab"]:hover { color: var(--ad-color-text-primary); }
+[role="tablist"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ border-bottom-color: var(--ad-color-accent);
+}
+
+[role="tablist"][data-variant="segmented"] {
+ display: inline-flex;
+ gap: 0.125rem;
+ padding: 0.1875rem;
+ border-bottom: 0;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-md);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"] {
+ all: unset;
+ cursor: pointer;
+ padding: 0.375rem 0.875rem;
+ border-radius: var(--ad-radius-sm);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 550;
+ color: var(--ad-color-text-secondary);
+ transition: background-color var(--ad-dur-fast) var(--ad-ease), color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ background: var(--ad-color-surface-card);
+ box-shadow: var(--ad-shadow-sm);
+}
+
+[role="tabpanel"] { padding-top: var(--ad-space-s); }
+[role="tabpanel"][hidden] { display: none; }
+[role="tabpanel"]:focus-visible { outline-offset: 4px; }
+
+/* Documentation site chrome. Everything below the `.ad-doc-` prefix belongs
+ to this site rather than to the design system it documents — including the
+ width/height constants used directly below. The source design system
+ defines no container-width tokens at all, so there is nothing under
+ `--ad-*` for a doc shell to reach for; the Space foundation page's own
+ notes say to omit that group entirely rather than invent one. */
+
+.ad-doc-skip {
+ position: absolute;
+ inset-inline-start: var(--ad-space-xs);
+ inset-block-start: -4rem;
+ z-index: 40;
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ box-shadow: var(--ad-shadow-float);
+ transition: inset-block-start var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-skip:focus { inset-block-start: var(--ad-space-2xs); }
+
+.ad-doc-header {
+ position: sticky;
+ inset-block-start: 0;
+ z-index: 30;
+ height: 3.5rem;
+ background: color-mix(in srgb, var(--ad-color-surface-page) 92%, transparent);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-header__inner {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ height: 100%;
+ max-width: 72rem;
+ margin-inline: auto;
+ padding-inline: var(--ad-space-xs);
+}
+
+.ad-doc-wordmark {
+ display: flex;
+ align-items: baseline;
+ gap: var(--ad-space-2xs);
+ color: var(--ad-color-text-primary);
+ text-decoration: none;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ font-weight: 600;
+ letter-spacing: -0.01em;
+ white-space: nowrap;
+}
+.ad-doc-wordmark:hover { color: var(--ad-color-text-primary); }
+.ad-doc-wordmark__mark {
+ width: 1.375rem;
+ height: 1.375rem;
+ flex: none;
+ align-self: center;
+ color: var(--ad-color-accent);
+}
+.ad-doc-wordmark__version {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+
+.ad-doc-header__actions { margin-inline-start: auto; display: flex; align-items: center; gap: var(--ad-space-2xs); }
+
+.ad-doc-layout {
+ display: grid;
+ grid-template-columns: 16rem minmax(0, 1fr);
+ gap: var(--ad-space-l);
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-m) var(--ad-space-xs) var(--ad-space-2xl);
+}
+
+.ad-doc-sidebar {
+ position: sticky;
+ inset-block-start: calc(3.5rem + var(--ad-space-m));
+ align-self: start;
+ max-height: calc(100vh - 3.5rem - var(--ad-space-l));
+ overflow-y: auto;
+ padding-inline-end: var(--ad-space-2xs);
+}
+.ad-doc-nav__group + .ad-doc-nav__group { margin-top: var(--ad-space-s); }
+.ad-doc-nav__heading {
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-doc-nav__link {
+ display: block;
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ margin-inline-start: calc(var(--ad-space-xs) * -1);
+ border-inline-start: 2px solid transparent;
+ border-radius: 0 var(--ad-radius-sm) var(--ad-radius-sm) 0;
+ color: var(--ad-color-text-secondary);
+ font-size: var(--ad-step--1);
+ text-decoration: none;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-nav__link:hover { color: var(--ad-color-text-primary); background: var(--ad-color-surface-fill); }
+.ad-doc-nav__link[aria-current="page"] {
+ color: var(--ad-color-accent-ink);
+ border-inline-start-color: var(--ad-color-accent);
+ background: var(--ad-color-accent-soft);
+ font-weight: 500;
+}
+
+.ad-doc-main {
+ min-width: 0;
+ /* The design system's own body-text token (--ad-step-0, 18–20px) is right
+ for a product screen but too large for long-form documentation prose.
+ This site's own copy gets a plain, fixed 16px instead — the token above
+ is unchanged, and specimen tables/demos below still show its real
+ value. */
+ font-size: 1rem;
+}
+.ad-doc-main > * { max-width: 50rem; }
+
+.ad-doc-title { margin-bottom: var(--ad-space-xs); }
+.ad-doc-title__section {
+ display: block;
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-accent);
+}
+
+.ad-doc-main h2 {
+ margin-block: var(--ad-space-2xl) var(--ad-space-xs);
+ padding-bottom: var(--ad-space-2xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-main h3 { margin-block: var(--ad-space-l) var(--ad-space-2xs); }
+.ad-doc-main h4 { margin-block: var(--ad-space-s) var(--ad-space-2xs); }
+.ad-doc-main p,
+.ad-doc-main ul,
+.ad-doc-main ol { max-width: 68ch; }
+.ad-doc-main ul:not([class]),
+.ad-doc-main ol:not([class]) { padding-inline-start: var(--ad-space-s); }
+
+.ad-doc-footer {
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-s) var(--ad-space-xs) var(--ad-space-l);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-tertiary);
+ font-size: var(--ad-step--1);
+}
+.ad-doc-footer__inner { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); justify-content: space-between; }
+
+/* `button.ad-doc-nav-toggle` (element + class) rather than a bare class:
+ button.css's own base rule is `button:not(.icon-button)`, which is also
+ one element + one class in specificity and sets `display: inline-flex` —
+ a bare `.ad-doc-nav-toggle { display: none }` loses that fight and the
+ toggle stays visible at every width. Matching its specificity here (and
+ coming later in the concatenated stylesheet) settles it in source order. */
+button.ad-doc-nav-toggle { display: none; }
+
+@media (max-width: 60rem) {
+ .ad-doc-layout { grid-template-columns: minmax(0, 1fr); gap: var(--ad-space-s); }
+ .ad-doc-sidebar {
+ position: static;
+ max-height: none;
+ }
+ /* The toggle is a normal Button; the panel it opens gets its own card
+ treatment. Putting the same card styling on the <aside> wrapper too
+ nested a button-shaped box inside an outer box for no reason. */
+ .ad-doc-nav {
+ margin-top: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ }
+ .ad-doc-sidebar[data-collapsed="true"] > .ad-doc-nav { display: none; }
+ button.ad-doc-nav-toggle {
+ display: flex;
+ width: 100%;
+ justify-content: space-between;
+ }
+}
+
+@media (max-width: 34rem) {
+ .ad-doc-wordmark__version { display: none; }
+ .ad-doc-layout { padding-inline: var(--ad-space-2xs); }
+}
+
+/* The live-example figure used throughout the docs — see the "How the
+ example demos work" section on the Using the CSS page. One class on the
+ outer <figure>; everything inside is styled by plain element selectors,
+ since figcaption/div/details/summary/pre already say what they are.
+ `data-layout` and `data-surface` on the preview <div> distinguish the
+ handful of demos that need a non-default arrangement. */
+
+.example {
+ margin: var(--ad-space-s) 0 var(--ad-space-l);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+ background: var(--ad-color-surface-card);
+ overflow: hidden;
+}
+
+.example figcaption {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-secondary);
+}
+
+.example > div {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-s);
+}
+/* nowrap is load-bearing: a column-direction flex container with wrap enabled
+ sizes its single line against the container rather than the content, which
+ left every stacked example with a few hundred pixels of dead space. */
+.example > div[data-layout="stack"] { flex-direction: column; flex-wrap: nowrap; align-items: stretch; }
+.example > div[data-layout="grid"] {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
+ align-items: start;
+}
+.example > div[data-surface="sunken"] { background: var(--ad-color-surface-fill); }
+.example > div > * { min-width: 0; }
+
+.example details {
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+.example summary {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ color: var(--ad-color-text-secondary);
+ cursor: pointer;
+ list-style: none;
+ user-select: none;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+}
+.example summary::-webkit-details-marker { display: none; }
+.example summary::before {
+ content: "";
+ width: 0.5rem;
+ height: 0.5rem;
+ border-inline-end: 1.5px solid currentColor;
+ border-block-end: 1.5px solid currentColor;
+ rotate: -45deg;
+ transition: rotate var(--ad-dur-fast) var(--ad-ease);
+}
+.example details[open] > summary::before { rotate: 45deg; }
+.example summary:hover { color: var(--ad-color-text-primary); }
+
+.example pre {
+ margin: 0;
+ padding: var(--ad-space-xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+ tab-size: 2;
+}
+.example pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Standalone fenced code blocks in the prose (unhighlighted — there is no
+ syntax highlighter in this build any more, just Kramdown's plain
+ <pre><code class="language-*">). */
+.ad-doc-main > pre {
+ margin-block: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+}
+.ad-doc-main > pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Presentation for the token reference tables and swatch grids, plus a
+ generic data-table utility. All of this is documentation chrome — the
+ design system itself has no Table component (see the note on the removed
+ `table`/`progress`/`callout` pages in git history). */
+
+.ad-swatch-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs) var(--ad-space-s);
+ padding: 0;
+ list-style: none;
+}
+.ad-swatch {
+ display: flex;
+ flex-direction: column;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ overflow: hidden;
+ background: var(--ad-color-surface-card);
+}
+.ad-swatch__chip {
+ display: flex;
+ align-items: flex-end;
+ justify-content: flex-end;
+ height: 4rem;
+ padding: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+}
+.ad-swatch__meta {
+ display: flex;
+ flex-direction: column;
+ gap: 0.1rem;
+ padding: var(--ad-space-2xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+}
+.ad-swatch__hex { color: var(--ad-color-text-primary); text-transform: uppercase; }
+.ad-swatch__ratio { color: var(--ad-color-text-tertiary); }
+
+.ad-alias-row { display: flex; align-items: center; gap: var(--ad-space-2xs); }
+.ad-alias-chip {
+ width: 1.5rem;
+ height: 1.5rem;
+ flex: none;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+}
+.ad-alias-chip--pair { display: flex; }
+.ad-alias-chip--pair > span { flex: 1; }
+
+/* Type specimen rows. */
+.ad-specimen {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ gap: var(--ad-space-3xs);
+ padding-block: var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-specimen__meta {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-specimen__sample {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: var(--ad-color-text-primary);
+}
+
+/* Space and radius rulers. */
+.ad-ruler { display: flex; flex-direction: column; gap: var(--ad-space-2xs); }
+.ad-ruler__row { display: grid; grid-template-columns: 6.5rem 4.5rem 1fr; align-items: center; gap: var(--ad-space-xs); font-family: var(--ad-font-mono); font-size: 11px; }
+.ad-ruler__bar { height: 0.75rem; background: var(--ad-color-accent); border-radius: var(--ad-radius-sm); }
+.ad-ruler__num { color: var(--ad-color-text-tertiary); text-align: right; }
+
+.ad-radius-grid, .ad-elevation-grid, .ad-motion-grid, .ad-accent-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs);
+ padding: 0;
+ list-style: none;
+}
+.ad-radius-demo {
+ height: 4.5rem;
+ background: var(--ad-color-accent-soft);
+ border: 1px solid var(--ad-color-accent);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-elevation-demo {
+ height: 4.5rem;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-motion-demo {
+ height: 4.5rem;
+ display: grid;
+ place-items: center;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-md);
+ margin-bottom: var(--ad-space-2xs);
+ cursor: pointer;
+ overflow: hidden;
+}
+.ad-motion-demo__dot {
+ width: 1.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent);
+ translate: -3rem 0;
+ transition-property: translate;
+ transition-duration: var(--ad-dur-base);
+ transition-timing-function: var(--ad-ease);
+}
+.ad-motion-demo:hover .ad-motion-demo__dot,
+.ad-motion-demo:focus-visible .ad-motion-demo__dot { translate: 3rem 0; }
+
+.ad-accent-demo {
+ height: 4.5rem;
+ display: flex;
+ align-items: flex-end;
+ padding: var(--ad-space-2xs);
+ border-radius: var(--ad-radius-md);
+ color: #fff;
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ margin-bottom: var(--ad-space-2xs);
+}
+
+/* Token names read `<code>` sitewide, which already carries a mono chip
+ look for running prose (see :not(pre) > code in base/typography.css).
+ Inside these specimen/swatch/ruler rows the old `.ad-token-name` class was
+ plain mono text with no chip, so `<code>` is flattened back to that look
+ here the same way table cells are. */
+.ad-swatch code, .ad-specimen code, .ad-ruler code, .ad-alias-row code {
+ background: none;
+ border: 0;
+ padding: 0;
+ font-size: var(--ad-step--2);
+ color: var(--ad-color-text-primary);
+ white-space: nowrap;
+}
+
+/* A documentation aside. Like `<table>`, this is site chrome for the prose
+ itself, not a system component — the real system has no Callout. */
+.ad-callout {
+ margin-block: var(--ad-space-s);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-left: 3px solid var(--ad-color-accent);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+}
+/* Generic reference tables (component APIs, alias tables, the contrast
+ contract). `<table>` IS a table — no class needed to say so. Every table
+ on this site uses the one compact density, so there is no separate
+ modifier for it. `display: block` plus `overflow-x: auto` gives the table
+ its own horizontal scrollbar on narrow viewports without a wrapper div;
+ the anonymous table box the browser generates around `<thead>`/`<tbody>`
+ still does the row/column layout. `<code>` replaces the old
+ `.ad-token-name` / `.ad-table__code` cells — it is already styled
+ sitewide, just re-flattened here to a plain mono run instead of the
+ chip-with-border look prose code gets, to match this table's original
+ look. `.num` is the one sanctioned class left, for right-aligned numeric
+ columns — there is no attribute that means "this column is a number". */
+table { width: 100%; border-collapse: collapse; font-size: var(--ad-step--1); display: block; overflow-x: auto; }
+table th, table td {
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ text-align: left;
+ vertical-align: top;
+}
+table th { font-family: var(--ad-font-mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ad-color-text-secondary); font-weight: 500; }
+table code { background: none; border: 0; padding: 0; font-size: var(--ad-step--2); white-space: nowrap; }
+.num { text-align: right; font-variant-numeric: tabular-nums; }
+
+/* Home page. */
+.ad-hero { padding-block: var(--ad-space-m) var(--ad-space-l); }
+.ad-hero__title {
+ font-size: var(--ad-step-4);
+ margin-bottom: var(--ad-space-xs);
+}
+.ad-hero__actions { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); margin-top: var(--ad-space-s); }
+.ad-stat-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-l);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+}
+.ad-stat__value {
+ display: block;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-2);
+ font-weight: 600;
+ line-height: 1.16;
+ font-variant-numeric: tabular-nums;
+}
+.ad-stat__label {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+
+/* Card chrome. The pane supplies the frame; this just insets the page. */
+body { padding: var(--ad-space-l) var(--ad-space-m); }
+.ad-doc-main { max-width: 50rem; margin-inline: auto; }
+.ad-doc-main > * { max-width: none; }
+</style>
+</head>
+<body>
+<main class="ad-doc-main">
+<header class="ad-doc-title"><span class="ad-doc-title__section">Components</span>
+ <h1>Toast</h1>
+</header>
+<p class="ad-lead">A fixed bottom-right flash bar — the real pattern used for delete confirmations. Auto-dismisses after roughly 8 seconds in practice; wire that timer into the consuming app.</p>
+<h2 id="basic">Basic</h2>
+
+<p><code>role="status"</code> is the correct live-region role for a transient status
+message, so it doubles as the entire styling hook — no base class. The
+example below stays in document flow so it does not cover the page;
+<code>[data-fixed]</code> is what actually pins it bottom-right in a real app. The
+action and dismiss buttons are plain <code><button></code>s told apart by
+<code>aria-label="Dismiss"</code>, an attribute the icon-only close control needs
+anyway.</p>
+
+<figure class="example">
+<figcaption>Toast</figcaption>
+<div>
+<div role="status">
+ <span>Deleted "Call Mom"</span>
+ <button>Undo</button>
+ <button aria-label="Dismiss">×</button>
+</div>
+</div>
+<details open=""><summary>Markup</summary>
+<pre><code><div role="status">
+ <span>Deleted "Call Mom"</span>
+ <button>Undo</button>
+ <button aria-label="Dismiss">×</button>
+</div></code></pre>
+</details>
+</figure>
+
+<h2 id="without-an-action">Without an action</h2>
+
+<figure class="example">
+<figcaption>No action</figcaption>
+<div>
+<div role="status">
+ <span>Series created</span>
+ <button aria-label="Dismiss">×</button>
+</div>
+</div>
+<details><summary>Markup</summary>
+<pre><code><div role="status">
+ <span>Series created</span>
+ <button aria-label="Dismiss">×</button>
+</div></code></pre>
+</details>
+</figure>
+
+<h2 id="api">API</h2>
+
+<table>
+ <thead><tr><th scope="col">Element / attribute</th><th scope="col">Values</th><th scope="col">Notes</th></tr></thead>
+ <tbody>
+ <tr><td><code>[role="status"]</code></td><td></td><td>Required base, on a <code><div></code>.</td></tr>
+ <tr><td><code>> span</code></td><td>string</td><td>The message. Required.</td></tr>
+ <tr><td><code>> button</code></td><td></td><td>Renders a destructive-colored action, typically "Undo".</td></tr>
+ <tr><td><code>> button[aria-label="Dismiss"]</code></td><td></td><td>The × control — the label is what tells it apart from the action button.</td></tr>
+ <tr><td><code>data-fixed</code></td><td>boolean</td><td>Pins the toast bottom-right of the viewport. Omit to stay in document flow.</td></tr>
+ </tbody>
+</table>
+</main>
+<script>
+// Behavior for the pieces of the system that need it. Everything else is
+// CSS and semantic HTML on purpose — a design system that only works with
+// JavaScript running is a component library with extra steps.
+(function () {
+ "use strict";
+
+ var STORAGE_KEY = "ad-theme";
+
+ function readStored() {
+ try {
+ return localStorage.getItem(STORAGE_KEY);
+ } catch (error) {
+ return null;
+ }
+ }
+
+ function writeStored(value) {
+ try {
+ localStorage.setItem(STORAGE_KEY, value);
+ } catch (error) {
+ /* Nothing to do — the media query still gives a sensible default. */
+ }
+ }
+
+ function systemPrefersDark() {
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
+ }
+
+ function currentTheme() {
+ var explicit = document.documentElement.getAttribute("data-theme");
+ if (explicit) return explicit;
+ return systemPrefersDark() ? "dark" : "light";
+ }
+
+ function paintToggle(theme) {
+ document.querySelectorAll("[data-theme-toggle]").forEach(function (toggle) {
+ var next = theme === "dark" ? "light" : "dark";
+ toggle.querySelectorAll("[data-theme-icon]").forEach(function (icon) {
+ icon.hidden = icon.dataset.themeIcon !== theme;
+ });
+ toggle.setAttribute("aria-label", "Switch to the " + next + " theme");
+ toggle.setAttribute("title", "Switch to the " + next + " theme");
+ var label = toggle.querySelector("[data-theme-label]");
+ if (label) label.textContent = "Switch to the " + next + " theme";
+ });
+ }
+
+ function setUpTheme() {
+ paintToggle(currentTheme());
+
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-theme-toggle]");
+ if (!toggle) return;
+
+ var next = currentTheme() === "dark" ? "light" : "dark";
+ document.documentElement.setAttribute("data-theme", next);
+ writeStored(next);
+ paintToggle(next);
+ });
+
+ // Follow the OS while the reader has not made an explicit choice.
+ window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", function () {
+ if (!readStored()) paintToggle(currentTheme());
+ });
+ }
+
+ // Tabs, wired to the WAI-ARIA authoring practices: arrows move, Home/End
+ // jump, and only the selected tab stays in the tab order.
+ function setUpTabs() {
+ document.querySelectorAll("[data-tabs]").forEach(function (root) {
+ var tabs = Array.prototype.slice.call(root.querySelectorAll('[role="tab"]'));
+ if (!tabs.length) return;
+
+ function select(tab, focus) {
+ tabs.forEach(function (candidate) {
+ var selected = candidate === tab;
+ candidate.setAttribute("aria-selected", String(selected));
+ candidate.tabIndex = selected ? 0 : -1;
+ var panel = document.getElementById(candidate.getAttribute("aria-controls"));
+ if (panel) panel.hidden = !selected;
+ });
+ if (focus) tab.focus();
+ }
+
+ root.addEventListener("click", function (event) {
+ var tab = event.target.closest('[role="tab"]');
+ if (tab) select(tab, false);
+ });
+
+ root.addEventListener("keydown", function (event) {
+ var index = tabs.indexOf(document.activeElement);
+ if (index === -1) return;
+
+ var next = null;
+ if (event.key === "ArrowRight") next = tabs[(index + 1) % tabs.length];
+ else if (event.key === "ArrowLeft") next = tabs[(index - 1 + tabs.length) % tabs.length];
+ else if (event.key === "Home") next = tabs[0];
+ else if (event.key === "End") next = tabs[tabs.length - 1];
+
+ if (next) {
+ event.preventDefault();
+ select(next, true);
+ }
+ });
+
+ select(tabs.filter(function (tab) {
+ return tab.getAttribute("aria-selected") === "true";
+ })[0] || tabs[0], false);
+ });
+ }
+
+ function setUpNavToggle() {
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-nav-toggle]");
+ if (!toggle) return;
+
+ var sidebar = toggle.closest("[data-sidebar]");
+ var collapsed = sidebar.dataset.collapsed !== "false";
+ sidebar.dataset.collapsed = String(!collapsed);
+ toggle.setAttribute("aria-expanded", String(collapsed));
+ });
+ }
+
+ setUpTheme();
+ setUpTabs();
+ setUpNavToggle();
+})();
+
+</script>
+</body>
+</html>
diff --git a/design-bundle/components/tooltip.html b/design-bundle/components/tooltip.html
new file mode 100644
index 0000000..a87954e
--- /dev/null
+++ b/design-bundle/components/tooltip.html
@@ -0,0 +1,1812 @@
+<!-- @dsCard group="Feedback" -->
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Tooltip</title>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&display=swap">
+<style>
+/* Arbitrary Definitions — design tokens. Hand-maintained; edit values directly. */
+
+:root {
+ /* Palette ramps — reference only. Components use the aliases below. */
+ --ad-neutral-0: #faf9f7;
+ --ad-neutral-50: #fdfdfb;
+ --ad-neutral-100: #f1f0ed;
+ --ad-neutral-150: #e8e6e2;
+ --ad-neutral-200: #dcdad7;
+ --ad-neutral-300: #c0bdb9;
+ --ad-neutral-500: #888681;
+ --ad-neutral-600: #5f5d59;
+ --ad-neutral-800: #2a2926;
+ --ad-neutral-900: #100f0d;
+ --ad-neutral-dark-0: #0e0d0b;
+ --ad-neutral-dark-50: #151412;
+ --ad-neutral-dark-100: #1e1d1a;
+ --ad-neutral-dark-150: #252421;
+ --ad-neutral-dark-200: #2c2b29;
+ --ad-neutral-dark-300: #44423f;
+ --ad-neutral-dark-500: #7c7a76;
+ --ad-neutral-dark-600: #a09e9a;
+ --ad-neutral-dark-800: #d9d7d4;
+ --ad-neutral-dark-900: #e9e8e6;
+ --ad-accent-teal: #3a6a63;
+ --ad-accent-clay: #9a5a3c;
+ --ad-accent-ink-blue: #2b4a78;
+ --ad-accent-ochre: #93701f;
+ --ad-accent-plum: #6a3a5c;
+
+ /* Surfaces */
+ --ad-color-surface-page: #faf9f7;
+ --ad-color-surface-card: #fdfdfb;
+ --ad-color-surface-fill: #f1f0ed;
+ --ad-color-surface-fill-2: #e8e6e2;
+
+ /* Borders */
+ --ad-color-border-hairline: #dcdad7;
+ --ad-color-border-default: #c0bdb9;
+
+ /* Text */
+ --ad-color-text-primary: #100f0d;
+ --ad-color-text-secondary: #5f5d59;
+ --ad-color-text-tertiary: #888681;
+
+ /* Accent */
+ --ad-color-accent: #3a6a63;
+ --ad-color-accent-fg: #ffffff;
+ --ad-color-accent-ink: color-mix(in oklch, var(--ad-color-accent) 78%, black);
+ --ad-color-accent-soft: color-mix(in oklch, var(--ad-color-accent) 12%, white);
+
+ /* Status (fixed — never swapped like the accent) */
+ --ad-color-success: #3f7a4a;
+ --ad-color-success-bg: color-mix(in oklch, var(--ad-color-success) 12%, white);
+ --ad-color-danger: #ad3a2c;
+ --ad-color-danger-bg: color-mix(in oklch, var(--ad-color-danger) 10%, white);
+
+ /* Type families */
+ --ad-font-sans: -apple-system, "Helvetica Neue", Helvetica, Arial, system-ui, sans-serif;
+ --ad-font-mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, Consolas, "Courier New", monospace;
+ --ad-font-serif: Georgia, "Times New Roman", "Iowan Old Style", serif;
+
+ /* Type steps — fluid (Utopia scale), the design system's own body-text size */
+ --ad-step--2: clamp(0.7813rem, 0.7736rem + 0.0341vw, 0.8rem);
+ --ad-step--1: clamp(0.9375rem, 0.9119rem + 0.1136vw, 1rem);
+ --ad-step-0: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-step-1: clamp(1.35rem, 1.2631rem + 0.3864vw, 1.5625rem);
+ --ad-step-2: clamp(1.62rem, 1.4837rem + 0.6057vw, 1.9531rem);
+ --ad-step-3: clamp(1.944rem, 1.7405rem + 0.9044vw, 2.4414rem);
+ --ad-step-4: clamp(2.3328rem, 2.0387rem + 1.3072vw, 3.0518rem);
+ --ad-step-5: clamp(2.7994rem, 2.384rem + 1.8461vw, 3.8147rem);
+
+ /* Space */
+ --ad-space-3xs: clamp(0.3125rem, 0.3125rem + 0vw, 0.3125rem);
+ --ad-space-2xs: clamp(0.5625rem, 0.5369rem + 0.1136vw, 0.625rem);
+ --ad-space-xs: clamp(0.875rem, 0.8494rem + 0.1136vw, 0.9375rem);
+ --ad-space-s: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-space-m: clamp(1.6875rem, 1.6108rem + 0.3409vw, 1.875rem);
+ --ad-space-l: clamp(2.25rem, 2.1477rem + 0.4545vw, 2.5rem);
+ --ad-space-xl: clamp(3.375rem, 3.2216rem + 0.6818vw, 3.75rem);
+ --ad-space-2xl: clamp(4.5rem, 4.2955rem + 0.9091vw, 5rem);
+ --ad-space-3xl: clamp(6.75rem, 6.4432rem + 1.3636vw, 7.5rem);
+
+ /* Radius */
+ --ad-radius-sm: 4px;
+ --ad-radius-md: 6px;
+ --ad-radius-lg: 10px;
+ --ad-radius-full: 999px;
+ --ad-border-w: 1px;
+
+ /* Elevation */
+ --ad-shadow-sm: 0 1px 2px rgba(20, 18, 14, 0.05);
+ --ad-shadow-float: 0 2px 6px rgba(20, 18, 14, 0.06), 0 10px 24px -12px rgba(20, 18, 14, 0.18);
+
+ /* Motion */
+ --ad-dur-fast: 0.12s;
+ --ad-dur-base: 0.16s;
+ --ad-ease: cubic-bezier(0.2, 0, 0, 1);
+}
+
+@media (prefers-color-scheme: dark) {
+ :root:not([data-theme="light"]) {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+ }
+}
+
+:root[data-theme="dark"] {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ :root {
+ --ad-dur-fast: 0ms;
+ --ad-dur-base: 0ms;
+ }
+}
+
+/* A short reset. Everything it sets is something the system would otherwise
+ have to override in every component.
+
+ The rem values used for this documentation site's own chrome (measure,
+ content width, sidebar width, header height) belong to this site, not to
+ the design system it documents — the source design system defines no
+ container-width tokens at all, so there is nothing under `--ad-*` to reach
+ for here. See the header comment in site/shell.css. */
+
+*,
+*::before,
+*::after { box-sizing: border-box; }
+
+html {
+ -webkit-text-size-adjust: 100%;
+ scroll-behavior: smooth;
+ scroll-padding-top: calc(3.5rem + var(--ad-space-s));
+}
+
+@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;
+ }
+}
+
+body {
+ margin: 0;
+ background: var(--ad-color-surface-page);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ line-height: 1.55;
+ font-synthesis-weight: none;
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+}
+
+::selection {
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+}
+
+h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd { margin: 0; }
+ul[class], ol[class] { list-style: none; margin: 0; padding: 0; }
+
+img, svg, video { display: block; max-width: 100%; height: auto; }
+
+button, input, select, textarea {
+ font: inherit;
+ color: inherit;
+}
+
+hr {
+ height: 1px;
+ margin: var(--ad-space-l) 0;
+ border: 0;
+ background: var(--ad-color-border-hairline);
+}
+
+/* One focus ring, defined once. No component is allowed its own. */
+:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+ border-radius: var(--ad-radius-sm);
+}
+:focus:not(:focus-visible) { outline: none; }
+
+[hidden] { display: none !important; }
+
+.ad-visually-hidden {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip-path: inset(50%);
+ white-space: nowrap;
+ border: 0;
+}
+
+/* Every rule here reads a type token for family and size. Weight, line-height,
+ and letter-spacing are hardcoded to the exact numbers the source ships —
+ there is no `--ad-weight-*` / `--ad-leading-*` / `--ad-tracking-*` scale in
+ the real system, only eight fixed compound styles (see the Typography
+ foundation page, which this file implements verbatim). */
+
+.type-display { font: 600 var(--ad-step-4)/1.08 var(--ad-font-sans); letter-spacing: -0.015em; }
+.type-h1, h1 { font: 600 var(--ad-step-3)/1.12 var(--ad-font-sans); letter-spacing: -0.012em; }
+.type-h2, h2 { font: 600 var(--ad-step-2)/1.16 var(--ad-font-sans); letter-spacing: -0.01em; }
+.type-h3, h3 { font: 600 var(--ad-step-1)/1.25 var(--ad-font-sans); }
+.type-body { font: 400 var(--ad-step-0)/1.55 var(--ad-font-sans); }
+.type-body-sm { font: 400 var(--ad-step--1)/1.5 var(--ad-font-sans); }
+.type-label {
+ font: 500 var(--ad-step--2)/1 var(--ad-font-mono);
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: var(--ad-color-text-secondary);
+}
+.type-mono { font: 400 var(--ad-step--1)/1.5 var(--ad-font-mono); }
+
+/* Prose defaults for the documentation body copy. These read the same
+ tokens the compound classes above do; they are not a second scale. */
+h1, h2, h3, h4 { font-weight: 600; text-wrap: balance; }
+
+h4 { font: 600 var(--ad-step-0)/1.3 var(--ad-font-sans); }
+
+p, ul, ol { margin: 0 0 var(--ad-space-s); }
+li + li { margin-top: var(--ad-space-3xs); }
+
+a {
+ color: var(--ad-color-accent);
+ text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
+ text-underline-offset: 0.18em;
+ text-decoration-thickness: 1px;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ text-decoration-color var(--ad-dur-fast) var(--ad-ease);
+}
+a:hover {
+ color: var(--ad-color-accent-ink);
+ text-decoration-color: currentColor;
+}
+
+strong, b { font-weight: 600; }
+
+small { font-size: var(--ad-step--1); }
+
+.ad-lead {
+ font-size: var(--ad-step-1);
+ line-height: 1.5;
+ color: var(--ad-color-text-secondary);
+ max-width: 68ch;
+ text-wrap: pretty;
+}
+
+.ad-muted { color: var(--ad-color-text-secondary); }
+.ad-subtle { color: var(--ad-color-text-tertiary); }
+
+code, kbd, samp, pre {
+ font-family: var(--ad-font-mono);
+ font-feature-settings: "liga" 0;
+}
+
+:not(pre) > code {
+ padding: 0.15em 0.4em;
+ font-size: 0.875em;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-sm);
+ white-space: nowrap;
+}
+
+kbd {
+ display: inline-block;
+ padding: 0.1em 0.45em;
+ font-size: var(--ad-step--2);
+ line-height: 1.6;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-bottom-width: 2px;
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-secondary);
+}
+
+blockquote {
+ margin-block: var(--ad-space-s);
+ padding-left: var(--ad-space-s);
+ border-left: 2px solid var(--ad-color-accent);
+ color: var(--ad-color-text-secondary);
+}
+
+/* Button
+ A flat, quiet button that recedes rather than performs — no gradients, no
+ shine. Variants: default, primary, dark, ghost, danger. Sizes: sm, md
+ (default), lg — padding and font step only, weight and radius stay put.
+
+ The contract is the element itself: a bare <button> is styled directly, no
+ class required. An <a> that acts like a button (navigates rather than
+ changes something — see the Button page's "Links that act like buttons")
+ opts in with a single `.btn` class, since a bare `a` selector would catch
+ every ordinary prose link on the site. `:not(.icon-button)` keeps this
+ block from leaking onto IconButton, which shares the <button> tag but is
+ an entirely different shape. */
+
+button:not(.icon-button),
+a.btn {
+ --_bg: var(--ad-color-surface-card);
+ --_fg: var(--ad-color-text-primary);
+ --_border: var(--ad-color-border-default);
+ --_pad: var(--ad-space-xs) var(--ad-space-s);
+ --_font: var(--ad-step--1);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: var(--ad-space-2xs);
+ padding: var(--_pad);
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-sans);
+ font-size: var(--_font);
+ font-weight: 550;
+ letter-spacing: -0.01em;
+ line-height: 1.2;
+ text-align: center;
+ text-decoration: none;
+ white-space: nowrap;
+ cursor: pointer;
+ transition: filter var(--ad-dur-fast) var(--ad-ease),
+ border-color var(--ad-dur-fast) var(--ad-ease);
+}
+
+:is(button:not(.icon-button), a.btn):hover:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.96); }
+:is(button:not(.icon-button), a.btn):active:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.92); }
+
+button:not(.icon-button):disabled,
+:is(button:not(.icon-button), a.btn)[aria-disabled="true"] {
+ opacity: 0.5;
+ cursor: default;
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="primary"] {
+ --_bg: var(--ad-color-accent);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-accent);
+}
+
+/* Reserved for the single most-committing action in a view — a final Save,
+ never a whole row of them. */
+:is(button:not(.icon-button), a.btn)[data-variant="dark"] {
+ --_bg: var(--ad-color-text-primary);
+ --_fg: var(--ad-color-surface-page);
+ --_border: var(--ad-color-text-primary);
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="ghost"] {
+ --_bg: transparent;
+ --_fg: var(--ad-color-text-primary);
+ --_border: transparent;
+}
+
+/* The source hardcodes this label white rather than deriving it — danger
+ is not "on a filled accent surface", so --ad-color-accent-fg is reused
+ here only because its resolved value is the same literal white, not
+ because the two roles are the same thing. */
+:is(button:not(.icon-button), a.btn)[data-variant="danger"] {
+ --_bg: var(--ad-color-danger);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-danger);
+}
+
+:is(button:not(.icon-button), a.btn)[data-size="sm"] { --_pad: 0.375rem 0.75rem; --_font: 0.8125rem; }
+:is(button:not(.icon-button), a.btn)[data-size="lg"] { --_pad: 0.75rem 1.375rem; --_font: var(--ad-step-0); }
+
+:is(button:not(.icon-button), a.btn)[data-block] { width: 100%; }
+
+/* Button group — [role="group"] is already the correct ARIA role for a
+ toolbar of related buttons, so it doubles as the styling hook for free. */
+[role="group"] {
+ display: inline-flex;
+ isolation: isolate;
+}
+[role="group"] > button { border-radius: 0; }
+[role="group"] > button:first-child { border-start-start-radius: var(--ad-radius-md); border-end-start-radius: var(--ad-radius-md); }
+[role="group"] > button:last-child { border-start-end-radius: var(--ad-radius-md); border-end-end-radius: var(--ad-radius-md); }
+[role="group"] > button + button { margin-inline-start: -1px; }
+[role="group"] > button:hover,
+[role="group"] > button:focus-visible { z-index: 1; }
+
+/* IconButton — a square icon-only button. Always needs an aria-label; it
+ becomes both the accessible name and the native tooltip. There is no
+ attribute that distinguishes "a button that is only an icon" from a plain
+ Button, so this is the one sanctioned class in this file. */
+.icon-button {
+ --_size: 2.125rem;
+ --_bg: transparent;
+ --_border: transparent;
+ --_fg: var(--ad-color-text-secondary);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ padding: 0;
+ flex: none;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease),
+ color var(--ad-dur-fast) var(--ad-ease);
+}
+.icon-button:hover:not(:disabled) { background: var(--ad-color-surface-fill-2); }
+.icon-button:disabled { opacity: 0.5; cursor: default; }
+
+.icon-button[data-size="sm"] { --_size: 1.75rem; }
+.icon-button[data-size="lg"] { --_size: 2.5rem; }
+
+.icon-button[data-variant="outline"] { --_bg: var(--ad-color-surface-card); --_border: var(--ad-color-border-default); --_fg: var(--ad-color-text-primary); }
+.icon-button[data-variant="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); }
+
+/* Field
+ The labeled field wrapper — a <label> that wraps its own caption text
+ together with the control, so the association is native and needs no
+ `for`/`id` pair. The caption is mono, uppercase, tracked — the same
+ catalog-label convention as Badge and type-label.
+
+ A <label> is reused for three different shapes across this system (this
+ wrapper, Checkbox/Radio's row, Switch's row), so it cannot be styled bare
+ without one shape leaking into another. Field is the one of the three with
+ no distinguishing attribute of its own, so it gets the sanctioned class;
+ Checkbox/Radio tell themselves apart structurally instead (see below). */
+.field {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ min-width: 0;
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Hint and error read the native fine-print element. `role="alert"` is a
+ real live-region role for validation text, so it doubles as the selector
+ that turns a hint red. */
+.field > small {
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+.field > small[role="alert"] { color: var(--ad-color-danger); }
+
+/* Input / Select — bordered, matched to each other exactly. Both tags ARE
+ the component, so they are styled bare; nothing else on this site uses a
+ text input or a select. */
+input:not([type="checkbox"]):not([type="radio"]),
+select {
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ outline: none;
+ transition: border-color var(--ad-dur-fast) var(--ad-ease),
+ box-shadow var(--ad-dur-fast) var(--ad-ease);
+}
+input::placeholder { color: var(--ad-color-text-tertiary); }
+
+input:not([type="checkbox"]):not([type="radio"]):focus,
+select:focus {
+ border-color: var(--ad-color-accent);
+ box-shadow: 0 0 0 2px var(--ad-color-accent-soft);
+}
+
+input:not([type="checkbox"]):not([type="radio"]):disabled,
+select:disabled {
+ opacity: 0.5;
+ cursor: default;
+}
+
+/* The chevron is the one flagged color exception in this component set:
+ CSS cannot point a data-URI background-image at a custom property, so its
+ stroke is a literal hex rather than a var(--ad-*) reference. It is picked
+ to match --ad-color-text-secondary and commented here so it is not
+ mistaken for a stray color. */
+select {
+ appearance: none;
+ padding-inline-end: var(--ad-space-l);
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%235f5d59' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right var(--ad-space-xs) center;
+}
+
+input[aria-invalid="true"],
+select[aria-invalid="true"] {
+ border-color: var(--ad-color-danger);
+ background-color: var(--ad-color-danger-bg);
+}
+
+/* Checkbox / Radio — the real native controls, tinted with accent-color
+ instead of a hand-drawn box. This is not just a rename: it deletes the
+ custom SVG-checkmark markup entirely and lets the browser draw its own
+ (platform-appropriate) check and dot. `role="switch"` is excluded here so
+ Switch — a checkbox playing a different role — can style itself below
+ without this rule fighting it. A <label> wrapping a checkbox/radio is told
+ apart from Field's <label> structurally: it :has() one as a direct child,
+ no class required. */
+input[type="checkbox"]:not([role="switch"]),
+input[type="radio"] {
+ width: 1.125rem;
+ height: 1.125rem;
+ margin: 0;
+ accent-color: var(--ad-color-accent);
+ cursor: pointer;
+}
+input[type="checkbox"]:not([role="switch"]):focus-visible,
+input[type="radio"]:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+}
+
+label:has(> input[type="checkbox"]:not([role="switch"])),
+label:has(> input[type="radio"]) {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 400;
+ text-transform: none;
+ letter-spacing: normal;
+ color: var(--ad-color-text-primary);
+}
+label:has(> input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Switch — for on/off settings only, never a substitute for Checkbox.
+ `input[type="checkbox"][role="switch"]` is real ARIA (the correct role for
+ a toggle), not an invented hook, but the visual track+thumb needs a second
+ element for the thumb's independent position and transition — more than
+ `appearance: none` plus pseudo-elements on the input itself can cleanly
+ carry — so `.switch` is a deliberate, documented fallback class rather
+ than a first choice. */
+.switch {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ color: var(--ad-color-text-primary);
+}
+.switch input[role="switch"] { position: absolute; opacity: 0; width: 2.25rem; height: 1.25rem; margin: 0; }
+
+.switch span {
+ position: relative;
+ flex: none;
+ width: 2.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-border-default);
+ transition: background-color var(--ad-dur-base) var(--ad-ease);
+}
+.switch span::after {
+ content: "";
+ position: absolute;
+ inset-block-start: 2px;
+ inset-inline-start: 2px;
+ width: 1rem;
+ height: 1rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-fg);
+ box-shadow: var(--ad-shadow-sm);
+ transition: translate var(--ad-dur-base) var(--ad-ease);
+}
+.switch input[role="switch"]:checked + span { background: var(--ad-color-accent); }
+.switch input[role="switch"]:checked + span::after { translate: 1rem 0; }
+.switch input[role="switch"]:focus-visible + span { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+.switch:has(input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Rows of fields — used by the settings-form pattern. `<form>`/`<fieldset>`
+ are the elements that already mean this, so they are styled bare. A row
+ grouping two fields side by side has no element of its own, so it keeps
+ one plain class. */
+form {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-s);
+ max-width: 28rem;
+}
+.form-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
+ gap: var(--ad-space-xs);
+}
+fieldset {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ margin: 0;
+ padding: 0;
+ border: 0;
+}
+fieldset > legend {
+ padding: 0 0 var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Badge
+ Mono, uppercase, tracked — the catalog-label convention for status and
+ meta values (SAVED, OVERDUE, RECOMMENDED). A label, never a button.
+
+ A <span> is too generic to style globally — it is used all over running
+ prose for unrelated reasons — so Badge gets the one sanctioned class this
+ shape needs. Tone is an attribute, not a modifier class. */
+
+.badge {
+ --_bg: var(--ad-color-surface-fill);
+ --_fg: var(--ad-color-text-secondary);
+ --_border: var(--ad-color-border-hairline);
+
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.1875rem 0.5rem;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-sm);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ line-height: 1;
+ white-space: nowrap;
+}
+
+.badge[data-tone="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); --_border: transparent; }
+.badge[data-tone="success"] { --_bg: var(--ad-color-success-bg); --_fg: var(--ad-color-success); --_border: transparent; }
+.badge[data-tone="danger"] { --_bg: var(--ad-color-danger-bg); --_fg: var(--ad-color-danger); --_border: transparent; }
+
+.badge-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-2xs);
+}
+
+/* Tag
+ A removable mono chip for freeform tags — locations, categories.
+ `[data-dashed]` renders an "add tag" affordance instead of a value. Same
+ reasoning as Badge: <span> is too generic to select bare, so Tag gets one
+ class. The remove control is a real <button>, styled through the
+ descendant selector rather than a class of its own. */
+
+.tag {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.375rem;
+ padding: 0.25rem 0.625rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-default);
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ line-height: 1;
+}
+
+.tag[data-dashed] {
+ background: transparent;
+ border-style: dashed;
+ color: var(--ad-color-text-tertiary);
+ cursor: pointer;
+}
+
+.tag button {
+ all: unset;
+ cursor: pointer;
+ color: var(--ad-color-text-tertiary);
+ line-height: 1;
+ font-size: 0.75rem;
+}
+.tag button:hover { color: var(--ad-color-text-primary); }
+
+.tag-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.375rem;
+}
+
+/* Card
+ Flat surface, hairline border, radius-lg. `[data-elevated]` is opt-in and
+ reserved for the one focused/primary card in a view — a dialog, the
+ primary capture card — flat is the default everywhere else.
+
+ A <div>/<article> is too generic to select bare, so Card keeps one
+ sanctioned class. When Card's job is holding a list of rows — the common
+ case, entry lists, task lists, settings rows — it is a real <ul class="
+ card">, and CardRow is a bare <li> underneath it: no second class needed,
+ the row look comes from the `.card > li` descendant selector alone. The
+ first row's top border is removed automatically so callers never have to
+ override it by hand. */
+
+.card {
+ display: flex;
+ flex-direction: column;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: none;
+ overflow: hidden;
+}
+
+.card[data-elevated] { box-shadow: var(--ad-shadow-float); }
+
+.card > li {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+}
+.card > li:first-child { border-top: none; }
+
+/* A row whose ENTIRE content is wrapped in a real <button> or <a> — the
+ accessible way to make a row clickable — fills the row edge-to-edge and
+ picks up a hover fill and pointer cursor. `:only-child` is what tells this
+ apart from a row that merely contains a small button among other content
+ (a leading checkbox next to a title and a trailing value, say): only a
+ button/anchor that IS the row's one and only child means "the whole row
+ is the control." */
+.card > li:has(> button:only-child),
+.card > li:has(> a:only-child) {
+ padding: 0;
+}
+.card > li > button:only-child,
+.card > li > a:only-child {
+ all: unset;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ box-sizing: border-box;
+ color: inherit;
+ font: inherit;
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.card > li > button:only-child:hover,
+.card > li > a:only-child:hover { background: var(--ad-color-surface-fill); }
+
+/* Documentation-only extras: a body/footer pair and a responsive grid, used
+ by the prose pages (rationale.md, index.md) to show a Card as a plain
+ content tile rather than a data row. Neither is part of Card's props —
+ they exist because a content tile's inner layout has no element of its
+ own to hang a selector on. */
+.card-body {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-m);
+}
+.card-footer {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ margin-top: auto;
+ padding: var(--ad-space-xs) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+
+.card[data-link] { transition: border-color var(--ad-dur-fast) var(--ad-ease), box-shadow var(--ad-dur-fast) var(--ad-ease); }
+.card[data-link]:hover { border-color: var(--ad-color-border-default); box-shadow: var(--ad-shadow-sm); }
+.card[data-link]:focus-within { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+
+.card-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
+ gap: var(--ad-space-s);
+}
+
+/* Avatar — lives here because it almost never appears outside a card or a
+ row (the Loop dashboard's initials chip, Archive's file-type chip). A
+ <span> is too generic for a bare selector, so it keeps one class. */
+.avatar {
+ --_size: 1.875rem;
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ flex: none;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 600;
+ overflow: hidden;
+}
+.avatar[data-square] { border-radius: var(--ad-radius-md); background: var(--ad-color-surface-fill); color: var(--ad-color-text-secondary); border: 1px solid var(--ad-color-border-hairline); }
+.avatar[data-size="sm"] { --_size: 1.5rem; }
+.avatar[data-size="lg"] { --_size: 2.25rem; }
+
+/* Tooltip — a dark mono label on hover, positioned above the child. Pure CSS:
+ the trigger and bubble are siblings inside one relatively-positioned span,
+ shown on :hover/:focus-within so it works from the keyboard too.
+
+ `[role="tooltip"]` is the correct ARIA role for the bubble itself, so it
+ doubles as the styling hook — zero classes there. The wrapping span has no
+ attribute of its own, but it does not need one: `:has()` picks out "a span
+ that contains a [role=tooltip] bubble" precisely, without reaching for a
+ class on an otherwise-generic element. */
+span:has(> [role="tooltip"]) {
+ position: relative;
+ display: inline-flex;
+}
+
+[role="tooltip"] {
+ position: absolute;
+ bottom: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ margin-bottom: 0.375rem;
+ padding: 0.25rem 0.5rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-text-primary);
+ color: var(--ad-color-surface-page);
+ font-family: var(--ad-font-mono);
+ /* 11px is the source's own literal choice — smaller than --ad-step--2, the
+ smallest step on the scale, because a tooltip is meant to read as a
+ glance-only aside rather than body text. */
+ font-size: 11px;
+ white-space: nowrap;
+ line-height: 1.4;
+ z-index: 20;
+ opacity: 0;
+ visibility: hidden;
+ pointer-events: none;
+ transition: opacity var(--ad-dur-fast) var(--ad-ease);
+}
+span:has(> [role="tooltip"]):hover > [role="tooltip"],
+span:has(> [role="tooltip"]):focus-within > [role="tooltip"] {
+ opacity: 1;
+ visibility: visible;
+}
+
+/* Toast — the real pattern used for delete confirmations: a flat bar with an
+ optional destructive action and a dismiss. `[data-fixed]` pins it
+ bottom-right for real use; without it the toast stays in document flow for
+ documentation. `role="status"` is the correct live-region role for a
+ transient status message, so it is both the accessibility hook and the
+ style selector — no base class needed. The action and dismiss buttons are
+ plain <button>s told apart by `aria-label="Dismiss"`, an attribute they
+ need anyway for the icon-only close control. */
+[role="status"] {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ min-width: 20rem;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+}
+[role="status"][data-fixed] {
+ position: fixed;
+ inset-block-end: var(--ad-space-s);
+ inset-inline-end: var(--ad-space-s);
+ z-index: 60;
+}
+[role="status"] > span { flex: 1; }
+[role="status"] button {
+ all: unset;
+ cursor: pointer;
+ font-weight: 600;
+ color: var(--ad-color-danger);
+}
+[role="status"] button[aria-label="Dismiss"] {
+ color: var(--ad-color-text-tertiary);
+ font-size: 1rem;
+ font-weight: 400;
+ line-height: 1;
+}
+[role="status"] button[aria-label="Dismiss"]:hover { color: var(--ad-color-text-primary); }
+
+/* Dialog — the native <dialog> element: a centered modal, flat surface, no
+ colored header, hairline dividers between title/body/footer. A <header>
+ holds the title and a <footer> holds the actions, matching the elements
+ the tag already wants; everything else in between is the body, styled by
+ excluding those two rather than by a class of its own.
+
+ `.dialog-demo` scopes a static, always-open example to its own preview
+ container instead of the viewport, so it can sit safely inside a
+ documentation page — this is a doc-only affordance, not part of Dialog's
+ contract. It relies on the browser's own dialog centering: the UA
+ stylesheet already gives an open <dialog> `position: absolute; inset: 0;
+ margin: auto`, which centers it inside the nearest positioned ancestor —
+ here, `.dialog-demo` itself. A real `.showModal()` call gets the genuine
+ viewport-fixed overlay and `::backdrop` for free; a plain `open` attribute
+ (used here so the demo renders without JavaScript) does not paint a
+ backdrop at all, since `::backdrop` only exists for a dialog shown
+ modally — wire the real open/close state and call `.showModal()` in the
+ consuming app. */
+.dialog-demo {
+ position: relative;
+ min-height: 20rem;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-lg);
+}
+
+dialog {
+ width: 26rem;
+ max-width: calc(100% - (2 * var(--ad-space-s)));
+ height: fit-content;
+ padding: 0;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ overflow: hidden;
+}
+dialog::backdrop {
+ background: rgba(20, 18, 14, 0.35);
+}
+dialog > header {
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-sans);
+ font-weight: 600;
+ font-size: var(--ad-step-0);
+ color: var(--ad-color-text-primary);
+}
+dialog > footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+}
+dialog > *:not(header):not(footer) {
+ padding: var(--ad-space-m);
+ margin: 0;
+ color: var(--ad-color-text-primary);
+ font-size: var(--ad-step--1);
+}
+
+/* Tabs
+ Follows the real WAI-ARIA tabs pattern: a `[role="tablist"]` of
+ `[role="tab"]` buttons controlling `[role="tabpanel"]`s. Those roles are
+ required for the pattern to be accessible in the first place, so they
+ double as the entire styling contract — no classes at all for the base
+ shape. The keyboard wiring lives in assets/js/system.js and is
+ variant-agnostic — it only cares about `[data-tabs]` / `[role=tab]`.
+
+ Two real variants: `underline` (mono, accent underline — primary section
+ nav, the default) and `segmented` (pill track, active segment lifts to the
+ surface — filters like All · Overdue · Upcoming, never primary
+ navigation), chosen with `data-variant="segmented"` on the tablist since
+ neither has a natural attribute of its own. */
+
+/* Every rule below the list container pairs [role="tablist"] with
+ [role="tab"] rather than styling [role="tab"] alone — Tab buttons are
+ still literal <button> elements, so the compound selector's extra
+ specificity is what keeps Button's base styling (button.css) from
+ winning the cascade on shared properties like padding and background. */
+
+[role="tablist"] {
+ display: flex;
+ gap: var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+[role="tablist"] [role="tab"] {
+ padding: 0.5rem 0;
+ margin-bottom: -1px;
+ background: none;
+ border: 0;
+ border-bottom: 2px solid transparent;
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--1);
+ letter-spacing: 0.02em;
+ cursor: pointer;
+ transition: color var(--ad-dur-fast) var(--ad-ease), border-color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"] [role="tab"]:hover { color: var(--ad-color-text-primary); }
+[role="tablist"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ border-bottom-color: var(--ad-color-accent);
+}
+
+[role="tablist"][data-variant="segmented"] {
+ display: inline-flex;
+ gap: 0.125rem;
+ padding: 0.1875rem;
+ border-bottom: 0;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-md);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"] {
+ all: unset;
+ cursor: pointer;
+ padding: 0.375rem 0.875rem;
+ border-radius: var(--ad-radius-sm);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 550;
+ color: var(--ad-color-text-secondary);
+ transition: background-color var(--ad-dur-fast) var(--ad-ease), color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ background: var(--ad-color-surface-card);
+ box-shadow: var(--ad-shadow-sm);
+}
+
+[role="tabpanel"] { padding-top: var(--ad-space-s); }
+[role="tabpanel"][hidden] { display: none; }
+[role="tabpanel"]:focus-visible { outline-offset: 4px; }
+
+/* Documentation site chrome. Everything below the `.ad-doc-` prefix belongs
+ to this site rather than to the design system it documents — including the
+ width/height constants used directly below. The source design system
+ defines no container-width tokens at all, so there is nothing under
+ `--ad-*` for a doc shell to reach for; the Space foundation page's own
+ notes say to omit that group entirely rather than invent one. */
+
+.ad-doc-skip {
+ position: absolute;
+ inset-inline-start: var(--ad-space-xs);
+ inset-block-start: -4rem;
+ z-index: 40;
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ box-shadow: var(--ad-shadow-float);
+ transition: inset-block-start var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-skip:focus { inset-block-start: var(--ad-space-2xs); }
+
+.ad-doc-header {
+ position: sticky;
+ inset-block-start: 0;
+ z-index: 30;
+ height: 3.5rem;
+ background: color-mix(in srgb, var(--ad-color-surface-page) 92%, transparent);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-header__inner {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ height: 100%;
+ max-width: 72rem;
+ margin-inline: auto;
+ padding-inline: var(--ad-space-xs);
+}
+
+.ad-doc-wordmark {
+ display: flex;
+ align-items: baseline;
+ gap: var(--ad-space-2xs);
+ color: var(--ad-color-text-primary);
+ text-decoration: none;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ font-weight: 600;
+ letter-spacing: -0.01em;
+ white-space: nowrap;
+}
+.ad-doc-wordmark:hover { color: var(--ad-color-text-primary); }
+.ad-doc-wordmark__mark {
+ width: 1.375rem;
+ height: 1.375rem;
+ flex: none;
+ align-self: center;
+ color: var(--ad-color-accent);
+}
+.ad-doc-wordmark__version {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+
+.ad-doc-header__actions { margin-inline-start: auto; display: flex; align-items: center; gap: var(--ad-space-2xs); }
+
+.ad-doc-layout {
+ display: grid;
+ grid-template-columns: 16rem minmax(0, 1fr);
+ gap: var(--ad-space-l);
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-m) var(--ad-space-xs) var(--ad-space-2xl);
+}
+
+.ad-doc-sidebar {
+ position: sticky;
+ inset-block-start: calc(3.5rem + var(--ad-space-m));
+ align-self: start;
+ max-height: calc(100vh - 3.5rem - var(--ad-space-l));
+ overflow-y: auto;
+ padding-inline-end: var(--ad-space-2xs);
+}
+.ad-doc-nav__group + .ad-doc-nav__group { margin-top: var(--ad-space-s); }
+.ad-doc-nav__heading {
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-doc-nav__link {
+ display: block;
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ margin-inline-start: calc(var(--ad-space-xs) * -1);
+ border-inline-start: 2px solid transparent;
+ border-radius: 0 var(--ad-radius-sm) var(--ad-radius-sm) 0;
+ color: var(--ad-color-text-secondary);
+ font-size: var(--ad-step--1);
+ text-decoration: none;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-nav__link:hover { color: var(--ad-color-text-primary); background: var(--ad-color-surface-fill); }
+.ad-doc-nav__link[aria-current="page"] {
+ color: var(--ad-color-accent-ink);
+ border-inline-start-color: var(--ad-color-accent);
+ background: var(--ad-color-accent-soft);
+ font-weight: 500;
+}
+
+.ad-doc-main {
+ min-width: 0;
+ /* The design system's own body-text token (--ad-step-0, 18–20px) is right
+ for a product screen but too large for long-form documentation prose.
+ This site's own copy gets a plain, fixed 16px instead — the token above
+ is unchanged, and specimen tables/demos below still show its real
+ value. */
+ font-size: 1rem;
+}
+.ad-doc-main > * { max-width: 50rem; }
+
+.ad-doc-title { margin-bottom: var(--ad-space-xs); }
+.ad-doc-title__section {
+ display: block;
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-accent);
+}
+
+.ad-doc-main h2 {
+ margin-block: var(--ad-space-2xl) var(--ad-space-xs);
+ padding-bottom: var(--ad-space-2xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-main h3 { margin-block: var(--ad-space-l) var(--ad-space-2xs); }
+.ad-doc-main h4 { margin-block: var(--ad-space-s) var(--ad-space-2xs); }
+.ad-doc-main p,
+.ad-doc-main ul,
+.ad-doc-main ol { max-width: 68ch; }
+.ad-doc-main ul:not([class]),
+.ad-doc-main ol:not([class]) { padding-inline-start: var(--ad-space-s); }
+
+.ad-doc-footer {
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-s) var(--ad-space-xs) var(--ad-space-l);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-tertiary);
+ font-size: var(--ad-step--1);
+}
+.ad-doc-footer__inner { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); justify-content: space-between; }
+
+/* `button.ad-doc-nav-toggle` (element + class) rather than a bare class:
+ button.css's own base rule is `button:not(.icon-button)`, which is also
+ one element + one class in specificity and sets `display: inline-flex` —
+ a bare `.ad-doc-nav-toggle { display: none }` loses that fight and the
+ toggle stays visible at every width. Matching its specificity here (and
+ coming later in the concatenated stylesheet) settles it in source order. */
+button.ad-doc-nav-toggle { display: none; }
+
+@media (max-width: 60rem) {
+ .ad-doc-layout { grid-template-columns: minmax(0, 1fr); gap: var(--ad-space-s); }
+ .ad-doc-sidebar {
+ position: static;
+ max-height: none;
+ }
+ /* The toggle is a normal Button; the panel it opens gets its own card
+ treatment. Putting the same card styling on the <aside> wrapper too
+ nested a button-shaped box inside an outer box for no reason. */
+ .ad-doc-nav {
+ margin-top: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ }
+ .ad-doc-sidebar[data-collapsed="true"] > .ad-doc-nav { display: none; }
+ button.ad-doc-nav-toggle {
+ display: flex;
+ width: 100%;
+ justify-content: space-between;
+ }
+}
+
+@media (max-width: 34rem) {
+ .ad-doc-wordmark__version { display: none; }
+ .ad-doc-layout { padding-inline: var(--ad-space-2xs); }
+}
+
+/* The live-example figure used throughout the docs — see the "How the
+ example demos work" section on the Using the CSS page. One class on the
+ outer <figure>; everything inside is styled by plain element selectors,
+ since figcaption/div/details/summary/pre already say what they are.
+ `data-layout` and `data-surface` on the preview <div> distinguish the
+ handful of demos that need a non-default arrangement. */
+
+.example {
+ margin: var(--ad-space-s) 0 var(--ad-space-l);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+ background: var(--ad-color-surface-card);
+ overflow: hidden;
+}
+
+.example figcaption {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-secondary);
+}
+
+.example > div {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-s);
+}
+/* nowrap is load-bearing: a column-direction flex container with wrap enabled
+ sizes its single line against the container rather than the content, which
+ left every stacked example with a few hundred pixels of dead space. */
+.example > div[data-layout="stack"] { flex-direction: column; flex-wrap: nowrap; align-items: stretch; }
+.example > div[data-layout="grid"] {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
+ align-items: start;
+}
+.example > div[data-surface="sunken"] { background: var(--ad-color-surface-fill); }
+.example > div > * { min-width: 0; }
+
+.example details {
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+.example summary {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ color: var(--ad-color-text-secondary);
+ cursor: pointer;
+ list-style: none;
+ user-select: none;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+}
+.example summary::-webkit-details-marker { display: none; }
+.example summary::before {
+ content: "";
+ width: 0.5rem;
+ height: 0.5rem;
+ border-inline-end: 1.5px solid currentColor;
+ border-block-end: 1.5px solid currentColor;
+ rotate: -45deg;
+ transition: rotate var(--ad-dur-fast) var(--ad-ease);
+}
+.example details[open] > summary::before { rotate: 45deg; }
+.example summary:hover { color: var(--ad-color-text-primary); }
+
+.example pre {
+ margin: 0;
+ padding: var(--ad-space-xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+ tab-size: 2;
+}
+.example pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Standalone fenced code blocks in the prose (unhighlighted — there is no
+ syntax highlighter in this build any more, just Kramdown's plain
+ <pre><code class="language-*">). */
+.ad-doc-main > pre {
+ margin-block: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+}
+.ad-doc-main > pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Presentation for the token reference tables and swatch grids, plus a
+ generic data-table utility. All of this is documentation chrome — the
+ design system itself has no Table component (see the note on the removed
+ `table`/`progress`/`callout` pages in git history). */
+
+.ad-swatch-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs) var(--ad-space-s);
+ padding: 0;
+ list-style: none;
+}
+.ad-swatch {
+ display: flex;
+ flex-direction: column;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ overflow: hidden;
+ background: var(--ad-color-surface-card);
+}
+.ad-swatch__chip {
+ display: flex;
+ align-items: flex-end;
+ justify-content: flex-end;
+ height: 4rem;
+ padding: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+}
+.ad-swatch__meta {
+ display: flex;
+ flex-direction: column;
+ gap: 0.1rem;
+ padding: var(--ad-space-2xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+}
+.ad-swatch__hex { color: var(--ad-color-text-primary); text-transform: uppercase; }
+.ad-swatch__ratio { color: var(--ad-color-text-tertiary); }
+
+.ad-alias-row { display: flex; align-items: center; gap: var(--ad-space-2xs); }
+.ad-alias-chip {
+ width: 1.5rem;
+ height: 1.5rem;
+ flex: none;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+}
+.ad-alias-chip--pair { display: flex; }
+.ad-alias-chip--pair > span { flex: 1; }
+
+/* Type specimen rows. */
+.ad-specimen {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ gap: var(--ad-space-3xs);
+ padding-block: var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-specimen__meta {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-specimen__sample {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: var(--ad-color-text-primary);
+}
+
+/* Space and radius rulers. */
+.ad-ruler { display: flex; flex-direction: column; gap: var(--ad-space-2xs); }
+.ad-ruler__row { display: grid; grid-template-columns: 6.5rem 4.5rem 1fr; align-items: center; gap: var(--ad-space-xs); font-family: var(--ad-font-mono); font-size: 11px; }
+.ad-ruler__bar { height: 0.75rem; background: var(--ad-color-accent); border-radius: var(--ad-radius-sm); }
+.ad-ruler__num { color: var(--ad-color-text-tertiary); text-align: right; }
+
+.ad-radius-grid, .ad-elevation-grid, .ad-motion-grid, .ad-accent-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs);
+ padding: 0;
+ list-style: none;
+}
+.ad-radius-demo {
+ height: 4.5rem;
+ background: var(--ad-color-accent-soft);
+ border: 1px solid var(--ad-color-accent);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-elevation-demo {
+ height: 4.5rem;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-motion-demo {
+ height: 4.5rem;
+ display: grid;
+ place-items: center;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-md);
+ margin-bottom: var(--ad-space-2xs);
+ cursor: pointer;
+ overflow: hidden;
+}
+.ad-motion-demo__dot {
+ width: 1.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent);
+ translate: -3rem 0;
+ transition-property: translate;
+ transition-duration: var(--ad-dur-base);
+ transition-timing-function: var(--ad-ease);
+}
+.ad-motion-demo:hover .ad-motion-demo__dot,
+.ad-motion-demo:focus-visible .ad-motion-demo__dot { translate: 3rem 0; }
+
+.ad-accent-demo {
+ height: 4.5rem;
+ display: flex;
+ align-items: flex-end;
+ padding: var(--ad-space-2xs);
+ border-radius: var(--ad-radius-md);
+ color: #fff;
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ margin-bottom: var(--ad-space-2xs);
+}
+
+/* Token names read `<code>` sitewide, which already carries a mono chip
+ look for running prose (see :not(pre) > code in base/typography.css).
+ Inside these specimen/swatch/ruler rows the old `.ad-token-name` class was
+ plain mono text with no chip, so `<code>` is flattened back to that look
+ here the same way table cells are. */
+.ad-swatch code, .ad-specimen code, .ad-ruler code, .ad-alias-row code {
+ background: none;
+ border: 0;
+ padding: 0;
+ font-size: var(--ad-step--2);
+ color: var(--ad-color-text-primary);
+ white-space: nowrap;
+}
+
+/* A documentation aside. Like `<table>`, this is site chrome for the prose
+ itself, not a system component — the real system has no Callout. */
+.ad-callout {
+ margin-block: var(--ad-space-s);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-left: 3px solid var(--ad-color-accent);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+}
+/* Generic reference tables (component APIs, alias tables, the contrast
+ contract). `<table>` IS a table — no class needed to say so. Every table
+ on this site uses the one compact density, so there is no separate
+ modifier for it. `display: block` plus `overflow-x: auto` gives the table
+ its own horizontal scrollbar on narrow viewports without a wrapper div;
+ the anonymous table box the browser generates around `<thead>`/`<tbody>`
+ still does the row/column layout. `<code>` replaces the old
+ `.ad-token-name` / `.ad-table__code` cells — it is already styled
+ sitewide, just re-flattened here to a plain mono run instead of the
+ chip-with-border look prose code gets, to match this table's original
+ look. `.num` is the one sanctioned class left, for right-aligned numeric
+ columns — there is no attribute that means "this column is a number". */
+table { width: 100%; border-collapse: collapse; font-size: var(--ad-step--1); display: block; overflow-x: auto; }
+table th, table td {
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ text-align: left;
+ vertical-align: top;
+}
+table th { font-family: var(--ad-font-mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ad-color-text-secondary); font-weight: 500; }
+table code { background: none; border: 0; padding: 0; font-size: var(--ad-step--2); white-space: nowrap; }
+.num { text-align: right; font-variant-numeric: tabular-nums; }
+
+/* Home page. */
+.ad-hero { padding-block: var(--ad-space-m) var(--ad-space-l); }
+.ad-hero__title {
+ font-size: var(--ad-step-4);
+ margin-bottom: var(--ad-space-xs);
+}
+.ad-hero__actions { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); margin-top: var(--ad-space-s); }
+.ad-stat-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-l);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+}
+.ad-stat__value {
+ display: block;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-2);
+ font-weight: 600;
+ line-height: 1.16;
+ font-variant-numeric: tabular-nums;
+}
+.ad-stat__label {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+
+/* Card chrome. The pane supplies the frame; this just insets the page. */
+body { padding: var(--ad-space-l) var(--ad-space-m); }
+.ad-doc-main { max-width: 50rem; margin-inline: auto; }
+.ad-doc-main > * { max-width: none; }
+</style>
+</head>
+<body>
+<main class="ad-doc-main">
+<header class="ad-doc-title"><span class="ad-doc-title__section">Components</span>
+ <h1>Tooltip</h1>
+</header>
+<p class="ad-lead">Dark mono tooltip on hover, positioned above the child. Pure CSS — shown on hover and keyboard focus, no JavaScript required.</p>
+<h2 id="basic">Basic</h2>
+
+<p><code>role="tooltip"</code> is the correct ARIA role for the bubble itself, so it
+doubles as the styling hook — no class on the bubble. The wrapping <code><span></code>
+has no attribute of its own, but it does not need one: <code>:has()</code> selects “a
+span that contains a <code>[role=tooltip]</code> bubble” precisely, so the wrapper stays
+bare too. Pair the trigger’s <code>aria-describedby</code> with the bubble’s <code>id</code> so the
+tooltip is announced, not just shown.</p>
+
+<p>Hover or tab to the button below.</p>
+
+<figure class="example">
+<figcaption>Tooltip</figcaption>
+<div>
+<span>
+ <button class="icon-button" aria-label="Delete" aria-describedby="tooltip-basic">
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13" /></svg>
+ </button>
+ <span role="tooltip" id="tooltip-basic">Delete</span>
+</span>
+</div>
+<details open=""><summary>Markup</summary>
+<pre><code><span>
+ <button class="icon-button" aria-label="Delete" aria-describedby="tooltip-basic">
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13" /></svg>
+ </button>
+ <span role="tooltip" id="tooltip-basic">Delete</span>
+</span></code></pre>
+</details>
+</figure>
+
+<h2 id="on-any-element">On any element</h2>
+
+<p>A Tooltip wraps whatever it labels — most often an <a href="https://kejadlen.github.io/arbitrary-definitions-design-system/components/icon-button/">IconButton</a>, since that is the control with no visible text of its own.</p>
+
+<figure class="example">
+<figcaption>On a badge</figcaption>
+<div>
+<span>
+ <span class="badge" data-tone="danger" tabindex="0" aria-describedby="tooltip-badge">3d overdue</span>
+ <span role="tooltip" id="tooltip-badge">Due Tuesday, was Saturday</span>
+</span>
+</div>
+<details><summary>Markup</summary>
+<pre><code><span>
+ <span class="badge" data-tone="danger" tabindex="0" aria-describedby="tooltip-badge">3d overdue</span>
+ <span role="tooltip" id="tooltip-badge">Due Tuesday, was Saturday</span>
+</span></code></pre>
+</details>
+</figure>
+
+<h2 id="api">API</h2>
+
+<table>
+ <thead><tr><th scope="col">Element / attribute</th><th scope="col">Values</th><th scope="col">Notes</th></tr></thead>
+ <tbody>
+ <tr><td><code>span</code></td><td></td><td>Required wrapper around the trigger and the bubble. Bare — matched structurally via <code>:has()</code>, no class.</td></tr>
+ <tr><td><code>[role="tooltip"]</code></td><td>string content</td><td>Required — the bubble. Give it an <code>id</code>.</td></tr>
+ <tr><td><code>aria-describedby</code></td><td>the bubble's <code>id</code></td><td>Put it on the trigger element, which needs a real accessible name of its own — a tooltip is not one.</td></tr>
+ </tbody>
+</table>
+</main>
+<script>
+// Behavior for the pieces of the system that need it. Everything else is
+// CSS and semantic HTML on purpose — a design system that only works with
+// JavaScript running is a component library with extra steps.
+(function () {
+ "use strict";
+
+ var STORAGE_KEY = "ad-theme";
+
+ function readStored() {
+ try {
+ return localStorage.getItem(STORAGE_KEY);
+ } catch (error) {
+ return null;
+ }
+ }
+
+ function writeStored(value) {
+ try {
+ localStorage.setItem(STORAGE_KEY, value);
+ } catch (error) {
+ /* Nothing to do — the media query still gives a sensible default. */
+ }
+ }
+
+ function systemPrefersDark() {
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
+ }
+
+ function currentTheme() {
+ var explicit = document.documentElement.getAttribute("data-theme");
+ if (explicit) return explicit;
+ return systemPrefersDark() ? "dark" : "light";
+ }
+
+ function paintToggle(theme) {
+ document.querySelectorAll("[data-theme-toggle]").forEach(function (toggle) {
+ var next = theme === "dark" ? "light" : "dark";
+ toggle.querySelectorAll("[data-theme-icon]").forEach(function (icon) {
+ icon.hidden = icon.dataset.themeIcon !== theme;
+ });
+ toggle.setAttribute("aria-label", "Switch to the " + next + " theme");
+ toggle.setAttribute("title", "Switch to the " + next + " theme");
+ var label = toggle.querySelector("[data-theme-label]");
+ if (label) label.textContent = "Switch to the " + next + " theme";
+ });
+ }
+
+ function setUpTheme() {
+ paintToggle(currentTheme());
+
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-theme-toggle]");
+ if (!toggle) return;
+
+ var next = currentTheme() === "dark" ? "light" : "dark";
+ document.documentElement.setAttribute("data-theme", next);
+ writeStored(next);
+ paintToggle(next);
+ });
+
+ // Follow the OS while the reader has not made an explicit choice.
+ window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", function () {
+ if (!readStored()) paintToggle(currentTheme());
+ });
+ }
+
+ // Tabs, wired to the WAI-ARIA authoring practices: arrows move, Home/End
+ // jump, and only the selected tab stays in the tab order.
+ function setUpTabs() {
+ document.querySelectorAll("[data-tabs]").forEach(function (root) {
+ var tabs = Array.prototype.slice.call(root.querySelectorAll('[role="tab"]'));
+ if (!tabs.length) return;
+
+ function select(tab, focus) {
+ tabs.forEach(function (candidate) {
+ var selected = candidate === tab;
+ candidate.setAttribute("aria-selected", String(selected));
+ candidate.tabIndex = selected ? 0 : -1;
+ var panel = document.getElementById(candidate.getAttribute("aria-controls"));
+ if (panel) panel.hidden = !selected;
+ });
+ if (focus) tab.focus();
+ }
+
+ root.addEventListener("click", function (event) {
+ var tab = event.target.closest('[role="tab"]');
+ if (tab) select(tab, false);
+ });
+
+ root.addEventListener("keydown", function (event) {
+ var index = tabs.indexOf(document.activeElement);
+ if (index === -1) return;
+
+ var next = null;
+ if (event.key === "ArrowRight") next = tabs[(index + 1) % tabs.length];
+ else if (event.key === "ArrowLeft") next = tabs[(index - 1 + tabs.length) % tabs.length];
+ else if (event.key === "Home") next = tabs[0];
+ else if (event.key === "End") next = tabs[tabs.length - 1];
+
+ if (next) {
+ event.preventDefault();
+ select(next, true);
+ }
+ });
+
+ select(tabs.filter(function (tab) {
+ return tab.getAttribute("aria-selected") === "true";
+ })[0] || tabs[0], false);
+ });
+ }
+
+ function setUpNavToggle() {
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-nav-toggle]");
+ if (!toggle) return;
+
+ var sidebar = toggle.closest("[data-sidebar]");
+ var collapsed = sidebar.dataset.collapsed !== "false";
+ sidebar.dataset.collapsed = String(!collapsed);
+ toggle.setAttribute("aria-expanded", String(collapsed));
+ });
+ }
+
+ setUpTheme();
+ setUpTabs();
+ setUpNavToggle();
+})();
+
+</script>
+</body>
+</html>
diff --git a/design-bundle/css/style.css b/design-bundle/css/style.css
new file mode 100644
index 0000000..b3f3073
--- /dev/null
+++ b/design-bundle/css/style.css
@@ -0,0 +1,1459 @@
+/* A short reset. Everything it sets is something the system would otherwise
+ have to override in every component.
+
+ The rem values used for this documentation site's own chrome (measure,
+ content width, sidebar width, header height) belong to this site, not to
+ the design system it documents — the source design system defines no
+ container-width tokens at all, so there is nothing under `--ad-*` to reach
+ for here. See the header comment in site/shell.css. */
+
+*,
+*::before,
+*::after { box-sizing: border-box; }
+
+html {
+ -webkit-text-size-adjust: 100%;
+ scroll-behavior: smooth;
+ scroll-padding-top: calc(3.5rem + var(--ad-space-s));
+}
+
+@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;
+ }
+}
+
+body {
+ margin: 0;
+ background: var(--ad-color-surface-page);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ line-height: 1.55;
+ font-synthesis-weight: none;
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+}
+
+::selection {
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+}
+
+h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd { margin: 0; }
+ul[class], ol[class] { list-style: none; margin: 0; padding: 0; }
+
+img, svg, video { display: block; max-width: 100%; height: auto; }
+
+button, input, select, textarea {
+ font: inherit;
+ color: inherit;
+}
+
+hr {
+ height: 1px;
+ margin: var(--ad-space-l) 0;
+ border: 0;
+ background: var(--ad-color-border-hairline);
+}
+
+/* One focus ring, defined once. No component is allowed its own. */
+:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+ border-radius: var(--ad-radius-sm);
+}
+:focus:not(:focus-visible) { outline: none; }
+
+[hidden] { display: none !important; }
+
+.ad-visually-hidden {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip-path: inset(50%);
+ white-space: nowrap;
+ border: 0;
+}
+
+/* Every rule here reads a type token for family and size. Weight, line-height,
+ and letter-spacing are hardcoded to the exact numbers the source ships —
+ there is no `--ad-weight-*` / `--ad-leading-*` / `--ad-tracking-*` scale in
+ the real system, only eight fixed compound styles (see the Typography
+ foundation page, which this file implements verbatim). */
+
+.type-display { font: 600 var(--ad-step-4)/1.08 var(--ad-font-sans); letter-spacing: -0.015em; }
+.type-h1, h1 { font: 600 var(--ad-step-3)/1.12 var(--ad-font-sans); letter-spacing: -0.012em; }
+.type-h2, h2 { font: 600 var(--ad-step-2)/1.16 var(--ad-font-sans); letter-spacing: -0.01em; }
+.type-h3, h3 { font: 600 var(--ad-step-1)/1.25 var(--ad-font-sans); }
+.type-body { font: 400 var(--ad-step-0)/1.55 var(--ad-font-sans); }
+.type-body-sm { font: 400 var(--ad-step--1)/1.5 var(--ad-font-sans); }
+.type-label {
+ font: 500 var(--ad-step--2)/1 var(--ad-font-mono);
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: var(--ad-color-text-secondary);
+}
+.type-mono { font: 400 var(--ad-step--1)/1.5 var(--ad-font-mono); }
+
+/* Prose defaults for the documentation body copy. These read the same
+ tokens the compound classes above do; they are not a second scale. */
+h1, h2, h3, h4 { font-weight: 600; text-wrap: balance; }
+
+h4 { font: 600 var(--ad-step-0)/1.3 var(--ad-font-sans); }
+
+p, ul, ol { margin: 0 0 var(--ad-space-s); }
+li + li { margin-top: var(--ad-space-3xs); }
+
+a {
+ color: var(--ad-color-accent);
+ text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
+ text-underline-offset: 0.18em;
+ text-decoration-thickness: 1px;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ text-decoration-color var(--ad-dur-fast) var(--ad-ease);
+}
+a:hover {
+ color: var(--ad-color-accent-ink);
+ text-decoration-color: currentColor;
+}
+
+strong, b { font-weight: 600; }
+
+small { font-size: var(--ad-step--1); }
+
+.ad-lead {
+ font-size: var(--ad-step-1);
+ line-height: 1.5;
+ color: var(--ad-color-text-secondary);
+ max-width: 68ch;
+ text-wrap: pretty;
+}
+
+.ad-muted { color: var(--ad-color-text-secondary); }
+.ad-subtle { color: var(--ad-color-text-tertiary); }
+
+code, kbd, samp, pre {
+ font-family: var(--ad-font-mono);
+ font-feature-settings: "liga" 0;
+}
+
+:not(pre) > code {
+ padding: 0.15em 0.4em;
+ font-size: 0.875em;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-sm);
+ white-space: nowrap;
+}
+
+kbd {
+ display: inline-block;
+ padding: 0.1em 0.45em;
+ font-size: var(--ad-step--2);
+ line-height: 1.6;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-bottom-width: 2px;
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-secondary);
+}
+
+blockquote {
+ margin-block: var(--ad-space-s);
+ padding-left: var(--ad-space-s);
+ border-left: 2px solid var(--ad-color-accent);
+ color: var(--ad-color-text-secondary);
+}
+
+/* Button
+ A flat, quiet button that recedes rather than performs — no gradients, no
+ shine. Variants: default, primary, dark, ghost, danger. Sizes: sm, md
+ (default), lg — padding and font step only, weight and radius stay put.
+
+ The contract is the element itself: a bare <button> is styled directly, no
+ class required. An <a> that acts like a button (navigates rather than
+ changes something — see the Button page's "Links that act like buttons")
+ opts in with a single `.btn` class, since a bare `a` selector would catch
+ every ordinary prose link on the site. `:not(.icon-button)` keeps this
+ block from leaking onto IconButton, which shares the <button> tag but is
+ an entirely different shape. */
+
+button:not(.icon-button),
+a.btn {
+ --_bg: var(--ad-color-surface-card);
+ --_fg: var(--ad-color-text-primary);
+ --_border: var(--ad-color-border-default);
+ --_pad: var(--ad-space-xs) var(--ad-space-s);
+ --_font: var(--ad-step--1);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: var(--ad-space-2xs);
+ padding: var(--_pad);
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-sans);
+ font-size: var(--_font);
+ font-weight: 550;
+ letter-spacing: -0.01em;
+ line-height: 1.2;
+ text-align: center;
+ text-decoration: none;
+ white-space: nowrap;
+ cursor: pointer;
+ transition: filter var(--ad-dur-fast) var(--ad-ease),
+ border-color var(--ad-dur-fast) var(--ad-ease);
+}
+
+:is(button:not(.icon-button), a.btn):hover:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.96); }
+:is(button:not(.icon-button), a.btn):active:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.92); }
+
+button:not(.icon-button):disabled,
+:is(button:not(.icon-button), a.btn)[aria-disabled="true"] {
+ opacity: 0.5;
+ cursor: default;
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="primary"] {
+ --_bg: var(--ad-color-accent);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-accent);
+}
+
+/* Reserved for the single most-committing action in a view — a final Save,
+ never a whole row of them. */
+:is(button:not(.icon-button), a.btn)[data-variant="dark"] {
+ --_bg: var(--ad-color-text-primary);
+ --_fg: var(--ad-color-surface-page);
+ --_border: var(--ad-color-text-primary);
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="ghost"] {
+ --_bg: transparent;
+ --_fg: var(--ad-color-text-primary);
+ --_border: transparent;
+}
+
+/* The source hardcodes this label white rather than deriving it — danger
+ is not "on a filled accent surface", so --ad-color-accent-fg is reused
+ here only because its resolved value is the same literal white, not
+ because the two roles are the same thing. */
+:is(button:not(.icon-button), a.btn)[data-variant="danger"] {
+ --_bg: var(--ad-color-danger);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-danger);
+}
+
+:is(button:not(.icon-button), a.btn)[data-size="sm"] { --_pad: 0.375rem 0.75rem; --_font: 0.8125rem; }
+:is(button:not(.icon-button), a.btn)[data-size="lg"] { --_pad: 0.75rem 1.375rem; --_font: var(--ad-step-0); }
+
+:is(button:not(.icon-button), a.btn)[data-block] { width: 100%; }
+
+/* Button group — [role="group"] is already the correct ARIA role for a
+ toolbar of related buttons, so it doubles as the styling hook for free. */
+[role="group"] {
+ display: inline-flex;
+ isolation: isolate;
+}
+[role="group"] > button { border-radius: 0; }
+[role="group"] > button:first-child { border-start-start-radius: var(--ad-radius-md); border-end-start-radius: var(--ad-radius-md); }
+[role="group"] > button:last-child { border-start-end-radius: var(--ad-radius-md); border-end-end-radius: var(--ad-radius-md); }
+[role="group"] > button + button { margin-inline-start: -1px; }
+[role="group"] > button:hover,
+[role="group"] > button:focus-visible { z-index: 1; }
+
+/* IconButton — a square icon-only button. Always needs an aria-label; it
+ becomes both the accessible name and the native tooltip. There is no
+ attribute that distinguishes "a button that is only an icon" from a plain
+ Button, so this is the one sanctioned class in this file. */
+.icon-button {
+ --_size: 2.125rem;
+ --_bg: transparent;
+ --_border: transparent;
+ --_fg: var(--ad-color-text-secondary);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ padding: 0;
+ flex: none;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease),
+ color var(--ad-dur-fast) var(--ad-ease);
+}
+.icon-button:hover:not(:disabled) { background: var(--ad-color-surface-fill-2); }
+.icon-button:disabled { opacity: 0.5; cursor: default; }
+
+.icon-button[data-size="sm"] { --_size: 1.75rem; }
+.icon-button[data-size="lg"] { --_size: 2.5rem; }
+
+.icon-button[data-variant="outline"] { --_bg: var(--ad-color-surface-card); --_border: var(--ad-color-border-default); --_fg: var(--ad-color-text-primary); }
+.icon-button[data-variant="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); }
+
+/* Field
+ The labeled field wrapper — a <label> that wraps its own caption text
+ together with the control, so the association is native and needs no
+ `for`/`id` pair. The caption is mono, uppercase, tracked — the same
+ catalog-label convention as Badge and type-label.
+
+ A <label> is reused for three different shapes across this system (this
+ wrapper, Checkbox/Radio's row, Switch's row), so it cannot be styled bare
+ without one shape leaking into another. Field is the one of the three with
+ no distinguishing attribute of its own, so it gets the sanctioned class;
+ Checkbox/Radio tell themselves apart structurally instead (see below). */
+.field {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ min-width: 0;
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Hint and error read the native fine-print element. `role="alert"` is a
+ real live-region role for validation text, so it doubles as the selector
+ that turns a hint red. */
+.field > small {
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+.field > small[role="alert"] { color: var(--ad-color-danger); }
+
+/* Input / Select — bordered, matched to each other exactly. Both tags ARE
+ the component, so they are styled bare; nothing else on this site uses a
+ text input or a select. */
+input:not([type="checkbox"]):not([type="radio"]),
+select {
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ outline: none;
+ transition: border-color var(--ad-dur-fast) var(--ad-ease),
+ box-shadow var(--ad-dur-fast) var(--ad-ease);
+}
+input::placeholder { color: var(--ad-color-text-tertiary); }
+
+input:not([type="checkbox"]):not([type="radio"]):focus,
+select:focus {
+ border-color: var(--ad-color-accent);
+ box-shadow: 0 0 0 2px var(--ad-color-accent-soft);
+}
+
+input:not([type="checkbox"]):not([type="radio"]):disabled,
+select:disabled {
+ opacity: 0.5;
+ cursor: default;
+}
+
+/* The chevron is the one flagged color exception in this component set:
+ CSS cannot point a data-URI background-image at a custom property, so its
+ stroke is a literal hex rather than a var(--ad-*) reference. It is picked
+ to match --ad-color-text-secondary and commented here so it is not
+ mistaken for a stray color. */
+select {
+ appearance: none;
+ padding-inline-end: var(--ad-space-l);
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%235f5d59' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right var(--ad-space-xs) center;
+}
+
+input[aria-invalid="true"],
+select[aria-invalid="true"] {
+ border-color: var(--ad-color-danger);
+ background-color: var(--ad-color-danger-bg);
+}
+
+/* Checkbox / Radio — the real native controls, tinted with accent-color
+ instead of a hand-drawn box. This is not just a rename: it deletes the
+ custom SVG-checkmark markup entirely and lets the browser draw its own
+ (platform-appropriate) check and dot. `role="switch"` is excluded here so
+ Switch — a checkbox playing a different role — can style itself below
+ without this rule fighting it. A <label> wrapping a checkbox/radio is told
+ apart from Field's <label> structurally: it :has() one as a direct child,
+ no class required. */
+input[type="checkbox"]:not([role="switch"]),
+input[type="radio"] {
+ width: 1.125rem;
+ height: 1.125rem;
+ margin: 0;
+ accent-color: var(--ad-color-accent);
+ cursor: pointer;
+}
+input[type="checkbox"]:not([role="switch"]):focus-visible,
+input[type="radio"]:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+}
+
+label:has(> input[type="checkbox"]:not([role="switch"])),
+label:has(> input[type="radio"]) {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 400;
+ text-transform: none;
+ letter-spacing: normal;
+ color: var(--ad-color-text-primary);
+}
+label:has(> input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Switch — for on/off settings only, never a substitute for Checkbox.
+ `input[type="checkbox"][role="switch"]` is real ARIA (the correct role for
+ a toggle), not an invented hook, but the visual track+thumb needs a second
+ element for the thumb's independent position and transition — more than
+ `appearance: none` plus pseudo-elements on the input itself can cleanly
+ carry — so `.switch` is a deliberate, documented fallback class rather
+ than a first choice. */
+.switch {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ color: var(--ad-color-text-primary);
+}
+.switch input[role="switch"] { position: absolute; opacity: 0; width: 2.25rem; height: 1.25rem; margin: 0; }
+
+.switch span {
+ position: relative;
+ flex: none;
+ width: 2.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-border-default);
+ transition: background-color var(--ad-dur-base) var(--ad-ease);
+}
+.switch span::after {
+ content: "";
+ position: absolute;
+ inset-block-start: 2px;
+ inset-inline-start: 2px;
+ width: 1rem;
+ height: 1rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-fg);
+ box-shadow: var(--ad-shadow-sm);
+ transition: translate var(--ad-dur-base) var(--ad-ease);
+}
+.switch input[role="switch"]:checked + span { background: var(--ad-color-accent); }
+.switch input[role="switch"]:checked + span::after { translate: 1rem 0; }
+.switch input[role="switch"]:focus-visible + span { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+.switch:has(input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Rows of fields — used by the settings-form pattern. `<form>`/`<fieldset>`
+ are the elements that already mean this, so they are styled bare. A row
+ grouping two fields side by side has no element of its own, so it keeps
+ one plain class. */
+form {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-s);
+ max-width: 28rem;
+}
+.form-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
+ gap: var(--ad-space-xs);
+}
+fieldset {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ margin: 0;
+ padding: 0;
+ border: 0;
+}
+fieldset > legend {
+ padding: 0 0 var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Badge
+ Mono, uppercase, tracked — the catalog-label convention for status and
+ meta values (SAVED, OVERDUE, RECOMMENDED). A label, never a button.
+
+ A <span> is too generic to style globally — it is used all over running
+ prose for unrelated reasons — so Badge gets the one sanctioned class this
+ shape needs. Tone is an attribute, not a modifier class. */
+
+.badge {
+ --_bg: var(--ad-color-surface-fill);
+ --_fg: var(--ad-color-text-secondary);
+ --_border: var(--ad-color-border-hairline);
+
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.1875rem 0.5rem;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-sm);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ line-height: 1;
+ white-space: nowrap;
+}
+
+.badge[data-tone="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); --_border: transparent; }
+.badge[data-tone="success"] { --_bg: var(--ad-color-success-bg); --_fg: var(--ad-color-success); --_border: transparent; }
+.badge[data-tone="danger"] { --_bg: var(--ad-color-danger-bg); --_fg: var(--ad-color-danger); --_border: transparent; }
+
+.badge-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-2xs);
+}
+
+/* Tag
+ A removable mono chip for freeform tags — locations, categories.
+ `[data-dashed]` renders an "add tag" affordance instead of a value. Same
+ reasoning as Badge: <span> is too generic to select bare, so Tag gets one
+ class. The remove control is a real <button>, styled through the
+ descendant selector rather than a class of its own. */
+
+.tag {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.375rem;
+ padding: 0.25rem 0.625rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-default);
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ line-height: 1;
+}
+
+.tag[data-dashed] {
+ background: transparent;
+ border-style: dashed;
+ color: var(--ad-color-text-tertiary);
+ cursor: pointer;
+}
+
+.tag button {
+ all: unset;
+ cursor: pointer;
+ color: var(--ad-color-text-tertiary);
+ line-height: 1;
+ font-size: 0.75rem;
+}
+.tag button:hover { color: var(--ad-color-text-primary); }
+
+.tag-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.375rem;
+}
+
+/* Card
+ Flat surface, hairline border, radius-lg. `[data-elevated]` is opt-in and
+ reserved for the one focused/primary card in a view — a dialog, the
+ primary capture card — flat is the default everywhere else.
+
+ A <div>/<article> is too generic to select bare, so Card keeps one
+ sanctioned class. When Card's job is holding a list of rows — the common
+ case, entry lists, task lists, settings rows — it is a real <ul class="
+ card">, and CardRow is a bare <li> underneath it: no second class needed,
+ the row look comes from the `.card > li` descendant selector alone. The
+ first row's top border is removed automatically so callers never have to
+ override it by hand. */
+
+.card {
+ display: flex;
+ flex-direction: column;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: none;
+ overflow: hidden;
+}
+
+.card[data-elevated] { box-shadow: var(--ad-shadow-float); }
+
+.card > li {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+}
+.card > li:first-child { border-top: none; }
+
+/* A row whose ENTIRE content is wrapped in a real <button> or <a> — the
+ accessible way to make a row clickable — fills the row edge-to-edge and
+ picks up a hover fill and pointer cursor. `:only-child` is what tells this
+ apart from a row that merely contains a small button among other content
+ (a leading checkbox next to a title and a trailing value, say): only a
+ button/anchor that IS the row's one and only child means "the whole row
+ is the control." */
+.card > li:has(> button:only-child),
+.card > li:has(> a:only-child) {
+ padding: 0;
+}
+.card > li > button:only-child,
+.card > li > a:only-child {
+ all: unset;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ box-sizing: border-box;
+ color: inherit;
+ font: inherit;
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.card > li > button:only-child:hover,
+.card > li > a:only-child:hover { background: var(--ad-color-surface-fill); }
+
+/* Documentation-only extras: a body/footer pair and a responsive grid, used
+ by the prose pages (rationale.md, index.md) to show a Card as a plain
+ content tile rather than a data row. Neither is part of Card's props —
+ they exist because a content tile's inner layout has no element of its
+ own to hang a selector on. */
+.card-body {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-m);
+}
+.card-footer {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ margin-top: auto;
+ padding: var(--ad-space-xs) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+
+.card[data-link] { transition: border-color var(--ad-dur-fast) var(--ad-ease), box-shadow var(--ad-dur-fast) var(--ad-ease); }
+.card[data-link]:hover { border-color: var(--ad-color-border-default); box-shadow: var(--ad-shadow-sm); }
+.card[data-link]:focus-within { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+
+.card-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
+ gap: var(--ad-space-s);
+}
+
+/* Avatar — lives here because it almost never appears outside a card or a
+ row (the Loop dashboard's initials chip, Archive's file-type chip). A
+ <span> is too generic for a bare selector, so it keeps one class. */
+.avatar {
+ --_size: 1.875rem;
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ flex: none;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 600;
+ overflow: hidden;
+}
+.avatar[data-square] { border-radius: var(--ad-radius-md); background: var(--ad-color-surface-fill); color: var(--ad-color-text-secondary); border: 1px solid var(--ad-color-border-hairline); }
+.avatar[data-size="sm"] { --_size: 1.5rem; }
+.avatar[data-size="lg"] { --_size: 2.25rem; }
+
+/* Tooltip — a dark mono label on hover, positioned above the child. Pure CSS:
+ the trigger and bubble are siblings inside one relatively-positioned span,
+ shown on :hover/:focus-within so it works from the keyboard too.
+
+ `[role="tooltip"]` is the correct ARIA role for the bubble itself, so it
+ doubles as the styling hook — zero classes there. The wrapping span has no
+ attribute of its own, but it does not need one: `:has()` picks out "a span
+ that contains a [role=tooltip] bubble" precisely, without reaching for a
+ class on an otherwise-generic element. */
+span:has(> [role="tooltip"]) {
+ position: relative;
+ display: inline-flex;
+}
+
+[role="tooltip"] {
+ position: absolute;
+ bottom: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ margin-bottom: 0.375rem;
+ padding: 0.25rem 0.5rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-text-primary);
+ color: var(--ad-color-surface-page);
+ font-family: var(--ad-font-mono);
+ /* 11px is the source's own literal choice — smaller than --ad-step--2, the
+ smallest step on the scale, because a tooltip is meant to read as a
+ glance-only aside rather than body text. */
+ font-size: 11px;
+ white-space: nowrap;
+ line-height: 1.4;
+ z-index: 20;
+ opacity: 0;
+ visibility: hidden;
+ pointer-events: none;
+ transition: opacity var(--ad-dur-fast) var(--ad-ease);
+}
+span:has(> [role="tooltip"]):hover > [role="tooltip"],
+span:has(> [role="tooltip"]):focus-within > [role="tooltip"] {
+ opacity: 1;
+ visibility: visible;
+}
+
+/* Toast — the real pattern used for delete confirmations: a flat bar with an
+ optional destructive action and a dismiss. `[data-fixed]` pins it
+ bottom-right for real use; without it the toast stays in document flow for
+ documentation. `role="status"` is the correct live-region role for a
+ transient status message, so it is both the accessibility hook and the
+ style selector — no base class needed. The action and dismiss buttons are
+ plain <button>s told apart by `aria-label="Dismiss"`, an attribute they
+ need anyway for the icon-only close control. */
+[role="status"] {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ min-width: 20rem;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+}
+[role="status"][data-fixed] {
+ position: fixed;
+ inset-block-end: var(--ad-space-s);
+ inset-inline-end: var(--ad-space-s);
+ z-index: 60;
+}
+[role="status"] > span { flex: 1; }
+[role="status"] button {
+ all: unset;
+ cursor: pointer;
+ font-weight: 600;
+ color: var(--ad-color-danger);
+}
+[role="status"] button[aria-label="Dismiss"] {
+ color: var(--ad-color-text-tertiary);
+ font-size: 1rem;
+ font-weight: 400;
+ line-height: 1;
+}
+[role="status"] button[aria-label="Dismiss"]:hover { color: var(--ad-color-text-primary); }
+
+/* Dialog — the native <dialog> element: a centered modal, flat surface, no
+ colored header, hairline dividers between title/body/footer. A <header>
+ holds the title and a <footer> holds the actions, matching the elements
+ the tag already wants; everything else in between is the body, styled by
+ excluding those two rather than by a class of its own.
+
+ `.dialog-demo` scopes a static, always-open example to its own preview
+ container instead of the viewport, so it can sit safely inside a
+ documentation page — this is a doc-only affordance, not part of Dialog's
+ contract. It relies on the browser's own dialog centering: the UA
+ stylesheet already gives an open <dialog> `position: absolute; inset: 0;
+ margin: auto`, which centers it inside the nearest positioned ancestor —
+ here, `.dialog-demo` itself. A real `.showModal()` call gets the genuine
+ viewport-fixed overlay and `::backdrop` for free; a plain `open` attribute
+ (used here so the demo renders without JavaScript) does not paint a
+ backdrop at all, since `::backdrop` only exists for a dialog shown
+ modally — wire the real open/close state and call `.showModal()` in the
+ consuming app. */
+.dialog-demo {
+ position: relative;
+ min-height: 20rem;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-lg);
+}
+
+dialog {
+ width: 26rem;
+ max-width: calc(100% - (2 * var(--ad-space-s)));
+ height: fit-content;
+ padding: 0;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ overflow: hidden;
+}
+dialog::backdrop {
+ background: rgba(20, 18, 14, 0.35);
+}
+dialog > header {
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-sans);
+ font-weight: 600;
+ font-size: var(--ad-step-0);
+ color: var(--ad-color-text-primary);
+}
+dialog > footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+}
+dialog > *:not(header):not(footer) {
+ padding: var(--ad-space-m);
+ margin: 0;
+ color: var(--ad-color-text-primary);
+ font-size: var(--ad-step--1);
+}
+
+/* Tabs
+ Follows the real WAI-ARIA tabs pattern: a `[role="tablist"]` of
+ `[role="tab"]` buttons controlling `[role="tabpanel"]`s. Those roles are
+ required for the pattern to be accessible in the first place, so they
+ double as the entire styling contract — no classes at all for the base
+ shape. The keyboard wiring lives in assets/js/system.js and is
+ variant-agnostic — it only cares about `[data-tabs]` / `[role=tab]`.
+
+ Two real variants: `underline` (mono, accent underline — primary section
+ nav, the default) and `segmented` (pill track, active segment lifts to the
+ surface — filters like All · Overdue · Upcoming, never primary
+ navigation), chosen with `data-variant="segmented"` on the tablist since
+ neither has a natural attribute of its own. */
+
+/* Every rule below the list container pairs [role="tablist"] with
+ [role="tab"] rather than styling [role="tab"] alone — Tab buttons are
+ still literal <button> elements, so the compound selector's extra
+ specificity is what keeps Button's base styling (button.css) from
+ winning the cascade on shared properties like padding and background. */
+
+[role="tablist"] {
+ display: flex;
+ gap: var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+[role="tablist"] [role="tab"] {
+ padding: 0.5rem 0;
+ margin-bottom: -1px;
+ background: none;
+ border: 0;
+ border-bottom: 2px solid transparent;
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--1);
+ letter-spacing: 0.02em;
+ cursor: pointer;
+ transition: color var(--ad-dur-fast) var(--ad-ease), border-color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"] [role="tab"]:hover { color: var(--ad-color-text-primary); }
+[role="tablist"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ border-bottom-color: var(--ad-color-accent);
+}
+
+[role="tablist"][data-variant="segmented"] {
+ display: inline-flex;
+ gap: 0.125rem;
+ padding: 0.1875rem;
+ border-bottom: 0;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-md);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"] {
+ all: unset;
+ cursor: pointer;
+ padding: 0.375rem 0.875rem;
+ border-radius: var(--ad-radius-sm);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 550;
+ color: var(--ad-color-text-secondary);
+ transition: background-color var(--ad-dur-fast) var(--ad-ease), color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ background: var(--ad-color-surface-card);
+ box-shadow: var(--ad-shadow-sm);
+}
+
+[role="tabpanel"] { padding-top: var(--ad-space-s); }
+[role="tabpanel"][hidden] { display: none; }
+[role="tabpanel"]:focus-visible { outline-offset: 4px; }
+
+/* Documentation site chrome. Everything below the `.ad-doc-` prefix belongs
+ to this site rather than to the design system it documents — including the
+ width/height constants used directly below. The source design system
+ defines no container-width tokens at all, so there is nothing under
+ `--ad-*` for a doc shell to reach for; the Space foundation page's own
+ notes say to omit that group entirely rather than invent one. */
+
+.ad-doc-skip {
+ position: absolute;
+ inset-inline-start: var(--ad-space-xs);
+ inset-block-start: -4rem;
+ z-index: 40;
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ box-shadow: var(--ad-shadow-float);
+ transition: inset-block-start var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-skip:focus { inset-block-start: var(--ad-space-2xs); }
+
+.ad-doc-header {
+ position: sticky;
+ inset-block-start: 0;
+ z-index: 30;
+ height: 3.5rem;
+ background: color-mix(in srgb, var(--ad-color-surface-page) 92%, transparent);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-header__inner {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ height: 100%;
+ max-width: 72rem;
+ margin-inline: auto;
+ padding-inline: var(--ad-space-xs);
+}
+
+.ad-doc-wordmark {
+ display: flex;
+ align-items: baseline;
+ gap: var(--ad-space-2xs);
+ color: var(--ad-color-text-primary);
+ text-decoration: none;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ font-weight: 600;
+ letter-spacing: -0.01em;
+ white-space: nowrap;
+}
+.ad-doc-wordmark:hover { color: var(--ad-color-text-primary); }
+.ad-doc-wordmark__mark {
+ width: 1.375rem;
+ height: 1.375rem;
+ flex: none;
+ align-self: center;
+ color: var(--ad-color-accent);
+}
+.ad-doc-wordmark__version {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+
+.ad-doc-header__actions { margin-inline-start: auto; display: flex; align-items: center; gap: var(--ad-space-2xs); }
+
+.ad-doc-layout {
+ display: grid;
+ grid-template-columns: 16rem minmax(0, 1fr);
+ gap: var(--ad-space-l);
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-m) var(--ad-space-xs) var(--ad-space-2xl);
+}
+
+.ad-doc-sidebar {
+ position: sticky;
+ inset-block-start: calc(3.5rem + var(--ad-space-m));
+ align-self: start;
+ max-height: calc(100vh - 3.5rem - var(--ad-space-l));
+ overflow-y: auto;
+ padding-inline-end: var(--ad-space-2xs);
+}
+.ad-doc-nav__group + .ad-doc-nav__group { margin-top: var(--ad-space-s); }
+.ad-doc-nav__heading {
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-doc-nav__link {
+ display: block;
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ margin-inline-start: calc(var(--ad-space-xs) * -1);
+ border-inline-start: 2px solid transparent;
+ border-radius: 0 var(--ad-radius-sm) var(--ad-radius-sm) 0;
+ color: var(--ad-color-text-secondary);
+ font-size: var(--ad-step--1);
+ text-decoration: none;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-nav__link:hover { color: var(--ad-color-text-primary); background: var(--ad-color-surface-fill); }
+.ad-doc-nav__link[aria-current="page"] {
+ color: var(--ad-color-accent-ink);
+ border-inline-start-color: var(--ad-color-accent);
+ background: var(--ad-color-accent-soft);
+ font-weight: 500;
+}
+
+.ad-doc-main {
+ min-width: 0;
+ /* The design system's own body-text token (--ad-step-0, 18–20px) is right
+ for a product screen but too large for long-form documentation prose.
+ This site's own copy gets a plain, fixed 16px instead — the token above
+ is unchanged, and specimen tables/demos below still show its real
+ value. */
+ font-size: 1rem;
+}
+.ad-doc-main > * { max-width: 50rem; }
+
+.ad-doc-title { margin-bottom: var(--ad-space-xs); }
+.ad-doc-title__section {
+ display: block;
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-accent);
+}
+
+.ad-doc-main h2 {
+ margin-block: var(--ad-space-2xl) var(--ad-space-xs);
+ padding-bottom: var(--ad-space-2xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-main h3 { margin-block: var(--ad-space-l) var(--ad-space-2xs); }
+.ad-doc-main h4 { margin-block: var(--ad-space-s) var(--ad-space-2xs); }
+.ad-doc-main p,
+.ad-doc-main ul,
+.ad-doc-main ol { max-width: 68ch; }
+.ad-doc-main ul:not([class]),
+.ad-doc-main ol:not([class]) { padding-inline-start: var(--ad-space-s); }
+
+.ad-doc-footer {
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-s) var(--ad-space-xs) var(--ad-space-l);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-tertiary);
+ font-size: var(--ad-step--1);
+}
+.ad-doc-footer__inner { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); justify-content: space-between; }
+
+/* `button.ad-doc-nav-toggle` (element + class) rather than a bare class:
+ button.css's own base rule is `button:not(.icon-button)`, which is also
+ one element + one class in specificity and sets `display: inline-flex` —
+ a bare `.ad-doc-nav-toggle { display: none }` loses that fight and the
+ toggle stays visible at every width. Matching its specificity here (and
+ coming later in the concatenated stylesheet) settles it in source order. */
+button.ad-doc-nav-toggle { display: none; }
+
+@media (max-width: 60rem) {
+ .ad-doc-layout { grid-template-columns: minmax(0, 1fr); gap: var(--ad-space-s); }
+ .ad-doc-sidebar {
+ position: static;
+ max-height: none;
+ }
+ /* The toggle is a normal Button; the panel it opens gets its own card
+ treatment. Putting the same card styling on the <aside> wrapper too
+ nested a button-shaped box inside an outer box for no reason. */
+ .ad-doc-nav {
+ margin-top: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ }
+ .ad-doc-sidebar[data-collapsed="true"] > .ad-doc-nav { display: none; }
+ button.ad-doc-nav-toggle {
+ display: flex;
+ width: 100%;
+ justify-content: space-between;
+ }
+}
+
+@media (max-width: 34rem) {
+ .ad-doc-wordmark__version { display: none; }
+ .ad-doc-layout { padding-inline: var(--ad-space-2xs); }
+}
+
+/* The live-example figure used throughout the docs — see the "How the
+ example demos work" section on the Using the CSS page. One class on the
+ outer <figure>; everything inside is styled by plain element selectors,
+ since figcaption/div/details/summary/pre already say what they are.
+ `data-layout` and `data-surface` on the preview <div> distinguish the
+ handful of demos that need a non-default arrangement. */
+
+.example {
+ margin: var(--ad-space-s) 0 var(--ad-space-l);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+ background: var(--ad-color-surface-card);
+ overflow: hidden;
+}
+
+.example figcaption {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-secondary);
+}
+
+.example > div {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-s);
+}
+/* nowrap is load-bearing: a column-direction flex container with wrap enabled
+ sizes its single line against the container rather than the content, which
+ left every stacked example with a few hundred pixels of dead space. */
+.example > div[data-layout="stack"] { flex-direction: column; flex-wrap: nowrap; align-items: stretch; }
+.example > div[data-layout="grid"] {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
+ align-items: start;
+}
+.example > div[data-surface="sunken"] { background: var(--ad-color-surface-fill); }
+.example > div > * { min-width: 0; }
+
+.example details {
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+.example summary {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ color: var(--ad-color-text-secondary);
+ cursor: pointer;
+ list-style: none;
+ user-select: none;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+}
+.example summary::-webkit-details-marker { display: none; }
+.example summary::before {
+ content: "";
+ width: 0.5rem;
+ height: 0.5rem;
+ border-inline-end: 1.5px solid currentColor;
+ border-block-end: 1.5px solid currentColor;
+ rotate: -45deg;
+ transition: rotate var(--ad-dur-fast) var(--ad-ease);
+}
+.example details[open] > summary::before { rotate: 45deg; }
+.example summary:hover { color: var(--ad-color-text-primary); }
+
+.example pre {
+ margin: 0;
+ padding: var(--ad-space-xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+ tab-size: 2;
+}
+.example pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Standalone fenced code blocks in the prose (unhighlighted — there is no
+ syntax highlighter in this build any more, just Kramdown's plain
+ <pre><code class="language-*">). */
+.ad-doc-main > pre {
+ margin-block: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+}
+.ad-doc-main > pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Presentation for the token reference tables and swatch grids, plus a
+ generic data-table utility. All of this is documentation chrome — the
+ design system itself has no Table component (see the note on the removed
+ `table`/`progress`/`callout` pages in git history). */
+
+.ad-swatch-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs) var(--ad-space-s);
+ padding: 0;
+ list-style: none;
+}
+.ad-swatch {
+ display: flex;
+ flex-direction: column;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ overflow: hidden;
+ background: var(--ad-color-surface-card);
+}
+.ad-swatch__chip {
+ display: flex;
+ align-items: flex-end;
+ justify-content: flex-end;
+ height: 4rem;
+ padding: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+}
+.ad-swatch__meta {
+ display: flex;
+ flex-direction: column;
+ gap: 0.1rem;
+ padding: var(--ad-space-2xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+}
+.ad-swatch__hex { color: var(--ad-color-text-primary); text-transform: uppercase; }
+.ad-swatch__ratio { color: var(--ad-color-text-tertiary); }
+
+.ad-alias-row { display: flex; align-items: center; gap: var(--ad-space-2xs); }
+.ad-alias-chip {
+ width: 1.5rem;
+ height: 1.5rem;
+ flex: none;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+}
+.ad-alias-chip--pair { display: flex; }
+.ad-alias-chip--pair > span { flex: 1; }
+
+/* Type specimen rows. */
+.ad-specimen {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ gap: var(--ad-space-3xs);
+ padding-block: var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-specimen__meta {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-specimen__sample {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: var(--ad-color-text-primary);
+}
+
+/* Space and radius rulers. */
+.ad-ruler { display: flex; flex-direction: column; gap: var(--ad-space-2xs); }
+.ad-ruler__row { display: grid; grid-template-columns: 6.5rem 4.5rem 1fr; align-items: center; gap: var(--ad-space-xs); font-family: var(--ad-font-mono); font-size: 11px; }
+.ad-ruler__bar { height: 0.75rem; background: var(--ad-color-accent); border-radius: var(--ad-radius-sm); }
+.ad-ruler__num { color: var(--ad-color-text-tertiary); text-align: right; }
+
+.ad-radius-grid, .ad-elevation-grid, .ad-motion-grid, .ad-accent-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs);
+ padding: 0;
+ list-style: none;
+}
+.ad-radius-demo {
+ height: 4.5rem;
+ background: var(--ad-color-accent-soft);
+ border: 1px solid var(--ad-color-accent);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-elevation-demo {
+ height: 4.5rem;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-motion-demo {
+ height: 4.5rem;
+ display: grid;
+ place-items: center;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-md);
+ margin-bottom: var(--ad-space-2xs);
+ cursor: pointer;
+ overflow: hidden;
+}
+.ad-motion-demo__dot {
+ width: 1.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent);
+ translate: -3rem 0;
+ transition-property: translate;
+ transition-duration: var(--ad-dur-base);
+ transition-timing-function: var(--ad-ease);
+}
+.ad-motion-demo:hover .ad-motion-demo__dot,
+.ad-motion-demo:focus-visible .ad-motion-demo__dot { translate: 3rem 0; }
+
+.ad-accent-demo {
+ height: 4.5rem;
+ display: flex;
+ align-items: flex-end;
+ padding: var(--ad-space-2xs);
+ border-radius: var(--ad-radius-md);
+ color: #fff;
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ margin-bottom: var(--ad-space-2xs);
+}
+
+/* Token names read `<code>` sitewide, which already carries a mono chip
+ look for running prose (see :not(pre) > code in base/typography.css).
+ Inside these specimen/swatch/ruler rows the old `.ad-token-name` class was
+ plain mono text with no chip, so `<code>` is flattened back to that look
+ here the same way table cells are. */
+.ad-swatch code, .ad-specimen code, .ad-ruler code, .ad-alias-row code {
+ background: none;
+ border: 0;
+ padding: 0;
+ font-size: var(--ad-step--2);
+ color: var(--ad-color-text-primary);
+ white-space: nowrap;
+}
+
+/* A documentation aside. Like `<table>`, this is site chrome for the prose
+ itself, not a system component — the real system has no Callout. */
+.ad-callout {
+ margin-block: var(--ad-space-s);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-left: 3px solid var(--ad-color-accent);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+}
+/* Generic reference tables (component APIs, alias tables, the contrast
+ contract). `<table>` IS a table — no class needed to say so. Every table
+ on this site uses the one compact density, so there is no separate
+ modifier for it. `display: block` plus `overflow-x: auto` gives the table
+ its own horizontal scrollbar on narrow viewports without a wrapper div;
+ the anonymous table box the browser generates around `<thead>`/`<tbody>`
+ still does the row/column layout. `<code>` replaces the old
+ `.ad-token-name` / `.ad-table__code` cells — it is already styled
+ sitewide, just re-flattened here to a plain mono run instead of the
+ chip-with-border look prose code gets, to match this table's original
+ look. `.num` is the one sanctioned class left, for right-aligned numeric
+ columns — there is no attribute that means "this column is a number". */
+table { width: 100%; border-collapse: collapse; font-size: var(--ad-step--1); display: block; overflow-x: auto; }
+table th, table td {
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ text-align: left;
+ vertical-align: top;
+}
+table th { font-family: var(--ad-font-mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ad-color-text-secondary); font-weight: 500; }
+table code { background: none; border: 0; padding: 0; font-size: var(--ad-step--2); white-space: nowrap; }
+.num { text-align: right; font-variant-numeric: tabular-nums; }
+
+/* Home page. */
+.ad-hero { padding-block: var(--ad-space-m) var(--ad-space-l); }
+.ad-hero__title {
+ font-size: var(--ad-step-4);
+ margin-bottom: var(--ad-space-xs);
+}
+.ad-hero__actions { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); margin-top: var(--ad-space-s); }
+.ad-stat-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-l);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+}
+.ad-stat__value {
+ display: block;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-2);
+ font-weight: 600;
+ line-height: 1.16;
+ font-variant-numeric: tabular-nums;
+}
+.ad-stat__label {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
diff --git a/design-bundle/css/tokens.css b/design-bundle/css/tokens.css
new file mode 100644
index 0000000..34ff530
--- /dev/null
+++ b/design-bundle/css/tokens.css
@@ -0,0 +1,134 @@
+/* Arbitrary Definitions — design tokens. Hand-maintained; edit values directly. */
+
+:root {
+ /* Palette ramps — reference only. Components use the aliases below. */
+ --ad-neutral-0: #faf9f7;
+ --ad-neutral-50: #fdfdfb;
+ --ad-neutral-100: #f1f0ed;
+ --ad-neutral-150: #e8e6e2;
+ --ad-neutral-200: #dcdad7;
+ --ad-neutral-300: #c0bdb9;
+ --ad-neutral-500: #888681;
+ --ad-neutral-600: #5f5d59;
+ --ad-neutral-800: #2a2926;
+ --ad-neutral-900: #100f0d;
+ --ad-neutral-dark-0: #0e0d0b;
+ --ad-neutral-dark-50: #151412;
+ --ad-neutral-dark-100: #1e1d1a;
+ --ad-neutral-dark-150: #252421;
+ --ad-neutral-dark-200: #2c2b29;
+ --ad-neutral-dark-300: #44423f;
+ --ad-neutral-dark-500: #7c7a76;
+ --ad-neutral-dark-600: #a09e9a;
+ --ad-neutral-dark-800: #d9d7d4;
+ --ad-neutral-dark-900: #e9e8e6;
+ --ad-accent-teal: #3a6a63;
+ --ad-accent-clay: #9a5a3c;
+ --ad-accent-ink-blue: #2b4a78;
+ --ad-accent-ochre: #93701f;
+ --ad-accent-plum: #6a3a5c;
+
+ /* Surfaces */
+ --ad-color-surface-page: #faf9f7;
+ --ad-color-surface-card: #fdfdfb;
+ --ad-color-surface-fill: #f1f0ed;
+ --ad-color-surface-fill-2: #e8e6e2;
+
+ /* Borders */
+ --ad-color-border-hairline: #dcdad7;
+ --ad-color-border-default: #c0bdb9;
+
+ /* Text */
+ --ad-color-text-primary: #100f0d;
+ --ad-color-text-secondary: #5f5d59;
+ --ad-color-text-tertiary: #888681;
+
+ /* Accent */
+ --ad-color-accent: #3a6a63;
+ --ad-color-accent-fg: #ffffff;
+ --ad-color-accent-ink: color-mix(in oklch, var(--ad-color-accent) 78%, black);
+ --ad-color-accent-soft: color-mix(in oklch, var(--ad-color-accent) 12%, white);
+
+ /* Status (fixed — never swapped like the accent) */
+ --ad-color-success: #3f7a4a;
+ --ad-color-success-bg: color-mix(in oklch, var(--ad-color-success) 12%, white);
+ --ad-color-danger: #ad3a2c;
+ --ad-color-danger-bg: color-mix(in oklch, var(--ad-color-danger) 10%, white);
+
+ /* Type families */
+ --ad-font-sans: -apple-system, "Helvetica Neue", Helvetica, Arial, system-ui, sans-serif;
+ --ad-font-mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, Consolas, "Courier New", monospace;
+ --ad-font-serif: Georgia, "Times New Roman", "Iowan Old Style", serif;
+
+ /* Type steps — fluid (Utopia scale), the design system's own body-text size */
+ --ad-step--2: clamp(0.7813rem, 0.7736rem + 0.0341vw, 0.8rem);
+ --ad-step--1: clamp(0.9375rem, 0.9119rem + 0.1136vw, 1rem);
+ --ad-step-0: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-step-1: clamp(1.35rem, 1.2631rem + 0.3864vw, 1.5625rem);
+ --ad-step-2: clamp(1.62rem, 1.4837rem + 0.6057vw, 1.9531rem);
+ --ad-step-3: clamp(1.944rem, 1.7405rem + 0.9044vw, 2.4414rem);
+ --ad-step-4: clamp(2.3328rem, 2.0387rem + 1.3072vw, 3.0518rem);
+ --ad-step-5: clamp(2.7994rem, 2.384rem + 1.8461vw, 3.8147rem);
+
+ /* Space */
+ --ad-space-3xs: clamp(0.3125rem, 0.3125rem + 0vw, 0.3125rem);
+ --ad-space-2xs: clamp(0.5625rem, 0.5369rem + 0.1136vw, 0.625rem);
+ --ad-space-xs: clamp(0.875rem, 0.8494rem + 0.1136vw, 0.9375rem);
+ --ad-space-s: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-space-m: clamp(1.6875rem, 1.6108rem + 0.3409vw, 1.875rem);
+ --ad-space-l: clamp(2.25rem, 2.1477rem + 0.4545vw, 2.5rem);
+ --ad-space-xl: clamp(3.375rem, 3.2216rem + 0.6818vw, 3.75rem);
+ --ad-space-2xl: clamp(4.5rem, 4.2955rem + 0.9091vw, 5rem);
+ --ad-space-3xl: clamp(6.75rem, 6.4432rem + 1.3636vw, 7.5rem);
+
+ /* Radius */
+ --ad-radius-sm: 4px;
+ --ad-radius-md: 6px;
+ --ad-radius-lg: 10px;
+ --ad-radius-full: 999px;
+ --ad-border-w: 1px;
+
+ /* Elevation */
+ --ad-shadow-sm: 0 1px 2px rgba(20, 18, 14, 0.05);
+ --ad-shadow-float: 0 2px 6px rgba(20, 18, 14, 0.06), 0 10px 24px -12px rgba(20, 18, 14, 0.18);
+
+ /* Motion */
+ --ad-dur-fast: 0.12s;
+ --ad-dur-base: 0.16s;
+ --ad-ease: cubic-bezier(0.2, 0, 0, 1);
+}
+
+@media (prefers-color-scheme: dark) {
+ :root:not([data-theme="light"]) {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+ }
+}
+
+:root[data-theme="dark"] {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ :root {
+ --ad-dur-fast: 0ms;
+ --ad-dur-base: 0ms;
+ }
+}
diff --git a/design-bundle/foundations/color.html b/design-bundle/foundations/color.html
new file mode 100644
index 0000000..56cf6c1
--- /dev/null
+++ b/design-bundle/foundations/color.html
@@ -0,0 +1,2399 @@
+<!-- @dsCard group="Foundations" -->
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Color</title>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&display=swap">
+<style>
+/* Arbitrary Definitions — design tokens. Hand-maintained; edit values directly. */
+
+:root {
+ /* Palette ramps — reference only. Components use the aliases below. */
+ --ad-neutral-0: #faf9f7;
+ --ad-neutral-50: #fdfdfb;
+ --ad-neutral-100: #f1f0ed;
+ --ad-neutral-150: #e8e6e2;
+ --ad-neutral-200: #dcdad7;
+ --ad-neutral-300: #c0bdb9;
+ --ad-neutral-500: #888681;
+ --ad-neutral-600: #5f5d59;
+ --ad-neutral-800: #2a2926;
+ --ad-neutral-900: #100f0d;
+ --ad-neutral-dark-0: #0e0d0b;
+ --ad-neutral-dark-50: #151412;
+ --ad-neutral-dark-100: #1e1d1a;
+ --ad-neutral-dark-150: #252421;
+ --ad-neutral-dark-200: #2c2b29;
+ --ad-neutral-dark-300: #44423f;
+ --ad-neutral-dark-500: #7c7a76;
+ --ad-neutral-dark-600: #a09e9a;
+ --ad-neutral-dark-800: #d9d7d4;
+ --ad-neutral-dark-900: #e9e8e6;
+ --ad-accent-teal: #3a6a63;
+ --ad-accent-clay: #9a5a3c;
+ --ad-accent-ink-blue: #2b4a78;
+ --ad-accent-ochre: #93701f;
+ --ad-accent-plum: #6a3a5c;
+
+ /* Surfaces */
+ --ad-color-surface-page: #faf9f7;
+ --ad-color-surface-card: #fdfdfb;
+ --ad-color-surface-fill: #f1f0ed;
+ --ad-color-surface-fill-2: #e8e6e2;
+
+ /* Borders */
+ --ad-color-border-hairline: #dcdad7;
+ --ad-color-border-default: #c0bdb9;
+
+ /* Text */
+ --ad-color-text-primary: #100f0d;
+ --ad-color-text-secondary: #5f5d59;
+ --ad-color-text-tertiary: #888681;
+
+ /* Accent */
+ --ad-color-accent: #3a6a63;
+ --ad-color-accent-fg: #ffffff;
+ --ad-color-accent-ink: color-mix(in oklch, var(--ad-color-accent) 78%, black);
+ --ad-color-accent-soft: color-mix(in oklch, var(--ad-color-accent) 12%, white);
+
+ /* Status (fixed — never swapped like the accent) */
+ --ad-color-success: #3f7a4a;
+ --ad-color-success-bg: color-mix(in oklch, var(--ad-color-success) 12%, white);
+ --ad-color-danger: #ad3a2c;
+ --ad-color-danger-bg: color-mix(in oklch, var(--ad-color-danger) 10%, white);
+
+ /* Type families */
+ --ad-font-sans: -apple-system, "Helvetica Neue", Helvetica, Arial, system-ui, sans-serif;
+ --ad-font-mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, Consolas, "Courier New", monospace;
+ --ad-font-serif: Georgia, "Times New Roman", "Iowan Old Style", serif;
+
+ /* Type steps — fluid (Utopia scale), the design system's own body-text size */
+ --ad-step--2: clamp(0.7813rem, 0.7736rem + 0.0341vw, 0.8rem);
+ --ad-step--1: clamp(0.9375rem, 0.9119rem + 0.1136vw, 1rem);
+ --ad-step-0: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-step-1: clamp(1.35rem, 1.2631rem + 0.3864vw, 1.5625rem);
+ --ad-step-2: clamp(1.62rem, 1.4837rem + 0.6057vw, 1.9531rem);
+ --ad-step-3: clamp(1.944rem, 1.7405rem + 0.9044vw, 2.4414rem);
+ --ad-step-4: clamp(2.3328rem, 2.0387rem + 1.3072vw, 3.0518rem);
+ --ad-step-5: clamp(2.7994rem, 2.384rem + 1.8461vw, 3.8147rem);
+
+ /* Space */
+ --ad-space-3xs: clamp(0.3125rem, 0.3125rem + 0vw, 0.3125rem);
+ --ad-space-2xs: clamp(0.5625rem, 0.5369rem + 0.1136vw, 0.625rem);
+ --ad-space-xs: clamp(0.875rem, 0.8494rem + 0.1136vw, 0.9375rem);
+ --ad-space-s: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-space-m: clamp(1.6875rem, 1.6108rem + 0.3409vw, 1.875rem);
+ --ad-space-l: clamp(2.25rem, 2.1477rem + 0.4545vw, 2.5rem);
+ --ad-space-xl: clamp(3.375rem, 3.2216rem + 0.6818vw, 3.75rem);
+ --ad-space-2xl: clamp(4.5rem, 4.2955rem + 0.9091vw, 5rem);
+ --ad-space-3xl: clamp(6.75rem, 6.4432rem + 1.3636vw, 7.5rem);
+
+ /* Radius */
+ --ad-radius-sm: 4px;
+ --ad-radius-md: 6px;
+ --ad-radius-lg: 10px;
+ --ad-radius-full: 999px;
+ --ad-border-w: 1px;
+
+ /* Elevation */
+ --ad-shadow-sm: 0 1px 2px rgba(20, 18, 14, 0.05);
+ --ad-shadow-float: 0 2px 6px rgba(20, 18, 14, 0.06), 0 10px 24px -12px rgba(20, 18, 14, 0.18);
+
+ /* Motion */
+ --ad-dur-fast: 0.12s;
+ --ad-dur-base: 0.16s;
+ --ad-ease: cubic-bezier(0.2, 0, 0, 1);
+}
+
+@media (prefers-color-scheme: dark) {
+ :root:not([data-theme="light"]) {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+ }
+}
+
+:root[data-theme="dark"] {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ :root {
+ --ad-dur-fast: 0ms;
+ --ad-dur-base: 0ms;
+ }
+}
+
+/* A short reset. Everything it sets is something the system would otherwise
+ have to override in every component.
+
+ The rem values used for this documentation site's own chrome (measure,
+ content width, sidebar width, header height) belong to this site, not to
+ the design system it documents — the source design system defines no
+ container-width tokens at all, so there is nothing under `--ad-*` to reach
+ for here. See the header comment in site/shell.css. */
+
+*,
+*::before,
+*::after { box-sizing: border-box; }
+
+html {
+ -webkit-text-size-adjust: 100%;
+ scroll-behavior: smooth;
+ scroll-padding-top: calc(3.5rem + var(--ad-space-s));
+}
+
+@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;
+ }
+}
+
+body {
+ margin: 0;
+ background: var(--ad-color-surface-page);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ line-height: 1.55;
+ font-synthesis-weight: none;
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+}
+
+::selection {
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+}
+
+h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd { margin: 0; }
+ul[class], ol[class] { list-style: none; margin: 0; padding: 0; }
+
+img, svg, video { display: block; max-width: 100%; height: auto; }
+
+button, input, select, textarea {
+ font: inherit;
+ color: inherit;
+}
+
+hr {
+ height: 1px;
+ margin: var(--ad-space-l) 0;
+ border: 0;
+ background: var(--ad-color-border-hairline);
+}
+
+/* One focus ring, defined once. No component is allowed its own. */
+:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+ border-radius: var(--ad-radius-sm);
+}
+:focus:not(:focus-visible) { outline: none; }
+
+[hidden] { display: none !important; }
+
+.ad-visually-hidden {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip-path: inset(50%);
+ white-space: nowrap;
+ border: 0;
+}
+
+/* Every rule here reads a type token for family and size. Weight, line-height,
+ and letter-spacing are hardcoded to the exact numbers the source ships —
+ there is no `--ad-weight-*` / `--ad-leading-*` / `--ad-tracking-*` scale in
+ the real system, only eight fixed compound styles (see the Typography
+ foundation page, which this file implements verbatim). */
+
+.type-display { font: 600 var(--ad-step-4)/1.08 var(--ad-font-sans); letter-spacing: -0.015em; }
+.type-h1, h1 { font: 600 var(--ad-step-3)/1.12 var(--ad-font-sans); letter-spacing: -0.012em; }
+.type-h2, h2 { font: 600 var(--ad-step-2)/1.16 var(--ad-font-sans); letter-spacing: -0.01em; }
+.type-h3, h3 { font: 600 var(--ad-step-1)/1.25 var(--ad-font-sans); }
+.type-body { font: 400 var(--ad-step-0)/1.55 var(--ad-font-sans); }
+.type-body-sm { font: 400 var(--ad-step--1)/1.5 var(--ad-font-sans); }
+.type-label {
+ font: 500 var(--ad-step--2)/1 var(--ad-font-mono);
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: var(--ad-color-text-secondary);
+}
+.type-mono { font: 400 var(--ad-step--1)/1.5 var(--ad-font-mono); }
+
+/* Prose defaults for the documentation body copy. These read the same
+ tokens the compound classes above do; they are not a second scale. */
+h1, h2, h3, h4 { font-weight: 600; text-wrap: balance; }
+
+h4 { font: 600 var(--ad-step-0)/1.3 var(--ad-font-sans); }
+
+p, ul, ol { margin: 0 0 var(--ad-space-s); }
+li + li { margin-top: var(--ad-space-3xs); }
+
+a {
+ color: var(--ad-color-accent);
+ text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
+ text-underline-offset: 0.18em;
+ text-decoration-thickness: 1px;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ text-decoration-color var(--ad-dur-fast) var(--ad-ease);
+}
+a:hover {
+ color: var(--ad-color-accent-ink);
+ text-decoration-color: currentColor;
+}
+
+strong, b { font-weight: 600; }
+
+small { font-size: var(--ad-step--1); }
+
+.ad-lead {
+ font-size: var(--ad-step-1);
+ line-height: 1.5;
+ color: var(--ad-color-text-secondary);
+ max-width: 68ch;
+ text-wrap: pretty;
+}
+
+.ad-muted { color: var(--ad-color-text-secondary); }
+.ad-subtle { color: var(--ad-color-text-tertiary); }
+
+code, kbd, samp, pre {
+ font-family: var(--ad-font-mono);
+ font-feature-settings: "liga" 0;
+}
+
+:not(pre) > code {
+ padding: 0.15em 0.4em;
+ font-size: 0.875em;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-sm);
+ white-space: nowrap;
+}
+
+kbd {
+ display: inline-block;
+ padding: 0.1em 0.45em;
+ font-size: var(--ad-step--2);
+ line-height: 1.6;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-bottom-width: 2px;
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-secondary);
+}
+
+blockquote {
+ margin-block: var(--ad-space-s);
+ padding-left: var(--ad-space-s);
+ border-left: 2px solid var(--ad-color-accent);
+ color: var(--ad-color-text-secondary);
+}
+
+/* Button
+ A flat, quiet button that recedes rather than performs — no gradients, no
+ shine. Variants: default, primary, dark, ghost, danger. Sizes: sm, md
+ (default), lg — padding and font step only, weight and radius stay put.
+
+ The contract is the element itself: a bare <button> is styled directly, no
+ class required. An <a> that acts like a button (navigates rather than
+ changes something — see the Button page's "Links that act like buttons")
+ opts in with a single `.btn` class, since a bare `a` selector would catch
+ every ordinary prose link on the site. `:not(.icon-button)` keeps this
+ block from leaking onto IconButton, which shares the <button> tag but is
+ an entirely different shape. */
+
+button:not(.icon-button),
+a.btn {
+ --_bg: var(--ad-color-surface-card);
+ --_fg: var(--ad-color-text-primary);
+ --_border: var(--ad-color-border-default);
+ --_pad: var(--ad-space-xs) var(--ad-space-s);
+ --_font: var(--ad-step--1);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: var(--ad-space-2xs);
+ padding: var(--_pad);
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-sans);
+ font-size: var(--_font);
+ font-weight: 550;
+ letter-spacing: -0.01em;
+ line-height: 1.2;
+ text-align: center;
+ text-decoration: none;
+ white-space: nowrap;
+ cursor: pointer;
+ transition: filter var(--ad-dur-fast) var(--ad-ease),
+ border-color var(--ad-dur-fast) var(--ad-ease);
+}
+
+:is(button:not(.icon-button), a.btn):hover:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.96); }
+:is(button:not(.icon-button), a.btn):active:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.92); }
+
+button:not(.icon-button):disabled,
+:is(button:not(.icon-button), a.btn)[aria-disabled="true"] {
+ opacity: 0.5;
+ cursor: default;
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="primary"] {
+ --_bg: var(--ad-color-accent);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-accent);
+}
+
+/* Reserved for the single most-committing action in a view — a final Save,
+ never a whole row of them. */
+:is(button:not(.icon-button), a.btn)[data-variant="dark"] {
+ --_bg: var(--ad-color-text-primary);
+ --_fg: var(--ad-color-surface-page);
+ --_border: var(--ad-color-text-primary);
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="ghost"] {
+ --_bg: transparent;
+ --_fg: var(--ad-color-text-primary);
+ --_border: transparent;
+}
+
+/* The source hardcodes this label white rather than deriving it — danger
+ is not "on a filled accent surface", so --ad-color-accent-fg is reused
+ here only because its resolved value is the same literal white, not
+ because the two roles are the same thing. */
+:is(button:not(.icon-button), a.btn)[data-variant="danger"] {
+ --_bg: var(--ad-color-danger);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-danger);
+}
+
+:is(button:not(.icon-button), a.btn)[data-size="sm"] { --_pad: 0.375rem 0.75rem; --_font: 0.8125rem; }
+:is(button:not(.icon-button), a.btn)[data-size="lg"] { --_pad: 0.75rem 1.375rem; --_font: var(--ad-step-0); }
+
+:is(button:not(.icon-button), a.btn)[data-block] { width: 100%; }
+
+/* Button group — [role="group"] is already the correct ARIA role for a
+ toolbar of related buttons, so it doubles as the styling hook for free. */
+[role="group"] {
+ display: inline-flex;
+ isolation: isolate;
+}
+[role="group"] > button { border-radius: 0; }
+[role="group"] > button:first-child { border-start-start-radius: var(--ad-radius-md); border-end-start-radius: var(--ad-radius-md); }
+[role="group"] > button:last-child { border-start-end-radius: var(--ad-radius-md); border-end-end-radius: var(--ad-radius-md); }
+[role="group"] > button + button { margin-inline-start: -1px; }
+[role="group"] > button:hover,
+[role="group"] > button:focus-visible { z-index: 1; }
+
+/* IconButton — a square icon-only button. Always needs an aria-label; it
+ becomes both the accessible name and the native tooltip. There is no
+ attribute that distinguishes "a button that is only an icon" from a plain
+ Button, so this is the one sanctioned class in this file. */
+.icon-button {
+ --_size: 2.125rem;
+ --_bg: transparent;
+ --_border: transparent;
+ --_fg: var(--ad-color-text-secondary);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ padding: 0;
+ flex: none;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease),
+ color var(--ad-dur-fast) var(--ad-ease);
+}
+.icon-button:hover:not(:disabled) { background: var(--ad-color-surface-fill-2); }
+.icon-button:disabled { opacity: 0.5; cursor: default; }
+
+.icon-button[data-size="sm"] { --_size: 1.75rem; }
+.icon-button[data-size="lg"] { --_size: 2.5rem; }
+
+.icon-button[data-variant="outline"] { --_bg: var(--ad-color-surface-card); --_border: var(--ad-color-border-default); --_fg: var(--ad-color-text-primary); }
+.icon-button[data-variant="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); }
+
+/* Field
+ The labeled field wrapper — a <label> that wraps its own caption text
+ together with the control, so the association is native and needs no
+ `for`/`id` pair. The caption is mono, uppercase, tracked — the same
+ catalog-label convention as Badge and type-label.
+
+ A <label> is reused for three different shapes across this system (this
+ wrapper, Checkbox/Radio's row, Switch's row), so it cannot be styled bare
+ without one shape leaking into another. Field is the one of the three with
+ no distinguishing attribute of its own, so it gets the sanctioned class;
+ Checkbox/Radio tell themselves apart structurally instead (see below). */
+.field {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ min-width: 0;
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Hint and error read the native fine-print element. `role="alert"` is a
+ real live-region role for validation text, so it doubles as the selector
+ that turns a hint red. */
+.field > small {
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+.field > small[role="alert"] { color: var(--ad-color-danger); }
+
+/* Input / Select — bordered, matched to each other exactly. Both tags ARE
+ the component, so they are styled bare; nothing else on this site uses a
+ text input or a select. */
+input:not([type="checkbox"]):not([type="radio"]),
+select {
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ outline: none;
+ transition: border-color var(--ad-dur-fast) var(--ad-ease),
+ box-shadow var(--ad-dur-fast) var(--ad-ease);
+}
+input::placeholder { color: var(--ad-color-text-tertiary); }
+
+input:not([type="checkbox"]):not([type="radio"]):focus,
+select:focus {
+ border-color: var(--ad-color-accent);
+ box-shadow: 0 0 0 2px var(--ad-color-accent-soft);
+}
+
+input:not([type="checkbox"]):not([type="radio"]):disabled,
+select:disabled {
+ opacity: 0.5;
+ cursor: default;
+}
+
+/* The chevron is the one flagged color exception in this component set:
+ CSS cannot point a data-URI background-image at a custom property, so its
+ stroke is a literal hex rather than a var(--ad-*) reference. It is picked
+ to match --ad-color-text-secondary and commented here so it is not
+ mistaken for a stray color. */
+select {
+ appearance: none;
+ padding-inline-end: var(--ad-space-l);
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%235f5d59' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right var(--ad-space-xs) center;
+}
+
+input[aria-invalid="true"],
+select[aria-invalid="true"] {
+ border-color: var(--ad-color-danger);
+ background-color: var(--ad-color-danger-bg);
+}
+
+/* Checkbox / Radio — the real native controls, tinted with accent-color
+ instead of a hand-drawn box. This is not just a rename: it deletes the
+ custom SVG-checkmark markup entirely and lets the browser draw its own
+ (platform-appropriate) check and dot. `role="switch"` is excluded here so
+ Switch — a checkbox playing a different role — can style itself below
+ without this rule fighting it. A <label> wrapping a checkbox/radio is told
+ apart from Field's <label> structurally: it :has() one as a direct child,
+ no class required. */
+input[type="checkbox"]:not([role="switch"]),
+input[type="radio"] {
+ width: 1.125rem;
+ height: 1.125rem;
+ margin: 0;
+ accent-color: var(--ad-color-accent);
+ cursor: pointer;
+}
+input[type="checkbox"]:not([role="switch"]):focus-visible,
+input[type="radio"]:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+}
+
+label:has(> input[type="checkbox"]:not([role="switch"])),
+label:has(> input[type="radio"]) {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 400;
+ text-transform: none;
+ letter-spacing: normal;
+ color: var(--ad-color-text-primary);
+}
+label:has(> input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Switch — for on/off settings only, never a substitute for Checkbox.
+ `input[type="checkbox"][role="switch"]` is real ARIA (the correct role for
+ a toggle), not an invented hook, but the visual track+thumb needs a second
+ element for the thumb's independent position and transition — more than
+ `appearance: none` plus pseudo-elements on the input itself can cleanly
+ carry — so `.switch` is a deliberate, documented fallback class rather
+ than a first choice. */
+.switch {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ color: var(--ad-color-text-primary);
+}
+.switch input[role="switch"] { position: absolute; opacity: 0; width: 2.25rem; height: 1.25rem; margin: 0; }
+
+.switch span {
+ position: relative;
+ flex: none;
+ width: 2.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-border-default);
+ transition: background-color var(--ad-dur-base) var(--ad-ease);
+}
+.switch span::after {
+ content: "";
+ position: absolute;
+ inset-block-start: 2px;
+ inset-inline-start: 2px;
+ width: 1rem;
+ height: 1rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-fg);
+ box-shadow: var(--ad-shadow-sm);
+ transition: translate var(--ad-dur-base) var(--ad-ease);
+}
+.switch input[role="switch"]:checked + span { background: var(--ad-color-accent); }
+.switch input[role="switch"]:checked + span::after { translate: 1rem 0; }
+.switch input[role="switch"]:focus-visible + span { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+.switch:has(input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Rows of fields — used by the settings-form pattern. `<form>`/`<fieldset>`
+ are the elements that already mean this, so they are styled bare. A row
+ grouping two fields side by side has no element of its own, so it keeps
+ one plain class. */
+form {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-s);
+ max-width: 28rem;
+}
+.form-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
+ gap: var(--ad-space-xs);
+}
+fieldset {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ margin: 0;
+ padding: 0;
+ border: 0;
+}
+fieldset > legend {
+ padding: 0 0 var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Badge
+ Mono, uppercase, tracked — the catalog-label convention for status and
+ meta values (SAVED, OVERDUE, RECOMMENDED). A label, never a button.
+
+ A <span> is too generic to style globally — it is used all over running
+ prose for unrelated reasons — so Badge gets the one sanctioned class this
+ shape needs. Tone is an attribute, not a modifier class. */
+
+.badge {
+ --_bg: var(--ad-color-surface-fill);
+ --_fg: var(--ad-color-text-secondary);
+ --_border: var(--ad-color-border-hairline);
+
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.1875rem 0.5rem;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-sm);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ line-height: 1;
+ white-space: nowrap;
+}
+
+.badge[data-tone="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); --_border: transparent; }
+.badge[data-tone="success"] { --_bg: var(--ad-color-success-bg); --_fg: var(--ad-color-success); --_border: transparent; }
+.badge[data-tone="danger"] { --_bg: var(--ad-color-danger-bg); --_fg: var(--ad-color-danger); --_border: transparent; }
+
+.badge-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-2xs);
+}
+
+/* Tag
+ A removable mono chip for freeform tags — locations, categories.
+ `[data-dashed]` renders an "add tag" affordance instead of a value. Same
+ reasoning as Badge: <span> is too generic to select bare, so Tag gets one
+ class. The remove control is a real <button>, styled through the
+ descendant selector rather than a class of its own. */
+
+.tag {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.375rem;
+ padding: 0.25rem 0.625rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-default);
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ line-height: 1;
+}
+
+.tag[data-dashed] {
+ background: transparent;
+ border-style: dashed;
+ color: var(--ad-color-text-tertiary);
+ cursor: pointer;
+}
+
+.tag button {
+ all: unset;
+ cursor: pointer;
+ color: var(--ad-color-text-tertiary);
+ line-height: 1;
+ font-size: 0.75rem;
+}
+.tag button:hover { color: var(--ad-color-text-primary); }
+
+.tag-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.375rem;
+}
+
+/* Card
+ Flat surface, hairline border, radius-lg. `[data-elevated]` is opt-in and
+ reserved for the one focused/primary card in a view — a dialog, the
+ primary capture card — flat is the default everywhere else.
+
+ A <div>/<article> is too generic to select bare, so Card keeps one
+ sanctioned class. When Card's job is holding a list of rows — the common
+ case, entry lists, task lists, settings rows — it is a real <ul class="
+ card">, and CardRow is a bare <li> underneath it: no second class needed,
+ the row look comes from the `.card > li` descendant selector alone. The
+ first row's top border is removed automatically so callers never have to
+ override it by hand. */
+
+.card {
+ display: flex;
+ flex-direction: column;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: none;
+ overflow: hidden;
+}
+
+.card[data-elevated] { box-shadow: var(--ad-shadow-float); }
+
+.card > li {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+}
+.card > li:first-child { border-top: none; }
+
+/* A row whose ENTIRE content is wrapped in a real <button> or <a> — the
+ accessible way to make a row clickable — fills the row edge-to-edge and
+ picks up a hover fill and pointer cursor. `:only-child` is what tells this
+ apart from a row that merely contains a small button among other content
+ (a leading checkbox next to a title and a trailing value, say): only a
+ button/anchor that IS the row's one and only child means "the whole row
+ is the control." */
+.card > li:has(> button:only-child),
+.card > li:has(> a:only-child) {
+ padding: 0;
+}
+.card > li > button:only-child,
+.card > li > a:only-child {
+ all: unset;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ box-sizing: border-box;
+ color: inherit;
+ font: inherit;
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.card > li > button:only-child:hover,
+.card > li > a:only-child:hover { background: var(--ad-color-surface-fill); }
+
+/* Documentation-only extras: a body/footer pair and a responsive grid, used
+ by the prose pages (rationale.md, index.md) to show a Card as a plain
+ content tile rather than a data row. Neither is part of Card's props —
+ they exist because a content tile's inner layout has no element of its
+ own to hang a selector on. */
+.card-body {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-m);
+}
+.card-footer {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ margin-top: auto;
+ padding: var(--ad-space-xs) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+
+.card[data-link] { transition: border-color var(--ad-dur-fast) var(--ad-ease), box-shadow var(--ad-dur-fast) var(--ad-ease); }
+.card[data-link]:hover { border-color: var(--ad-color-border-default); box-shadow: var(--ad-shadow-sm); }
+.card[data-link]:focus-within { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+
+.card-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
+ gap: var(--ad-space-s);
+}
+
+/* Avatar — lives here because it almost never appears outside a card or a
+ row (the Loop dashboard's initials chip, Archive's file-type chip). A
+ <span> is too generic for a bare selector, so it keeps one class. */
+.avatar {
+ --_size: 1.875rem;
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ flex: none;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 600;
+ overflow: hidden;
+}
+.avatar[data-square] { border-radius: var(--ad-radius-md); background: var(--ad-color-surface-fill); color: var(--ad-color-text-secondary); border: 1px solid var(--ad-color-border-hairline); }
+.avatar[data-size="sm"] { --_size: 1.5rem; }
+.avatar[data-size="lg"] { --_size: 2.25rem; }
+
+/* Tooltip — a dark mono label on hover, positioned above the child. Pure CSS:
+ the trigger and bubble are siblings inside one relatively-positioned span,
+ shown on :hover/:focus-within so it works from the keyboard too.
+
+ `[role="tooltip"]` is the correct ARIA role for the bubble itself, so it
+ doubles as the styling hook — zero classes there. The wrapping span has no
+ attribute of its own, but it does not need one: `:has()` picks out "a span
+ that contains a [role=tooltip] bubble" precisely, without reaching for a
+ class on an otherwise-generic element. */
+span:has(> [role="tooltip"]) {
+ position: relative;
+ display: inline-flex;
+}
+
+[role="tooltip"] {
+ position: absolute;
+ bottom: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ margin-bottom: 0.375rem;
+ padding: 0.25rem 0.5rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-text-primary);
+ color: var(--ad-color-surface-page);
+ font-family: var(--ad-font-mono);
+ /* 11px is the source's own literal choice — smaller than --ad-step--2, the
+ smallest step on the scale, because a tooltip is meant to read as a
+ glance-only aside rather than body text. */
+ font-size: 11px;
+ white-space: nowrap;
+ line-height: 1.4;
+ z-index: 20;
+ opacity: 0;
+ visibility: hidden;
+ pointer-events: none;
+ transition: opacity var(--ad-dur-fast) var(--ad-ease);
+}
+span:has(> [role="tooltip"]):hover > [role="tooltip"],
+span:has(> [role="tooltip"]):focus-within > [role="tooltip"] {
+ opacity: 1;
+ visibility: visible;
+}
+
+/* Toast — the real pattern used for delete confirmations: a flat bar with an
+ optional destructive action and a dismiss. `[data-fixed]` pins it
+ bottom-right for real use; without it the toast stays in document flow for
+ documentation. `role="status"` is the correct live-region role for a
+ transient status message, so it is both the accessibility hook and the
+ style selector — no base class needed. The action and dismiss buttons are
+ plain <button>s told apart by `aria-label="Dismiss"`, an attribute they
+ need anyway for the icon-only close control. */
+[role="status"] {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ min-width: 20rem;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+}
+[role="status"][data-fixed] {
+ position: fixed;
+ inset-block-end: var(--ad-space-s);
+ inset-inline-end: var(--ad-space-s);
+ z-index: 60;
+}
+[role="status"] > span { flex: 1; }
+[role="status"] button {
+ all: unset;
+ cursor: pointer;
+ font-weight: 600;
+ color: var(--ad-color-danger);
+}
+[role="status"] button[aria-label="Dismiss"] {
+ color: var(--ad-color-text-tertiary);
+ font-size: 1rem;
+ font-weight: 400;
+ line-height: 1;
+}
+[role="status"] button[aria-label="Dismiss"]:hover { color: var(--ad-color-text-primary); }
+
+/* Dialog — the native <dialog> element: a centered modal, flat surface, no
+ colored header, hairline dividers between title/body/footer. A <header>
+ holds the title and a <footer> holds the actions, matching the elements
+ the tag already wants; everything else in between is the body, styled by
+ excluding those two rather than by a class of its own.
+
+ `.dialog-demo` scopes a static, always-open example to its own preview
+ container instead of the viewport, so it can sit safely inside a
+ documentation page — this is a doc-only affordance, not part of Dialog's
+ contract. It relies on the browser's own dialog centering: the UA
+ stylesheet already gives an open <dialog> `position: absolute; inset: 0;
+ margin: auto`, which centers it inside the nearest positioned ancestor —
+ here, `.dialog-demo` itself. A real `.showModal()` call gets the genuine
+ viewport-fixed overlay and `::backdrop` for free; a plain `open` attribute
+ (used here so the demo renders without JavaScript) does not paint a
+ backdrop at all, since `::backdrop` only exists for a dialog shown
+ modally — wire the real open/close state and call `.showModal()` in the
+ consuming app. */
+.dialog-demo {
+ position: relative;
+ min-height: 20rem;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-lg);
+}
+
+dialog {
+ width: 26rem;
+ max-width: calc(100% - (2 * var(--ad-space-s)));
+ height: fit-content;
+ padding: 0;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ overflow: hidden;
+}
+dialog::backdrop {
+ background: rgba(20, 18, 14, 0.35);
+}
+dialog > header {
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-sans);
+ font-weight: 600;
+ font-size: var(--ad-step-0);
+ color: var(--ad-color-text-primary);
+}
+dialog > footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+}
+dialog > *:not(header):not(footer) {
+ padding: var(--ad-space-m);
+ margin: 0;
+ color: var(--ad-color-text-primary);
+ font-size: var(--ad-step--1);
+}
+
+/* Tabs
+ Follows the real WAI-ARIA tabs pattern: a `[role="tablist"]` of
+ `[role="tab"]` buttons controlling `[role="tabpanel"]`s. Those roles are
+ required for the pattern to be accessible in the first place, so they
+ double as the entire styling contract — no classes at all for the base
+ shape. The keyboard wiring lives in assets/js/system.js and is
+ variant-agnostic — it only cares about `[data-tabs]` / `[role=tab]`.
+
+ Two real variants: `underline` (mono, accent underline — primary section
+ nav, the default) and `segmented` (pill track, active segment lifts to the
+ surface — filters like All · Overdue · Upcoming, never primary
+ navigation), chosen with `data-variant="segmented"` on the tablist since
+ neither has a natural attribute of its own. */
+
+/* Every rule below the list container pairs [role="tablist"] with
+ [role="tab"] rather than styling [role="tab"] alone — Tab buttons are
+ still literal <button> elements, so the compound selector's extra
+ specificity is what keeps Button's base styling (button.css) from
+ winning the cascade on shared properties like padding and background. */
+
+[role="tablist"] {
+ display: flex;
+ gap: var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+[role="tablist"] [role="tab"] {
+ padding: 0.5rem 0;
+ margin-bottom: -1px;
+ background: none;
+ border: 0;
+ border-bottom: 2px solid transparent;
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--1);
+ letter-spacing: 0.02em;
+ cursor: pointer;
+ transition: color var(--ad-dur-fast) var(--ad-ease), border-color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"] [role="tab"]:hover { color: var(--ad-color-text-primary); }
+[role="tablist"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ border-bottom-color: var(--ad-color-accent);
+}
+
+[role="tablist"][data-variant="segmented"] {
+ display: inline-flex;
+ gap: 0.125rem;
+ padding: 0.1875rem;
+ border-bottom: 0;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-md);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"] {
+ all: unset;
+ cursor: pointer;
+ padding: 0.375rem 0.875rem;
+ border-radius: var(--ad-radius-sm);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 550;
+ color: var(--ad-color-text-secondary);
+ transition: background-color var(--ad-dur-fast) var(--ad-ease), color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ background: var(--ad-color-surface-card);
+ box-shadow: var(--ad-shadow-sm);
+}
+
+[role="tabpanel"] { padding-top: var(--ad-space-s); }
+[role="tabpanel"][hidden] { display: none; }
+[role="tabpanel"]:focus-visible { outline-offset: 4px; }
+
+/* Documentation site chrome. Everything below the `.ad-doc-` prefix belongs
+ to this site rather than to the design system it documents — including the
+ width/height constants used directly below. The source design system
+ defines no container-width tokens at all, so there is nothing under
+ `--ad-*` for a doc shell to reach for; the Space foundation page's own
+ notes say to omit that group entirely rather than invent one. */
+
+.ad-doc-skip {
+ position: absolute;
+ inset-inline-start: var(--ad-space-xs);
+ inset-block-start: -4rem;
+ z-index: 40;
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ box-shadow: var(--ad-shadow-float);
+ transition: inset-block-start var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-skip:focus { inset-block-start: var(--ad-space-2xs); }
+
+.ad-doc-header {
+ position: sticky;
+ inset-block-start: 0;
+ z-index: 30;
+ height: 3.5rem;
+ background: color-mix(in srgb, var(--ad-color-surface-page) 92%, transparent);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-header__inner {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ height: 100%;
+ max-width: 72rem;
+ margin-inline: auto;
+ padding-inline: var(--ad-space-xs);
+}
+
+.ad-doc-wordmark {
+ display: flex;
+ align-items: baseline;
+ gap: var(--ad-space-2xs);
+ color: var(--ad-color-text-primary);
+ text-decoration: none;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ font-weight: 600;
+ letter-spacing: -0.01em;
+ white-space: nowrap;
+}
+.ad-doc-wordmark:hover { color: var(--ad-color-text-primary); }
+.ad-doc-wordmark__mark {
+ width: 1.375rem;
+ height: 1.375rem;
+ flex: none;
+ align-self: center;
+ color: var(--ad-color-accent);
+}
+.ad-doc-wordmark__version {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+
+.ad-doc-header__actions { margin-inline-start: auto; display: flex; align-items: center; gap: var(--ad-space-2xs); }
+
+.ad-doc-layout {
+ display: grid;
+ grid-template-columns: 16rem minmax(0, 1fr);
+ gap: var(--ad-space-l);
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-m) var(--ad-space-xs) var(--ad-space-2xl);
+}
+
+.ad-doc-sidebar {
+ position: sticky;
+ inset-block-start: calc(3.5rem + var(--ad-space-m));
+ align-self: start;
+ max-height: calc(100vh - 3.5rem - var(--ad-space-l));
+ overflow-y: auto;
+ padding-inline-end: var(--ad-space-2xs);
+}
+.ad-doc-nav__group + .ad-doc-nav__group { margin-top: var(--ad-space-s); }
+.ad-doc-nav__heading {
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-doc-nav__link {
+ display: block;
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ margin-inline-start: calc(var(--ad-space-xs) * -1);
+ border-inline-start: 2px solid transparent;
+ border-radius: 0 var(--ad-radius-sm) var(--ad-radius-sm) 0;
+ color: var(--ad-color-text-secondary);
+ font-size: var(--ad-step--1);
+ text-decoration: none;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-nav__link:hover { color: var(--ad-color-text-primary); background: var(--ad-color-surface-fill); }
+.ad-doc-nav__link[aria-current="page"] {
+ color: var(--ad-color-accent-ink);
+ border-inline-start-color: var(--ad-color-accent);
+ background: var(--ad-color-accent-soft);
+ font-weight: 500;
+}
+
+.ad-doc-main {
+ min-width: 0;
+ /* The design system's own body-text token (--ad-step-0, 18–20px) is right
+ for a product screen but too large for long-form documentation prose.
+ This site's own copy gets a plain, fixed 16px instead — the token above
+ is unchanged, and specimen tables/demos below still show its real
+ value. */
+ font-size: 1rem;
+}
+.ad-doc-main > * { max-width: 50rem; }
+
+.ad-doc-title { margin-bottom: var(--ad-space-xs); }
+.ad-doc-title__section {
+ display: block;
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-accent);
+}
+
+.ad-doc-main h2 {
+ margin-block: var(--ad-space-2xl) var(--ad-space-xs);
+ padding-bottom: var(--ad-space-2xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-main h3 { margin-block: var(--ad-space-l) var(--ad-space-2xs); }
+.ad-doc-main h4 { margin-block: var(--ad-space-s) var(--ad-space-2xs); }
+.ad-doc-main p,
+.ad-doc-main ul,
+.ad-doc-main ol { max-width: 68ch; }
+.ad-doc-main ul:not([class]),
+.ad-doc-main ol:not([class]) { padding-inline-start: var(--ad-space-s); }
+
+.ad-doc-footer {
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-s) var(--ad-space-xs) var(--ad-space-l);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-tertiary);
+ font-size: var(--ad-step--1);
+}
+.ad-doc-footer__inner { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); justify-content: space-between; }
+
+/* `button.ad-doc-nav-toggle` (element + class) rather than a bare class:
+ button.css's own base rule is `button:not(.icon-button)`, which is also
+ one element + one class in specificity and sets `display: inline-flex` —
+ a bare `.ad-doc-nav-toggle { display: none }` loses that fight and the
+ toggle stays visible at every width. Matching its specificity here (and
+ coming later in the concatenated stylesheet) settles it in source order. */
+button.ad-doc-nav-toggle { display: none; }
+
+@media (max-width: 60rem) {
+ .ad-doc-layout { grid-template-columns: minmax(0, 1fr); gap: var(--ad-space-s); }
+ .ad-doc-sidebar {
+ position: static;
+ max-height: none;
+ }
+ /* The toggle is a normal Button; the panel it opens gets its own card
+ treatment. Putting the same card styling on the <aside> wrapper too
+ nested a button-shaped box inside an outer box for no reason. */
+ .ad-doc-nav {
+ margin-top: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ }
+ .ad-doc-sidebar[data-collapsed="true"] > .ad-doc-nav { display: none; }
+ button.ad-doc-nav-toggle {
+ display: flex;
+ width: 100%;
+ justify-content: space-between;
+ }
+}
+
+@media (max-width: 34rem) {
+ .ad-doc-wordmark__version { display: none; }
+ .ad-doc-layout { padding-inline: var(--ad-space-2xs); }
+}
+
+/* The live-example figure used throughout the docs — see the "How the
+ example demos work" section on the Using the CSS page. One class on the
+ outer <figure>; everything inside is styled by plain element selectors,
+ since figcaption/div/details/summary/pre already say what they are.
+ `data-layout` and `data-surface` on the preview <div> distinguish the
+ handful of demos that need a non-default arrangement. */
+
+.example {
+ margin: var(--ad-space-s) 0 var(--ad-space-l);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+ background: var(--ad-color-surface-card);
+ overflow: hidden;
+}
+
+.example figcaption {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-secondary);
+}
+
+.example > div {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-s);
+}
+/* nowrap is load-bearing: a column-direction flex container with wrap enabled
+ sizes its single line against the container rather than the content, which
+ left every stacked example with a few hundred pixels of dead space. */
+.example > div[data-layout="stack"] { flex-direction: column; flex-wrap: nowrap; align-items: stretch; }
+.example > div[data-layout="grid"] {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
+ align-items: start;
+}
+.example > div[data-surface="sunken"] { background: var(--ad-color-surface-fill); }
+.example > div > * { min-width: 0; }
+
+.example details {
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+.example summary {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ color: var(--ad-color-text-secondary);
+ cursor: pointer;
+ list-style: none;
+ user-select: none;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+}
+.example summary::-webkit-details-marker { display: none; }
+.example summary::before {
+ content: "";
+ width: 0.5rem;
+ height: 0.5rem;
+ border-inline-end: 1.5px solid currentColor;
+ border-block-end: 1.5px solid currentColor;
+ rotate: -45deg;
+ transition: rotate var(--ad-dur-fast) var(--ad-ease);
+}
+.example details[open] > summary::before { rotate: 45deg; }
+.example summary:hover { color: var(--ad-color-text-primary); }
+
+.example pre {
+ margin: 0;
+ padding: var(--ad-space-xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+ tab-size: 2;
+}
+.example pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Standalone fenced code blocks in the prose (unhighlighted — there is no
+ syntax highlighter in this build any more, just Kramdown's plain
+ <pre><code class="language-*">). */
+.ad-doc-main > pre {
+ margin-block: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+}
+.ad-doc-main > pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Presentation for the token reference tables and swatch grids, plus a
+ generic data-table utility. All of this is documentation chrome — the
+ design system itself has no Table component (see the note on the removed
+ `table`/`progress`/`callout` pages in git history). */
+
+.ad-swatch-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs) var(--ad-space-s);
+ padding: 0;
+ list-style: none;
+}
+.ad-swatch {
+ display: flex;
+ flex-direction: column;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ overflow: hidden;
+ background: var(--ad-color-surface-card);
+}
+.ad-swatch__chip {
+ display: flex;
+ align-items: flex-end;
+ justify-content: flex-end;
+ height: 4rem;
+ padding: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+}
+.ad-swatch__meta {
+ display: flex;
+ flex-direction: column;
+ gap: 0.1rem;
+ padding: var(--ad-space-2xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+}
+.ad-swatch__hex { color: var(--ad-color-text-primary); text-transform: uppercase; }
+.ad-swatch__ratio { color: var(--ad-color-text-tertiary); }
+
+.ad-alias-row { display: flex; align-items: center; gap: var(--ad-space-2xs); }
+.ad-alias-chip {
+ width: 1.5rem;
+ height: 1.5rem;
+ flex: none;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+}
+.ad-alias-chip--pair { display: flex; }
+.ad-alias-chip--pair > span { flex: 1; }
+
+/* Type specimen rows. */
+.ad-specimen {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ gap: var(--ad-space-3xs);
+ padding-block: var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-specimen__meta {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-specimen__sample {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: var(--ad-color-text-primary);
+}
+
+/* Space and radius rulers. */
+.ad-ruler { display: flex; flex-direction: column; gap: var(--ad-space-2xs); }
+.ad-ruler__row { display: grid; grid-template-columns: 6.5rem 4.5rem 1fr; align-items: center; gap: var(--ad-space-xs); font-family: var(--ad-font-mono); font-size: 11px; }
+.ad-ruler__bar { height: 0.75rem; background: var(--ad-color-accent); border-radius: var(--ad-radius-sm); }
+.ad-ruler__num { color: var(--ad-color-text-tertiary); text-align: right; }
+
+.ad-radius-grid, .ad-elevation-grid, .ad-motion-grid, .ad-accent-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs);
+ padding: 0;
+ list-style: none;
+}
+.ad-radius-demo {
+ height: 4.5rem;
+ background: var(--ad-color-accent-soft);
+ border: 1px solid var(--ad-color-accent);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-elevation-demo {
+ height: 4.5rem;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-motion-demo {
+ height: 4.5rem;
+ display: grid;
+ place-items: center;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-md);
+ margin-bottom: var(--ad-space-2xs);
+ cursor: pointer;
+ overflow: hidden;
+}
+.ad-motion-demo__dot {
+ width: 1.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent);
+ translate: -3rem 0;
+ transition-property: translate;
+ transition-duration: var(--ad-dur-base);
+ transition-timing-function: var(--ad-ease);
+}
+.ad-motion-demo:hover .ad-motion-demo__dot,
+.ad-motion-demo:focus-visible .ad-motion-demo__dot { translate: 3rem 0; }
+
+.ad-accent-demo {
+ height: 4.5rem;
+ display: flex;
+ align-items: flex-end;
+ padding: var(--ad-space-2xs);
+ border-radius: var(--ad-radius-md);
+ color: #fff;
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ margin-bottom: var(--ad-space-2xs);
+}
+
+/* Token names read `<code>` sitewide, which already carries a mono chip
+ look for running prose (see :not(pre) > code in base/typography.css).
+ Inside these specimen/swatch/ruler rows the old `.ad-token-name` class was
+ plain mono text with no chip, so `<code>` is flattened back to that look
+ here the same way table cells are. */
+.ad-swatch code, .ad-specimen code, .ad-ruler code, .ad-alias-row code {
+ background: none;
+ border: 0;
+ padding: 0;
+ font-size: var(--ad-step--2);
+ color: var(--ad-color-text-primary);
+ white-space: nowrap;
+}
+
+/* A documentation aside. Like `<table>`, this is site chrome for the prose
+ itself, not a system component — the real system has no Callout. */
+.ad-callout {
+ margin-block: var(--ad-space-s);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-left: 3px solid var(--ad-color-accent);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+}
+/* Generic reference tables (component APIs, alias tables, the contrast
+ contract). `<table>` IS a table — no class needed to say so. Every table
+ on this site uses the one compact density, so there is no separate
+ modifier for it. `display: block` plus `overflow-x: auto` gives the table
+ its own horizontal scrollbar on narrow viewports without a wrapper div;
+ the anonymous table box the browser generates around `<thead>`/`<tbody>`
+ still does the row/column layout. `<code>` replaces the old
+ `.ad-token-name` / `.ad-table__code` cells — it is already styled
+ sitewide, just re-flattened here to a plain mono run instead of the
+ chip-with-border look prose code gets, to match this table's original
+ look. `.num` is the one sanctioned class left, for right-aligned numeric
+ columns — there is no attribute that means "this column is a number". */
+table { width: 100%; border-collapse: collapse; font-size: var(--ad-step--1); display: block; overflow-x: auto; }
+table th, table td {
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ text-align: left;
+ vertical-align: top;
+}
+table th { font-family: var(--ad-font-mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ad-color-text-secondary); font-weight: 500; }
+table code { background: none; border: 0; padding: 0; font-size: var(--ad-step--2); white-space: nowrap; }
+.num { text-align: right; font-variant-numeric: tabular-nums; }
+
+/* Home page. */
+.ad-hero { padding-block: var(--ad-space-m) var(--ad-space-l); }
+.ad-hero__title {
+ font-size: var(--ad-step-4);
+ margin-bottom: var(--ad-space-xs);
+}
+.ad-hero__actions { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); margin-top: var(--ad-space-s); }
+.ad-stat-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-l);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+}
+.ad-stat__value {
+ display: block;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-2);
+ font-weight: 600;
+ line-height: 1.16;
+ font-variant-numeric: tabular-nums;
+}
+.ad-stat__label {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+
+/* Card chrome. The pane supplies the frame; this just insets the page. */
+body { padding: var(--ad-space-l) var(--ad-space-m); }
+.ad-doc-main { max-width: 50rem; margin-inline: auto; }
+.ad-doc-main > * { max-width: none; }
+</style>
+</head>
+<body>
+<main class="ad-doc-main">
+<header class="ad-doc-title"><span class="ad-doc-title__section">Foundations</span>
+ <h1>Color</h1>
+</header>
+<p class="ad-lead">Two neutral ramps, five accents, two fixed semantic colors, seventeen semantic aliases. Low-chroma warm-gray, never stark white or black.</p>
+<h2 id="the-two-layers">The two layers</h2>
+
+<p>The system separates <em>what a color is</em> from <em>what a color is for</em>.</p>
+
+<p>The <strong>ramps</strong> below are raw material: ten steps of neutral (light), the same
+ten inverted for dark mode, and five accent hues. No component references
+them directly.</p>
+
+<p>The <strong>aliases</strong> underneath are the working vocabulary — <code>--ad-color-text-primary</code>,
+<code>--ad-color-surface-card</code>, <code>--ad-color-accent</code>. Every component stylesheet
+reads from these. Retheming, or switching to dark, means repointing aliases
+at different values — which is exactly what the dark block does.</p>
+
+<div class="ad-callout">
+ <p style="margin:0; font-size: var(--ad-step--1);">
+ The neutral ramps were authored in <code>oklch()</code> by the three
+ projects this system was synthesized from (see
+ <a href="https://kejadlen.github.io/arbitrary-definitions-design-system/rationale/">Why arbitrary</a>). Every
+ value below is that same color converted to flat sRGB hex — not
+ re-picked, not rounded. The contrast figures on this page are computed
+ from those hex values with the standard WCAG 2.1 relative-luminance
+ formula, then transcribed here as fact rather than typed by eye.
+ </p>
+</div>
+
+<h2 id="neutral">Neutral</h2>
+
+<p>Low-chroma warm-gray — the intersection of ketchup’s plain grays, quire’s
+warm cream/dark palette, and domus’s warm paper. Ten steps, not twelve or
+sixteen: the source defines exactly these.</p>
+
+<p class="ad-muted">Low-chroma warm-gray — the intersection of ketchup's plain grays, quire's warm cream/dark palette, and domus's warm paper. Not stark white or black, and not any one project's specific warmth. Ten steps, not twelve: the source defines exactly 0/50/100/150/200/300/500/600/800/900 — steps 400 and 700 do not exist.</p>
+<ul class="ad-swatch-grid">
+ <li class="ad-swatch">
+ <div class="ad-swatch__chip" style="background: #faf9f7; color: #171512;">n-0</div>
+ <div class="ad-swatch__meta">
+ <span class="ad-swatch__hex">#faf9f7</span>
+ <span class="ad-swatch__ratio">1.05 on white</span>
+ </div>
+ </li>
+ <li class="ad-swatch">
+ <div class="ad-swatch__chip" style="background: #fdfdfb; color: #171512;">n-50</div>
+ <div class="ad-swatch__meta">
+ <span class="ad-swatch__hex">#fdfdfb</span>
+ <span class="ad-swatch__ratio">1.02 on white</span>
+ </div>
+ </li>
+ <li class="ad-swatch">
+ <div class="ad-swatch__chip" style="background: #f1f0ed; color: #171512;">n-100</div>
+ <div class="ad-swatch__meta">
+ <span class="ad-swatch__hex">#f1f0ed</span>
+ <span class="ad-swatch__ratio">1.14 on white</span>
+ </div>
+ </li>
+ <li class="ad-swatch">
+ <div class="ad-swatch__chip" style="background: #e8e6e2; color: #171512;">n-150</div>
+ <div class="ad-swatch__meta">
+ <span class="ad-swatch__hex">#e8e6e2</span>
+ <span class="ad-swatch__ratio">1.25 on white</span>
+ </div>
+ </li>
+ <li class="ad-swatch">
+ <div class="ad-swatch__chip" style="background: #dcdad7; color: #171512;">n-200</div>
+ <div class="ad-swatch__meta">
+ <span class="ad-swatch__hex">#dcdad7</span>
+ <span class="ad-swatch__ratio">1.40 on white</span>
+ </div>
+ </li>
+ <li class="ad-swatch">
+ <div class="ad-swatch__chip" style="background: #c0bdb9; color: #171512;">n-300</div>
+ <div class="ad-swatch__meta">
+ <span class="ad-swatch__hex">#c0bdb9</span>
+ <span class="ad-swatch__ratio">1.87 on white</span>
+ </div>
+ </li>
+ <li class="ad-swatch">
+ <div class="ad-swatch__chip" style="background: #888681; color: #171512;">n-500</div>
+ <div class="ad-swatch__meta">
+ <span class="ad-swatch__hex">#888681</span>
+ <span class="ad-swatch__ratio">3.64 on white</span>
+ </div>
+ </li>
+ <li class="ad-swatch">
+ <div class="ad-swatch__chip" style="background: #5f5d59; color: #ffffff;">n-600</div>
+ <div class="ad-swatch__meta">
+ <span class="ad-swatch__hex">#5f5d59</span>
+ <span class="ad-swatch__ratio">6.57 on white</span>
+ </div>
+ </li>
+ <li class="ad-swatch">
+ <div class="ad-swatch__chip" style="background: #2a2926; color: #ffffff;">n-800</div>
+ <div class="ad-swatch__meta">
+ <span class="ad-swatch__hex">#2a2926</span>
+ <span class="ad-swatch__ratio">14.55 on white</span>
+ </div>
+ </li>
+ <li class="ad-swatch">
+ <div class="ad-swatch__chip" style="background: #100f0d; color: #ffffff;">n-900</div>
+ <div class="ad-swatch__meta">
+ <span class="ad-swatch__hex">#100f0d</span>
+ <span class="ad-swatch__ratio">19.16 on white</span>
+ </div>
+ </li>
+</ul>
+
+<p class="ad-muted">The same low-chroma warm-gray scale, inverted, matching quire's OS-preference dark palette. Same ten step numbers as `neutral`; `0` is the darkest step here (the dark-theme page background) rather than the lightest.</p>
+<ul class="ad-swatch-grid">
+ <li class="ad-swatch">
+ <div class="ad-swatch__chip" style="background: #0e0d0b; color: #ffffff;">nd-0</div>
+ <div class="ad-swatch__meta">
+ <span class="ad-swatch__hex">#0e0d0b</span>
+ <span class="ad-swatch__ratio">1.08 on black</span>
+ </div>
+ </li>
+ <li class="ad-swatch">
+ <div class="ad-swatch__chip" style="background: #151412; color: #ffffff;">nd-50</div>
+ <div class="ad-swatch__meta">
+ <span class="ad-swatch__hex">#151412</span>
+ <span class="ad-swatch__ratio">1.14 on black</span>
+ </div>
+ </li>
+ <li class="ad-swatch">
+ <div class="ad-swatch__chip" style="background: #1e1d1a; color: #ffffff;">nd-100</div>
+ <div class="ad-swatch__meta">
+ <span class="ad-swatch__hex">#1e1d1a</span>
+ <span class="ad-swatch__ratio">1.25 on black</span>
+ </div>
+ </li>
+ <li class="ad-swatch">
+ <div class="ad-swatch__chip" style="background: #252421; color: #ffffff;">nd-150</div>
+ <div class="ad-swatch__meta">
+ <span class="ad-swatch__hex">#252421</span>
+ <span class="ad-swatch__ratio">1.35 on black</span>
+ </div>
+ </li>
+ <li class="ad-swatch">
+ <div class="ad-swatch__chip" style="background: #2c2b29; color: #ffffff;">nd-200</div>
+ <div class="ad-swatch__meta">
+ <span class="ad-swatch__hex">#2c2b29</span>
+ <span class="ad-swatch__ratio">1.48 on black</span>
+ </div>
+ </li>
+ <li class="ad-swatch">
+ <div class="ad-swatch__chip" style="background: #44423f; color: #ffffff;">nd-300</div>
+ <div class="ad-swatch__meta">
+ <span class="ad-swatch__hex">#44423f</span>
+ <span class="ad-swatch__ratio">2.10 on black</span>
+ </div>
+ </li>
+ <li class="ad-swatch">
+ <div class="ad-swatch__chip" style="background: #7c7a76; color: #ffffff;">nd-500</div>
+ <div class="ad-swatch__meta">
+ <span class="ad-swatch__hex">#7c7a76</span>
+ <span class="ad-swatch__ratio">4.90 on black</span>
+ </div>
+ </li>
+ <li class="ad-swatch">
+ <div class="ad-swatch__chip" style="background: #a09e9a; color: #171512;">nd-600</div>
+ <div class="ad-swatch__meta">
+ <span class="ad-swatch__hex">#a09e9a</span>
+ <span class="ad-swatch__ratio">7.85 on black</span>
+ </div>
+ </li>
+ <li class="ad-swatch">
+ <div class="ad-swatch__chip" style="background: #d9d7d4; color: #171512;">nd-800</div>
+ <div class="ad-swatch__meta">
+ <span class="ad-swatch__hex">#d9d7d4</span>
+ <span class="ad-swatch__ratio">14.62 on black</span>
+ </div>
+ </li>
+ <li class="ad-swatch">
+ <div class="ad-swatch__chip" style="background: #e9e8e6; color: #171512;">nd-900</div>
+ <div class="ad-swatch__meta">
+ <span class="ad-swatch__hex">#e9e8e6</span>
+ <span class="ad-swatch__ratio">17.15 on black</span>
+ </div>
+ </li>
+</ul>
+
+<h2 id="accent">Accent</h2>
+
+<p>One swappable accent per project — never two in the same view. Signal Teal is
+the default; the other four are the sanctioned menu a project can switch to.</p>
+
+<ul class="ad-accent-grid">
+ <li>
+ <div class="ad-accent-demo" style="background: #3a6a63;">teal (default)</div>
+ <div class="ad-specimen__meta"><code>#3a6a63</code><span>6.14:1 on white</span></div>
+ <p class="ad-muted" style="font-size: var(--ad-step--2); margin: var(--ad-space-3xs) 0 0;">Default. Neutral, technical.</p>
+ </li>
+ <li>
+ <div class="ad-accent-demo" style="background: #9a5a3c;">clay</div>
+ <div class="ad-specimen__meta"><code>#9a5a3c</code><span>5.38:1 on white</span></div>
+ <p class="ad-muted" style="font-size: var(--ad-step--2); margin: var(--ad-space-3xs) 0 0;">Warm, archival — domus's flavor.</p>
+ </li>
+ <li>
+ <div class="ad-accent-demo" style="background: #2b4a78;">ink-blue</div>
+ <div class="ad-specimen__meta"><code>#2b4a78</code><span>8.92:1 on white</span></div>
+ <p class="ad-muted" style="font-size: var(--ad-step--2); margin: var(--ad-space-3xs) 0 0;">Sober, utilitarian.</p>
+ </li>
+ <li>
+ <div class="ad-accent-demo" style="background: #93701f;">ochre</div>
+ <div class="ad-specimen__meta"><code>#93701f</code><span>4.59:1 on white</span></div>
+ <p class="ad-muted" style="font-size: var(--ad-step--2); margin: var(--ad-space-3xs) 0 0;">Dry, papery.</p>
+ </li>
+ <li>
+ <div class="ad-accent-demo" style="background: #6a3a5c;">plum</div>
+ <div class="ad-specimen__meta"><code>#6a3a5c</code><span>8.85:1 on white</span></div>
+ <p class="ad-muted" style="font-size: var(--ad-step--2); margin: var(--ad-space-3xs) 0 0;">Quiet, personal.</p>
+ </li>
+</ul>
+
+<div class="ad-callout">
+ <p style="margin:0; font-size: var(--ad-step--1);">
+ All five clear AA (4.5:1) for white text at their own value, but Ochre is
+ the tightest of the five at roughly 4.6:1 — closer to the floor than the
+ others (Ink Blue and Plum both clear 8.5:1). That is a real property of a
+ dry, papery ochre and is not being rounded away; a project choosing Ochre
+ as its accent should re-check any place it sets small white text directly
+ on the accent fill, rather than through <code>--ad-color-accent-fg</code>
+ on a full-size Button.
+ </p>
+</div>
+
+<h2 id="semantic-aliases">Semantic aliases</h2>
+
+<p>Both themes, side by side. The left half of each chip is the light value and
+the right half is the dark one.</p>
+
+<h3 id="surfaces">Surfaces</h3>
+
+<p class="ad-muted">Backgrounds, from the page itself up to a card sitting on it.</p>
+
+<table>
+ <thead>
+ <tr>
+ <th scope="col">Token</th>
+ <th scope="col">Light</th>
+ <th scope="col">Dark</th>
+ <th scope="col">Use for</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>
+ <div class="ad-alias-row">
+ <span class="ad-alias-chip ad-alias-chip--pair" aria-hidden="true">
+ <span style="background: #faf9f7"></span>
+ <span style="background: #0e0d0b"></span>
+ </span>
+ <code>--ad-color-surface-page</code>
+ </div>
+ </td>
+ <td><code>neutral.0</code></td>
+ <td><code>neutral_dark.0</code></td>
+ <td>The page itself. Nothing sits behind it.</td>
+ </tr>
+ <tr>
+ <td>
+ <div class="ad-alias-row">
+ <span class="ad-alias-chip ad-alias-chip--pair" aria-hidden="true">
+ <span style="background: #fdfdfb"></span>
+ <span style="background: #151412"></span>
+ </span>
+ <code>--ad-color-surface-card</code>
+ </div>
+ </td>
+ <td><code>neutral.50</code></td>
+ <td><code>neutral_dark.50</code></td>
+ <td>Cards, dialogs, toasts — anything that reads as a sheet on the page.</td>
+ </tr>
+ <tr>
+ <td>
+ <div class="ad-alias-row">
+ <span class="ad-alias-chip ad-alias-chip--pair" aria-hidden="true">
+ <span style="background: #f1f0ed"></span>
+ <span style="background: #1e1d1a"></span>
+ </span>
+ <code>--ad-color-surface-fill</code>
+ </div>
+ </td>
+ <td><code>neutral.100</code></td>
+ <td><code>neutral_dark.100</code></td>
+ <td>Recessed fill — a hovered row, a segmented-tab track, a photo placeholder.</td>
+ </tr>
+ <tr>
+ <td>
+ <div class="ad-alias-row">
+ <span class="ad-alias-chip ad-alias-chip--pair" aria-hidden="true">
+ <span style="background: #e8e6e2"></span>
+ <span style="background: #252421"></span>
+ </span>
+ <code>--ad-color-surface-fill-2</code>
+ </div>
+ </td>
+ <td><code>neutral.150</code></td>
+ <td><code>neutral_dark.150</code></td>
+ <td>A second, deeper fill step — the diagonal-grain stripe inside an empty photo slot.</td>
+ </tr>
+ </tbody>
+ </table>
+
+<h3 id="borders">Borders</h3>
+
+<p class="ad-muted">Two weights. Hairlines do most of the elevation work in this system, so there was never a reason for a third.</p>
+
+<table>
+ <thead>
+ <tr>
+ <th scope="col">Token</th>
+ <th scope="col">Light</th>
+ <th scope="col">Dark</th>
+ <th scope="col">Use for</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>
+ <div class="ad-alias-row">
+ <span class="ad-alias-chip ad-alias-chip--pair" aria-hidden="true">
+ <span style="background: #dcdad7"></span>
+ <span style="background: #2c2b29"></span>
+ </span>
+ <code>--ad-color-border-hairline</code>
+ </div>
+ </td>
+ <td><code>neutral.200</code></td>
+ <td><code>neutral_dark.200</code></td>
+ <td>Card edges, row dividers, dialog dividers. The default edge everywhere.</td>
+ </tr>
+ <tr>
+ <td>
+ <div class="ad-alias-row">
+ <span class="ad-alias-chip ad-alias-chip--pair" aria-hidden="true">
+ <span style="background: #c0bdb9"></span>
+ <span style="background: #44423f"></span>
+ </span>
+ <code>--ad-color-border-default</code>
+ </div>
+ </td>
+ <td><code>neutral.300</code></td>
+ <td><code>neutral_dark.300</code></td>
+ <td>Input and button borders — anything a reader has to recognize as a control.</td>
+ </tr>
+ </tbody>
+ </table>
+
+<h3 id="text">Text</h3>
+
+<p class="ad-muted">Three levels of emphasis, deliberately not four. Past three, a reader stops perceiving a hierarchy and starts perceiving noise.</p>
+
+<table>
+ <thead>
+ <tr>
+ <th scope="col">Token</th>
+ <th scope="col">Light</th>
+ <th scope="col">Dark</th>
+ <th scope="col">Use for</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>
+ <div class="ad-alias-row">
+ <span class="ad-alias-chip ad-alias-chip--pair" aria-hidden="true">
+ <span style="background: #100f0d"></span>
+ <span style="background: #e9e8e6"></span>
+ </span>
+ <code>--ad-color-text-primary</code>
+ </div>
+ </td>
+ <td><code>neutral.900</code></td>
+ <td><code>neutral_dark.900</code></td>
+ <td>Body copy, headings, and every control label.</td>
+ </tr>
+ <tr>
+ <td>
+ <div class="ad-alias-row">
+ <span class="ad-alias-chip ad-alias-chip--pair" aria-hidden="true">
+ <span style="background: #5f5d59"></span>
+ <span style="background: #a09e9a"></span>
+ </span>
+ <code>--ad-color-text-secondary</code>
+ </div>
+ </td>
+ <td><code>neutral.600</code></td>
+ <td><code>neutral_dark.600</code></td>
+ <td>Supporting copy, hints, mono meta values, type-label's color.</td>
+ </tr>
+ <tr>
+ <td>
+ <div class="ad-alias-row">
+ <span class="ad-alias-chip ad-alias-chip--pair" aria-hidden="true">
+ <span style="background: #888681"></span>
+ <span style="background: #7c7a76"></span>
+ </span>
+ <code>--ad-color-text-tertiary</code>
+ </div>
+ </td>
+ <td><code>neutral.500</code></td>
+ <td><code>neutral_dark.500</code></td>
+ <td>Placeholders and de-emphasized metadata only — never body text. This step is intentionally sub-AA against the page; see the caveat on the Color page.</td>
+ </tr>
+ </tbody>
+ </table>
+
+<h3 id="accent-1">Accent</h3>
+
+<p class="ad-muted">One swappable accent per project, held constant across a whole view. `-ink` and `-soft` re-derive from `--ad-color-accent` via `color-mix()` exactly as the source computes them, so retheming the accent retones both automatically.</p>
+
+<table>
+ <thead>
+ <tr>
+ <th scope="col">Token</th>
+ <th scope="col">Light</th>
+ <th scope="col">Dark</th>
+ <th scope="col">Use for</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>
+ <div class="ad-alias-row">
+ <span class="ad-alias-chip ad-alias-chip--pair" aria-hidden="true">
+ <span style="background: #3a6a63"></span>
+ <span style="background: #3a6a63"></span>
+ </span>
+ <code>--ad-color-accent</code>
+ </div>
+ </td>
+ <td><code>accent.teal</code></td>
+ <td><code>accent.teal</code></td>
+ <td>Primary buttons, links, focus affordance, the active tab underline. Never two accents in one view.</td>
+ </tr>
+ <tr>
+ <td>
+ <div class="ad-alias-row">
+ <span class="ad-alias-chip ad-alias-chip--pair" aria-hidden="true">
+ <span style="background: #ffffff"></span>
+ <span style="background: #ffffff"></span>
+ </span>
+ <code>--ad-color-accent-fg</code>
+ </div>
+ </td>
+ <td><code>#ffffff</code></td>
+ <td><code>#ffffff</code></td>
+ <td>Label and icon color on a filled accent surface. Fixed white, not a ramp reference.</td>
+ </tr>
+ <tr>
+ <td>
+ <div class="ad-alias-row">
+ <span class="ad-alias-chip" aria-hidden="true" style="background: repeating-linear-gradient(45deg, var(--ad-color-surface-fill) 0 4px, var(--ad-color-surface-card) 4px 8px);"></span>
+ <code>--ad-color-accent-ink</code>
+ </div>
+ </td>
+ <td><code>color-mix(in oklch, var(--ad-color-accent) 78%, black)</code></td>
+ <td><code>color-mix(in oklch, var(--ad-color-accent) 78%, black)</code></td>
+ <td>Text sitting on an `-soft` accent tint — an IconButton's active state, an accent avatar's initials.</td>
+ </tr>
+ <tr>
+ <td>
+ <div class="ad-alias-row">
+ <span class="ad-alias-chip" aria-hidden="true" style="background: repeating-linear-gradient(45deg, var(--ad-color-surface-fill) 0 4px, var(--ad-color-surface-card) 4px 8px);"></span>
+ <code>--ad-color-accent-soft</code>
+ </div>
+ </td>
+ <td><code>color-mix(in oklch, var(--ad-color-accent) 12%, white)</code></td>
+ <td><code>color-mix(in oklch, var(--ad-color-accent) 12%, white)</code></td>
+ <td>The tint behind an accent Badge, a toggled IconButton, an accent avatar chip.</td>
+ </tr>
+ </tbody>
+ </table>
+
+<h3 id="status">Status</h3>
+
+<p class="ad-muted">Exactly two states, both fixed — they read the same in every project and are never swapped the way the accent is.</p>
+
+<table>
+ <thead>
+ <tr>
+ <th scope="col">Token</th>
+ <th scope="col">Light</th>
+ <th scope="col">Dark</th>
+ <th scope="col">Use for</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>
+ <div class="ad-alias-row">
+ <span class="ad-alias-chip ad-alias-chip--pair" aria-hidden="true">
+ <span style="background: #3f7a4a"></span>
+ <span style="background: #3f7a4a"></span>
+ </span>
+ <code>--ad-color-success</code>
+ </div>
+ </td>
+ <td><code>#3f7a4a</code></td>
+ <td><code>#3f7a4a</code></td>
+ <td>Success text and icons — a completed task's check, a saved-state badge.</td>
+ </tr>
+ <tr>
+ <td>
+ <div class="ad-alias-row">
+ <span class="ad-alias-chip" aria-hidden="true" style="background: repeating-linear-gradient(45deg, var(--ad-color-surface-fill) 0 4px, var(--ad-color-surface-card) 4px 8px);"></span>
+ <code>--ad-color-success-bg</code>
+ </div>
+ </td>
+ <td><code>color-mix(in oklch, var(--ad-color-success) 12%, white)</code></td>
+ <td><code>color-mix(in oklch, var(--ad-color-success) 12%, white)</code></td>
+ <td>Tint behind a success Badge. Mixed against literal white, so it stays constant across themes.</td>
+ </tr>
+ <tr>
+ <td>
+ <div class="ad-alias-row">
+ <span class="ad-alias-chip ad-alias-chip--pair" aria-hidden="true">
+ <span style="background: #ad3a2c"></span>
+ <span style="background: #ad3a2c"></span>
+ </span>
+ <code>--ad-color-danger</code>
+ </div>
+ </td>
+ <td><code>#ad3a2c</code></td>
+ <td><code>#ad3a2c</code></td>
+ <td>Overdue/destructive text, the Toast undo action, a danger Button's fill.</td>
+ </tr>
+ <tr>
+ <td>
+ <div class="ad-alias-row">
+ <span class="ad-alias-chip" aria-hidden="true" style="background: repeating-linear-gradient(45deg, var(--ad-color-surface-fill) 0 4px, var(--ad-color-surface-card) 4px 8px);"></span>
+ <code>--ad-color-danger-bg</code>
+ </div>
+ </td>
+ <td><code>color-mix(in oklch, var(--ad-color-danger) 10%, white)</code></td>
+ <td><code>color-mix(in oklch, var(--ad-color-danger) 10%, white)</code></td>
+ <td>Tint behind a danger Badge. Mixed against literal white, so it stays constant across themes.</td>
+ </tr>
+ </tbody>
+ </table>
+
+<h2 id="contrast-that-has-to-hold">Contrast that has to hold</h2>
+
+<p>Color is the one part of this system that is not arbitrary: a ratio is a
+fact about two colors and a pair of eyes, and it does not care what the
+palette wants. The pairings below are fixed facts about the token values
+above, computed once with the standard WCAG 2.1 relative-luminance formula
+and checked by hand rather than by a build-time test.</p>
+
+<h3 id="text--451-minimum">Text — 4.5:1 minimum</h3>
+
+<p class="ad-muted">WCAG 2.1 SC 1.4.3 (normal-size text)</p>
+
+<table>
+ <thead>
+ <tr>
+ <th scope="col">Pairing</th>
+ <th scope="col">Where it shows up</th>
+ <th scope="col" class="num">Light</th>
+ <th scope="col" class="num">Dark</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>
+ <span class="ad-alias-row">
+ <span class="ad-alias-chip" aria-hidden="true" style="background: #faf9f7; color: #100f0d; display: grid; place-items: center; font-size: 10px;">Aa</span>
+ <code>color-text-primary<br /><span class="ad-subtle">on color-surface-page</span></code>
+ </span>
+ </td>
+ <td>Body copy</td>
+ <td class="num">18.21</td>
+ <td class="num">15.86</td>
+ </tr>
+ <tr>
+ <td>
+ <span class="ad-alias-row">
+ <span class="ad-alias-chip" aria-hidden="true" style="background: #faf9f7; color: #5f5d59; display: grid; place-items: center; font-size: 10px;">Aa</span>
+ <code>color-text-secondary<br /><span class="ad-subtle">on color-surface-page</span></code>
+ </span>
+ </td>
+ <td>Supporting copy</td>
+ <td class="num">6.24</td>
+ <td class="num">7.26</td>
+ </tr>
+ <tr>
+ <td>
+ <span class="ad-alias-row">
+ <span class="ad-alias-chip" aria-hidden="true" style="background: #3a6a63; color: #ffffff; display: grid; place-items: center; font-size: 10px;">Aa</span>
+ <code>color-accent-fg<br /><span class="ad-subtle">on color-accent</span></code>
+ </span>
+ </td>
+ <td>The label on a primary or dark-variant Button.</td>
+ <td class="num">6.14</td>
+ <td class="num">6.14</td>
+ </tr>
+ <tr>
+ <td>
+ <span class="ad-alias-row">
+ <span class="ad-alias-chip" aria-hidden="true" style="background: #ffffff; color: #3f7a4a; display: grid; place-items: center; font-size: 10px;">Aa</span>
+ <code>color-success<br /><span class="ad-subtle">on color-accent-fg</span></code>
+ </span>
+ </td>
+ <td>Success text on a light backdrop — proxied by color-accent-fg</td>
+ <td class="num">5.13</td>
+ <td class="num">5.13</td>
+ </tr>
+ <tr>
+ <td>
+ <span class="ad-alias-row">
+ <span class="ad-alias-chip" aria-hidden="true" style="background: #ffffff; color: #ad3a2c; display: grid; place-items: center; font-size: 10px;">Aa</span>
+ <code>color-danger<br /><span class="ad-subtle">on color-accent-fg</span></code>
+ </span>
+ </td>
+ <td>Overdue/destructive text on a light backdrop</td>
+ <td class="num">6.14</td>
+ <td class="num">6.14</td>
+ </tr>
+ </tbody>
+ </table>
+
+<div class="ad-callout">
+ <p style="margin:0; font-size: var(--ad-step--1);">
+ Two things this contract deliberately does not assert.
+ <strong>Text tertiary</strong> — <code>--ad-color-text-tertiary</code>
+ (neutral.500) is roughly 3.46:1 against the page in light mode, under the
+ 4.5:1 floor. The source documents that step as muted-faint text —
+ placeholders and de-emphasized metadata, never body copy — so this is a
+ real, intentional property of the palette, not a bug to patch by
+ darkening the step.
+ <strong>Borders</strong> — <code>--ad-color-border-hairline</code> and
+ <code>--ad-color-border-default</code> sit well under the 3:1 that WCAG
+ 1.4.11 asks of a meaningful boundary. The source's own words are
+ "hairline (1px) borders and dividers do most of the elevation work" —
+ borders here are deliberately quiet, and the source never claims a 3:1
+ bar for them, so this contract does not invent one.
+ </p>
+</div>
+
+<h2 id="rules">Rules</h2>
+
+<ul>
+ <li><strong>Never write a hex outside <code>assets/css/tokens.css</code>.</strong> If a component
+needs a color that does not exist yet, add the alias.</li>
+ <li><strong>One accent per project, never two in a view.</strong> Swapping <code>--ad-color-accent</code>
+re-derives <code>-ink</code> and <code>-soft</code> automatically via <code>color-mix()</code>.</li>
+ <li><strong><code>--ad-color-success</code> and <code>--ad-color-danger</code> are fixed.</strong> They read the
+same in every project and are never swapped the way the accent is.</li>
+</ul>
+</main>
+<script>
+// Behavior for the pieces of the system that need it. Everything else is
+// CSS and semantic HTML on purpose — a design system that only works with
+// JavaScript running is a component library with extra steps.
+(function () {
+ "use strict";
+
+ var STORAGE_KEY = "ad-theme";
+
+ function readStored() {
+ try {
+ return localStorage.getItem(STORAGE_KEY);
+ } catch (error) {
+ return null;
+ }
+ }
+
+ function writeStored(value) {
+ try {
+ localStorage.setItem(STORAGE_KEY, value);
+ } catch (error) {
+ /* Nothing to do — the media query still gives a sensible default. */
+ }
+ }
+
+ function systemPrefersDark() {
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
+ }
+
+ function currentTheme() {
+ var explicit = document.documentElement.getAttribute("data-theme");
+ if (explicit) return explicit;
+ return systemPrefersDark() ? "dark" : "light";
+ }
+
+ function paintToggle(theme) {
+ document.querySelectorAll("[data-theme-toggle]").forEach(function (toggle) {
+ var next = theme === "dark" ? "light" : "dark";
+ toggle.querySelectorAll("[data-theme-icon]").forEach(function (icon) {
+ icon.hidden = icon.dataset.themeIcon !== theme;
+ });
+ toggle.setAttribute("aria-label", "Switch to the " + next + " theme");
+ toggle.setAttribute("title", "Switch to the " + next + " theme");
+ var label = toggle.querySelector("[data-theme-label]");
+ if (label) label.textContent = "Switch to the " + next + " theme";
+ });
+ }
+
+ function setUpTheme() {
+ paintToggle(currentTheme());
+
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-theme-toggle]");
+ if (!toggle) return;
+
+ var next = currentTheme() === "dark" ? "light" : "dark";
+ document.documentElement.setAttribute("data-theme", next);
+ writeStored(next);
+ paintToggle(next);
+ });
+
+ // Follow the OS while the reader has not made an explicit choice.
+ window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", function () {
+ if (!readStored()) paintToggle(currentTheme());
+ });
+ }
+
+ // Tabs, wired to the WAI-ARIA authoring practices: arrows move, Home/End
+ // jump, and only the selected tab stays in the tab order.
+ function setUpTabs() {
+ document.querySelectorAll("[data-tabs]").forEach(function (root) {
+ var tabs = Array.prototype.slice.call(root.querySelectorAll('[role="tab"]'));
+ if (!tabs.length) return;
+
+ function select(tab, focus) {
+ tabs.forEach(function (candidate) {
+ var selected = candidate === tab;
+ candidate.setAttribute("aria-selected", String(selected));
+ candidate.tabIndex = selected ? 0 : -1;
+ var panel = document.getElementById(candidate.getAttribute("aria-controls"));
+ if (panel) panel.hidden = !selected;
+ });
+ if (focus) tab.focus();
+ }
+
+ root.addEventListener("click", function (event) {
+ var tab = event.target.closest('[role="tab"]');
+ if (tab) select(tab, false);
+ });
+
+ root.addEventListener("keydown", function (event) {
+ var index = tabs.indexOf(document.activeElement);
+ if (index === -1) return;
+
+ var next = null;
+ if (event.key === "ArrowRight") next = tabs[(index + 1) % tabs.length];
+ else if (event.key === "ArrowLeft") next = tabs[(index - 1 + tabs.length) % tabs.length];
+ else if (event.key === "Home") next = tabs[0];
+ else if (event.key === "End") next = tabs[tabs.length - 1];
+
+ if (next) {
+ event.preventDefault();
+ select(next, true);
+ }
+ });
+
+ select(tabs.filter(function (tab) {
+ return tab.getAttribute("aria-selected") === "true";
+ })[0] || tabs[0], false);
+ });
+ }
+
+ function setUpNavToggle() {
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-nav-toggle]");
+ if (!toggle) return;
+
+ var sidebar = toggle.closest("[data-sidebar]");
+ var collapsed = sidebar.dataset.collapsed !== "false";
+ sidebar.dataset.collapsed = String(!collapsed);
+ toggle.setAttribute("aria-expanded", String(collapsed));
+ });
+ }
+
+ setUpTheme();
+ setUpTabs();
+ setUpNavToggle();
+})();
+
+</script>
+</body>
+</html>
diff --git a/design-bundle/foundations/elevation.html b/design-bundle/foundations/elevation.html
new file mode 100644
index 0000000..670422e
--- /dev/null
+++ b/design-bundle/foundations/elevation.html
@@ -0,0 +1,1821 @@
+<!-- @dsCard group="Foundations" -->
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Elevation</title>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&display=swap">
+<style>
+/* Arbitrary Definitions — design tokens. Hand-maintained; edit values directly. */
+
+:root {
+ /* Palette ramps — reference only. Components use the aliases below. */
+ --ad-neutral-0: #faf9f7;
+ --ad-neutral-50: #fdfdfb;
+ --ad-neutral-100: #f1f0ed;
+ --ad-neutral-150: #e8e6e2;
+ --ad-neutral-200: #dcdad7;
+ --ad-neutral-300: #c0bdb9;
+ --ad-neutral-500: #888681;
+ --ad-neutral-600: #5f5d59;
+ --ad-neutral-800: #2a2926;
+ --ad-neutral-900: #100f0d;
+ --ad-neutral-dark-0: #0e0d0b;
+ --ad-neutral-dark-50: #151412;
+ --ad-neutral-dark-100: #1e1d1a;
+ --ad-neutral-dark-150: #252421;
+ --ad-neutral-dark-200: #2c2b29;
+ --ad-neutral-dark-300: #44423f;
+ --ad-neutral-dark-500: #7c7a76;
+ --ad-neutral-dark-600: #a09e9a;
+ --ad-neutral-dark-800: #d9d7d4;
+ --ad-neutral-dark-900: #e9e8e6;
+ --ad-accent-teal: #3a6a63;
+ --ad-accent-clay: #9a5a3c;
+ --ad-accent-ink-blue: #2b4a78;
+ --ad-accent-ochre: #93701f;
+ --ad-accent-plum: #6a3a5c;
+
+ /* Surfaces */
+ --ad-color-surface-page: #faf9f7;
+ --ad-color-surface-card: #fdfdfb;
+ --ad-color-surface-fill: #f1f0ed;
+ --ad-color-surface-fill-2: #e8e6e2;
+
+ /* Borders */
+ --ad-color-border-hairline: #dcdad7;
+ --ad-color-border-default: #c0bdb9;
+
+ /* Text */
+ --ad-color-text-primary: #100f0d;
+ --ad-color-text-secondary: #5f5d59;
+ --ad-color-text-tertiary: #888681;
+
+ /* Accent */
+ --ad-color-accent: #3a6a63;
+ --ad-color-accent-fg: #ffffff;
+ --ad-color-accent-ink: color-mix(in oklch, var(--ad-color-accent) 78%, black);
+ --ad-color-accent-soft: color-mix(in oklch, var(--ad-color-accent) 12%, white);
+
+ /* Status (fixed — never swapped like the accent) */
+ --ad-color-success: #3f7a4a;
+ --ad-color-success-bg: color-mix(in oklch, var(--ad-color-success) 12%, white);
+ --ad-color-danger: #ad3a2c;
+ --ad-color-danger-bg: color-mix(in oklch, var(--ad-color-danger) 10%, white);
+
+ /* Type families */
+ --ad-font-sans: -apple-system, "Helvetica Neue", Helvetica, Arial, system-ui, sans-serif;
+ --ad-font-mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, Consolas, "Courier New", monospace;
+ --ad-font-serif: Georgia, "Times New Roman", "Iowan Old Style", serif;
+
+ /* Type steps — fluid (Utopia scale), the design system's own body-text size */
+ --ad-step--2: clamp(0.7813rem, 0.7736rem + 0.0341vw, 0.8rem);
+ --ad-step--1: clamp(0.9375rem, 0.9119rem + 0.1136vw, 1rem);
+ --ad-step-0: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-step-1: clamp(1.35rem, 1.2631rem + 0.3864vw, 1.5625rem);
+ --ad-step-2: clamp(1.62rem, 1.4837rem + 0.6057vw, 1.9531rem);
+ --ad-step-3: clamp(1.944rem, 1.7405rem + 0.9044vw, 2.4414rem);
+ --ad-step-4: clamp(2.3328rem, 2.0387rem + 1.3072vw, 3.0518rem);
+ --ad-step-5: clamp(2.7994rem, 2.384rem + 1.8461vw, 3.8147rem);
+
+ /* Space */
+ --ad-space-3xs: clamp(0.3125rem, 0.3125rem + 0vw, 0.3125rem);
+ --ad-space-2xs: clamp(0.5625rem, 0.5369rem + 0.1136vw, 0.625rem);
+ --ad-space-xs: clamp(0.875rem, 0.8494rem + 0.1136vw, 0.9375rem);
+ --ad-space-s: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-space-m: clamp(1.6875rem, 1.6108rem + 0.3409vw, 1.875rem);
+ --ad-space-l: clamp(2.25rem, 2.1477rem + 0.4545vw, 2.5rem);
+ --ad-space-xl: clamp(3.375rem, 3.2216rem + 0.6818vw, 3.75rem);
+ --ad-space-2xl: clamp(4.5rem, 4.2955rem + 0.9091vw, 5rem);
+ --ad-space-3xl: clamp(6.75rem, 6.4432rem + 1.3636vw, 7.5rem);
+
+ /* Radius */
+ --ad-radius-sm: 4px;
+ --ad-radius-md: 6px;
+ --ad-radius-lg: 10px;
+ --ad-radius-full: 999px;
+ --ad-border-w: 1px;
+
+ /* Elevation */
+ --ad-shadow-sm: 0 1px 2px rgba(20, 18, 14, 0.05);
+ --ad-shadow-float: 0 2px 6px rgba(20, 18, 14, 0.06), 0 10px 24px -12px rgba(20, 18, 14, 0.18);
+
+ /* Motion */
+ --ad-dur-fast: 0.12s;
+ --ad-dur-base: 0.16s;
+ --ad-ease: cubic-bezier(0.2, 0, 0, 1);
+}
+
+@media (prefers-color-scheme: dark) {
+ :root:not([data-theme="light"]) {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+ }
+}
+
+:root[data-theme="dark"] {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ :root {
+ --ad-dur-fast: 0ms;
+ --ad-dur-base: 0ms;
+ }
+}
+
+/* A short reset. Everything it sets is something the system would otherwise
+ have to override in every component.
+
+ The rem values used for this documentation site's own chrome (measure,
+ content width, sidebar width, header height) belong to this site, not to
+ the design system it documents — the source design system defines no
+ container-width tokens at all, so there is nothing under `--ad-*` to reach
+ for here. See the header comment in site/shell.css. */
+
+*,
+*::before,
+*::after { box-sizing: border-box; }
+
+html {
+ -webkit-text-size-adjust: 100%;
+ scroll-behavior: smooth;
+ scroll-padding-top: calc(3.5rem + var(--ad-space-s));
+}
+
+@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;
+ }
+}
+
+body {
+ margin: 0;
+ background: var(--ad-color-surface-page);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ line-height: 1.55;
+ font-synthesis-weight: none;
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+}
+
+::selection {
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+}
+
+h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd { margin: 0; }
+ul[class], ol[class] { list-style: none; margin: 0; padding: 0; }
+
+img, svg, video { display: block; max-width: 100%; height: auto; }
+
+button, input, select, textarea {
+ font: inherit;
+ color: inherit;
+}
+
+hr {
+ height: 1px;
+ margin: var(--ad-space-l) 0;
+ border: 0;
+ background: var(--ad-color-border-hairline);
+}
+
+/* One focus ring, defined once. No component is allowed its own. */
+:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+ border-radius: var(--ad-radius-sm);
+}
+:focus:not(:focus-visible) { outline: none; }
+
+[hidden] { display: none !important; }
+
+.ad-visually-hidden {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip-path: inset(50%);
+ white-space: nowrap;
+ border: 0;
+}
+
+/* Every rule here reads a type token for family and size. Weight, line-height,
+ and letter-spacing are hardcoded to the exact numbers the source ships —
+ there is no `--ad-weight-*` / `--ad-leading-*` / `--ad-tracking-*` scale in
+ the real system, only eight fixed compound styles (see the Typography
+ foundation page, which this file implements verbatim). */
+
+.type-display { font: 600 var(--ad-step-4)/1.08 var(--ad-font-sans); letter-spacing: -0.015em; }
+.type-h1, h1 { font: 600 var(--ad-step-3)/1.12 var(--ad-font-sans); letter-spacing: -0.012em; }
+.type-h2, h2 { font: 600 var(--ad-step-2)/1.16 var(--ad-font-sans); letter-spacing: -0.01em; }
+.type-h3, h3 { font: 600 var(--ad-step-1)/1.25 var(--ad-font-sans); }
+.type-body { font: 400 var(--ad-step-0)/1.55 var(--ad-font-sans); }
+.type-body-sm { font: 400 var(--ad-step--1)/1.5 var(--ad-font-sans); }
+.type-label {
+ font: 500 var(--ad-step--2)/1 var(--ad-font-mono);
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: var(--ad-color-text-secondary);
+}
+.type-mono { font: 400 var(--ad-step--1)/1.5 var(--ad-font-mono); }
+
+/* Prose defaults for the documentation body copy. These read the same
+ tokens the compound classes above do; they are not a second scale. */
+h1, h2, h3, h4 { font-weight: 600; text-wrap: balance; }
+
+h4 { font: 600 var(--ad-step-0)/1.3 var(--ad-font-sans); }
+
+p, ul, ol { margin: 0 0 var(--ad-space-s); }
+li + li { margin-top: var(--ad-space-3xs); }
+
+a {
+ color: var(--ad-color-accent);
+ text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
+ text-underline-offset: 0.18em;
+ text-decoration-thickness: 1px;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ text-decoration-color var(--ad-dur-fast) var(--ad-ease);
+}
+a:hover {
+ color: var(--ad-color-accent-ink);
+ text-decoration-color: currentColor;
+}
+
+strong, b { font-weight: 600; }
+
+small { font-size: var(--ad-step--1); }
+
+.ad-lead {
+ font-size: var(--ad-step-1);
+ line-height: 1.5;
+ color: var(--ad-color-text-secondary);
+ max-width: 68ch;
+ text-wrap: pretty;
+}
+
+.ad-muted { color: var(--ad-color-text-secondary); }
+.ad-subtle { color: var(--ad-color-text-tertiary); }
+
+code, kbd, samp, pre {
+ font-family: var(--ad-font-mono);
+ font-feature-settings: "liga" 0;
+}
+
+:not(pre) > code {
+ padding: 0.15em 0.4em;
+ font-size: 0.875em;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-sm);
+ white-space: nowrap;
+}
+
+kbd {
+ display: inline-block;
+ padding: 0.1em 0.45em;
+ font-size: var(--ad-step--2);
+ line-height: 1.6;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-bottom-width: 2px;
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-secondary);
+}
+
+blockquote {
+ margin-block: var(--ad-space-s);
+ padding-left: var(--ad-space-s);
+ border-left: 2px solid var(--ad-color-accent);
+ color: var(--ad-color-text-secondary);
+}
+
+/* Button
+ A flat, quiet button that recedes rather than performs — no gradients, no
+ shine. Variants: default, primary, dark, ghost, danger. Sizes: sm, md
+ (default), lg — padding and font step only, weight and radius stay put.
+
+ The contract is the element itself: a bare <button> is styled directly, no
+ class required. An <a> that acts like a button (navigates rather than
+ changes something — see the Button page's "Links that act like buttons")
+ opts in with a single `.btn` class, since a bare `a` selector would catch
+ every ordinary prose link on the site. `:not(.icon-button)` keeps this
+ block from leaking onto IconButton, which shares the <button> tag but is
+ an entirely different shape. */
+
+button:not(.icon-button),
+a.btn {
+ --_bg: var(--ad-color-surface-card);
+ --_fg: var(--ad-color-text-primary);
+ --_border: var(--ad-color-border-default);
+ --_pad: var(--ad-space-xs) var(--ad-space-s);
+ --_font: var(--ad-step--1);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: var(--ad-space-2xs);
+ padding: var(--_pad);
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-sans);
+ font-size: var(--_font);
+ font-weight: 550;
+ letter-spacing: -0.01em;
+ line-height: 1.2;
+ text-align: center;
+ text-decoration: none;
+ white-space: nowrap;
+ cursor: pointer;
+ transition: filter var(--ad-dur-fast) var(--ad-ease),
+ border-color var(--ad-dur-fast) var(--ad-ease);
+}
+
+:is(button:not(.icon-button), a.btn):hover:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.96); }
+:is(button:not(.icon-button), a.btn):active:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.92); }
+
+button:not(.icon-button):disabled,
+:is(button:not(.icon-button), a.btn)[aria-disabled="true"] {
+ opacity: 0.5;
+ cursor: default;
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="primary"] {
+ --_bg: var(--ad-color-accent);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-accent);
+}
+
+/* Reserved for the single most-committing action in a view — a final Save,
+ never a whole row of them. */
+:is(button:not(.icon-button), a.btn)[data-variant="dark"] {
+ --_bg: var(--ad-color-text-primary);
+ --_fg: var(--ad-color-surface-page);
+ --_border: var(--ad-color-text-primary);
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="ghost"] {
+ --_bg: transparent;
+ --_fg: var(--ad-color-text-primary);
+ --_border: transparent;
+}
+
+/* The source hardcodes this label white rather than deriving it — danger
+ is not "on a filled accent surface", so --ad-color-accent-fg is reused
+ here only because its resolved value is the same literal white, not
+ because the two roles are the same thing. */
+:is(button:not(.icon-button), a.btn)[data-variant="danger"] {
+ --_bg: var(--ad-color-danger);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-danger);
+}
+
+:is(button:not(.icon-button), a.btn)[data-size="sm"] { --_pad: 0.375rem 0.75rem; --_font: 0.8125rem; }
+:is(button:not(.icon-button), a.btn)[data-size="lg"] { --_pad: 0.75rem 1.375rem; --_font: var(--ad-step-0); }
+
+:is(button:not(.icon-button), a.btn)[data-block] { width: 100%; }
+
+/* Button group — [role="group"] is already the correct ARIA role for a
+ toolbar of related buttons, so it doubles as the styling hook for free. */
+[role="group"] {
+ display: inline-flex;
+ isolation: isolate;
+}
+[role="group"] > button { border-radius: 0; }
+[role="group"] > button:first-child { border-start-start-radius: var(--ad-radius-md); border-end-start-radius: var(--ad-radius-md); }
+[role="group"] > button:last-child { border-start-end-radius: var(--ad-radius-md); border-end-end-radius: var(--ad-radius-md); }
+[role="group"] > button + button { margin-inline-start: -1px; }
+[role="group"] > button:hover,
+[role="group"] > button:focus-visible { z-index: 1; }
+
+/* IconButton — a square icon-only button. Always needs an aria-label; it
+ becomes both the accessible name and the native tooltip. There is no
+ attribute that distinguishes "a button that is only an icon" from a plain
+ Button, so this is the one sanctioned class in this file. */
+.icon-button {
+ --_size: 2.125rem;
+ --_bg: transparent;
+ --_border: transparent;
+ --_fg: var(--ad-color-text-secondary);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ padding: 0;
+ flex: none;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease),
+ color var(--ad-dur-fast) var(--ad-ease);
+}
+.icon-button:hover:not(:disabled) { background: var(--ad-color-surface-fill-2); }
+.icon-button:disabled { opacity: 0.5; cursor: default; }
+
+.icon-button[data-size="sm"] { --_size: 1.75rem; }
+.icon-button[data-size="lg"] { --_size: 2.5rem; }
+
+.icon-button[data-variant="outline"] { --_bg: var(--ad-color-surface-card); --_border: var(--ad-color-border-default); --_fg: var(--ad-color-text-primary); }
+.icon-button[data-variant="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); }
+
+/* Field
+ The labeled field wrapper — a <label> that wraps its own caption text
+ together with the control, so the association is native and needs no
+ `for`/`id` pair. The caption is mono, uppercase, tracked — the same
+ catalog-label convention as Badge and type-label.
+
+ A <label> is reused for three different shapes across this system (this
+ wrapper, Checkbox/Radio's row, Switch's row), so it cannot be styled bare
+ without one shape leaking into another. Field is the one of the three with
+ no distinguishing attribute of its own, so it gets the sanctioned class;
+ Checkbox/Radio tell themselves apart structurally instead (see below). */
+.field {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ min-width: 0;
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Hint and error read the native fine-print element. `role="alert"` is a
+ real live-region role for validation text, so it doubles as the selector
+ that turns a hint red. */
+.field > small {
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+.field > small[role="alert"] { color: var(--ad-color-danger); }
+
+/* Input / Select — bordered, matched to each other exactly. Both tags ARE
+ the component, so they are styled bare; nothing else on this site uses a
+ text input or a select. */
+input:not([type="checkbox"]):not([type="radio"]),
+select {
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ outline: none;
+ transition: border-color var(--ad-dur-fast) var(--ad-ease),
+ box-shadow var(--ad-dur-fast) var(--ad-ease);
+}
+input::placeholder { color: var(--ad-color-text-tertiary); }
+
+input:not([type="checkbox"]):not([type="radio"]):focus,
+select:focus {
+ border-color: var(--ad-color-accent);
+ box-shadow: 0 0 0 2px var(--ad-color-accent-soft);
+}
+
+input:not([type="checkbox"]):not([type="radio"]):disabled,
+select:disabled {
+ opacity: 0.5;
+ cursor: default;
+}
+
+/* The chevron is the one flagged color exception in this component set:
+ CSS cannot point a data-URI background-image at a custom property, so its
+ stroke is a literal hex rather than a var(--ad-*) reference. It is picked
+ to match --ad-color-text-secondary and commented here so it is not
+ mistaken for a stray color. */
+select {
+ appearance: none;
+ padding-inline-end: var(--ad-space-l);
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%235f5d59' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right var(--ad-space-xs) center;
+}
+
+input[aria-invalid="true"],
+select[aria-invalid="true"] {
+ border-color: var(--ad-color-danger);
+ background-color: var(--ad-color-danger-bg);
+}
+
+/* Checkbox / Radio — the real native controls, tinted with accent-color
+ instead of a hand-drawn box. This is not just a rename: it deletes the
+ custom SVG-checkmark markup entirely and lets the browser draw its own
+ (platform-appropriate) check and dot. `role="switch"` is excluded here so
+ Switch — a checkbox playing a different role — can style itself below
+ without this rule fighting it. A <label> wrapping a checkbox/radio is told
+ apart from Field's <label> structurally: it :has() one as a direct child,
+ no class required. */
+input[type="checkbox"]:not([role="switch"]),
+input[type="radio"] {
+ width: 1.125rem;
+ height: 1.125rem;
+ margin: 0;
+ accent-color: var(--ad-color-accent);
+ cursor: pointer;
+}
+input[type="checkbox"]:not([role="switch"]):focus-visible,
+input[type="radio"]:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+}
+
+label:has(> input[type="checkbox"]:not([role="switch"])),
+label:has(> input[type="radio"]) {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 400;
+ text-transform: none;
+ letter-spacing: normal;
+ color: var(--ad-color-text-primary);
+}
+label:has(> input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Switch — for on/off settings only, never a substitute for Checkbox.
+ `input[type="checkbox"][role="switch"]` is real ARIA (the correct role for
+ a toggle), not an invented hook, but the visual track+thumb needs a second
+ element for the thumb's independent position and transition — more than
+ `appearance: none` plus pseudo-elements on the input itself can cleanly
+ carry — so `.switch` is a deliberate, documented fallback class rather
+ than a first choice. */
+.switch {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ color: var(--ad-color-text-primary);
+}
+.switch input[role="switch"] { position: absolute; opacity: 0; width: 2.25rem; height: 1.25rem; margin: 0; }
+
+.switch span {
+ position: relative;
+ flex: none;
+ width: 2.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-border-default);
+ transition: background-color var(--ad-dur-base) var(--ad-ease);
+}
+.switch span::after {
+ content: "";
+ position: absolute;
+ inset-block-start: 2px;
+ inset-inline-start: 2px;
+ width: 1rem;
+ height: 1rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-fg);
+ box-shadow: var(--ad-shadow-sm);
+ transition: translate var(--ad-dur-base) var(--ad-ease);
+}
+.switch input[role="switch"]:checked + span { background: var(--ad-color-accent); }
+.switch input[role="switch"]:checked + span::after { translate: 1rem 0; }
+.switch input[role="switch"]:focus-visible + span { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+.switch:has(input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Rows of fields — used by the settings-form pattern. `<form>`/`<fieldset>`
+ are the elements that already mean this, so they are styled bare. A row
+ grouping two fields side by side has no element of its own, so it keeps
+ one plain class. */
+form {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-s);
+ max-width: 28rem;
+}
+.form-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
+ gap: var(--ad-space-xs);
+}
+fieldset {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ margin: 0;
+ padding: 0;
+ border: 0;
+}
+fieldset > legend {
+ padding: 0 0 var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Badge
+ Mono, uppercase, tracked — the catalog-label convention for status and
+ meta values (SAVED, OVERDUE, RECOMMENDED). A label, never a button.
+
+ A <span> is too generic to style globally — it is used all over running
+ prose for unrelated reasons — so Badge gets the one sanctioned class this
+ shape needs. Tone is an attribute, not a modifier class. */
+
+.badge {
+ --_bg: var(--ad-color-surface-fill);
+ --_fg: var(--ad-color-text-secondary);
+ --_border: var(--ad-color-border-hairline);
+
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.1875rem 0.5rem;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-sm);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ line-height: 1;
+ white-space: nowrap;
+}
+
+.badge[data-tone="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); --_border: transparent; }
+.badge[data-tone="success"] { --_bg: var(--ad-color-success-bg); --_fg: var(--ad-color-success); --_border: transparent; }
+.badge[data-tone="danger"] { --_bg: var(--ad-color-danger-bg); --_fg: var(--ad-color-danger); --_border: transparent; }
+
+.badge-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-2xs);
+}
+
+/* Tag
+ A removable mono chip for freeform tags — locations, categories.
+ `[data-dashed]` renders an "add tag" affordance instead of a value. Same
+ reasoning as Badge: <span> is too generic to select bare, so Tag gets one
+ class. The remove control is a real <button>, styled through the
+ descendant selector rather than a class of its own. */
+
+.tag {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.375rem;
+ padding: 0.25rem 0.625rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-default);
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ line-height: 1;
+}
+
+.tag[data-dashed] {
+ background: transparent;
+ border-style: dashed;
+ color: var(--ad-color-text-tertiary);
+ cursor: pointer;
+}
+
+.tag button {
+ all: unset;
+ cursor: pointer;
+ color: var(--ad-color-text-tertiary);
+ line-height: 1;
+ font-size: 0.75rem;
+}
+.tag button:hover { color: var(--ad-color-text-primary); }
+
+.tag-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.375rem;
+}
+
+/* Card
+ Flat surface, hairline border, radius-lg. `[data-elevated]` is opt-in and
+ reserved for the one focused/primary card in a view — a dialog, the
+ primary capture card — flat is the default everywhere else.
+
+ A <div>/<article> is too generic to select bare, so Card keeps one
+ sanctioned class. When Card's job is holding a list of rows — the common
+ case, entry lists, task lists, settings rows — it is a real <ul class="
+ card">, and CardRow is a bare <li> underneath it: no second class needed,
+ the row look comes from the `.card > li` descendant selector alone. The
+ first row's top border is removed automatically so callers never have to
+ override it by hand. */
+
+.card {
+ display: flex;
+ flex-direction: column;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: none;
+ overflow: hidden;
+}
+
+.card[data-elevated] { box-shadow: var(--ad-shadow-float); }
+
+.card > li {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+}
+.card > li:first-child { border-top: none; }
+
+/* A row whose ENTIRE content is wrapped in a real <button> or <a> — the
+ accessible way to make a row clickable — fills the row edge-to-edge and
+ picks up a hover fill and pointer cursor. `:only-child` is what tells this
+ apart from a row that merely contains a small button among other content
+ (a leading checkbox next to a title and a trailing value, say): only a
+ button/anchor that IS the row's one and only child means "the whole row
+ is the control." */
+.card > li:has(> button:only-child),
+.card > li:has(> a:only-child) {
+ padding: 0;
+}
+.card > li > button:only-child,
+.card > li > a:only-child {
+ all: unset;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ box-sizing: border-box;
+ color: inherit;
+ font: inherit;
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.card > li > button:only-child:hover,
+.card > li > a:only-child:hover { background: var(--ad-color-surface-fill); }
+
+/* Documentation-only extras: a body/footer pair and a responsive grid, used
+ by the prose pages (rationale.md, index.md) to show a Card as a plain
+ content tile rather than a data row. Neither is part of Card's props —
+ they exist because a content tile's inner layout has no element of its
+ own to hang a selector on. */
+.card-body {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-m);
+}
+.card-footer {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ margin-top: auto;
+ padding: var(--ad-space-xs) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+
+.card[data-link] { transition: border-color var(--ad-dur-fast) var(--ad-ease), box-shadow var(--ad-dur-fast) var(--ad-ease); }
+.card[data-link]:hover { border-color: var(--ad-color-border-default); box-shadow: var(--ad-shadow-sm); }
+.card[data-link]:focus-within { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+
+.card-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
+ gap: var(--ad-space-s);
+}
+
+/* Avatar — lives here because it almost never appears outside a card or a
+ row (the Loop dashboard's initials chip, Archive's file-type chip). A
+ <span> is too generic for a bare selector, so it keeps one class. */
+.avatar {
+ --_size: 1.875rem;
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ flex: none;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 600;
+ overflow: hidden;
+}
+.avatar[data-square] { border-radius: var(--ad-radius-md); background: var(--ad-color-surface-fill); color: var(--ad-color-text-secondary); border: 1px solid var(--ad-color-border-hairline); }
+.avatar[data-size="sm"] { --_size: 1.5rem; }
+.avatar[data-size="lg"] { --_size: 2.25rem; }
+
+/* Tooltip — a dark mono label on hover, positioned above the child. Pure CSS:
+ the trigger and bubble are siblings inside one relatively-positioned span,
+ shown on :hover/:focus-within so it works from the keyboard too.
+
+ `[role="tooltip"]` is the correct ARIA role for the bubble itself, so it
+ doubles as the styling hook — zero classes there. The wrapping span has no
+ attribute of its own, but it does not need one: `:has()` picks out "a span
+ that contains a [role=tooltip] bubble" precisely, without reaching for a
+ class on an otherwise-generic element. */
+span:has(> [role="tooltip"]) {
+ position: relative;
+ display: inline-flex;
+}
+
+[role="tooltip"] {
+ position: absolute;
+ bottom: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ margin-bottom: 0.375rem;
+ padding: 0.25rem 0.5rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-text-primary);
+ color: var(--ad-color-surface-page);
+ font-family: var(--ad-font-mono);
+ /* 11px is the source's own literal choice — smaller than --ad-step--2, the
+ smallest step on the scale, because a tooltip is meant to read as a
+ glance-only aside rather than body text. */
+ font-size: 11px;
+ white-space: nowrap;
+ line-height: 1.4;
+ z-index: 20;
+ opacity: 0;
+ visibility: hidden;
+ pointer-events: none;
+ transition: opacity var(--ad-dur-fast) var(--ad-ease);
+}
+span:has(> [role="tooltip"]):hover > [role="tooltip"],
+span:has(> [role="tooltip"]):focus-within > [role="tooltip"] {
+ opacity: 1;
+ visibility: visible;
+}
+
+/* Toast — the real pattern used for delete confirmations: a flat bar with an
+ optional destructive action and a dismiss. `[data-fixed]` pins it
+ bottom-right for real use; without it the toast stays in document flow for
+ documentation. `role="status"` is the correct live-region role for a
+ transient status message, so it is both the accessibility hook and the
+ style selector — no base class needed. The action and dismiss buttons are
+ plain <button>s told apart by `aria-label="Dismiss"`, an attribute they
+ need anyway for the icon-only close control. */
+[role="status"] {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ min-width: 20rem;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+}
+[role="status"][data-fixed] {
+ position: fixed;
+ inset-block-end: var(--ad-space-s);
+ inset-inline-end: var(--ad-space-s);
+ z-index: 60;
+}
+[role="status"] > span { flex: 1; }
+[role="status"] button {
+ all: unset;
+ cursor: pointer;
+ font-weight: 600;
+ color: var(--ad-color-danger);
+}
+[role="status"] button[aria-label="Dismiss"] {
+ color: var(--ad-color-text-tertiary);
+ font-size: 1rem;
+ font-weight: 400;
+ line-height: 1;
+}
+[role="status"] button[aria-label="Dismiss"]:hover { color: var(--ad-color-text-primary); }
+
+/* Dialog — the native <dialog> element: a centered modal, flat surface, no
+ colored header, hairline dividers between title/body/footer. A <header>
+ holds the title and a <footer> holds the actions, matching the elements
+ the tag already wants; everything else in between is the body, styled by
+ excluding those two rather than by a class of its own.
+
+ `.dialog-demo` scopes a static, always-open example to its own preview
+ container instead of the viewport, so it can sit safely inside a
+ documentation page — this is a doc-only affordance, not part of Dialog's
+ contract. It relies on the browser's own dialog centering: the UA
+ stylesheet already gives an open <dialog> `position: absolute; inset: 0;
+ margin: auto`, which centers it inside the nearest positioned ancestor —
+ here, `.dialog-demo` itself. A real `.showModal()` call gets the genuine
+ viewport-fixed overlay and `::backdrop` for free; a plain `open` attribute
+ (used here so the demo renders without JavaScript) does not paint a
+ backdrop at all, since `::backdrop` only exists for a dialog shown
+ modally — wire the real open/close state and call `.showModal()` in the
+ consuming app. */
+.dialog-demo {
+ position: relative;
+ min-height: 20rem;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-lg);
+}
+
+dialog {
+ width: 26rem;
+ max-width: calc(100% - (2 * var(--ad-space-s)));
+ height: fit-content;
+ padding: 0;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ overflow: hidden;
+}
+dialog::backdrop {
+ background: rgba(20, 18, 14, 0.35);
+}
+dialog > header {
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-sans);
+ font-weight: 600;
+ font-size: var(--ad-step-0);
+ color: var(--ad-color-text-primary);
+}
+dialog > footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+}
+dialog > *:not(header):not(footer) {
+ padding: var(--ad-space-m);
+ margin: 0;
+ color: var(--ad-color-text-primary);
+ font-size: var(--ad-step--1);
+}
+
+/* Tabs
+ Follows the real WAI-ARIA tabs pattern: a `[role="tablist"]` of
+ `[role="tab"]` buttons controlling `[role="tabpanel"]`s. Those roles are
+ required for the pattern to be accessible in the first place, so they
+ double as the entire styling contract — no classes at all for the base
+ shape. The keyboard wiring lives in assets/js/system.js and is
+ variant-agnostic — it only cares about `[data-tabs]` / `[role=tab]`.
+
+ Two real variants: `underline` (mono, accent underline — primary section
+ nav, the default) and `segmented` (pill track, active segment lifts to the
+ surface — filters like All · Overdue · Upcoming, never primary
+ navigation), chosen with `data-variant="segmented"` on the tablist since
+ neither has a natural attribute of its own. */
+
+/* Every rule below the list container pairs [role="tablist"] with
+ [role="tab"] rather than styling [role="tab"] alone — Tab buttons are
+ still literal <button> elements, so the compound selector's extra
+ specificity is what keeps Button's base styling (button.css) from
+ winning the cascade on shared properties like padding and background. */
+
+[role="tablist"] {
+ display: flex;
+ gap: var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+[role="tablist"] [role="tab"] {
+ padding: 0.5rem 0;
+ margin-bottom: -1px;
+ background: none;
+ border: 0;
+ border-bottom: 2px solid transparent;
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--1);
+ letter-spacing: 0.02em;
+ cursor: pointer;
+ transition: color var(--ad-dur-fast) var(--ad-ease), border-color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"] [role="tab"]:hover { color: var(--ad-color-text-primary); }
+[role="tablist"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ border-bottom-color: var(--ad-color-accent);
+}
+
+[role="tablist"][data-variant="segmented"] {
+ display: inline-flex;
+ gap: 0.125rem;
+ padding: 0.1875rem;
+ border-bottom: 0;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-md);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"] {
+ all: unset;
+ cursor: pointer;
+ padding: 0.375rem 0.875rem;
+ border-radius: var(--ad-radius-sm);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 550;
+ color: var(--ad-color-text-secondary);
+ transition: background-color var(--ad-dur-fast) var(--ad-ease), color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ background: var(--ad-color-surface-card);
+ box-shadow: var(--ad-shadow-sm);
+}
+
+[role="tabpanel"] { padding-top: var(--ad-space-s); }
+[role="tabpanel"][hidden] { display: none; }
+[role="tabpanel"]:focus-visible { outline-offset: 4px; }
+
+/* Documentation site chrome. Everything below the `.ad-doc-` prefix belongs
+ to this site rather than to the design system it documents — including the
+ width/height constants used directly below. The source design system
+ defines no container-width tokens at all, so there is nothing under
+ `--ad-*` for a doc shell to reach for; the Space foundation page's own
+ notes say to omit that group entirely rather than invent one. */
+
+.ad-doc-skip {
+ position: absolute;
+ inset-inline-start: var(--ad-space-xs);
+ inset-block-start: -4rem;
+ z-index: 40;
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ box-shadow: var(--ad-shadow-float);
+ transition: inset-block-start var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-skip:focus { inset-block-start: var(--ad-space-2xs); }
+
+.ad-doc-header {
+ position: sticky;
+ inset-block-start: 0;
+ z-index: 30;
+ height: 3.5rem;
+ background: color-mix(in srgb, var(--ad-color-surface-page) 92%, transparent);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-header__inner {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ height: 100%;
+ max-width: 72rem;
+ margin-inline: auto;
+ padding-inline: var(--ad-space-xs);
+}
+
+.ad-doc-wordmark {
+ display: flex;
+ align-items: baseline;
+ gap: var(--ad-space-2xs);
+ color: var(--ad-color-text-primary);
+ text-decoration: none;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ font-weight: 600;
+ letter-spacing: -0.01em;
+ white-space: nowrap;
+}
+.ad-doc-wordmark:hover { color: var(--ad-color-text-primary); }
+.ad-doc-wordmark__mark {
+ width: 1.375rem;
+ height: 1.375rem;
+ flex: none;
+ align-self: center;
+ color: var(--ad-color-accent);
+}
+.ad-doc-wordmark__version {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+
+.ad-doc-header__actions { margin-inline-start: auto; display: flex; align-items: center; gap: var(--ad-space-2xs); }
+
+.ad-doc-layout {
+ display: grid;
+ grid-template-columns: 16rem minmax(0, 1fr);
+ gap: var(--ad-space-l);
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-m) var(--ad-space-xs) var(--ad-space-2xl);
+}
+
+.ad-doc-sidebar {
+ position: sticky;
+ inset-block-start: calc(3.5rem + var(--ad-space-m));
+ align-self: start;
+ max-height: calc(100vh - 3.5rem - var(--ad-space-l));
+ overflow-y: auto;
+ padding-inline-end: var(--ad-space-2xs);
+}
+.ad-doc-nav__group + .ad-doc-nav__group { margin-top: var(--ad-space-s); }
+.ad-doc-nav__heading {
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-doc-nav__link {
+ display: block;
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ margin-inline-start: calc(var(--ad-space-xs) * -1);
+ border-inline-start: 2px solid transparent;
+ border-radius: 0 var(--ad-radius-sm) var(--ad-radius-sm) 0;
+ color: var(--ad-color-text-secondary);
+ font-size: var(--ad-step--1);
+ text-decoration: none;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-nav__link:hover { color: var(--ad-color-text-primary); background: var(--ad-color-surface-fill); }
+.ad-doc-nav__link[aria-current="page"] {
+ color: var(--ad-color-accent-ink);
+ border-inline-start-color: var(--ad-color-accent);
+ background: var(--ad-color-accent-soft);
+ font-weight: 500;
+}
+
+.ad-doc-main {
+ min-width: 0;
+ /* The design system's own body-text token (--ad-step-0, 18–20px) is right
+ for a product screen but too large for long-form documentation prose.
+ This site's own copy gets a plain, fixed 16px instead — the token above
+ is unchanged, and specimen tables/demos below still show its real
+ value. */
+ font-size: 1rem;
+}
+.ad-doc-main > * { max-width: 50rem; }
+
+.ad-doc-title { margin-bottom: var(--ad-space-xs); }
+.ad-doc-title__section {
+ display: block;
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-accent);
+}
+
+.ad-doc-main h2 {
+ margin-block: var(--ad-space-2xl) var(--ad-space-xs);
+ padding-bottom: var(--ad-space-2xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-main h3 { margin-block: var(--ad-space-l) var(--ad-space-2xs); }
+.ad-doc-main h4 { margin-block: var(--ad-space-s) var(--ad-space-2xs); }
+.ad-doc-main p,
+.ad-doc-main ul,
+.ad-doc-main ol { max-width: 68ch; }
+.ad-doc-main ul:not([class]),
+.ad-doc-main ol:not([class]) { padding-inline-start: var(--ad-space-s); }
+
+.ad-doc-footer {
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-s) var(--ad-space-xs) var(--ad-space-l);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-tertiary);
+ font-size: var(--ad-step--1);
+}
+.ad-doc-footer__inner { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); justify-content: space-between; }
+
+/* `button.ad-doc-nav-toggle` (element + class) rather than a bare class:
+ button.css's own base rule is `button:not(.icon-button)`, which is also
+ one element + one class in specificity and sets `display: inline-flex` —
+ a bare `.ad-doc-nav-toggle { display: none }` loses that fight and the
+ toggle stays visible at every width. Matching its specificity here (and
+ coming later in the concatenated stylesheet) settles it in source order. */
+button.ad-doc-nav-toggle { display: none; }
+
+@media (max-width: 60rem) {
+ .ad-doc-layout { grid-template-columns: minmax(0, 1fr); gap: var(--ad-space-s); }
+ .ad-doc-sidebar {
+ position: static;
+ max-height: none;
+ }
+ /* The toggle is a normal Button; the panel it opens gets its own card
+ treatment. Putting the same card styling on the <aside> wrapper too
+ nested a button-shaped box inside an outer box for no reason. */
+ .ad-doc-nav {
+ margin-top: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ }
+ .ad-doc-sidebar[data-collapsed="true"] > .ad-doc-nav { display: none; }
+ button.ad-doc-nav-toggle {
+ display: flex;
+ width: 100%;
+ justify-content: space-between;
+ }
+}
+
+@media (max-width: 34rem) {
+ .ad-doc-wordmark__version { display: none; }
+ .ad-doc-layout { padding-inline: var(--ad-space-2xs); }
+}
+
+/* The live-example figure used throughout the docs — see the "How the
+ example demos work" section on the Using the CSS page. One class on the
+ outer <figure>; everything inside is styled by plain element selectors,
+ since figcaption/div/details/summary/pre already say what they are.
+ `data-layout` and `data-surface` on the preview <div> distinguish the
+ handful of demos that need a non-default arrangement. */
+
+.example {
+ margin: var(--ad-space-s) 0 var(--ad-space-l);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+ background: var(--ad-color-surface-card);
+ overflow: hidden;
+}
+
+.example figcaption {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-secondary);
+}
+
+.example > div {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-s);
+}
+/* nowrap is load-bearing: a column-direction flex container with wrap enabled
+ sizes its single line against the container rather than the content, which
+ left every stacked example with a few hundred pixels of dead space. */
+.example > div[data-layout="stack"] { flex-direction: column; flex-wrap: nowrap; align-items: stretch; }
+.example > div[data-layout="grid"] {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
+ align-items: start;
+}
+.example > div[data-surface="sunken"] { background: var(--ad-color-surface-fill); }
+.example > div > * { min-width: 0; }
+
+.example details {
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+.example summary {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ color: var(--ad-color-text-secondary);
+ cursor: pointer;
+ list-style: none;
+ user-select: none;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+}
+.example summary::-webkit-details-marker { display: none; }
+.example summary::before {
+ content: "";
+ width: 0.5rem;
+ height: 0.5rem;
+ border-inline-end: 1.5px solid currentColor;
+ border-block-end: 1.5px solid currentColor;
+ rotate: -45deg;
+ transition: rotate var(--ad-dur-fast) var(--ad-ease);
+}
+.example details[open] > summary::before { rotate: 45deg; }
+.example summary:hover { color: var(--ad-color-text-primary); }
+
+.example pre {
+ margin: 0;
+ padding: var(--ad-space-xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+ tab-size: 2;
+}
+.example pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Standalone fenced code blocks in the prose (unhighlighted — there is no
+ syntax highlighter in this build any more, just Kramdown's plain
+ <pre><code class="language-*">). */
+.ad-doc-main > pre {
+ margin-block: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+}
+.ad-doc-main > pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Presentation for the token reference tables and swatch grids, plus a
+ generic data-table utility. All of this is documentation chrome — the
+ design system itself has no Table component (see the note on the removed
+ `table`/`progress`/`callout` pages in git history). */
+
+.ad-swatch-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs) var(--ad-space-s);
+ padding: 0;
+ list-style: none;
+}
+.ad-swatch {
+ display: flex;
+ flex-direction: column;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ overflow: hidden;
+ background: var(--ad-color-surface-card);
+}
+.ad-swatch__chip {
+ display: flex;
+ align-items: flex-end;
+ justify-content: flex-end;
+ height: 4rem;
+ padding: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+}
+.ad-swatch__meta {
+ display: flex;
+ flex-direction: column;
+ gap: 0.1rem;
+ padding: var(--ad-space-2xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+}
+.ad-swatch__hex { color: var(--ad-color-text-primary); text-transform: uppercase; }
+.ad-swatch__ratio { color: var(--ad-color-text-tertiary); }
+
+.ad-alias-row { display: flex; align-items: center; gap: var(--ad-space-2xs); }
+.ad-alias-chip {
+ width: 1.5rem;
+ height: 1.5rem;
+ flex: none;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+}
+.ad-alias-chip--pair { display: flex; }
+.ad-alias-chip--pair > span { flex: 1; }
+
+/* Type specimen rows. */
+.ad-specimen {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ gap: var(--ad-space-3xs);
+ padding-block: var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-specimen__meta {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-specimen__sample {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: var(--ad-color-text-primary);
+}
+
+/* Space and radius rulers. */
+.ad-ruler { display: flex; flex-direction: column; gap: var(--ad-space-2xs); }
+.ad-ruler__row { display: grid; grid-template-columns: 6.5rem 4.5rem 1fr; align-items: center; gap: var(--ad-space-xs); font-family: var(--ad-font-mono); font-size: 11px; }
+.ad-ruler__bar { height: 0.75rem; background: var(--ad-color-accent); border-radius: var(--ad-radius-sm); }
+.ad-ruler__num { color: var(--ad-color-text-tertiary); text-align: right; }
+
+.ad-radius-grid, .ad-elevation-grid, .ad-motion-grid, .ad-accent-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs);
+ padding: 0;
+ list-style: none;
+}
+.ad-radius-demo {
+ height: 4.5rem;
+ background: var(--ad-color-accent-soft);
+ border: 1px solid var(--ad-color-accent);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-elevation-demo {
+ height: 4.5rem;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-motion-demo {
+ height: 4.5rem;
+ display: grid;
+ place-items: center;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-md);
+ margin-bottom: var(--ad-space-2xs);
+ cursor: pointer;
+ overflow: hidden;
+}
+.ad-motion-demo__dot {
+ width: 1.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent);
+ translate: -3rem 0;
+ transition-property: translate;
+ transition-duration: var(--ad-dur-base);
+ transition-timing-function: var(--ad-ease);
+}
+.ad-motion-demo:hover .ad-motion-demo__dot,
+.ad-motion-demo:focus-visible .ad-motion-demo__dot { translate: 3rem 0; }
+
+.ad-accent-demo {
+ height: 4.5rem;
+ display: flex;
+ align-items: flex-end;
+ padding: var(--ad-space-2xs);
+ border-radius: var(--ad-radius-md);
+ color: #fff;
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ margin-bottom: var(--ad-space-2xs);
+}
+
+/* Token names read `<code>` sitewide, which already carries a mono chip
+ look for running prose (see :not(pre) > code in base/typography.css).
+ Inside these specimen/swatch/ruler rows the old `.ad-token-name` class was
+ plain mono text with no chip, so `<code>` is flattened back to that look
+ here the same way table cells are. */
+.ad-swatch code, .ad-specimen code, .ad-ruler code, .ad-alias-row code {
+ background: none;
+ border: 0;
+ padding: 0;
+ font-size: var(--ad-step--2);
+ color: var(--ad-color-text-primary);
+ white-space: nowrap;
+}
+
+/* A documentation aside. Like `<table>`, this is site chrome for the prose
+ itself, not a system component — the real system has no Callout. */
+.ad-callout {
+ margin-block: var(--ad-space-s);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-left: 3px solid var(--ad-color-accent);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+}
+/* Generic reference tables (component APIs, alias tables, the contrast
+ contract). `<table>` IS a table — no class needed to say so. Every table
+ on this site uses the one compact density, so there is no separate
+ modifier for it. `display: block` plus `overflow-x: auto` gives the table
+ its own horizontal scrollbar on narrow viewports without a wrapper div;
+ the anonymous table box the browser generates around `<thead>`/`<tbody>`
+ still does the row/column layout. `<code>` replaces the old
+ `.ad-token-name` / `.ad-table__code` cells — it is already styled
+ sitewide, just re-flattened here to a plain mono run instead of the
+ chip-with-border look prose code gets, to match this table's original
+ look. `.num` is the one sanctioned class left, for right-aligned numeric
+ columns — there is no attribute that means "this column is a number". */
+table { width: 100%; border-collapse: collapse; font-size: var(--ad-step--1); display: block; overflow-x: auto; }
+table th, table td {
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ text-align: left;
+ vertical-align: top;
+}
+table th { font-family: var(--ad-font-mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ad-color-text-secondary); font-weight: 500; }
+table code { background: none; border: 0; padding: 0; font-size: var(--ad-step--2); white-space: nowrap; }
+.num { text-align: right; font-variant-numeric: tabular-nums; }
+
+/* Home page. */
+.ad-hero { padding-block: var(--ad-space-m) var(--ad-space-l); }
+.ad-hero__title {
+ font-size: var(--ad-step-4);
+ margin-bottom: var(--ad-space-xs);
+}
+.ad-hero__actions { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); margin-top: var(--ad-space-s); }
+.ad-stat-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-l);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+}
+.ad-stat__value {
+ display: block;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-2);
+ font-weight: 600;
+ line-height: 1.16;
+ font-variant-numeric: tabular-nums;
+}
+.ad-stat__label {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+
+/* Card chrome. The pane supplies the frame; this just insets the page. */
+body { padding: var(--ad-space-l) var(--ad-space-m); }
+.ad-doc-main { max-width: 50rem; margin-inline: auto; }
+.ad-doc-main > * { max-width: none; }
+</style>
+</head>
+<body>
+<main class="ad-doc-main">
+<header class="ad-doc-title"><span class="ad-doc-title__section">Foundations</span>
+ <h1>Elevation</h1>
+</header>
+<p class="ad-lead">Two shadow levels only. Hairline (1px) borders do most of the elevation work in this system; shadow is reserved for the one floating layer in a view.</p>
+<h2 id="borders-do-the-work">Borders do the work</h2>
+
+<p>The source’s own rule: “hairline (1px) borders and dividers do most of the
+elevation work”. A flat, bordered <a href="https://kejadlen.github.io/arbitrary-definitions-design-system/components/card/">Card</a>
+is the default everywhere. Shadow is the exception, not the baseline.</p>
+
+<h2 id="the-two-levels">The two levels</h2>
+
+<ul class="ad-elevation-grid">
+ <li>
+ <div class="ad-elevation-demo" style="box-shadow: 0 1px 2px rgba(20, 18, 14, 0.05);"></div>
+ <div class="ad-specimen__meta"><code>shadow-sm</code></div>
+ <p class="ad-muted" style="font-size: var(--ad-step--2); margin: var(--ad-space-3xs) 0 0;">Barely-there separation — a Switch thumb, a focused Input.</p>
+ </li>
+ <li>
+ <div class="ad-elevation-demo" style="box-shadow: 0 2px 6px rgba(20, 18, 14, 0.06), 0 10px 24px -12px rgba(20, 18, 14, 0.18);"></div>
+ <div class="ad-specimen__meta"><code>shadow-float</code></div>
+ <p class="ad-muted" style="font-size: var(--ad-step--2); margin: var(--ad-space-3xs) 0 0;">The one floating layer in a view. Apply it to at most one element on screen at a time.</p>
+ </li>
+</ul>
+
+<figure class="example">
+<figcaption>Flat vs. float</figcaption>
+<div data-layout="grid" data-surface="sunken">
+<article class="card">
+ <div class="card-body">
+ <h3 style="margin:0; font: 600 var(--ad-step--1)/1.3 var(--ad-font-sans);">Flat</h3>
+ <p style="margin:0; color: var(--ad-color-text-secondary); font-size: var(--ad-step--1);">The default. A hairline border, no shadow.</p>
+ </div>
+</article>
+<article class="card" data-elevated="">
+ <div class="card-body">
+ <h3 style="margin:0; font: 600 var(--ad-step--1)/1.3 var(--ad-font-sans);">Elevated</h3>
+ <p style="margin:0; color: var(--ad-color-text-secondary); font-size: var(--ad-step--1);">shadow-float. Reserved for one element per view — a Dialog, a Toast.</p>
+ </div>
+</article>
+</div>
+<details><summary>Markup</summary>
+<pre><code><article class="card">
+ <div class="card-body">
+ <h3>Flat</h3>
+ <p>The default. A hairline border, no shadow.</p>
+ </div>
+</article>
+<article class="card" data-elevated>
+ <div class="card-body">
+ <h3>Elevated</h3>
+ <p>shadow-float. Reserved for one element per view — a Dialog, a Toast.</p>
+ </div>
+</article></code></pre>
+</details>
+</figure>
+
+<h2 id="elevation-in-the-dark-theme">Elevation in the dark theme</h2>
+
+<p>Shadows barely register on a near-black surface. The dark theme leans on
+surface lightness instead — <code>--ad-color-surface-card</code> is one neutral step
+lighter than <code>--ad-color-surface-page</code>, same as in light mode. The shadow
+tokens still apply; they simply stop being the primary signal.</p>
+
+<h2 id="rules">Rules</h2>
+
+<ul>
+ <li><strong>At most one <code>--ad-shadow-float</code> on screen.</strong> If two things both claim to
+be the topmost layer, neither is.</li>
+ <li><strong>Border or shadow, not both, for a given surface’s edge.</strong> A card that
+both borders and floats looks like a sticker.</li>
+ <li><strong>Never animate a shadow’s blur.</strong> If a floated element needs to arrive,
+animate its <code>opacity</code>, or do not animate it at all.</li>
+</ul>
+</main>
+<script>
+// Behavior for the pieces of the system that need it. Everything else is
+// CSS and semantic HTML on purpose — a design system that only works with
+// JavaScript running is a component library with extra steps.
+(function () {
+ "use strict";
+
+ var STORAGE_KEY = "ad-theme";
+
+ function readStored() {
+ try {
+ return localStorage.getItem(STORAGE_KEY);
+ } catch (error) {
+ return null;
+ }
+ }
+
+ function writeStored(value) {
+ try {
+ localStorage.setItem(STORAGE_KEY, value);
+ } catch (error) {
+ /* Nothing to do — the media query still gives a sensible default. */
+ }
+ }
+
+ function systemPrefersDark() {
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
+ }
+
+ function currentTheme() {
+ var explicit = document.documentElement.getAttribute("data-theme");
+ if (explicit) return explicit;
+ return systemPrefersDark() ? "dark" : "light";
+ }
+
+ function paintToggle(theme) {
+ document.querySelectorAll("[data-theme-toggle]").forEach(function (toggle) {
+ var next = theme === "dark" ? "light" : "dark";
+ toggle.querySelectorAll("[data-theme-icon]").forEach(function (icon) {
+ icon.hidden = icon.dataset.themeIcon !== theme;
+ });
+ toggle.setAttribute("aria-label", "Switch to the " + next + " theme");
+ toggle.setAttribute("title", "Switch to the " + next + " theme");
+ var label = toggle.querySelector("[data-theme-label]");
+ if (label) label.textContent = "Switch to the " + next + " theme";
+ });
+ }
+
+ function setUpTheme() {
+ paintToggle(currentTheme());
+
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-theme-toggle]");
+ if (!toggle) return;
+
+ var next = currentTheme() === "dark" ? "light" : "dark";
+ document.documentElement.setAttribute("data-theme", next);
+ writeStored(next);
+ paintToggle(next);
+ });
+
+ // Follow the OS while the reader has not made an explicit choice.
+ window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", function () {
+ if (!readStored()) paintToggle(currentTheme());
+ });
+ }
+
+ // Tabs, wired to the WAI-ARIA authoring practices: arrows move, Home/End
+ // jump, and only the selected tab stays in the tab order.
+ function setUpTabs() {
+ document.querySelectorAll("[data-tabs]").forEach(function (root) {
+ var tabs = Array.prototype.slice.call(root.querySelectorAll('[role="tab"]'));
+ if (!tabs.length) return;
+
+ function select(tab, focus) {
+ tabs.forEach(function (candidate) {
+ var selected = candidate === tab;
+ candidate.setAttribute("aria-selected", String(selected));
+ candidate.tabIndex = selected ? 0 : -1;
+ var panel = document.getElementById(candidate.getAttribute("aria-controls"));
+ if (panel) panel.hidden = !selected;
+ });
+ if (focus) tab.focus();
+ }
+
+ root.addEventListener("click", function (event) {
+ var tab = event.target.closest('[role="tab"]');
+ if (tab) select(tab, false);
+ });
+
+ root.addEventListener("keydown", function (event) {
+ var index = tabs.indexOf(document.activeElement);
+ if (index === -1) return;
+
+ var next = null;
+ if (event.key === "ArrowRight") next = tabs[(index + 1) % tabs.length];
+ else if (event.key === "ArrowLeft") next = tabs[(index - 1 + tabs.length) % tabs.length];
+ else if (event.key === "Home") next = tabs[0];
+ else if (event.key === "End") next = tabs[tabs.length - 1];
+
+ if (next) {
+ event.preventDefault();
+ select(next, true);
+ }
+ });
+
+ select(tabs.filter(function (tab) {
+ return tab.getAttribute("aria-selected") === "true";
+ })[0] || tabs[0], false);
+ });
+ }
+
+ function setUpNavToggle() {
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-nav-toggle]");
+ if (!toggle) return;
+
+ var sidebar = toggle.closest("[data-sidebar]");
+ var collapsed = sidebar.dataset.collapsed !== "false";
+ sidebar.dataset.collapsed = String(!collapsed);
+ toggle.setAttribute("aria-expanded", String(collapsed));
+ });
+ }
+
+ setUpTheme();
+ setUpTabs();
+ setUpNavToggle();
+})();
+
+</script>
+</body>
+</html>
diff --git a/design-bundle/foundations/motion.html b/design-bundle/foundations/motion.html
new file mode 100644
index 0000000..059eb4b
--- /dev/null
+++ b/design-bundle/foundations/motion.html
@@ -0,0 +1,1843 @@
+<!-- @dsCard group="Foundations" -->
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Motion</title>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&display=swap">
+<style>
+/* Arbitrary Definitions — design tokens. Hand-maintained; edit values directly. */
+
+:root {
+ /* Palette ramps — reference only. Components use the aliases below. */
+ --ad-neutral-0: #faf9f7;
+ --ad-neutral-50: #fdfdfb;
+ --ad-neutral-100: #f1f0ed;
+ --ad-neutral-150: #e8e6e2;
+ --ad-neutral-200: #dcdad7;
+ --ad-neutral-300: #c0bdb9;
+ --ad-neutral-500: #888681;
+ --ad-neutral-600: #5f5d59;
+ --ad-neutral-800: #2a2926;
+ --ad-neutral-900: #100f0d;
+ --ad-neutral-dark-0: #0e0d0b;
+ --ad-neutral-dark-50: #151412;
+ --ad-neutral-dark-100: #1e1d1a;
+ --ad-neutral-dark-150: #252421;
+ --ad-neutral-dark-200: #2c2b29;
+ --ad-neutral-dark-300: #44423f;
+ --ad-neutral-dark-500: #7c7a76;
+ --ad-neutral-dark-600: #a09e9a;
+ --ad-neutral-dark-800: #d9d7d4;
+ --ad-neutral-dark-900: #e9e8e6;
+ --ad-accent-teal: #3a6a63;
+ --ad-accent-clay: #9a5a3c;
+ --ad-accent-ink-blue: #2b4a78;
+ --ad-accent-ochre: #93701f;
+ --ad-accent-plum: #6a3a5c;
+
+ /* Surfaces */
+ --ad-color-surface-page: #faf9f7;
+ --ad-color-surface-card: #fdfdfb;
+ --ad-color-surface-fill: #f1f0ed;
+ --ad-color-surface-fill-2: #e8e6e2;
+
+ /* Borders */
+ --ad-color-border-hairline: #dcdad7;
+ --ad-color-border-default: #c0bdb9;
+
+ /* Text */
+ --ad-color-text-primary: #100f0d;
+ --ad-color-text-secondary: #5f5d59;
+ --ad-color-text-tertiary: #888681;
+
+ /* Accent */
+ --ad-color-accent: #3a6a63;
+ --ad-color-accent-fg: #ffffff;
+ --ad-color-accent-ink: color-mix(in oklch, var(--ad-color-accent) 78%, black);
+ --ad-color-accent-soft: color-mix(in oklch, var(--ad-color-accent) 12%, white);
+
+ /* Status (fixed — never swapped like the accent) */
+ --ad-color-success: #3f7a4a;
+ --ad-color-success-bg: color-mix(in oklch, var(--ad-color-success) 12%, white);
+ --ad-color-danger: #ad3a2c;
+ --ad-color-danger-bg: color-mix(in oklch, var(--ad-color-danger) 10%, white);
+
+ /* Type families */
+ --ad-font-sans: -apple-system, "Helvetica Neue", Helvetica, Arial, system-ui, sans-serif;
+ --ad-font-mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, Consolas, "Courier New", monospace;
+ --ad-font-serif: Georgia, "Times New Roman", "Iowan Old Style", serif;
+
+ /* Type steps — fluid (Utopia scale), the design system's own body-text size */
+ --ad-step--2: clamp(0.7813rem, 0.7736rem + 0.0341vw, 0.8rem);
+ --ad-step--1: clamp(0.9375rem, 0.9119rem + 0.1136vw, 1rem);
+ --ad-step-0: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-step-1: clamp(1.35rem, 1.2631rem + 0.3864vw, 1.5625rem);
+ --ad-step-2: clamp(1.62rem, 1.4837rem + 0.6057vw, 1.9531rem);
+ --ad-step-3: clamp(1.944rem, 1.7405rem + 0.9044vw, 2.4414rem);
+ --ad-step-4: clamp(2.3328rem, 2.0387rem + 1.3072vw, 3.0518rem);
+ --ad-step-5: clamp(2.7994rem, 2.384rem + 1.8461vw, 3.8147rem);
+
+ /* Space */
+ --ad-space-3xs: clamp(0.3125rem, 0.3125rem + 0vw, 0.3125rem);
+ --ad-space-2xs: clamp(0.5625rem, 0.5369rem + 0.1136vw, 0.625rem);
+ --ad-space-xs: clamp(0.875rem, 0.8494rem + 0.1136vw, 0.9375rem);
+ --ad-space-s: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-space-m: clamp(1.6875rem, 1.6108rem + 0.3409vw, 1.875rem);
+ --ad-space-l: clamp(2.25rem, 2.1477rem + 0.4545vw, 2.5rem);
+ --ad-space-xl: clamp(3.375rem, 3.2216rem + 0.6818vw, 3.75rem);
+ --ad-space-2xl: clamp(4.5rem, 4.2955rem + 0.9091vw, 5rem);
+ --ad-space-3xl: clamp(6.75rem, 6.4432rem + 1.3636vw, 7.5rem);
+
+ /* Radius */
+ --ad-radius-sm: 4px;
+ --ad-radius-md: 6px;
+ --ad-radius-lg: 10px;
+ --ad-radius-full: 999px;
+ --ad-border-w: 1px;
+
+ /* Elevation */
+ --ad-shadow-sm: 0 1px 2px rgba(20, 18, 14, 0.05);
+ --ad-shadow-float: 0 2px 6px rgba(20, 18, 14, 0.06), 0 10px 24px -12px rgba(20, 18, 14, 0.18);
+
+ /* Motion */
+ --ad-dur-fast: 0.12s;
+ --ad-dur-base: 0.16s;
+ --ad-ease: cubic-bezier(0.2, 0, 0, 1);
+}
+
+@media (prefers-color-scheme: dark) {
+ :root:not([data-theme="light"]) {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+ }
+}
+
+:root[data-theme="dark"] {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ :root {
+ --ad-dur-fast: 0ms;
+ --ad-dur-base: 0ms;
+ }
+}
+
+/* A short reset. Everything it sets is something the system would otherwise
+ have to override in every component.
+
+ The rem values used for this documentation site's own chrome (measure,
+ content width, sidebar width, header height) belong to this site, not to
+ the design system it documents — the source design system defines no
+ container-width tokens at all, so there is nothing under `--ad-*` to reach
+ for here. See the header comment in site/shell.css. */
+
+*,
+*::before,
+*::after { box-sizing: border-box; }
+
+html {
+ -webkit-text-size-adjust: 100%;
+ scroll-behavior: smooth;
+ scroll-padding-top: calc(3.5rem + var(--ad-space-s));
+}
+
+@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;
+ }
+}
+
+body {
+ margin: 0;
+ background: var(--ad-color-surface-page);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ line-height: 1.55;
+ font-synthesis-weight: none;
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+}
+
+::selection {
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+}
+
+h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd { margin: 0; }
+ul[class], ol[class] { list-style: none; margin: 0; padding: 0; }
+
+img, svg, video { display: block; max-width: 100%; height: auto; }
+
+button, input, select, textarea {
+ font: inherit;
+ color: inherit;
+}
+
+hr {
+ height: 1px;
+ margin: var(--ad-space-l) 0;
+ border: 0;
+ background: var(--ad-color-border-hairline);
+}
+
+/* One focus ring, defined once. No component is allowed its own. */
+:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+ border-radius: var(--ad-radius-sm);
+}
+:focus:not(:focus-visible) { outline: none; }
+
+[hidden] { display: none !important; }
+
+.ad-visually-hidden {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip-path: inset(50%);
+ white-space: nowrap;
+ border: 0;
+}
+
+/* Every rule here reads a type token for family and size. Weight, line-height,
+ and letter-spacing are hardcoded to the exact numbers the source ships —
+ there is no `--ad-weight-*` / `--ad-leading-*` / `--ad-tracking-*` scale in
+ the real system, only eight fixed compound styles (see the Typography
+ foundation page, which this file implements verbatim). */
+
+.type-display { font: 600 var(--ad-step-4)/1.08 var(--ad-font-sans); letter-spacing: -0.015em; }
+.type-h1, h1 { font: 600 var(--ad-step-3)/1.12 var(--ad-font-sans); letter-spacing: -0.012em; }
+.type-h2, h2 { font: 600 var(--ad-step-2)/1.16 var(--ad-font-sans); letter-spacing: -0.01em; }
+.type-h3, h3 { font: 600 var(--ad-step-1)/1.25 var(--ad-font-sans); }
+.type-body { font: 400 var(--ad-step-0)/1.55 var(--ad-font-sans); }
+.type-body-sm { font: 400 var(--ad-step--1)/1.5 var(--ad-font-sans); }
+.type-label {
+ font: 500 var(--ad-step--2)/1 var(--ad-font-mono);
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: var(--ad-color-text-secondary);
+}
+.type-mono { font: 400 var(--ad-step--1)/1.5 var(--ad-font-mono); }
+
+/* Prose defaults for the documentation body copy. These read the same
+ tokens the compound classes above do; they are not a second scale. */
+h1, h2, h3, h4 { font-weight: 600; text-wrap: balance; }
+
+h4 { font: 600 var(--ad-step-0)/1.3 var(--ad-font-sans); }
+
+p, ul, ol { margin: 0 0 var(--ad-space-s); }
+li + li { margin-top: var(--ad-space-3xs); }
+
+a {
+ color: var(--ad-color-accent);
+ text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
+ text-underline-offset: 0.18em;
+ text-decoration-thickness: 1px;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ text-decoration-color var(--ad-dur-fast) var(--ad-ease);
+}
+a:hover {
+ color: var(--ad-color-accent-ink);
+ text-decoration-color: currentColor;
+}
+
+strong, b { font-weight: 600; }
+
+small { font-size: var(--ad-step--1); }
+
+.ad-lead {
+ font-size: var(--ad-step-1);
+ line-height: 1.5;
+ color: var(--ad-color-text-secondary);
+ max-width: 68ch;
+ text-wrap: pretty;
+}
+
+.ad-muted { color: var(--ad-color-text-secondary); }
+.ad-subtle { color: var(--ad-color-text-tertiary); }
+
+code, kbd, samp, pre {
+ font-family: var(--ad-font-mono);
+ font-feature-settings: "liga" 0;
+}
+
+:not(pre) > code {
+ padding: 0.15em 0.4em;
+ font-size: 0.875em;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-sm);
+ white-space: nowrap;
+}
+
+kbd {
+ display: inline-block;
+ padding: 0.1em 0.45em;
+ font-size: var(--ad-step--2);
+ line-height: 1.6;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-bottom-width: 2px;
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-secondary);
+}
+
+blockquote {
+ margin-block: var(--ad-space-s);
+ padding-left: var(--ad-space-s);
+ border-left: 2px solid var(--ad-color-accent);
+ color: var(--ad-color-text-secondary);
+}
+
+/* Button
+ A flat, quiet button that recedes rather than performs — no gradients, no
+ shine. Variants: default, primary, dark, ghost, danger. Sizes: sm, md
+ (default), lg — padding and font step only, weight and radius stay put.
+
+ The contract is the element itself: a bare <button> is styled directly, no
+ class required. An <a> that acts like a button (navigates rather than
+ changes something — see the Button page's "Links that act like buttons")
+ opts in with a single `.btn` class, since a bare `a` selector would catch
+ every ordinary prose link on the site. `:not(.icon-button)` keeps this
+ block from leaking onto IconButton, which shares the <button> tag but is
+ an entirely different shape. */
+
+button:not(.icon-button),
+a.btn {
+ --_bg: var(--ad-color-surface-card);
+ --_fg: var(--ad-color-text-primary);
+ --_border: var(--ad-color-border-default);
+ --_pad: var(--ad-space-xs) var(--ad-space-s);
+ --_font: var(--ad-step--1);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: var(--ad-space-2xs);
+ padding: var(--_pad);
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-sans);
+ font-size: var(--_font);
+ font-weight: 550;
+ letter-spacing: -0.01em;
+ line-height: 1.2;
+ text-align: center;
+ text-decoration: none;
+ white-space: nowrap;
+ cursor: pointer;
+ transition: filter var(--ad-dur-fast) var(--ad-ease),
+ border-color var(--ad-dur-fast) var(--ad-ease);
+}
+
+:is(button:not(.icon-button), a.btn):hover:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.96); }
+:is(button:not(.icon-button), a.btn):active:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.92); }
+
+button:not(.icon-button):disabled,
+:is(button:not(.icon-button), a.btn)[aria-disabled="true"] {
+ opacity: 0.5;
+ cursor: default;
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="primary"] {
+ --_bg: var(--ad-color-accent);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-accent);
+}
+
+/* Reserved for the single most-committing action in a view — a final Save,
+ never a whole row of them. */
+:is(button:not(.icon-button), a.btn)[data-variant="dark"] {
+ --_bg: var(--ad-color-text-primary);
+ --_fg: var(--ad-color-surface-page);
+ --_border: var(--ad-color-text-primary);
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="ghost"] {
+ --_bg: transparent;
+ --_fg: var(--ad-color-text-primary);
+ --_border: transparent;
+}
+
+/* The source hardcodes this label white rather than deriving it — danger
+ is not "on a filled accent surface", so --ad-color-accent-fg is reused
+ here only because its resolved value is the same literal white, not
+ because the two roles are the same thing. */
+:is(button:not(.icon-button), a.btn)[data-variant="danger"] {
+ --_bg: var(--ad-color-danger);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-danger);
+}
+
+:is(button:not(.icon-button), a.btn)[data-size="sm"] { --_pad: 0.375rem 0.75rem; --_font: 0.8125rem; }
+:is(button:not(.icon-button), a.btn)[data-size="lg"] { --_pad: 0.75rem 1.375rem; --_font: var(--ad-step-0); }
+
+:is(button:not(.icon-button), a.btn)[data-block] { width: 100%; }
+
+/* Button group — [role="group"] is already the correct ARIA role for a
+ toolbar of related buttons, so it doubles as the styling hook for free. */
+[role="group"] {
+ display: inline-flex;
+ isolation: isolate;
+}
+[role="group"] > button { border-radius: 0; }
+[role="group"] > button:first-child { border-start-start-radius: var(--ad-radius-md); border-end-start-radius: var(--ad-radius-md); }
+[role="group"] > button:last-child { border-start-end-radius: var(--ad-radius-md); border-end-end-radius: var(--ad-radius-md); }
+[role="group"] > button + button { margin-inline-start: -1px; }
+[role="group"] > button:hover,
+[role="group"] > button:focus-visible { z-index: 1; }
+
+/* IconButton — a square icon-only button. Always needs an aria-label; it
+ becomes both the accessible name and the native tooltip. There is no
+ attribute that distinguishes "a button that is only an icon" from a plain
+ Button, so this is the one sanctioned class in this file. */
+.icon-button {
+ --_size: 2.125rem;
+ --_bg: transparent;
+ --_border: transparent;
+ --_fg: var(--ad-color-text-secondary);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ padding: 0;
+ flex: none;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease),
+ color var(--ad-dur-fast) var(--ad-ease);
+}
+.icon-button:hover:not(:disabled) { background: var(--ad-color-surface-fill-2); }
+.icon-button:disabled { opacity: 0.5; cursor: default; }
+
+.icon-button[data-size="sm"] { --_size: 1.75rem; }
+.icon-button[data-size="lg"] { --_size: 2.5rem; }
+
+.icon-button[data-variant="outline"] { --_bg: var(--ad-color-surface-card); --_border: var(--ad-color-border-default); --_fg: var(--ad-color-text-primary); }
+.icon-button[data-variant="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); }
+
+/* Field
+ The labeled field wrapper — a <label> that wraps its own caption text
+ together with the control, so the association is native and needs no
+ `for`/`id` pair. The caption is mono, uppercase, tracked — the same
+ catalog-label convention as Badge and type-label.
+
+ A <label> is reused for three different shapes across this system (this
+ wrapper, Checkbox/Radio's row, Switch's row), so it cannot be styled bare
+ without one shape leaking into another. Field is the one of the three with
+ no distinguishing attribute of its own, so it gets the sanctioned class;
+ Checkbox/Radio tell themselves apart structurally instead (see below). */
+.field {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ min-width: 0;
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Hint and error read the native fine-print element. `role="alert"` is a
+ real live-region role for validation text, so it doubles as the selector
+ that turns a hint red. */
+.field > small {
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+.field > small[role="alert"] { color: var(--ad-color-danger); }
+
+/* Input / Select — bordered, matched to each other exactly. Both tags ARE
+ the component, so they are styled bare; nothing else on this site uses a
+ text input or a select. */
+input:not([type="checkbox"]):not([type="radio"]),
+select {
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ outline: none;
+ transition: border-color var(--ad-dur-fast) var(--ad-ease),
+ box-shadow var(--ad-dur-fast) var(--ad-ease);
+}
+input::placeholder { color: var(--ad-color-text-tertiary); }
+
+input:not([type="checkbox"]):not([type="radio"]):focus,
+select:focus {
+ border-color: var(--ad-color-accent);
+ box-shadow: 0 0 0 2px var(--ad-color-accent-soft);
+}
+
+input:not([type="checkbox"]):not([type="radio"]):disabled,
+select:disabled {
+ opacity: 0.5;
+ cursor: default;
+}
+
+/* The chevron is the one flagged color exception in this component set:
+ CSS cannot point a data-URI background-image at a custom property, so its
+ stroke is a literal hex rather than a var(--ad-*) reference. It is picked
+ to match --ad-color-text-secondary and commented here so it is not
+ mistaken for a stray color. */
+select {
+ appearance: none;
+ padding-inline-end: var(--ad-space-l);
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%235f5d59' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right var(--ad-space-xs) center;
+}
+
+input[aria-invalid="true"],
+select[aria-invalid="true"] {
+ border-color: var(--ad-color-danger);
+ background-color: var(--ad-color-danger-bg);
+}
+
+/* Checkbox / Radio — the real native controls, tinted with accent-color
+ instead of a hand-drawn box. This is not just a rename: it deletes the
+ custom SVG-checkmark markup entirely and lets the browser draw its own
+ (platform-appropriate) check and dot. `role="switch"` is excluded here so
+ Switch — a checkbox playing a different role — can style itself below
+ without this rule fighting it. A <label> wrapping a checkbox/radio is told
+ apart from Field's <label> structurally: it :has() one as a direct child,
+ no class required. */
+input[type="checkbox"]:not([role="switch"]),
+input[type="radio"] {
+ width: 1.125rem;
+ height: 1.125rem;
+ margin: 0;
+ accent-color: var(--ad-color-accent);
+ cursor: pointer;
+}
+input[type="checkbox"]:not([role="switch"]):focus-visible,
+input[type="radio"]:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+}
+
+label:has(> input[type="checkbox"]:not([role="switch"])),
+label:has(> input[type="radio"]) {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 400;
+ text-transform: none;
+ letter-spacing: normal;
+ color: var(--ad-color-text-primary);
+}
+label:has(> input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Switch — for on/off settings only, never a substitute for Checkbox.
+ `input[type="checkbox"][role="switch"]` is real ARIA (the correct role for
+ a toggle), not an invented hook, but the visual track+thumb needs a second
+ element for the thumb's independent position and transition — more than
+ `appearance: none` plus pseudo-elements on the input itself can cleanly
+ carry — so `.switch` is a deliberate, documented fallback class rather
+ than a first choice. */
+.switch {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ color: var(--ad-color-text-primary);
+}
+.switch input[role="switch"] { position: absolute; opacity: 0; width: 2.25rem; height: 1.25rem; margin: 0; }
+
+.switch span {
+ position: relative;
+ flex: none;
+ width: 2.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-border-default);
+ transition: background-color var(--ad-dur-base) var(--ad-ease);
+}
+.switch span::after {
+ content: "";
+ position: absolute;
+ inset-block-start: 2px;
+ inset-inline-start: 2px;
+ width: 1rem;
+ height: 1rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-fg);
+ box-shadow: var(--ad-shadow-sm);
+ transition: translate var(--ad-dur-base) var(--ad-ease);
+}
+.switch input[role="switch"]:checked + span { background: var(--ad-color-accent); }
+.switch input[role="switch"]:checked + span::after { translate: 1rem 0; }
+.switch input[role="switch"]:focus-visible + span { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+.switch:has(input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Rows of fields — used by the settings-form pattern. `<form>`/`<fieldset>`
+ are the elements that already mean this, so they are styled bare. A row
+ grouping two fields side by side has no element of its own, so it keeps
+ one plain class. */
+form {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-s);
+ max-width: 28rem;
+}
+.form-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
+ gap: var(--ad-space-xs);
+}
+fieldset {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ margin: 0;
+ padding: 0;
+ border: 0;
+}
+fieldset > legend {
+ padding: 0 0 var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Badge
+ Mono, uppercase, tracked — the catalog-label convention for status and
+ meta values (SAVED, OVERDUE, RECOMMENDED). A label, never a button.
+
+ A <span> is too generic to style globally — it is used all over running
+ prose for unrelated reasons — so Badge gets the one sanctioned class this
+ shape needs. Tone is an attribute, not a modifier class. */
+
+.badge {
+ --_bg: var(--ad-color-surface-fill);
+ --_fg: var(--ad-color-text-secondary);
+ --_border: var(--ad-color-border-hairline);
+
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.1875rem 0.5rem;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-sm);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ line-height: 1;
+ white-space: nowrap;
+}
+
+.badge[data-tone="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); --_border: transparent; }
+.badge[data-tone="success"] { --_bg: var(--ad-color-success-bg); --_fg: var(--ad-color-success); --_border: transparent; }
+.badge[data-tone="danger"] { --_bg: var(--ad-color-danger-bg); --_fg: var(--ad-color-danger); --_border: transparent; }
+
+.badge-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-2xs);
+}
+
+/* Tag
+ A removable mono chip for freeform tags — locations, categories.
+ `[data-dashed]` renders an "add tag" affordance instead of a value. Same
+ reasoning as Badge: <span> is too generic to select bare, so Tag gets one
+ class. The remove control is a real <button>, styled through the
+ descendant selector rather than a class of its own. */
+
+.tag {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.375rem;
+ padding: 0.25rem 0.625rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-default);
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ line-height: 1;
+}
+
+.tag[data-dashed] {
+ background: transparent;
+ border-style: dashed;
+ color: var(--ad-color-text-tertiary);
+ cursor: pointer;
+}
+
+.tag button {
+ all: unset;
+ cursor: pointer;
+ color: var(--ad-color-text-tertiary);
+ line-height: 1;
+ font-size: 0.75rem;
+}
+.tag button:hover { color: var(--ad-color-text-primary); }
+
+.tag-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.375rem;
+}
+
+/* Card
+ Flat surface, hairline border, radius-lg. `[data-elevated]` is opt-in and
+ reserved for the one focused/primary card in a view — a dialog, the
+ primary capture card — flat is the default everywhere else.
+
+ A <div>/<article> is too generic to select bare, so Card keeps one
+ sanctioned class. When Card's job is holding a list of rows — the common
+ case, entry lists, task lists, settings rows — it is a real <ul class="
+ card">, and CardRow is a bare <li> underneath it: no second class needed,
+ the row look comes from the `.card > li` descendant selector alone. The
+ first row's top border is removed automatically so callers never have to
+ override it by hand. */
+
+.card {
+ display: flex;
+ flex-direction: column;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: none;
+ overflow: hidden;
+}
+
+.card[data-elevated] { box-shadow: var(--ad-shadow-float); }
+
+.card > li {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+}
+.card > li:first-child { border-top: none; }
+
+/* A row whose ENTIRE content is wrapped in a real <button> or <a> — the
+ accessible way to make a row clickable — fills the row edge-to-edge and
+ picks up a hover fill and pointer cursor. `:only-child` is what tells this
+ apart from a row that merely contains a small button among other content
+ (a leading checkbox next to a title and a trailing value, say): only a
+ button/anchor that IS the row's one and only child means "the whole row
+ is the control." */
+.card > li:has(> button:only-child),
+.card > li:has(> a:only-child) {
+ padding: 0;
+}
+.card > li > button:only-child,
+.card > li > a:only-child {
+ all: unset;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ box-sizing: border-box;
+ color: inherit;
+ font: inherit;
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.card > li > button:only-child:hover,
+.card > li > a:only-child:hover { background: var(--ad-color-surface-fill); }
+
+/* Documentation-only extras: a body/footer pair and a responsive grid, used
+ by the prose pages (rationale.md, index.md) to show a Card as a plain
+ content tile rather than a data row. Neither is part of Card's props —
+ they exist because a content tile's inner layout has no element of its
+ own to hang a selector on. */
+.card-body {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-m);
+}
+.card-footer {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ margin-top: auto;
+ padding: var(--ad-space-xs) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+
+.card[data-link] { transition: border-color var(--ad-dur-fast) var(--ad-ease), box-shadow var(--ad-dur-fast) var(--ad-ease); }
+.card[data-link]:hover { border-color: var(--ad-color-border-default); box-shadow: var(--ad-shadow-sm); }
+.card[data-link]:focus-within { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+
+.card-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
+ gap: var(--ad-space-s);
+}
+
+/* Avatar — lives here because it almost never appears outside a card or a
+ row (the Loop dashboard's initials chip, Archive's file-type chip). A
+ <span> is too generic for a bare selector, so it keeps one class. */
+.avatar {
+ --_size: 1.875rem;
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ flex: none;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 600;
+ overflow: hidden;
+}
+.avatar[data-square] { border-radius: var(--ad-radius-md); background: var(--ad-color-surface-fill); color: var(--ad-color-text-secondary); border: 1px solid var(--ad-color-border-hairline); }
+.avatar[data-size="sm"] { --_size: 1.5rem; }
+.avatar[data-size="lg"] { --_size: 2.25rem; }
+
+/* Tooltip — a dark mono label on hover, positioned above the child. Pure CSS:
+ the trigger and bubble are siblings inside one relatively-positioned span,
+ shown on :hover/:focus-within so it works from the keyboard too.
+
+ `[role="tooltip"]` is the correct ARIA role for the bubble itself, so it
+ doubles as the styling hook — zero classes there. The wrapping span has no
+ attribute of its own, but it does not need one: `:has()` picks out "a span
+ that contains a [role=tooltip] bubble" precisely, without reaching for a
+ class on an otherwise-generic element. */
+span:has(> [role="tooltip"]) {
+ position: relative;
+ display: inline-flex;
+}
+
+[role="tooltip"] {
+ position: absolute;
+ bottom: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ margin-bottom: 0.375rem;
+ padding: 0.25rem 0.5rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-text-primary);
+ color: var(--ad-color-surface-page);
+ font-family: var(--ad-font-mono);
+ /* 11px is the source's own literal choice — smaller than --ad-step--2, the
+ smallest step on the scale, because a tooltip is meant to read as a
+ glance-only aside rather than body text. */
+ font-size: 11px;
+ white-space: nowrap;
+ line-height: 1.4;
+ z-index: 20;
+ opacity: 0;
+ visibility: hidden;
+ pointer-events: none;
+ transition: opacity var(--ad-dur-fast) var(--ad-ease);
+}
+span:has(> [role="tooltip"]):hover > [role="tooltip"],
+span:has(> [role="tooltip"]):focus-within > [role="tooltip"] {
+ opacity: 1;
+ visibility: visible;
+}
+
+/* Toast — the real pattern used for delete confirmations: a flat bar with an
+ optional destructive action and a dismiss. `[data-fixed]` pins it
+ bottom-right for real use; without it the toast stays in document flow for
+ documentation. `role="status"` is the correct live-region role for a
+ transient status message, so it is both the accessibility hook and the
+ style selector — no base class needed. The action and dismiss buttons are
+ plain <button>s told apart by `aria-label="Dismiss"`, an attribute they
+ need anyway for the icon-only close control. */
+[role="status"] {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ min-width: 20rem;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+}
+[role="status"][data-fixed] {
+ position: fixed;
+ inset-block-end: var(--ad-space-s);
+ inset-inline-end: var(--ad-space-s);
+ z-index: 60;
+}
+[role="status"] > span { flex: 1; }
+[role="status"] button {
+ all: unset;
+ cursor: pointer;
+ font-weight: 600;
+ color: var(--ad-color-danger);
+}
+[role="status"] button[aria-label="Dismiss"] {
+ color: var(--ad-color-text-tertiary);
+ font-size: 1rem;
+ font-weight: 400;
+ line-height: 1;
+}
+[role="status"] button[aria-label="Dismiss"]:hover { color: var(--ad-color-text-primary); }
+
+/* Dialog — the native <dialog> element: a centered modal, flat surface, no
+ colored header, hairline dividers between title/body/footer. A <header>
+ holds the title and a <footer> holds the actions, matching the elements
+ the tag already wants; everything else in between is the body, styled by
+ excluding those two rather than by a class of its own.
+
+ `.dialog-demo` scopes a static, always-open example to its own preview
+ container instead of the viewport, so it can sit safely inside a
+ documentation page — this is a doc-only affordance, not part of Dialog's
+ contract. It relies on the browser's own dialog centering: the UA
+ stylesheet already gives an open <dialog> `position: absolute; inset: 0;
+ margin: auto`, which centers it inside the nearest positioned ancestor —
+ here, `.dialog-demo` itself. A real `.showModal()` call gets the genuine
+ viewport-fixed overlay and `::backdrop` for free; a plain `open` attribute
+ (used here so the demo renders without JavaScript) does not paint a
+ backdrop at all, since `::backdrop` only exists for a dialog shown
+ modally — wire the real open/close state and call `.showModal()` in the
+ consuming app. */
+.dialog-demo {
+ position: relative;
+ min-height: 20rem;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-lg);
+}
+
+dialog {
+ width: 26rem;
+ max-width: calc(100% - (2 * var(--ad-space-s)));
+ height: fit-content;
+ padding: 0;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ overflow: hidden;
+}
+dialog::backdrop {
+ background: rgba(20, 18, 14, 0.35);
+}
+dialog > header {
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-sans);
+ font-weight: 600;
+ font-size: var(--ad-step-0);
+ color: var(--ad-color-text-primary);
+}
+dialog > footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+}
+dialog > *:not(header):not(footer) {
+ padding: var(--ad-space-m);
+ margin: 0;
+ color: var(--ad-color-text-primary);
+ font-size: var(--ad-step--1);
+}
+
+/* Tabs
+ Follows the real WAI-ARIA tabs pattern: a `[role="tablist"]` of
+ `[role="tab"]` buttons controlling `[role="tabpanel"]`s. Those roles are
+ required for the pattern to be accessible in the first place, so they
+ double as the entire styling contract — no classes at all for the base
+ shape. The keyboard wiring lives in assets/js/system.js and is
+ variant-agnostic — it only cares about `[data-tabs]` / `[role=tab]`.
+
+ Two real variants: `underline` (mono, accent underline — primary section
+ nav, the default) and `segmented` (pill track, active segment lifts to the
+ surface — filters like All · Overdue · Upcoming, never primary
+ navigation), chosen with `data-variant="segmented"` on the tablist since
+ neither has a natural attribute of its own. */
+
+/* Every rule below the list container pairs [role="tablist"] with
+ [role="tab"] rather than styling [role="tab"] alone — Tab buttons are
+ still literal <button> elements, so the compound selector's extra
+ specificity is what keeps Button's base styling (button.css) from
+ winning the cascade on shared properties like padding and background. */
+
+[role="tablist"] {
+ display: flex;
+ gap: var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+[role="tablist"] [role="tab"] {
+ padding: 0.5rem 0;
+ margin-bottom: -1px;
+ background: none;
+ border: 0;
+ border-bottom: 2px solid transparent;
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--1);
+ letter-spacing: 0.02em;
+ cursor: pointer;
+ transition: color var(--ad-dur-fast) var(--ad-ease), border-color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"] [role="tab"]:hover { color: var(--ad-color-text-primary); }
+[role="tablist"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ border-bottom-color: var(--ad-color-accent);
+}
+
+[role="tablist"][data-variant="segmented"] {
+ display: inline-flex;
+ gap: 0.125rem;
+ padding: 0.1875rem;
+ border-bottom: 0;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-md);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"] {
+ all: unset;
+ cursor: pointer;
+ padding: 0.375rem 0.875rem;
+ border-radius: var(--ad-radius-sm);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 550;
+ color: var(--ad-color-text-secondary);
+ transition: background-color var(--ad-dur-fast) var(--ad-ease), color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ background: var(--ad-color-surface-card);
+ box-shadow: var(--ad-shadow-sm);
+}
+
+[role="tabpanel"] { padding-top: var(--ad-space-s); }
+[role="tabpanel"][hidden] { display: none; }
+[role="tabpanel"]:focus-visible { outline-offset: 4px; }
+
+/* Documentation site chrome. Everything below the `.ad-doc-` prefix belongs
+ to this site rather than to the design system it documents — including the
+ width/height constants used directly below. The source design system
+ defines no container-width tokens at all, so there is nothing under
+ `--ad-*` for a doc shell to reach for; the Space foundation page's own
+ notes say to omit that group entirely rather than invent one. */
+
+.ad-doc-skip {
+ position: absolute;
+ inset-inline-start: var(--ad-space-xs);
+ inset-block-start: -4rem;
+ z-index: 40;
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ box-shadow: var(--ad-shadow-float);
+ transition: inset-block-start var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-skip:focus { inset-block-start: var(--ad-space-2xs); }
+
+.ad-doc-header {
+ position: sticky;
+ inset-block-start: 0;
+ z-index: 30;
+ height: 3.5rem;
+ background: color-mix(in srgb, var(--ad-color-surface-page) 92%, transparent);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-header__inner {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ height: 100%;
+ max-width: 72rem;
+ margin-inline: auto;
+ padding-inline: var(--ad-space-xs);
+}
+
+.ad-doc-wordmark {
+ display: flex;
+ align-items: baseline;
+ gap: var(--ad-space-2xs);
+ color: var(--ad-color-text-primary);
+ text-decoration: none;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ font-weight: 600;
+ letter-spacing: -0.01em;
+ white-space: nowrap;
+}
+.ad-doc-wordmark:hover { color: var(--ad-color-text-primary); }
+.ad-doc-wordmark__mark {
+ width: 1.375rem;
+ height: 1.375rem;
+ flex: none;
+ align-self: center;
+ color: var(--ad-color-accent);
+}
+.ad-doc-wordmark__version {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+
+.ad-doc-header__actions { margin-inline-start: auto; display: flex; align-items: center; gap: var(--ad-space-2xs); }
+
+.ad-doc-layout {
+ display: grid;
+ grid-template-columns: 16rem minmax(0, 1fr);
+ gap: var(--ad-space-l);
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-m) var(--ad-space-xs) var(--ad-space-2xl);
+}
+
+.ad-doc-sidebar {
+ position: sticky;
+ inset-block-start: calc(3.5rem + var(--ad-space-m));
+ align-self: start;
+ max-height: calc(100vh - 3.5rem - var(--ad-space-l));
+ overflow-y: auto;
+ padding-inline-end: var(--ad-space-2xs);
+}
+.ad-doc-nav__group + .ad-doc-nav__group { margin-top: var(--ad-space-s); }
+.ad-doc-nav__heading {
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-doc-nav__link {
+ display: block;
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ margin-inline-start: calc(var(--ad-space-xs) * -1);
+ border-inline-start: 2px solid transparent;
+ border-radius: 0 var(--ad-radius-sm) var(--ad-radius-sm) 0;
+ color: var(--ad-color-text-secondary);
+ font-size: var(--ad-step--1);
+ text-decoration: none;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-nav__link:hover { color: var(--ad-color-text-primary); background: var(--ad-color-surface-fill); }
+.ad-doc-nav__link[aria-current="page"] {
+ color: var(--ad-color-accent-ink);
+ border-inline-start-color: var(--ad-color-accent);
+ background: var(--ad-color-accent-soft);
+ font-weight: 500;
+}
+
+.ad-doc-main {
+ min-width: 0;
+ /* The design system's own body-text token (--ad-step-0, 18–20px) is right
+ for a product screen but too large for long-form documentation prose.
+ This site's own copy gets a plain, fixed 16px instead — the token above
+ is unchanged, and specimen tables/demos below still show its real
+ value. */
+ font-size: 1rem;
+}
+.ad-doc-main > * { max-width: 50rem; }
+
+.ad-doc-title { margin-bottom: var(--ad-space-xs); }
+.ad-doc-title__section {
+ display: block;
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-accent);
+}
+
+.ad-doc-main h2 {
+ margin-block: var(--ad-space-2xl) var(--ad-space-xs);
+ padding-bottom: var(--ad-space-2xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-main h3 { margin-block: var(--ad-space-l) var(--ad-space-2xs); }
+.ad-doc-main h4 { margin-block: var(--ad-space-s) var(--ad-space-2xs); }
+.ad-doc-main p,
+.ad-doc-main ul,
+.ad-doc-main ol { max-width: 68ch; }
+.ad-doc-main ul:not([class]),
+.ad-doc-main ol:not([class]) { padding-inline-start: var(--ad-space-s); }
+
+.ad-doc-footer {
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-s) var(--ad-space-xs) var(--ad-space-l);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-tertiary);
+ font-size: var(--ad-step--1);
+}
+.ad-doc-footer__inner { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); justify-content: space-between; }
+
+/* `button.ad-doc-nav-toggle` (element + class) rather than a bare class:
+ button.css's own base rule is `button:not(.icon-button)`, which is also
+ one element + one class in specificity and sets `display: inline-flex` —
+ a bare `.ad-doc-nav-toggle { display: none }` loses that fight and the
+ toggle stays visible at every width. Matching its specificity here (and
+ coming later in the concatenated stylesheet) settles it in source order. */
+button.ad-doc-nav-toggle { display: none; }
+
+@media (max-width: 60rem) {
+ .ad-doc-layout { grid-template-columns: minmax(0, 1fr); gap: var(--ad-space-s); }
+ .ad-doc-sidebar {
+ position: static;
+ max-height: none;
+ }
+ /* The toggle is a normal Button; the panel it opens gets its own card
+ treatment. Putting the same card styling on the <aside> wrapper too
+ nested a button-shaped box inside an outer box for no reason. */
+ .ad-doc-nav {
+ margin-top: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ }
+ .ad-doc-sidebar[data-collapsed="true"] > .ad-doc-nav { display: none; }
+ button.ad-doc-nav-toggle {
+ display: flex;
+ width: 100%;
+ justify-content: space-between;
+ }
+}
+
+@media (max-width: 34rem) {
+ .ad-doc-wordmark__version { display: none; }
+ .ad-doc-layout { padding-inline: var(--ad-space-2xs); }
+}
+
+/* The live-example figure used throughout the docs — see the "How the
+ example demos work" section on the Using the CSS page. One class on the
+ outer <figure>; everything inside is styled by plain element selectors,
+ since figcaption/div/details/summary/pre already say what they are.
+ `data-layout` and `data-surface` on the preview <div> distinguish the
+ handful of demos that need a non-default arrangement. */
+
+.example {
+ margin: var(--ad-space-s) 0 var(--ad-space-l);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+ background: var(--ad-color-surface-card);
+ overflow: hidden;
+}
+
+.example figcaption {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-secondary);
+}
+
+.example > div {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-s);
+}
+/* nowrap is load-bearing: a column-direction flex container with wrap enabled
+ sizes its single line against the container rather than the content, which
+ left every stacked example with a few hundred pixels of dead space. */
+.example > div[data-layout="stack"] { flex-direction: column; flex-wrap: nowrap; align-items: stretch; }
+.example > div[data-layout="grid"] {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
+ align-items: start;
+}
+.example > div[data-surface="sunken"] { background: var(--ad-color-surface-fill); }
+.example > div > * { min-width: 0; }
+
+.example details {
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+.example summary {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ color: var(--ad-color-text-secondary);
+ cursor: pointer;
+ list-style: none;
+ user-select: none;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+}
+.example summary::-webkit-details-marker { display: none; }
+.example summary::before {
+ content: "";
+ width: 0.5rem;
+ height: 0.5rem;
+ border-inline-end: 1.5px solid currentColor;
+ border-block-end: 1.5px solid currentColor;
+ rotate: -45deg;
+ transition: rotate var(--ad-dur-fast) var(--ad-ease);
+}
+.example details[open] > summary::before { rotate: 45deg; }
+.example summary:hover { color: var(--ad-color-text-primary); }
+
+.example pre {
+ margin: 0;
+ padding: var(--ad-space-xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+ tab-size: 2;
+}
+.example pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Standalone fenced code blocks in the prose (unhighlighted — there is no
+ syntax highlighter in this build any more, just Kramdown's plain
+ <pre><code class="language-*">). */
+.ad-doc-main > pre {
+ margin-block: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+}
+.ad-doc-main > pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Presentation for the token reference tables and swatch grids, plus a
+ generic data-table utility. All of this is documentation chrome — the
+ design system itself has no Table component (see the note on the removed
+ `table`/`progress`/`callout` pages in git history). */
+
+.ad-swatch-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs) var(--ad-space-s);
+ padding: 0;
+ list-style: none;
+}
+.ad-swatch {
+ display: flex;
+ flex-direction: column;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ overflow: hidden;
+ background: var(--ad-color-surface-card);
+}
+.ad-swatch__chip {
+ display: flex;
+ align-items: flex-end;
+ justify-content: flex-end;
+ height: 4rem;
+ padding: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+}
+.ad-swatch__meta {
+ display: flex;
+ flex-direction: column;
+ gap: 0.1rem;
+ padding: var(--ad-space-2xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+}
+.ad-swatch__hex { color: var(--ad-color-text-primary); text-transform: uppercase; }
+.ad-swatch__ratio { color: var(--ad-color-text-tertiary); }
+
+.ad-alias-row { display: flex; align-items: center; gap: var(--ad-space-2xs); }
+.ad-alias-chip {
+ width: 1.5rem;
+ height: 1.5rem;
+ flex: none;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+}
+.ad-alias-chip--pair { display: flex; }
+.ad-alias-chip--pair > span { flex: 1; }
+
+/* Type specimen rows. */
+.ad-specimen {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ gap: var(--ad-space-3xs);
+ padding-block: var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-specimen__meta {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-specimen__sample {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: var(--ad-color-text-primary);
+}
+
+/* Space and radius rulers. */
+.ad-ruler { display: flex; flex-direction: column; gap: var(--ad-space-2xs); }
+.ad-ruler__row { display: grid; grid-template-columns: 6.5rem 4.5rem 1fr; align-items: center; gap: var(--ad-space-xs); font-family: var(--ad-font-mono); font-size: 11px; }
+.ad-ruler__bar { height: 0.75rem; background: var(--ad-color-accent); border-radius: var(--ad-radius-sm); }
+.ad-ruler__num { color: var(--ad-color-text-tertiary); text-align: right; }
+
+.ad-radius-grid, .ad-elevation-grid, .ad-motion-grid, .ad-accent-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs);
+ padding: 0;
+ list-style: none;
+}
+.ad-radius-demo {
+ height: 4.5rem;
+ background: var(--ad-color-accent-soft);
+ border: 1px solid var(--ad-color-accent);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-elevation-demo {
+ height: 4.5rem;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-motion-demo {
+ height: 4.5rem;
+ display: grid;
+ place-items: center;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-md);
+ margin-bottom: var(--ad-space-2xs);
+ cursor: pointer;
+ overflow: hidden;
+}
+.ad-motion-demo__dot {
+ width: 1.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent);
+ translate: -3rem 0;
+ transition-property: translate;
+ transition-duration: var(--ad-dur-base);
+ transition-timing-function: var(--ad-ease);
+}
+.ad-motion-demo:hover .ad-motion-demo__dot,
+.ad-motion-demo:focus-visible .ad-motion-demo__dot { translate: 3rem 0; }
+
+.ad-accent-demo {
+ height: 4.5rem;
+ display: flex;
+ align-items: flex-end;
+ padding: var(--ad-space-2xs);
+ border-radius: var(--ad-radius-md);
+ color: #fff;
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ margin-bottom: var(--ad-space-2xs);
+}
+
+/* Token names read `<code>` sitewide, which already carries a mono chip
+ look for running prose (see :not(pre) > code in base/typography.css).
+ Inside these specimen/swatch/ruler rows the old `.ad-token-name` class was
+ plain mono text with no chip, so `<code>` is flattened back to that look
+ here the same way table cells are. */
+.ad-swatch code, .ad-specimen code, .ad-ruler code, .ad-alias-row code {
+ background: none;
+ border: 0;
+ padding: 0;
+ font-size: var(--ad-step--2);
+ color: var(--ad-color-text-primary);
+ white-space: nowrap;
+}
+
+/* A documentation aside. Like `<table>`, this is site chrome for the prose
+ itself, not a system component — the real system has no Callout. */
+.ad-callout {
+ margin-block: var(--ad-space-s);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-left: 3px solid var(--ad-color-accent);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+}
+/* Generic reference tables (component APIs, alias tables, the contrast
+ contract). `<table>` IS a table — no class needed to say so. Every table
+ on this site uses the one compact density, so there is no separate
+ modifier for it. `display: block` plus `overflow-x: auto` gives the table
+ its own horizontal scrollbar on narrow viewports without a wrapper div;
+ the anonymous table box the browser generates around `<thead>`/`<tbody>`
+ still does the row/column layout. `<code>` replaces the old
+ `.ad-token-name` / `.ad-table__code` cells — it is already styled
+ sitewide, just re-flattened here to a plain mono run instead of the
+ chip-with-border look prose code gets, to match this table's original
+ look. `.num` is the one sanctioned class left, for right-aligned numeric
+ columns — there is no attribute that means "this column is a number". */
+table { width: 100%; border-collapse: collapse; font-size: var(--ad-step--1); display: block; overflow-x: auto; }
+table th, table td {
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ text-align: left;
+ vertical-align: top;
+}
+table th { font-family: var(--ad-font-mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ad-color-text-secondary); font-weight: 500; }
+table code { background: none; border: 0; padding: 0; font-size: var(--ad-step--2); white-space: nowrap; }
+.num { text-align: right; font-variant-numeric: tabular-nums; }
+
+/* Home page. */
+.ad-hero { padding-block: var(--ad-space-m) var(--ad-space-l); }
+.ad-hero__title {
+ font-size: var(--ad-step-4);
+ margin-bottom: var(--ad-space-xs);
+}
+.ad-hero__actions { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); margin-top: var(--ad-space-s); }
+.ad-stat-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-l);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+}
+.ad-stat__value {
+ display: block;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-2);
+ font-weight: 600;
+ line-height: 1.16;
+ font-variant-numeric: tabular-nums;
+}
+.ad-stat__label {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+
+/* Card chrome. The pane supplies the frame; this just insets the page. */
+body { padding: var(--ad-space-l) var(--ad-space-m); }
+.ad-doc-main { max-width: 50rem; margin-inline: auto; }
+.ad-doc-main > * { max-width: none; }
+</style>
+</head>
+<body>
+<main class="ad-doc-main">
+<header class="ad-doc-title"><span class="ad-doc-title__section">Foundations</span>
+ <h1>Motion</h1>
+</header>
+<p class="ad-lead">Two durations, one easing curve, and one rule that overrides both: if the reader asked for less motion, they get none. This is an interface, not a title sequence.</p>
+<h2 id="durations">Durations</h2>
+
+<p>Short ease transitions on hover and focus only — no bounce, no springy
+easing, no page-transition choreography.</p>
+
+<table>
+ <thead><tr><th scope="col">Token</th><th scope="col">Value</th><th scope="col">Use for</th></tr></thead>
+ <tbody>
+ <tr>
+ <td><code>--ad-dur-fast</code></td>
+ <td><code>0.12s</code></td>
+ <td>Hover and focus color/border transitions — the default.</td>
+ </tr>
+ <tr>
+ <td><code>--ad-dur-base</code></td>
+ <td><code>0.16s</code></td>
+ <td>Switch track and thumb — the one moving part in the system.</td>
+ </tr>
+ </tbody>
+ </table>
+
+<h2 id="one-easing">One easing</h2>
+
+<p>There is exactly one curve in the system. Hover the tile below to run it.</p>
+
+<ul class="ad-motion-grid">
+ <li>
+ <div class="ad-motion-demo" tabindex="0">
+ <span class="ad-motion-demo__dot"></span>
+ </div>
+ <div class="ad-specimen__meta"><code>--ad-ease</code></div>
+ <p class="ad-muted" style="font-size: var(--ad-step--2); margin: var(--ad-space-3xs) 0 0;">The only easing curve in the system. Fast out, settles gently, applied to every transition.</p>
+ </li>
+</ul>
+
+<h2 id="reduced-motion-is-not-a-downgrade">Reduced motion is not a downgrade</h2>
+
+<p><code>tokens.css</code> includes a <code>prefers-reduced-motion: reduce</code> block that sets
+every duration token to <code>0ms</code>, and the base stylesheet clamps any remaining
+transition or animation to a hundredth of a millisecond. Components do not
+have to opt in, and cannot opt out.</p>
+
+<pre><code class="language-css">@media (prefers-reduced-motion: reduce) {
+ :root {
+ --ad-dur-fast: 0ms;
+ --ad-dur-base: 0ms;
+ }
+}
+</code></pre>
+
+<p>The state change still happens. The Switch still moves. It simply arrives
+instead of traveling.</p>
+
+<figure class="example">
+<figcaption>Motion in practice</figcaption>
+<div>
+<label class="switch">
+ <input type="checkbox" role="switch" checked="" />
+ <span></span>
+ Dark mode
+</label>
+<label class="switch">
+ <input type="checkbox" role="switch" />
+ <span></span>
+ Weekly digest
+</label>
+</div>
+<details><summary>Markup</summary>
+<pre><code><label class="switch">
+ <input type="checkbox" role="switch" checked>
+ <span></span>
+ Dark mode
+</label>
+<label class="switch">
+ <input type="checkbox" role="switch">
+ <span></span>
+ Weekly digest
+</label></code></pre>
+</details>
+</figure>
+
+<h2 id="rules">Rules</h2>
+
+<ul>
+ <li><strong>Transition named properties, never <code>all</code>.</strong> <code>transition: all</code> animates
+things you did not think about, including layout.</li>
+ <li><strong>Hover states shift a background one step deeper, never a color or
+opacity trick.</strong> Surface → fill is the whole vocabulary.</li>
+ <li><strong>Press is not animated.</strong> Rely on the hover-darken plus native <code>:active</code>,
+if anything at all.</li>
+ <li><strong>Motion is for hover and focus only.</strong> No page-transition choreography.</li>
+</ul>
+</main>
+<script>
+// Behavior for the pieces of the system that need it. Everything else is
+// CSS and semantic HTML on purpose — a design system that only works with
+// JavaScript running is a component library with extra steps.
+(function () {
+ "use strict";
+
+ var STORAGE_KEY = "ad-theme";
+
+ function readStored() {
+ try {
+ return localStorage.getItem(STORAGE_KEY);
+ } catch (error) {
+ return null;
+ }
+ }
+
+ function writeStored(value) {
+ try {
+ localStorage.setItem(STORAGE_KEY, value);
+ } catch (error) {
+ /* Nothing to do — the media query still gives a sensible default. */
+ }
+ }
+
+ function systemPrefersDark() {
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
+ }
+
+ function currentTheme() {
+ var explicit = document.documentElement.getAttribute("data-theme");
+ if (explicit) return explicit;
+ return systemPrefersDark() ? "dark" : "light";
+ }
+
+ function paintToggle(theme) {
+ document.querySelectorAll("[data-theme-toggle]").forEach(function (toggle) {
+ var next = theme === "dark" ? "light" : "dark";
+ toggle.querySelectorAll("[data-theme-icon]").forEach(function (icon) {
+ icon.hidden = icon.dataset.themeIcon !== theme;
+ });
+ toggle.setAttribute("aria-label", "Switch to the " + next + " theme");
+ toggle.setAttribute("title", "Switch to the " + next + " theme");
+ var label = toggle.querySelector("[data-theme-label]");
+ if (label) label.textContent = "Switch to the " + next + " theme";
+ });
+ }
+
+ function setUpTheme() {
+ paintToggle(currentTheme());
+
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-theme-toggle]");
+ if (!toggle) return;
+
+ var next = currentTheme() === "dark" ? "light" : "dark";
+ document.documentElement.setAttribute("data-theme", next);
+ writeStored(next);
+ paintToggle(next);
+ });
+
+ // Follow the OS while the reader has not made an explicit choice.
+ window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", function () {
+ if (!readStored()) paintToggle(currentTheme());
+ });
+ }
+
+ // Tabs, wired to the WAI-ARIA authoring practices: arrows move, Home/End
+ // jump, and only the selected tab stays in the tab order.
+ function setUpTabs() {
+ document.querySelectorAll("[data-tabs]").forEach(function (root) {
+ var tabs = Array.prototype.slice.call(root.querySelectorAll('[role="tab"]'));
+ if (!tabs.length) return;
+
+ function select(tab, focus) {
+ tabs.forEach(function (candidate) {
+ var selected = candidate === tab;
+ candidate.setAttribute("aria-selected", String(selected));
+ candidate.tabIndex = selected ? 0 : -1;
+ var panel = document.getElementById(candidate.getAttribute("aria-controls"));
+ if (panel) panel.hidden = !selected;
+ });
+ if (focus) tab.focus();
+ }
+
+ root.addEventListener("click", function (event) {
+ var tab = event.target.closest('[role="tab"]');
+ if (tab) select(tab, false);
+ });
+
+ root.addEventListener("keydown", function (event) {
+ var index = tabs.indexOf(document.activeElement);
+ if (index === -1) return;
+
+ var next = null;
+ if (event.key === "ArrowRight") next = tabs[(index + 1) % tabs.length];
+ else if (event.key === "ArrowLeft") next = tabs[(index - 1 + tabs.length) % tabs.length];
+ else if (event.key === "Home") next = tabs[0];
+ else if (event.key === "End") next = tabs[tabs.length - 1];
+
+ if (next) {
+ event.preventDefault();
+ select(next, true);
+ }
+ });
+
+ select(tabs.filter(function (tab) {
+ return tab.getAttribute("aria-selected") === "true";
+ })[0] || tabs[0], false);
+ });
+ }
+
+ function setUpNavToggle() {
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-nav-toggle]");
+ if (!toggle) return;
+
+ var sidebar = toggle.closest("[data-sidebar]");
+ var collapsed = sidebar.dataset.collapsed !== "false";
+ sidebar.dataset.collapsed = String(!collapsed);
+ toggle.setAttribute("aria-expanded", String(collapsed));
+ });
+ }
+
+ setUpTheme();
+ setUpTabs();
+ setUpNavToggle();
+})();
+
+</script>
+</body>
+</html>
diff --git a/design-bundle/foundations/space.html b/design-bundle/foundations/space.html
new file mode 100644
index 0000000..3e4bd50
--- /dev/null
+++ b/design-bundle/foundations/space.html
@@ -0,0 +1,1941 @@
+<!-- @dsCard group="Foundations" -->
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Space</title>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&display=swap">
+<style>
+/* Arbitrary Definitions — design tokens. Hand-maintained; edit values directly. */
+
+:root {
+ /* Palette ramps — reference only. Components use the aliases below. */
+ --ad-neutral-0: #faf9f7;
+ --ad-neutral-50: #fdfdfb;
+ --ad-neutral-100: #f1f0ed;
+ --ad-neutral-150: #e8e6e2;
+ --ad-neutral-200: #dcdad7;
+ --ad-neutral-300: #c0bdb9;
+ --ad-neutral-500: #888681;
+ --ad-neutral-600: #5f5d59;
+ --ad-neutral-800: #2a2926;
+ --ad-neutral-900: #100f0d;
+ --ad-neutral-dark-0: #0e0d0b;
+ --ad-neutral-dark-50: #151412;
+ --ad-neutral-dark-100: #1e1d1a;
+ --ad-neutral-dark-150: #252421;
+ --ad-neutral-dark-200: #2c2b29;
+ --ad-neutral-dark-300: #44423f;
+ --ad-neutral-dark-500: #7c7a76;
+ --ad-neutral-dark-600: #a09e9a;
+ --ad-neutral-dark-800: #d9d7d4;
+ --ad-neutral-dark-900: #e9e8e6;
+ --ad-accent-teal: #3a6a63;
+ --ad-accent-clay: #9a5a3c;
+ --ad-accent-ink-blue: #2b4a78;
+ --ad-accent-ochre: #93701f;
+ --ad-accent-plum: #6a3a5c;
+
+ /* Surfaces */
+ --ad-color-surface-page: #faf9f7;
+ --ad-color-surface-card: #fdfdfb;
+ --ad-color-surface-fill: #f1f0ed;
+ --ad-color-surface-fill-2: #e8e6e2;
+
+ /* Borders */
+ --ad-color-border-hairline: #dcdad7;
+ --ad-color-border-default: #c0bdb9;
+
+ /* Text */
+ --ad-color-text-primary: #100f0d;
+ --ad-color-text-secondary: #5f5d59;
+ --ad-color-text-tertiary: #888681;
+
+ /* Accent */
+ --ad-color-accent: #3a6a63;
+ --ad-color-accent-fg: #ffffff;
+ --ad-color-accent-ink: color-mix(in oklch, var(--ad-color-accent) 78%, black);
+ --ad-color-accent-soft: color-mix(in oklch, var(--ad-color-accent) 12%, white);
+
+ /* Status (fixed — never swapped like the accent) */
+ --ad-color-success: #3f7a4a;
+ --ad-color-success-bg: color-mix(in oklch, var(--ad-color-success) 12%, white);
+ --ad-color-danger: #ad3a2c;
+ --ad-color-danger-bg: color-mix(in oklch, var(--ad-color-danger) 10%, white);
+
+ /* Type families */
+ --ad-font-sans: -apple-system, "Helvetica Neue", Helvetica, Arial, system-ui, sans-serif;
+ --ad-font-mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, Consolas, "Courier New", monospace;
+ --ad-font-serif: Georgia, "Times New Roman", "Iowan Old Style", serif;
+
+ /* Type steps — fluid (Utopia scale), the design system's own body-text size */
+ --ad-step--2: clamp(0.7813rem, 0.7736rem + 0.0341vw, 0.8rem);
+ --ad-step--1: clamp(0.9375rem, 0.9119rem + 0.1136vw, 1rem);
+ --ad-step-0: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-step-1: clamp(1.35rem, 1.2631rem + 0.3864vw, 1.5625rem);
+ --ad-step-2: clamp(1.62rem, 1.4837rem + 0.6057vw, 1.9531rem);
+ --ad-step-3: clamp(1.944rem, 1.7405rem + 0.9044vw, 2.4414rem);
+ --ad-step-4: clamp(2.3328rem, 2.0387rem + 1.3072vw, 3.0518rem);
+ --ad-step-5: clamp(2.7994rem, 2.384rem + 1.8461vw, 3.8147rem);
+
+ /* Space */
+ --ad-space-3xs: clamp(0.3125rem, 0.3125rem + 0vw, 0.3125rem);
+ --ad-space-2xs: clamp(0.5625rem, 0.5369rem + 0.1136vw, 0.625rem);
+ --ad-space-xs: clamp(0.875rem, 0.8494rem + 0.1136vw, 0.9375rem);
+ --ad-space-s: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-space-m: clamp(1.6875rem, 1.6108rem + 0.3409vw, 1.875rem);
+ --ad-space-l: clamp(2.25rem, 2.1477rem + 0.4545vw, 2.5rem);
+ --ad-space-xl: clamp(3.375rem, 3.2216rem + 0.6818vw, 3.75rem);
+ --ad-space-2xl: clamp(4.5rem, 4.2955rem + 0.9091vw, 5rem);
+ --ad-space-3xl: clamp(6.75rem, 6.4432rem + 1.3636vw, 7.5rem);
+
+ /* Radius */
+ --ad-radius-sm: 4px;
+ --ad-radius-md: 6px;
+ --ad-radius-lg: 10px;
+ --ad-radius-full: 999px;
+ --ad-border-w: 1px;
+
+ /* Elevation */
+ --ad-shadow-sm: 0 1px 2px rgba(20, 18, 14, 0.05);
+ --ad-shadow-float: 0 2px 6px rgba(20, 18, 14, 0.06), 0 10px 24px -12px rgba(20, 18, 14, 0.18);
+
+ /* Motion */
+ --ad-dur-fast: 0.12s;
+ --ad-dur-base: 0.16s;
+ --ad-ease: cubic-bezier(0.2, 0, 0, 1);
+}
+
+@media (prefers-color-scheme: dark) {
+ :root:not([data-theme="light"]) {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+ }
+}
+
+:root[data-theme="dark"] {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ :root {
+ --ad-dur-fast: 0ms;
+ --ad-dur-base: 0ms;
+ }
+}
+
+/* A short reset. Everything it sets is something the system would otherwise
+ have to override in every component.
+
+ The rem values used for this documentation site's own chrome (measure,
+ content width, sidebar width, header height) belong to this site, not to
+ the design system it documents — the source design system defines no
+ container-width tokens at all, so there is nothing under `--ad-*` to reach
+ for here. See the header comment in site/shell.css. */
+
+*,
+*::before,
+*::after { box-sizing: border-box; }
+
+html {
+ -webkit-text-size-adjust: 100%;
+ scroll-behavior: smooth;
+ scroll-padding-top: calc(3.5rem + var(--ad-space-s));
+}
+
+@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;
+ }
+}
+
+body {
+ margin: 0;
+ background: var(--ad-color-surface-page);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ line-height: 1.55;
+ font-synthesis-weight: none;
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+}
+
+::selection {
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+}
+
+h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd { margin: 0; }
+ul[class], ol[class] { list-style: none; margin: 0; padding: 0; }
+
+img, svg, video { display: block; max-width: 100%; height: auto; }
+
+button, input, select, textarea {
+ font: inherit;
+ color: inherit;
+}
+
+hr {
+ height: 1px;
+ margin: var(--ad-space-l) 0;
+ border: 0;
+ background: var(--ad-color-border-hairline);
+}
+
+/* One focus ring, defined once. No component is allowed its own. */
+:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+ border-radius: var(--ad-radius-sm);
+}
+:focus:not(:focus-visible) { outline: none; }
+
+[hidden] { display: none !important; }
+
+.ad-visually-hidden {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip-path: inset(50%);
+ white-space: nowrap;
+ border: 0;
+}
+
+/* Every rule here reads a type token for family and size. Weight, line-height,
+ and letter-spacing are hardcoded to the exact numbers the source ships —
+ there is no `--ad-weight-*` / `--ad-leading-*` / `--ad-tracking-*` scale in
+ the real system, only eight fixed compound styles (see the Typography
+ foundation page, which this file implements verbatim). */
+
+.type-display { font: 600 var(--ad-step-4)/1.08 var(--ad-font-sans); letter-spacing: -0.015em; }
+.type-h1, h1 { font: 600 var(--ad-step-3)/1.12 var(--ad-font-sans); letter-spacing: -0.012em; }
+.type-h2, h2 { font: 600 var(--ad-step-2)/1.16 var(--ad-font-sans); letter-spacing: -0.01em; }
+.type-h3, h3 { font: 600 var(--ad-step-1)/1.25 var(--ad-font-sans); }
+.type-body { font: 400 var(--ad-step-0)/1.55 var(--ad-font-sans); }
+.type-body-sm { font: 400 var(--ad-step--1)/1.5 var(--ad-font-sans); }
+.type-label {
+ font: 500 var(--ad-step--2)/1 var(--ad-font-mono);
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: var(--ad-color-text-secondary);
+}
+.type-mono { font: 400 var(--ad-step--1)/1.5 var(--ad-font-mono); }
+
+/* Prose defaults for the documentation body copy. These read the same
+ tokens the compound classes above do; they are not a second scale. */
+h1, h2, h3, h4 { font-weight: 600; text-wrap: balance; }
+
+h4 { font: 600 var(--ad-step-0)/1.3 var(--ad-font-sans); }
+
+p, ul, ol { margin: 0 0 var(--ad-space-s); }
+li + li { margin-top: var(--ad-space-3xs); }
+
+a {
+ color: var(--ad-color-accent);
+ text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
+ text-underline-offset: 0.18em;
+ text-decoration-thickness: 1px;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ text-decoration-color var(--ad-dur-fast) var(--ad-ease);
+}
+a:hover {
+ color: var(--ad-color-accent-ink);
+ text-decoration-color: currentColor;
+}
+
+strong, b { font-weight: 600; }
+
+small { font-size: var(--ad-step--1); }
+
+.ad-lead {
+ font-size: var(--ad-step-1);
+ line-height: 1.5;
+ color: var(--ad-color-text-secondary);
+ max-width: 68ch;
+ text-wrap: pretty;
+}
+
+.ad-muted { color: var(--ad-color-text-secondary); }
+.ad-subtle { color: var(--ad-color-text-tertiary); }
+
+code, kbd, samp, pre {
+ font-family: var(--ad-font-mono);
+ font-feature-settings: "liga" 0;
+}
+
+:not(pre) > code {
+ padding: 0.15em 0.4em;
+ font-size: 0.875em;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-sm);
+ white-space: nowrap;
+}
+
+kbd {
+ display: inline-block;
+ padding: 0.1em 0.45em;
+ font-size: var(--ad-step--2);
+ line-height: 1.6;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-bottom-width: 2px;
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-secondary);
+}
+
+blockquote {
+ margin-block: var(--ad-space-s);
+ padding-left: var(--ad-space-s);
+ border-left: 2px solid var(--ad-color-accent);
+ color: var(--ad-color-text-secondary);
+}
+
+/* Button
+ A flat, quiet button that recedes rather than performs — no gradients, no
+ shine. Variants: default, primary, dark, ghost, danger. Sizes: sm, md
+ (default), lg — padding and font step only, weight and radius stay put.
+
+ The contract is the element itself: a bare <button> is styled directly, no
+ class required. An <a> that acts like a button (navigates rather than
+ changes something — see the Button page's "Links that act like buttons")
+ opts in with a single `.btn` class, since a bare `a` selector would catch
+ every ordinary prose link on the site. `:not(.icon-button)` keeps this
+ block from leaking onto IconButton, which shares the <button> tag but is
+ an entirely different shape. */
+
+button:not(.icon-button),
+a.btn {
+ --_bg: var(--ad-color-surface-card);
+ --_fg: var(--ad-color-text-primary);
+ --_border: var(--ad-color-border-default);
+ --_pad: var(--ad-space-xs) var(--ad-space-s);
+ --_font: var(--ad-step--1);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: var(--ad-space-2xs);
+ padding: var(--_pad);
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-sans);
+ font-size: var(--_font);
+ font-weight: 550;
+ letter-spacing: -0.01em;
+ line-height: 1.2;
+ text-align: center;
+ text-decoration: none;
+ white-space: nowrap;
+ cursor: pointer;
+ transition: filter var(--ad-dur-fast) var(--ad-ease),
+ border-color var(--ad-dur-fast) var(--ad-ease);
+}
+
+:is(button:not(.icon-button), a.btn):hover:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.96); }
+:is(button:not(.icon-button), a.btn):active:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.92); }
+
+button:not(.icon-button):disabled,
+:is(button:not(.icon-button), a.btn)[aria-disabled="true"] {
+ opacity: 0.5;
+ cursor: default;
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="primary"] {
+ --_bg: var(--ad-color-accent);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-accent);
+}
+
+/* Reserved for the single most-committing action in a view — a final Save,
+ never a whole row of them. */
+:is(button:not(.icon-button), a.btn)[data-variant="dark"] {
+ --_bg: var(--ad-color-text-primary);
+ --_fg: var(--ad-color-surface-page);
+ --_border: var(--ad-color-text-primary);
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="ghost"] {
+ --_bg: transparent;
+ --_fg: var(--ad-color-text-primary);
+ --_border: transparent;
+}
+
+/* The source hardcodes this label white rather than deriving it — danger
+ is not "on a filled accent surface", so --ad-color-accent-fg is reused
+ here only because its resolved value is the same literal white, not
+ because the two roles are the same thing. */
+:is(button:not(.icon-button), a.btn)[data-variant="danger"] {
+ --_bg: var(--ad-color-danger);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-danger);
+}
+
+:is(button:not(.icon-button), a.btn)[data-size="sm"] { --_pad: 0.375rem 0.75rem; --_font: 0.8125rem; }
+:is(button:not(.icon-button), a.btn)[data-size="lg"] { --_pad: 0.75rem 1.375rem; --_font: var(--ad-step-0); }
+
+:is(button:not(.icon-button), a.btn)[data-block] { width: 100%; }
+
+/* Button group — [role="group"] is already the correct ARIA role for a
+ toolbar of related buttons, so it doubles as the styling hook for free. */
+[role="group"] {
+ display: inline-flex;
+ isolation: isolate;
+}
+[role="group"] > button { border-radius: 0; }
+[role="group"] > button:first-child { border-start-start-radius: var(--ad-radius-md); border-end-start-radius: var(--ad-radius-md); }
+[role="group"] > button:last-child { border-start-end-radius: var(--ad-radius-md); border-end-end-radius: var(--ad-radius-md); }
+[role="group"] > button + button { margin-inline-start: -1px; }
+[role="group"] > button:hover,
+[role="group"] > button:focus-visible { z-index: 1; }
+
+/* IconButton — a square icon-only button. Always needs an aria-label; it
+ becomes both the accessible name and the native tooltip. There is no
+ attribute that distinguishes "a button that is only an icon" from a plain
+ Button, so this is the one sanctioned class in this file. */
+.icon-button {
+ --_size: 2.125rem;
+ --_bg: transparent;
+ --_border: transparent;
+ --_fg: var(--ad-color-text-secondary);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ padding: 0;
+ flex: none;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease),
+ color var(--ad-dur-fast) var(--ad-ease);
+}
+.icon-button:hover:not(:disabled) { background: var(--ad-color-surface-fill-2); }
+.icon-button:disabled { opacity: 0.5; cursor: default; }
+
+.icon-button[data-size="sm"] { --_size: 1.75rem; }
+.icon-button[data-size="lg"] { --_size: 2.5rem; }
+
+.icon-button[data-variant="outline"] { --_bg: var(--ad-color-surface-card); --_border: var(--ad-color-border-default); --_fg: var(--ad-color-text-primary); }
+.icon-button[data-variant="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); }
+
+/* Field
+ The labeled field wrapper — a <label> that wraps its own caption text
+ together with the control, so the association is native and needs no
+ `for`/`id` pair. The caption is mono, uppercase, tracked — the same
+ catalog-label convention as Badge and type-label.
+
+ A <label> is reused for three different shapes across this system (this
+ wrapper, Checkbox/Radio's row, Switch's row), so it cannot be styled bare
+ without one shape leaking into another. Field is the one of the three with
+ no distinguishing attribute of its own, so it gets the sanctioned class;
+ Checkbox/Radio tell themselves apart structurally instead (see below). */
+.field {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ min-width: 0;
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Hint and error read the native fine-print element. `role="alert"` is a
+ real live-region role for validation text, so it doubles as the selector
+ that turns a hint red. */
+.field > small {
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+.field > small[role="alert"] { color: var(--ad-color-danger); }
+
+/* Input / Select — bordered, matched to each other exactly. Both tags ARE
+ the component, so they are styled bare; nothing else on this site uses a
+ text input or a select. */
+input:not([type="checkbox"]):not([type="radio"]),
+select {
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ outline: none;
+ transition: border-color var(--ad-dur-fast) var(--ad-ease),
+ box-shadow var(--ad-dur-fast) var(--ad-ease);
+}
+input::placeholder { color: var(--ad-color-text-tertiary); }
+
+input:not([type="checkbox"]):not([type="radio"]):focus,
+select:focus {
+ border-color: var(--ad-color-accent);
+ box-shadow: 0 0 0 2px var(--ad-color-accent-soft);
+}
+
+input:not([type="checkbox"]):not([type="radio"]):disabled,
+select:disabled {
+ opacity: 0.5;
+ cursor: default;
+}
+
+/* The chevron is the one flagged color exception in this component set:
+ CSS cannot point a data-URI background-image at a custom property, so its
+ stroke is a literal hex rather than a var(--ad-*) reference. It is picked
+ to match --ad-color-text-secondary and commented here so it is not
+ mistaken for a stray color. */
+select {
+ appearance: none;
+ padding-inline-end: var(--ad-space-l);
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%235f5d59' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right var(--ad-space-xs) center;
+}
+
+input[aria-invalid="true"],
+select[aria-invalid="true"] {
+ border-color: var(--ad-color-danger);
+ background-color: var(--ad-color-danger-bg);
+}
+
+/* Checkbox / Radio — the real native controls, tinted with accent-color
+ instead of a hand-drawn box. This is not just a rename: it deletes the
+ custom SVG-checkmark markup entirely and lets the browser draw its own
+ (platform-appropriate) check and dot. `role="switch"` is excluded here so
+ Switch — a checkbox playing a different role — can style itself below
+ without this rule fighting it. A <label> wrapping a checkbox/radio is told
+ apart from Field's <label> structurally: it :has() one as a direct child,
+ no class required. */
+input[type="checkbox"]:not([role="switch"]),
+input[type="radio"] {
+ width: 1.125rem;
+ height: 1.125rem;
+ margin: 0;
+ accent-color: var(--ad-color-accent);
+ cursor: pointer;
+}
+input[type="checkbox"]:not([role="switch"]):focus-visible,
+input[type="radio"]:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+}
+
+label:has(> input[type="checkbox"]:not([role="switch"])),
+label:has(> input[type="radio"]) {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 400;
+ text-transform: none;
+ letter-spacing: normal;
+ color: var(--ad-color-text-primary);
+}
+label:has(> input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Switch — for on/off settings only, never a substitute for Checkbox.
+ `input[type="checkbox"][role="switch"]` is real ARIA (the correct role for
+ a toggle), not an invented hook, but the visual track+thumb needs a second
+ element for the thumb's independent position and transition — more than
+ `appearance: none` plus pseudo-elements on the input itself can cleanly
+ carry — so `.switch` is a deliberate, documented fallback class rather
+ than a first choice. */
+.switch {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ color: var(--ad-color-text-primary);
+}
+.switch input[role="switch"] { position: absolute; opacity: 0; width: 2.25rem; height: 1.25rem; margin: 0; }
+
+.switch span {
+ position: relative;
+ flex: none;
+ width: 2.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-border-default);
+ transition: background-color var(--ad-dur-base) var(--ad-ease);
+}
+.switch span::after {
+ content: "";
+ position: absolute;
+ inset-block-start: 2px;
+ inset-inline-start: 2px;
+ width: 1rem;
+ height: 1rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-fg);
+ box-shadow: var(--ad-shadow-sm);
+ transition: translate var(--ad-dur-base) var(--ad-ease);
+}
+.switch input[role="switch"]:checked + span { background: var(--ad-color-accent); }
+.switch input[role="switch"]:checked + span::after { translate: 1rem 0; }
+.switch input[role="switch"]:focus-visible + span { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+.switch:has(input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Rows of fields — used by the settings-form pattern. `<form>`/`<fieldset>`
+ are the elements that already mean this, so they are styled bare. A row
+ grouping two fields side by side has no element of its own, so it keeps
+ one plain class. */
+form {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-s);
+ max-width: 28rem;
+}
+.form-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
+ gap: var(--ad-space-xs);
+}
+fieldset {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ margin: 0;
+ padding: 0;
+ border: 0;
+}
+fieldset > legend {
+ padding: 0 0 var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Badge
+ Mono, uppercase, tracked — the catalog-label convention for status and
+ meta values (SAVED, OVERDUE, RECOMMENDED). A label, never a button.
+
+ A <span> is too generic to style globally — it is used all over running
+ prose for unrelated reasons — so Badge gets the one sanctioned class this
+ shape needs. Tone is an attribute, not a modifier class. */
+
+.badge {
+ --_bg: var(--ad-color-surface-fill);
+ --_fg: var(--ad-color-text-secondary);
+ --_border: var(--ad-color-border-hairline);
+
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.1875rem 0.5rem;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-sm);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ line-height: 1;
+ white-space: nowrap;
+}
+
+.badge[data-tone="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); --_border: transparent; }
+.badge[data-tone="success"] { --_bg: var(--ad-color-success-bg); --_fg: var(--ad-color-success); --_border: transparent; }
+.badge[data-tone="danger"] { --_bg: var(--ad-color-danger-bg); --_fg: var(--ad-color-danger); --_border: transparent; }
+
+.badge-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-2xs);
+}
+
+/* Tag
+ A removable mono chip for freeform tags — locations, categories.
+ `[data-dashed]` renders an "add tag" affordance instead of a value. Same
+ reasoning as Badge: <span> is too generic to select bare, so Tag gets one
+ class. The remove control is a real <button>, styled through the
+ descendant selector rather than a class of its own. */
+
+.tag {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.375rem;
+ padding: 0.25rem 0.625rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-default);
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ line-height: 1;
+}
+
+.tag[data-dashed] {
+ background: transparent;
+ border-style: dashed;
+ color: var(--ad-color-text-tertiary);
+ cursor: pointer;
+}
+
+.tag button {
+ all: unset;
+ cursor: pointer;
+ color: var(--ad-color-text-tertiary);
+ line-height: 1;
+ font-size: 0.75rem;
+}
+.tag button:hover { color: var(--ad-color-text-primary); }
+
+.tag-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.375rem;
+}
+
+/* Card
+ Flat surface, hairline border, radius-lg. `[data-elevated]` is opt-in and
+ reserved for the one focused/primary card in a view — a dialog, the
+ primary capture card — flat is the default everywhere else.
+
+ A <div>/<article> is too generic to select bare, so Card keeps one
+ sanctioned class. When Card's job is holding a list of rows — the common
+ case, entry lists, task lists, settings rows — it is a real <ul class="
+ card">, and CardRow is a bare <li> underneath it: no second class needed,
+ the row look comes from the `.card > li` descendant selector alone. The
+ first row's top border is removed automatically so callers never have to
+ override it by hand. */
+
+.card {
+ display: flex;
+ flex-direction: column;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: none;
+ overflow: hidden;
+}
+
+.card[data-elevated] { box-shadow: var(--ad-shadow-float); }
+
+.card > li {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+}
+.card > li:first-child { border-top: none; }
+
+/* A row whose ENTIRE content is wrapped in a real <button> or <a> — the
+ accessible way to make a row clickable — fills the row edge-to-edge and
+ picks up a hover fill and pointer cursor. `:only-child` is what tells this
+ apart from a row that merely contains a small button among other content
+ (a leading checkbox next to a title and a trailing value, say): only a
+ button/anchor that IS the row's one and only child means "the whole row
+ is the control." */
+.card > li:has(> button:only-child),
+.card > li:has(> a:only-child) {
+ padding: 0;
+}
+.card > li > button:only-child,
+.card > li > a:only-child {
+ all: unset;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ box-sizing: border-box;
+ color: inherit;
+ font: inherit;
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.card > li > button:only-child:hover,
+.card > li > a:only-child:hover { background: var(--ad-color-surface-fill); }
+
+/* Documentation-only extras: a body/footer pair and a responsive grid, used
+ by the prose pages (rationale.md, index.md) to show a Card as a plain
+ content tile rather than a data row. Neither is part of Card's props —
+ they exist because a content tile's inner layout has no element of its
+ own to hang a selector on. */
+.card-body {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-m);
+}
+.card-footer {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ margin-top: auto;
+ padding: var(--ad-space-xs) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+
+.card[data-link] { transition: border-color var(--ad-dur-fast) var(--ad-ease), box-shadow var(--ad-dur-fast) var(--ad-ease); }
+.card[data-link]:hover { border-color: var(--ad-color-border-default); box-shadow: var(--ad-shadow-sm); }
+.card[data-link]:focus-within { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+
+.card-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
+ gap: var(--ad-space-s);
+}
+
+/* Avatar — lives here because it almost never appears outside a card or a
+ row (the Loop dashboard's initials chip, Archive's file-type chip). A
+ <span> is too generic for a bare selector, so it keeps one class. */
+.avatar {
+ --_size: 1.875rem;
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ flex: none;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 600;
+ overflow: hidden;
+}
+.avatar[data-square] { border-radius: var(--ad-radius-md); background: var(--ad-color-surface-fill); color: var(--ad-color-text-secondary); border: 1px solid var(--ad-color-border-hairline); }
+.avatar[data-size="sm"] { --_size: 1.5rem; }
+.avatar[data-size="lg"] { --_size: 2.25rem; }
+
+/* Tooltip — a dark mono label on hover, positioned above the child. Pure CSS:
+ the trigger and bubble are siblings inside one relatively-positioned span,
+ shown on :hover/:focus-within so it works from the keyboard too.
+
+ `[role="tooltip"]` is the correct ARIA role for the bubble itself, so it
+ doubles as the styling hook — zero classes there. The wrapping span has no
+ attribute of its own, but it does not need one: `:has()` picks out "a span
+ that contains a [role=tooltip] bubble" precisely, without reaching for a
+ class on an otherwise-generic element. */
+span:has(> [role="tooltip"]) {
+ position: relative;
+ display: inline-flex;
+}
+
+[role="tooltip"] {
+ position: absolute;
+ bottom: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ margin-bottom: 0.375rem;
+ padding: 0.25rem 0.5rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-text-primary);
+ color: var(--ad-color-surface-page);
+ font-family: var(--ad-font-mono);
+ /* 11px is the source's own literal choice — smaller than --ad-step--2, the
+ smallest step on the scale, because a tooltip is meant to read as a
+ glance-only aside rather than body text. */
+ font-size: 11px;
+ white-space: nowrap;
+ line-height: 1.4;
+ z-index: 20;
+ opacity: 0;
+ visibility: hidden;
+ pointer-events: none;
+ transition: opacity var(--ad-dur-fast) var(--ad-ease);
+}
+span:has(> [role="tooltip"]):hover > [role="tooltip"],
+span:has(> [role="tooltip"]):focus-within > [role="tooltip"] {
+ opacity: 1;
+ visibility: visible;
+}
+
+/* Toast — the real pattern used for delete confirmations: a flat bar with an
+ optional destructive action and a dismiss. `[data-fixed]` pins it
+ bottom-right for real use; without it the toast stays in document flow for
+ documentation. `role="status"` is the correct live-region role for a
+ transient status message, so it is both the accessibility hook and the
+ style selector — no base class needed. The action and dismiss buttons are
+ plain <button>s told apart by `aria-label="Dismiss"`, an attribute they
+ need anyway for the icon-only close control. */
+[role="status"] {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ min-width: 20rem;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+}
+[role="status"][data-fixed] {
+ position: fixed;
+ inset-block-end: var(--ad-space-s);
+ inset-inline-end: var(--ad-space-s);
+ z-index: 60;
+}
+[role="status"] > span { flex: 1; }
+[role="status"] button {
+ all: unset;
+ cursor: pointer;
+ font-weight: 600;
+ color: var(--ad-color-danger);
+}
+[role="status"] button[aria-label="Dismiss"] {
+ color: var(--ad-color-text-tertiary);
+ font-size: 1rem;
+ font-weight: 400;
+ line-height: 1;
+}
+[role="status"] button[aria-label="Dismiss"]:hover { color: var(--ad-color-text-primary); }
+
+/* Dialog — the native <dialog> element: a centered modal, flat surface, no
+ colored header, hairline dividers between title/body/footer. A <header>
+ holds the title and a <footer> holds the actions, matching the elements
+ the tag already wants; everything else in between is the body, styled by
+ excluding those two rather than by a class of its own.
+
+ `.dialog-demo` scopes a static, always-open example to its own preview
+ container instead of the viewport, so it can sit safely inside a
+ documentation page — this is a doc-only affordance, not part of Dialog's
+ contract. It relies on the browser's own dialog centering: the UA
+ stylesheet already gives an open <dialog> `position: absolute; inset: 0;
+ margin: auto`, which centers it inside the nearest positioned ancestor —
+ here, `.dialog-demo` itself. A real `.showModal()` call gets the genuine
+ viewport-fixed overlay and `::backdrop` for free; a plain `open` attribute
+ (used here so the demo renders without JavaScript) does not paint a
+ backdrop at all, since `::backdrop` only exists for a dialog shown
+ modally — wire the real open/close state and call `.showModal()` in the
+ consuming app. */
+.dialog-demo {
+ position: relative;
+ min-height: 20rem;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-lg);
+}
+
+dialog {
+ width: 26rem;
+ max-width: calc(100% - (2 * var(--ad-space-s)));
+ height: fit-content;
+ padding: 0;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ overflow: hidden;
+}
+dialog::backdrop {
+ background: rgba(20, 18, 14, 0.35);
+}
+dialog > header {
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-sans);
+ font-weight: 600;
+ font-size: var(--ad-step-0);
+ color: var(--ad-color-text-primary);
+}
+dialog > footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+}
+dialog > *:not(header):not(footer) {
+ padding: var(--ad-space-m);
+ margin: 0;
+ color: var(--ad-color-text-primary);
+ font-size: var(--ad-step--1);
+}
+
+/* Tabs
+ Follows the real WAI-ARIA tabs pattern: a `[role="tablist"]` of
+ `[role="tab"]` buttons controlling `[role="tabpanel"]`s. Those roles are
+ required for the pattern to be accessible in the first place, so they
+ double as the entire styling contract — no classes at all for the base
+ shape. The keyboard wiring lives in assets/js/system.js and is
+ variant-agnostic — it only cares about `[data-tabs]` / `[role=tab]`.
+
+ Two real variants: `underline` (mono, accent underline — primary section
+ nav, the default) and `segmented` (pill track, active segment lifts to the
+ surface — filters like All · Overdue · Upcoming, never primary
+ navigation), chosen with `data-variant="segmented"` on the tablist since
+ neither has a natural attribute of its own. */
+
+/* Every rule below the list container pairs [role="tablist"] with
+ [role="tab"] rather than styling [role="tab"] alone — Tab buttons are
+ still literal <button> elements, so the compound selector's extra
+ specificity is what keeps Button's base styling (button.css) from
+ winning the cascade on shared properties like padding and background. */
+
+[role="tablist"] {
+ display: flex;
+ gap: var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+[role="tablist"] [role="tab"] {
+ padding: 0.5rem 0;
+ margin-bottom: -1px;
+ background: none;
+ border: 0;
+ border-bottom: 2px solid transparent;
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--1);
+ letter-spacing: 0.02em;
+ cursor: pointer;
+ transition: color var(--ad-dur-fast) var(--ad-ease), border-color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"] [role="tab"]:hover { color: var(--ad-color-text-primary); }
+[role="tablist"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ border-bottom-color: var(--ad-color-accent);
+}
+
+[role="tablist"][data-variant="segmented"] {
+ display: inline-flex;
+ gap: 0.125rem;
+ padding: 0.1875rem;
+ border-bottom: 0;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-md);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"] {
+ all: unset;
+ cursor: pointer;
+ padding: 0.375rem 0.875rem;
+ border-radius: var(--ad-radius-sm);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 550;
+ color: var(--ad-color-text-secondary);
+ transition: background-color var(--ad-dur-fast) var(--ad-ease), color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ background: var(--ad-color-surface-card);
+ box-shadow: var(--ad-shadow-sm);
+}
+
+[role="tabpanel"] { padding-top: var(--ad-space-s); }
+[role="tabpanel"][hidden] { display: none; }
+[role="tabpanel"]:focus-visible { outline-offset: 4px; }
+
+/* Documentation site chrome. Everything below the `.ad-doc-` prefix belongs
+ to this site rather than to the design system it documents — including the
+ width/height constants used directly below. The source design system
+ defines no container-width tokens at all, so there is nothing under
+ `--ad-*` for a doc shell to reach for; the Space foundation page's own
+ notes say to omit that group entirely rather than invent one. */
+
+.ad-doc-skip {
+ position: absolute;
+ inset-inline-start: var(--ad-space-xs);
+ inset-block-start: -4rem;
+ z-index: 40;
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ box-shadow: var(--ad-shadow-float);
+ transition: inset-block-start var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-skip:focus { inset-block-start: var(--ad-space-2xs); }
+
+.ad-doc-header {
+ position: sticky;
+ inset-block-start: 0;
+ z-index: 30;
+ height: 3.5rem;
+ background: color-mix(in srgb, var(--ad-color-surface-page) 92%, transparent);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-header__inner {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ height: 100%;
+ max-width: 72rem;
+ margin-inline: auto;
+ padding-inline: var(--ad-space-xs);
+}
+
+.ad-doc-wordmark {
+ display: flex;
+ align-items: baseline;
+ gap: var(--ad-space-2xs);
+ color: var(--ad-color-text-primary);
+ text-decoration: none;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ font-weight: 600;
+ letter-spacing: -0.01em;
+ white-space: nowrap;
+}
+.ad-doc-wordmark:hover { color: var(--ad-color-text-primary); }
+.ad-doc-wordmark__mark {
+ width: 1.375rem;
+ height: 1.375rem;
+ flex: none;
+ align-self: center;
+ color: var(--ad-color-accent);
+}
+.ad-doc-wordmark__version {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+
+.ad-doc-header__actions { margin-inline-start: auto; display: flex; align-items: center; gap: var(--ad-space-2xs); }
+
+.ad-doc-layout {
+ display: grid;
+ grid-template-columns: 16rem minmax(0, 1fr);
+ gap: var(--ad-space-l);
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-m) var(--ad-space-xs) var(--ad-space-2xl);
+}
+
+.ad-doc-sidebar {
+ position: sticky;
+ inset-block-start: calc(3.5rem + var(--ad-space-m));
+ align-self: start;
+ max-height: calc(100vh - 3.5rem - var(--ad-space-l));
+ overflow-y: auto;
+ padding-inline-end: var(--ad-space-2xs);
+}
+.ad-doc-nav__group + .ad-doc-nav__group { margin-top: var(--ad-space-s); }
+.ad-doc-nav__heading {
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-doc-nav__link {
+ display: block;
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ margin-inline-start: calc(var(--ad-space-xs) * -1);
+ border-inline-start: 2px solid transparent;
+ border-radius: 0 var(--ad-radius-sm) var(--ad-radius-sm) 0;
+ color: var(--ad-color-text-secondary);
+ font-size: var(--ad-step--1);
+ text-decoration: none;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-nav__link:hover { color: var(--ad-color-text-primary); background: var(--ad-color-surface-fill); }
+.ad-doc-nav__link[aria-current="page"] {
+ color: var(--ad-color-accent-ink);
+ border-inline-start-color: var(--ad-color-accent);
+ background: var(--ad-color-accent-soft);
+ font-weight: 500;
+}
+
+.ad-doc-main {
+ min-width: 0;
+ /* The design system's own body-text token (--ad-step-0, 18–20px) is right
+ for a product screen but too large for long-form documentation prose.
+ This site's own copy gets a plain, fixed 16px instead — the token above
+ is unchanged, and specimen tables/demos below still show its real
+ value. */
+ font-size: 1rem;
+}
+.ad-doc-main > * { max-width: 50rem; }
+
+.ad-doc-title { margin-bottom: var(--ad-space-xs); }
+.ad-doc-title__section {
+ display: block;
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-accent);
+}
+
+.ad-doc-main h2 {
+ margin-block: var(--ad-space-2xl) var(--ad-space-xs);
+ padding-bottom: var(--ad-space-2xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-main h3 { margin-block: var(--ad-space-l) var(--ad-space-2xs); }
+.ad-doc-main h4 { margin-block: var(--ad-space-s) var(--ad-space-2xs); }
+.ad-doc-main p,
+.ad-doc-main ul,
+.ad-doc-main ol { max-width: 68ch; }
+.ad-doc-main ul:not([class]),
+.ad-doc-main ol:not([class]) { padding-inline-start: var(--ad-space-s); }
+
+.ad-doc-footer {
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-s) var(--ad-space-xs) var(--ad-space-l);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-tertiary);
+ font-size: var(--ad-step--1);
+}
+.ad-doc-footer__inner { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); justify-content: space-between; }
+
+/* `button.ad-doc-nav-toggle` (element + class) rather than a bare class:
+ button.css's own base rule is `button:not(.icon-button)`, which is also
+ one element + one class in specificity and sets `display: inline-flex` —
+ a bare `.ad-doc-nav-toggle { display: none }` loses that fight and the
+ toggle stays visible at every width. Matching its specificity here (and
+ coming later in the concatenated stylesheet) settles it in source order. */
+button.ad-doc-nav-toggle { display: none; }
+
+@media (max-width: 60rem) {
+ .ad-doc-layout { grid-template-columns: minmax(0, 1fr); gap: var(--ad-space-s); }
+ .ad-doc-sidebar {
+ position: static;
+ max-height: none;
+ }
+ /* The toggle is a normal Button; the panel it opens gets its own card
+ treatment. Putting the same card styling on the <aside> wrapper too
+ nested a button-shaped box inside an outer box for no reason. */
+ .ad-doc-nav {
+ margin-top: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ }
+ .ad-doc-sidebar[data-collapsed="true"] > .ad-doc-nav { display: none; }
+ button.ad-doc-nav-toggle {
+ display: flex;
+ width: 100%;
+ justify-content: space-between;
+ }
+}
+
+@media (max-width: 34rem) {
+ .ad-doc-wordmark__version { display: none; }
+ .ad-doc-layout { padding-inline: var(--ad-space-2xs); }
+}
+
+/* The live-example figure used throughout the docs — see the "How the
+ example demos work" section on the Using the CSS page. One class on the
+ outer <figure>; everything inside is styled by plain element selectors,
+ since figcaption/div/details/summary/pre already say what they are.
+ `data-layout` and `data-surface` on the preview <div> distinguish the
+ handful of demos that need a non-default arrangement. */
+
+.example {
+ margin: var(--ad-space-s) 0 var(--ad-space-l);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+ background: var(--ad-color-surface-card);
+ overflow: hidden;
+}
+
+.example figcaption {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-secondary);
+}
+
+.example > div {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-s);
+}
+/* nowrap is load-bearing: a column-direction flex container with wrap enabled
+ sizes its single line against the container rather than the content, which
+ left every stacked example with a few hundred pixels of dead space. */
+.example > div[data-layout="stack"] { flex-direction: column; flex-wrap: nowrap; align-items: stretch; }
+.example > div[data-layout="grid"] {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
+ align-items: start;
+}
+.example > div[data-surface="sunken"] { background: var(--ad-color-surface-fill); }
+.example > div > * { min-width: 0; }
+
+.example details {
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+.example summary {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ color: var(--ad-color-text-secondary);
+ cursor: pointer;
+ list-style: none;
+ user-select: none;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+}
+.example summary::-webkit-details-marker { display: none; }
+.example summary::before {
+ content: "";
+ width: 0.5rem;
+ height: 0.5rem;
+ border-inline-end: 1.5px solid currentColor;
+ border-block-end: 1.5px solid currentColor;
+ rotate: -45deg;
+ transition: rotate var(--ad-dur-fast) var(--ad-ease);
+}
+.example details[open] > summary::before { rotate: 45deg; }
+.example summary:hover { color: var(--ad-color-text-primary); }
+
+.example pre {
+ margin: 0;
+ padding: var(--ad-space-xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+ tab-size: 2;
+}
+.example pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Standalone fenced code blocks in the prose (unhighlighted — there is no
+ syntax highlighter in this build any more, just Kramdown's plain
+ <pre><code class="language-*">). */
+.ad-doc-main > pre {
+ margin-block: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+}
+.ad-doc-main > pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Presentation for the token reference tables and swatch grids, plus a
+ generic data-table utility. All of this is documentation chrome — the
+ design system itself has no Table component (see the note on the removed
+ `table`/`progress`/`callout` pages in git history). */
+
+.ad-swatch-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs) var(--ad-space-s);
+ padding: 0;
+ list-style: none;
+}
+.ad-swatch {
+ display: flex;
+ flex-direction: column;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ overflow: hidden;
+ background: var(--ad-color-surface-card);
+}
+.ad-swatch__chip {
+ display: flex;
+ align-items: flex-end;
+ justify-content: flex-end;
+ height: 4rem;
+ padding: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+}
+.ad-swatch__meta {
+ display: flex;
+ flex-direction: column;
+ gap: 0.1rem;
+ padding: var(--ad-space-2xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+}
+.ad-swatch__hex { color: var(--ad-color-text-primary); text-transform: uppercase; }
+.ad-swatch__ratio { color: var(--ad-color-text-tertiary); }
+
+.ad-alias-row { display: flex; align-items: center; gap: var(--ad-space-2xs); }
+.ad-alias-chip {
+ width: 1.5rem;
+ height: 1.5rem;
+ flex: none;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+}
+.ad-alias-chip--pair { display: flex; }
+.ad-alias-chip--pair > span { flex: 1; }
+
+/* Type specimen rows. */
+.ad-specimen {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ gap: var(--ad-space-3xs);
+ padding-block: var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-specimen__meta {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-specimen__sample {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: var(--ad-color-text-primary);
+}
+
+/* Space and radius rulers. */
+.ad-ruler { display: flex; flex-direction: column; gap: var(--ad-space-2xs); }
+.ad-ruler__row { display: grid; grid-template-columns: 6.5rem 4.5rem 1fr; align-items: center; gap: var(--ad-space-xs); font-family: var(--ad-font-mono); font-size: 11px; }
+.ad-ruler__bar { height: 0.75rem; background: var(--ad-color-accent); border-radius: var(--ad-radius-sm); }
+.ad-ruler__num { color: var(--ad-color-text-tertiary); text-align: right; }
+
+.ad-radius-grid, .ad-elevation-grid, .ad-motion-grid, .ad-accent-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs);
+ padding: 0;
+ list-style: none;
+}
+.ad-radius-demo {
+ height: 4.5rem;
+ background: var(--ad-color-accent-soft);
+ border: 1px solid var(--ad-color-accent);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-elevation-demo {
+ height: 4.5rem;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-motion-demo {
+ height: 4.5rem;
+ display: grid;
+ place-items: center;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-md);
+ margin-bottom: var(--ad-space-2xs);
+ cursor: pointer;
+ overflow: hidden;
+}
+.ad-motion-demo__dot {
+ width: 1.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent);
+ translate: -3rem 0;
+ transition-property: translate;
+ transition-duration: var(--ad-dur-base);
+ transition-timing-function: var(--ad-ease);
+}
+.ad-motion-demo:hover .ad-motion-demo__dot,
+.ad-motion-demo:focus-visible .ad-motion-demo__dot { translate: 3rem 0; }
+
+.ad-accent-demo {
+ height: 4.5rem;
+ display: flex;
+ align-items: flex-end;
+ padding: var(--ad-space-2xs);
+ border-radius: var(--ad-radius-md);
+ color: #fff;
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ margin-bottom: var(--ad-space-2xs);
+}
+
+/* Token names read `<code>` sitewide, which already carries a mono chip
+ look for running prose (see :not(pre) > code in base/typography.css).
+ Inside these specimen/swatch/ruler rows the old `.ad-token-name` class was
+ plain mono text with no chip, so `<code>` is flattened back to that look
+ here the same way table cells are. */
+.ad-swatch code, .ad-specimen code, .ad-ruler code, .ad-alias-row code {
+ background: none;
+ border: 0;
+ padding: 0;
+ font-size: var(--ad-step--2);
+ color: var(--ad-color-text-primary);
+ white-space: nowrap;
+}
+
+/* A documentation aside. Like `<table>`, this is site chrome for the prose
+ itself, not a system component — the real system has no Callout. */
+.ad-callout {
+ margin-block: var(--ad-space-s);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-left: 3px solid var(--ad-color-accent);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+}
+/* Generic reference tables (component APIs, alias tables, the contrast
+ contract). `<table>` IS a table — no class needed to say so. Every table
+ on this site uses the one compact density, so there is no separate
+ modifier for it. `display: block` plus `overflow-x: auto` gives the table
+ its own horizontal scrollbar on narrow viewports without a wrapper div;
+ the anonymous table box the browser generates around `<thead>`/`<tbody>`
+ still does the row/column layout. `<code>` replaces the old
+ `.ad-token-name` / `.ad-table__code` cells — it is already styled
+ sitewide, just re-flattened here to a plain mono run instead of the
+ chip-with-border look prose code gets, to match this table's original
+ look. `.num` is the one sanctioned class left, for right-aligned numeric
+ columns — there is no attribute that means "this column is a number". */
+table { width: 100%; border-collapse: collapse; font-size: var(--ad-step--1); display: block; overflow-x: auto; }
+table th, table td {
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ text-align: left;
+ vertical-align: top;
+}
+table th { font-family: var(--ad-font-mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ad-color-text-secondary); font-weight: 500; }
+table code { background: none; border: 0; padding: 0; font-size: var(--ad-step--2); white-space: nowrap; }
+.num { text-align: right; font-variant-numeric: tabular-nums; }
+
+/* Home page. */
+.ad-hero { padding-block: var(--ad-space-m) var(--ad-space-l); }
+.ad-hero__title {
+ font-size: var(--ad-step-4);
+ margin-bottom: var(--ad-space-xs);
+}
+.ad-hero__actions { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); margin-top: var(--ad-space-s); }
+.ad-stat-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-l);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+}
+.ad-stat__value {
+ display: block;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-2);
+ font-weight: 600;
+ line-height: 1.16;
+ font-variant-numeric: tabular-nums;
+}
+.ad-stat__label {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+
+/* Card chrome. The pane supplies the frame; this just insets the page. */
+body { padding: var(--ad-space-l) var(--ad-space-m); }
+.ad-doc-main { max-width: 50rem; margin-inline: auto; }
+.ad-doc-main > * { max-width: none; }
+</style>
+</head>
+<body>
+<main class="ad-doc-main">
+<header class="ad-doc-title"><span class="ad-doc-title__section">Foundations</span>
+ <h1>Space</h1>
+</header>
+<p class="ad-lead">Nine fluid Utopia steps, 3xs through 3xl — the same clamp() config as type, because there is no fixed base unit here to count multiples of.</p>
+<h2 id="the-scale">The scale</h2>
+
+<div class="ad-ruler">
+ <div class="ad-ruler__row">
+ <code>space-3xs</code>
+ <span class="ad-ruler__num">5px</span>
+ <span class="ad-ruler__bar" style="width: clamp(0.3125rem, 0.3125rem + 0vw, 0.3125rem); min-width: 1px;"></span>
+ </div>
+ <div class="ad-ruler__row">
+ <code>space-2xs</code>
+ <span class="ad-ruler__num">9 → 10px</span>
+ <span class="ad-ruler__bar" style="width: clamp(0.5625rem, 0.5369rem + 0.1136vw, 0.625rem); min-width: 1px;"></span>
+ </div>
+ <div class="ad-ruler__row">
+ <code>space-xs</code>
+ <span class="ad-ruler__num">14 → 15px</span>
+ <span class="ad-ruler__bar" style="width: clamp(0.875rem, 0.8494rem + 0.1136vw, 0.9375rem); min-width: 1px;"></span>
+ </div>
+ <div class="ad-ruler__row">
+ <code>space-s</code>
+ <span class="ad-ruler__num">18 → 20px</span>
+ <span class="ad-ruler__bar" style="width: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem); min-width: 1px;"></span>
+ </div>
+ <div class="ad-ruler__row">
+ <code>space-m</code>
+ <span class="ad-ruler__num">27 → 30px</span>
+ <span class="ad-ruler__bar" style="width: clamp(1.6875rem, 1.6108rem + 0.3409vw, 1.875rem); min-width: 1px;"></span>
+ </div>
+ <div class="ad-ruler__row">
+ <code>space-l</code>
+ <span class="ad-ruler__num">36 → 40px</span>
+ <span class="ad-ruler__bar" style="width: clamp(2.25rem, 2.1477rem + 0.4545vw, 2.5rem); min-width: 1px;"></span>
+ </div>
+ <div class="ad-ruler__row">
+ <code>space-xl</code>
+ <span class="ad-ruler__num">54 → 60px</span>
+ <span class="ad-ruler__bar" style="width: clamp(3.375rem, 3.2216rem + 0.6818vw, 3.75rem); min-width: 1px;"></span>
+ </div>
+ <div class="ad-ruler__row">
+ <code>space-2xl</code>
+ <span class="ad-ruler__num">72 → 80px</span>
+ <span class="ad-ruler__bar" style="width: clamp(4.5rem, 4.2955rem + 0.9091vw, 5rem); min-width: 1px;"></span>
+ </div>
+ <div class="ad-ruler__row">
+ <code>space-3xl</code>
+ <span class="ad-ruler__num">108 → 120px</span>
+ <span class="ad-ruler__bar" style="width: clamp(6.75rem, 6.4432rem + 1.3636vw, 7.5rem); min-width: 1px;"></span>
+ </div>
+</div>
+
+<h2 id="how-to-pick-one">How to pick one</h2>
+
+<table>
+ <thead>
+ <tr>
+ <th>Distance between</th>
+ <th>Reach for</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>An icon and its own label</td>
+ <td><code>--ad-space-3xs</code> / <code>--ad-space-2xs</code></td>
+ </tr>
+ <tr>
+ <td>A field’s label and its control</td>
+ <td><code>--ad-space-2xs</code></td>
+ </tr>
+ <tr>
+ <td>Two controls in a row, CardRow padding</td>
+ <td><code>--ad-space-xs</code></td>
+ </tr>
+ <tr>
+ <td>Two fields in a form, a Dialog’s title/footer padding</td>
+ <td><code>--ad-space-s</code></td>
+ </tr>
+ <tr>
+ <td>Card body padding, a Dialog’s body padding</td>
+ <td><code>--ad-space-m</code></td>
+ </tr>
+ <tr>
+ <td>Two sections of a page</td>
+ <td><code>--ad-space-l</code></td>
+ </tr>
+ <tr>
+ <td>Major section spacing</td>
+ <td><code>--ad-space-xl</code> / <code>--ad-space-2xl</code></td>
+ </tr>
+ <tr>
+ <td>Hero and empty-state breathing room</td>
+ <td><code>--ad-space-3xl</code></td>
+ </tr>
+ </tbody>
+</table>
+
+<figure class="example">
+<figcaption>The same card at three densities</figcaption>
+<div data-layout="grid">
+<article class="card">
+ <div class="card-body" style="gap: var(--ad-space-3xs); padding: var(--ad-space-xs);">
+ <h3 style="margin:0; font: 600 var(--ad-step--1)/1.3 var(--ad-font-sans);">Compact</h3>
+ <p style="margin:0; color: var(--ad-color-text-secondary); font-size: var(--ad-step--1);">space-xs padding, space-3xs gap. For dense lists.</p>
+ </div>
+</article>
+<article class="card">
+ <div class="card-body">
+ <h3 style="margin:0; font: 600 var(--ad-step--1)/1.3 var(--ad-font-sans);">Default</h3>
+ <p style="margin:0; color: var(--ad-color-text-secondary); font-size: var(--ad-step--1);">space-m padding, space-2xs gap. What Card ships with.</p>
+ </div>
+</article>
+<article class="card">
+ <div class="card-body" style="gap: var(--ad-space-s); padding: var(--ad-space-l);">
+ <h3 style="margin:0; font: 600 var(--ad-step--1)/1.3 var(--ad-font-sans);">Roomy</h3>
+ <p style="margin:0; color: var(--ad-color-text-secondary); font-size: var(--ad-step--1);">space-l padding, space-s gap. For a card that is the whole screen.</p>
+ </div>
+</article>
+</div>
+<details><summary>Markup</summary>
+<pre><code><article class="card">
+ <div class="card-body" style="gap: var(--ad-space-3xs); padding: var(--ad-space-xs);">
+ <h3>Compact</h3>
+ <p>space-xs padding, space-3xs gap. For dense lists.</p>
+ </div>
+</article>
+<article class="card">
+ <div class="card-body">
+ <h3>Default</h3>
+ <p>space-m padding, space-2xs gap. What Card ships with.</p>
+ </div>
+</article>
+<article class="card">
+ <div class="card-body" style="gap: var(--ad-space-s); padding: var(--ad-space-l);">
+ <h3>Roomy</h3>
+ <p>space-l padding, space-s gap. For a card that is the whole screen.</p>
+ </div>
+</article></code></pre>
+</details>
+</figure>
+
+<h2 id="radius">Radius</h2>
+
+<p>4/6/10px — small and quiet, kept modest so it reads as a neutral default
+rather than any one source project’s specific softness. Nothing pill-shaped
+except true pills.</p>
+
+<ul class="ad-radius-grid">
+ <li>
+ <div class="ad-radius-demo" style="border-radius: 4px;"></div>
+ <div class="ad-specimen__meta">
+ <code>radius-sm</code>
+ <span>4px</span>
+ </div>
+ <p class="ad-muted" style="font-size: var(--ad-step--2); margin: var(--ad-space-3xs) 0 0;">Buttons, inputs, badges, tags.</p>
+ </li>
+ <li>
+ <div class="ad-radius-demo" style="border-radius: 6px;"></div>
+ <div class="ad-specimen__meta">
+ <code>radius-md</code>
+ <span>6px</span>
+ </div>
+ <p class="ad-muted" style="font-size: var(--ad-step--2); margin: var(--ad-space-3xs) 0 0;">IconButton, the segmented-tabs track, avatar chips.</p>
+ </li>
+ <li>
+ <div class="ad-radius-demo" style="border-radius: 10px;"></div>
+ <div class="ad-specimen__meta">
+ <code>radius-lg</code>
+ <span>10px</span>
+ </div>
+ <p class="ad-muted" style="font-size: var(--ad-step--2); margin: var(--ad-space-3xs) 0 0;">Cards, dialogs, toasts, photo slots — the system's "surface" radius.</p>
+ </li>
+ <li>
+ <div class="ad-radius-demo" style="border-radius: 999px;"></div>
+ <div class="ad-specimen__meta">
+ <code>radius-full</code>
+ <span>999px</span>
+ </div>
+ <p class="ad-muted" style="font-size: var(--ad-step--2); margin: var(--ad-space-3xs) 0 0;">True pills only — Badge, Tag, Switch, round avatars.</p>
+ </li>
+</ul>
+
+<h2 id="rules">Rules</h2>
+
+<ul>
+ <li><strong>Space comes from the parent.</strong> A component sets its own internal padding
+and never its external margin; the layout that contains it owns the gap.</li>
+ <li><strong>Use <code>gap</code>.</strong> Flex and grid gaps do not collapse and do not need a
+<code>:last-child</code> reset.</li>
+ <li><strong>A value not on the scale is a bug</strong>, not a nudge. If a gap looks wrong,
+the scale is wrong and should change for everybody.</li>
+</ul>
+</main>
+<script>
+// Behavior for the pieces of the system that need it. Everything else is
+// CSS and semantic HTML on purpose — a design system that only works with
+// JavaScript running is a component library with extra steps.
+(function () {
+ "use strict";
+
+ var STORAGE_KEY = "ad-theme";
+
+ function readStored() {
+ try {
+ return localStorage.getItem(STORAGE_KEY);
+ } catch (error) {
+ return null;
+ }
+ }
+
+ function writeStored(value) {
+ try {
+ localStorage.setItem(STORAGE_KEY, value);
+ } catch (error) {
+ /* Nothing to do — the media query still gives a sensible default. */
+ }
+ }
+
+ function systemPrefersDark() {
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
+ }
+
+ function currentTheme() {
+ var explicit = document.documentElement.getAttribute("data-theme");
+ if (explicit) return explicit;
+ return systemPrefersDark() ? "dark" : "light";
+ }
+
+ function paintToggle(theme) {
+ document.querySelectorAll("[data-theme-toggle]").forEach(function (toggle) {
+ var next = theme === "dark" ? "light" : "dark";
+ toggle.querySelectorAll("[data-theme-icon]").forEach(function (icon) {
+ icon.hidden = icon.dataset.themeIcon !== theme;
+ });
+ toggle.setAttribute("aria-label", "Switch to the " + next + " theme");
+ toggle.setAttribute("title", "Switch to the " + next + " theme");
+ var label = toggle.querySelector("[data-theme-label]");
+ if (label) label.textContent = "Switch to the " + next + " theme";
+ });
+ }
+
+ function setUpTheme() {
+ paintToggle(currentTheme());
+
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-theme-toggle]");
+ if (!toggle) return;
+
+ var next = currentTheme() === "dark" ? "light" : "dark";
+ document.documentElement.setAttribute("data-theme", next);
+ writeStored(next);
+ paintToggle(next);
+ });
+
+ // Follow the OS while the reader has not made an explicit choice.
+ window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", function () {
+ if (!readStored()) paintToggle(currentTheme());
+ });
+ }
+
+ // Tabs, wired to the WAI-ARIA authoring practices: arrows move, Home/End
+ // jump, and only the selected tab stays in the tab order.
+ function setUpTabs() {
+ document.querySelectorAll("[data-tabs]").forEach(function (root) {
+ var tabs = Array.prototype.slice.call(root.querySelectorAll('[role="tab"]'));
+ if (!tabs.length) return;
+
+ function select(tab, focus) {
+ tabs.forEach(function (candidate) {
+ var selected = candidate === tab;
+ candidate.setAttribute("aria-selected", String(selected));
+ candidate.tabIndex = selected ? 0 : -1;
+ var panel = document.getElementById(candidate.getAttribute("aria-controls"));
+ if (panel) panel.hidden = !selected;
+ });
+ if (focus) tab.focus();
+ }
+
+ root.addEventListener("click", function (event) {
+ var tab = event.target.closest('[role="tab"]');
+ if (tab) select(tab, false);
+ });
+
+ root.addEventListener("keydown", function (event) {
+ var index = tabs.indexOf(document.activeElement);
+ if (index === -1) return;
+
+ var next = null;
+ if (event.key === "ArrowRight") next = tabs[(index + 1) % tabs.length];
+ else if (event.key === "ArrowLeft") next = tabs[(index - 1 + tabs.length) % tabs.length];
+ else if (event.key === "Home") next = tabs[0];
+ else if (event.key === "End") next = tabs[tabs.length - 1];
+
+ if (next) {
+ event.preventDefault();
+ select(next, true);
+ }
+ });
+
+ select(tabs.filter(function (tab) {
+ return tab.getAttribute("aria-selected") === "true";
+ })[0] || tabs[0], false);
+ });
+ }
+
+ function setUpNavToggle() {
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-nav-toggle]");
+ if (!toggle) return;
+
+ var sidebar = toggle.closest("[data-sidebar]");
+ var collapsed = sidebar.dataset.collapsed !== "false";
+ sidebar.dataset.collapsed = String(!collapsed);
+ toggle.setAttribute("aria-expanded", String(collapsed));
+ });
+ }
+
+ setUpTheme();
+ setUpTabs();
+ setUpNavToggle();
+})();
+
+</script>
+</body>
+</html>
diff --git a/design-bundle/foundations/typography.html b/design-bundle/foundations/typography.html
new file mode 100644
index 0000000..cfa620b
--- /dev/null
+++ b/design-bundle/foundations/typography.html
@@ -0,0 +1,1987 @@
+<!-- @dsCard group="Foundations" -->
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Typography</title>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&display=swap">
+<style>
+/* Arbitrary Definitions — design tokens. Hand-maintained; edit values directly. */
+
+:root {
+ /* Palette ramps — reference only. Components use the aliases below. */
+ --ad-neutral-0: #faf9f7;
+ --ad-neutral-50: #fdfdfb;
+ --ad-neutral-100: #f1f0ed;
+ --ad-neutral-150: #e8e6e2;
+ --ad-neutral-200: #dcdad7;
+ --ad-neutral-300: #c0bdb9;
+ --ad-neutral-500: #888681;
+ --ad-neutral-600: #5f5d59;
+ --ad-neutral-800: #2a2926;
+ --ad-neutral-900: #100f0d;
+ --ad-neutral-dark-0: #0e0d0b;
+ --ad-neutral-dark-50: #151412;
+ --ad-neutral-dark-100: #1e1d1a;
+ --ad-neutral-dark-150: #252421;
+ --ad-neutral-dark-200: #2c2b29;
+ --ad-neutral-dark-300: #44423f;
+ --ad-neutral-dark-500: #7c7a76;
+ --ad-neutral-dark-600: #a09e9a;
+ --ad-neutral-dark-800: #d9d7d4;
+ --ad-neutral-dark-900: #e9e8e6;
+ --ad-accent-teal: #3a6a63;
+ --ad-accent-clay: #9a5a3c;
+ --ad-accent-ink-blue: #2b4a78;
+ --ad-accent-ochre: #93701f;
+ --ad-accent-plum: #6a3a5c;
+
+ /* Surfaces */
+ --ad-color-surface-page: #faf9f7;
+ --ad-color-surface-card: #fdfdfb;
+ --ad-color-surface-fill: #f1f0ed;
+ --ad-color-surface-fill-2: #e8e6e2;
+
+ /* Borders */
+ --ad-color-border-hairline: #dcdad7;
+ --ad-color-border-default: #c0bdb9;
+
+ /* Text */
+ --ad-color-text-primary: #100f0d;
+ --ad-color-text-secondary: #5f5d59;
+ --ad-color-text-tertiary: #888681;
+
+ /* Accent */
+ --ad-color-accent: #3a6a63;
+ --ad-color-accent-fg: #ffffff;
+ --ad-color-accent-ink: color-mix(in oklch, var(--ad-color-accent) 78%, black);
+ --ad-color-accent-soft: color-mix(in oklch, var(--ad-color-accent) 12%, white);
+
+ /* Status (fixed — never swapped like the accent) */
+ --ad-color-success: #3f7a4a;
+ --ad-color-success-bg: color-mix(in oklch, var(--ad-color-success) 12%, white);
+ --ad-color-danger: #ad3a2c;
+ --ad-color-danger-bg: color-mix(in oklch, var(--ad-color-danger) 10%, white);
+
+ /* Type families */
+ --ad-font-sans: -apple-system, "Helvetica Neue", Helvetica, Arial, system-ui, sans-serif;
+ --ad-font-mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, Consolas, "Courier New", monospace;
+ --ad-font-serif: Georgia, "Times New Roman", "Iowan Old Style", serif;
+
+ /* Type steps — fluid (Utopia scale), the design system's own body-text size */
+ --ad-step--2: clamp(0.7813rem, 0.7736rem + 0.0341vw, 0.8rem);
+ --ad-step--1: clamp(0.9375rem, 0.9119rem + 0.1136vw, 1rem);
+ --ad-step-0: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-step-1: clamp(1.35rem, 1.2631rem + 0.3864vw, 1.5625rem);
+ --ad-step-2: clamp(1.62rem, 1.4837rem + 0.6057vw, 1.9531rem);
+ --ad-step-3: clamp(1.944rem, 1.7405rem + 0.9044vw, 2.4414rem);
+ --ad-step-4: clamp(2.3328rem, 2.0387rem + 1.3072vw, 3.0518rem);
+ --ad-step-5: clamp(2.7994rem, 2.384rem + 1.8461vw, 3.8147rem);
+
+ /* Space */
+ --ad-space-3xs: clamp(0.3125rem, 0.3125rem + 0vw, 0.3125rem);
+ --ad-space-2xs: clamp(0.5625rem, 0.5369rem + 0.1136vw, 0.625rem);
+ --ad-space-xs: clamp(0.875rem, 0.8494rem + 0.1136vw, 0.9375rem);
+ --ad-space-s: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-space-m: clamp(1.6875rem, 1.6108rem + 0.3409vw, 1.875rem);
+ --ad-space-l: clamp(2.25rem, 2.1477rem + 0.4545vw, 2.5rem);
+ --ad-space-xl: clamp(3.375rem, 3.2216rem + 0.6818vw, 3.75rem);
+ --ad-space-2xl: clamp(4.5rem, 4.2955rem + 0.9091vw, 5rem);
+ --ad-space-3xl: clamp(6.75rem, 6.4432rem + 1.3636vw, 7.5rem);
+
+ /* Radius */
+ --ad-radius-sm: 4px;
+ --ad-radius-md: 6px;
+ --ad-radius-lg: 10px;
+ --ad-radius-full: 999px;
+ --ad-border-w: 1px;
+
+ /* Elevation */
+ --ad-shadow-sm: 0 1px 2px rgba(20, 18, 14, 0.05);
+ --ad-shadow-float: 0 2px 6px rgba(20, 18, 14, 0.06), 0 10px 24px -12px rgba(20, 18, 14, 0.18);
+
+ /* Motion */
+ --ad-dur-fast: 0.12s;
+ --ad-dur-base: 0.16s;
+ --ad-ease: cubic-bezier(0.2, 0, 0, 1);
+}
+
+@media (prefers-color-scheme: dark) {
+ :root:not([data-theme="light"]) {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+ }
+}
+
+:root[data-theme="dark"] {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ :root {
+ --ad-dur-fast: 0ms;
+ --ad-dur-base: 0ms;
+ }
+}
+
+/* A short reset. Everything it sets is something the system would otherwise
+ have to override in every component.
+
+ The rem values used for this documentation site's own chrome (measure,
+ content width, sidebar width, header height) belong to this site, not to
+ the design system it documents — the source design system defines no
+ container-width tokens at all, so there is nothing under `--ad-*` to reach
+ for here. See the header comment in site/shell.css. */
+
+*,
+*::before,
+*::after { box-sizing: border-box; }
+
+html {
+ -webkit-text-size-adjust: 100%;
+ scroll-behavior: smooth;
+ scroll-padding-top: calc(3.5rem + var(--ad-space-s));
+}
+
+@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;
+ }
+}
+
+body {
+ margin: 0;
+ background: var(--ad-color-surface-page);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ line-height: 1.55;
+ font-synthesis-weight: none;
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+}
+
+::selection {
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+}
+
+h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd { margin: 0; }
+ul[class], ol[class] { list-style: none; margin: 0; padding: 0; }
+
+img, svg, video { display: block; max-width: 100%; height: auto; }
+
+button, input, select, textarea {
+ font: inherit;
+ color: inherit;
+}
+
+hr {
+ height: 1px;
+ margin: var(--ad-space-l) 0;
+ border: 0;
+ background: var(--ad-color-border-hairline);
+}
+
+/* One focus ring, defined once. No component is allowed its own. */
+:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+ border-radius: var(--ad-radius-sm);
+}
+:focus:not(:focus-visible) { outline: none; }
+
+[hidden] { display: none !important; }
+
+.ad-visually-hidden {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip-path: inset(50%);
+ white-space: nowrap;
+ border: 0;
+}
+
+/* Every rule here reads a type token for family and size. Weight, line-height,
+ and letter-spacing are hardcoded to the exact numbers the source ships —
+ there is no `--ad-weight-*` / `--ad-leading-*` / `--ad-tracking-*` scale in
+ the real system, only eight fixed compound styles (see the Typography
+ foundation page, which this file implements verbatim). */
+
+.type-display { font: 600 var(--ad-step-4)/1.08 var(--ad-font-sans); letter-spacing: -0.015em; }
+.type-h1, h1 { font: 600 var(--ad-step-3)/1.12 var(--ad-font-sans); letter-spacing: -0.012em; }
+.type-h2, h2 { font: 600 var(--ad-step-2)/1.16 var(--ad-font-sans); letter-spacing: -0.01em; }
+.type-h3, h3 { font: 600 var(--ad-step-1)/1.25 var(--ad-font-sans); }
+.type-body { font: 400 var(--ad-step-0)/1.55 var(--ad-font-sans); }
+.type-body-sm { font: 400 var(--ad-step--1)/1.5 var(--ad-font-sans); }
+.type-label {
+ font: 500 var(--ad-step--2)/1 var(--ad-font-mono);
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: var(--ad-color-text-secondary);
+}
+.type-mono { font: 400 var(--ad-step--1)/1.5 var(--ad-font-mono); }
+
+/* Prose defaults for the documentation body copy. These read the same
+ tokens the compound classes above do; they are not a second scale. */
+h1, h2, h3, h4 { font-weight: 600; text-wrap: balance; }
+
+h4 { font: 600 var(--ad-step-0)/1.3 var(--ad-font-sans); }
+
+p, ul, ol { margin: 0 0 var(--ad-space-s); }
+li + li { margin-top: var(--ad-space-3xs); }
+
+a {
+ color: var(--ad-color-accent);
+ text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
+ text-underline-offset: 0.18em;
+ text-decoration-thickness: 1px;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ text-decoration-color var(--ad-dur-fast) var(--ad-ease);
+}
+a:hover {
+ color: var(--ad-color-accent-ink);
+ text-decoration-color: currentColor;
+}
+
+strong, b { font-weight: 600; }
+
+small { font-size: var(--ad-step--1); }
+
+.ad-lead {
+ font-size: var(--ad-step-1);
+ line-height: 1.5;
+ color: var(--ad-color-text-secondary);
+ max-width: 68ch;
+ text-wrap: pretty;
+}
+
+.ad-muted { color: var(--ad-color-text-secondary); }
+.ad-subtle { color: var(--ad-color-text-tertiary); }
+
+code, kbd, samp, pre {
+ font-family: var(--ad-font-mono);
+ font-feature-settings: "liga" 0;
+}
+
+:not(pre) > code {
+ padding: 0.15em 0.4em;
+ font-size: 0.875em;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-sm);
+ white-space: nowrap;
+}
+
+kbd {
+ display: inline-block;
+ padding: 0.1em 0.45em;
+ font-size: var(--ad-step--2);
+ line-height: 1.6;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-bottom-width: 2px;
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-secondary);
+}
+
+blockquote {
+ margin-block: var(--ad-space-s);
+ padding-left: var(--ad-space-s);
+ border-left: 2px solid var(--ad-color-accent);
+ color: var(--ad-color-text-secondary);
+}
+
+/* Button
+ A flat, quiet button that recedes rather than performs — no gradients, no
+ shine. Variants: default, primary, dark, ghost, danger. Sizes: sm, md
+ (default), lg — padding and font step only, weight and radius stay put.
+
+ The contract is the element itself: a bare <button> is styled directly, no
+ class required. An <a> that acts like a button (navigates rather than
+ changes something — see the Button page's "Links that act like buttons")
+ opts in with a single `.btn` class, since a bare `a` selector would catch
+ every ordinary prose link on the site. `:not(.icon-button)` keeps this
+ block from leaking onto IconButton, which shares the <button> tag but is
+ an entirely different shape. */
+
+button:not(.icon-button),
+a.btn {
+ --_bg: var(--ad-color-surface-card);
+ --_fg: var(--ad-color-text-primary);
+ --_border: var(--ad-color-border-default);
+ --_pad: var(--ad-space-xs) var(--ad-space-s);
+ --_font: var(--ad-step--1);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: var(--ad-space-2xs);
+ padding: var(--_pad);
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-sans);
+ font-size: var(--_font);
+ font-weight: 550;
+ letter-spacing: -0.01em;
+ line-height: 1.2;
+ text-align: center;
+ text-decoration: none;
+ white-space: nowrap;
+ cursor: pointer;
+ transition: filter var(--ad-dur-fast) var(--ad-ease),
+ border-color var(--ad-dur-fast) var(--ad-ease);
+}
+
+:is(button:not(.icon-button), a.btn):hover:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.96); }
+:is(button:not(.icon-button), a.btn):active:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.92); }
+
+button:not(.icon-button):disabled,
+:is(button:not(.icon-button), a.btn)[aria-disabled="true"] {
+ opacity: 0.5;
+ cursor: default;
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="primary"] {
+ --_bg: var(--ad-color-accent);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-accent);
+}
+
+/* Reserved for the single most-committing action in a view — a final Save,
+ never a whole row of them. */
+:is(button:not(.icon-button), a.btn)[data-variant="dark"] {
+ --_bg: var(--ad-color-text-primary);
+ --_fg: var(--ad-color-surface-page);
+ --_border: var(--ad-color-text-primary);
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="ghost"] {
+ --_bg: transparent;
+ --_fg: var(--ad-color-text-primary);
+ --_border: transparent;
+}
+
+/* The source hardcodes this label white rather than deriving it — danger
+ is not "on a filled accent surface", so --ad-color-accent-fg is reused
+ here only because its resolved value is the same literal white, not
+ because the two roles are the same thing. */
+:is(button:not(.icon-button), a.btn)[data-variant="danger"] {
+ --_bg: var(--ad-color-danger);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-danger);
+}
+
+:is(button:not(.icon-button), a.btn)[data-size="sm"] { --_pad: 0.375rem 0.75rem; --_font: 0.8125rem; }
+:is(button:not(.icon-button), a.btn)[data-size="lg"] { --_pad: 0.75rem 1.375rem; --_font: var(--ad-step-0); }
+
+:is(button:not(.icon-button), a.btn)[data-block] { width: 100%; }
+
+/* Button group — [role="group"] is already the correct ARIA role for a
+ toolbar of related buttons, so it doubles as the styling hook for free. */
+[role="group"] {
+ display: inline-flex;
+ isolation: isolate;
+}
+[role="group"] > button { border-radius: 0; }
+[role="group"] > button:first-child { border-start-start-radius: var(--ad-radius-md); border-end-start-radius: var(--ad-radius-md); }
+[role="group"] > button:last-child { border-start-end-radius: var(--ad-radius-md); border-end-end-radius: var(--ad-radius-md); }
+[role="group"] > button + button { margin-inline-start: -1px; }
+[role="group"] > button:hover,
+[role="group"] > button:focus-visible { z-index: 1; }
+
+/* IconButton — a square icon-only button. Always needs an aria-label; it
+ becomes both the accessible name and the native tooltip. There is no
+ attribute that distinguishes "a button that is only an icon" from a plain
+ Button, so this is the one sanctioned class in this file. */
+.icon-button {
+ --_size: 2.125rem;
+ --_bg: transparent;
+ --_border: transparent;
+ --_fg: var(--ad-color-text-secondary);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ padding: 0;
+ flex: none;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease),
+ color var(--ad-dur-fast) var(--ad-ease);
+}
+.icon-button:hover:not(:disabled) { background: var(--ad-color-surface-fill-2); }
+.icon-button:disabled { opacity: 0.5; cursor: default; }
+
+.icon-button[data-size="sm"] { --_size: 1.75rem; }
+.icon-button[data-size="lg"] { --_size: 2.5rem; }
+
+.icon-button[data-variant="outline"] { --_bg: var(--ad-color-surface-card); --_border: var(--ad-color-border-default); --_fg: var(--ad-color-text-primary); }
+.icon-button[data-variant="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); }
+
+/* Field
+ The labeled field wrapper — a <label> that wraps its own caption text
+ together with the control, so the association is native and needs no
+ `for`/`id` pair. The caption is mono, uppercase, tracked — the same
+ catalog-label convention as Badge and type-label.
+
+ A <label> is reused for three different shapes across this system (this
+ wrapper, Checkbox/Radio's row, Switch's row), so it cannot be styled bare
+ without one shape leaking into another. Field is the one of the three with
+ no distinguishing attribute of its own, so it gets the sanctioned class;
+ Checkbox/Radio tell themselves apart structurally instead (see below). */
+.field {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ min-width: 0;
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Hint and error read the native fine-print element. `role="alert"` is a
+ real live-region role for validation text, so it doubles as the selector
+ that turns a hint red. */
+.field > small {
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+.field > small[role="alert"] { color: var(--ad-color-danger); }
+
+/* Input / Select — bordered, matched to each other exactly. Both tags ARE
+ the component, so they are styled bare; nothing else on this site uses a
+ text input or a select. */
+input:not([type="checkbox"]):not([type="radio"]),
+select {
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ outline: none;
+ transition: border-color var(--ad-dur-fast) var(--ad-ease),
+ box-shadow var(--ad-dur-fast) var(--ad-ease);
+}
+input::placeholder { color: var(--ad-color-text-tertiary); }
+
+input:not([type="checkbox"]):not([type="radio"]):focus,
+select:focus {
+ border-color: var(--ad-color-accent);
+ box-shadow: 0 0 0 2px var(--ad-color-accent-soft);
+}
+
+input:not([type="checkbox"]):not([type="radio"]):disabled,
+select:disabled {
+ opacity: 0.5;
+ cursor: default;
+}
+
+/* The chevron is the one flagged color exception in this component set:
+ CSS cannot point a data-URI background-image at a custom property, so its
+ stroke is a literal hex rather than a var(--ad-*) reference. It is picked
+ to match --ad-color-text-secondary and commented here so it is not
+ mistaken for a stray color. */
+select {
+ appearance: none;
+ padding-inline-end: var(--ad-space-l);
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%235f5d59' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right var(--ad-space-xs) center;
+}
+
+input[aria-invalid="true"],
+select[aria-invalid="true"] {
+ border-color: var(--ad-color-danger);
+ background-color: var(--ad-color-danger-bg);
+}
+
+/* Checkbox / Radio — the real native controls, tinted with accent-color
+ instead of a hand-drawn box. This is not just a rename: it deletes the
+ custom SVG-checkmark markup entirely and lets the browser draw its own
+ (platform-appropriate) check and dot. `role="switch"` is excluded here so
+ Switch — a checkbox playing a different role — can style itself below
+ without this rule fighting it. A <label> wrapping a checkbox/radio is told
+ apart from Field's <label> structurally: it :has() one as a direct child,
+ no class required. */
+input[type="checkbox"]:not([role="switch"]),
+input[type="radio"] {
+ width: 1.125rem;
+ height: 1.125rem;
+ margin: 0;
+ accent-color: var(--ad-color-accent);
+ cursor: pointer;
+}
+input[type="checkbox"]:not([role="switch"]):focus-visible,
+input[type="radio"]:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+}
+
+label:has(> input[type="checkbox"]:not([role="switch"])),
+label:has(> input[type="radio"]) {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 400;
+ text-transform: none;
+ letter-spacing: normal;
+ color: var(--ad-color-text-primary);
+}
+label:has(> input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Switch — for on/off settings only, never a substitute for Checkbox.
+ `input[type="checkbox"][role="switch"]` is real ARIA (the correct role for
+ a toggle), not an invented hook, but the visual track+thumb needs a second
+ element for the thumb's independent position and transition — more than
+ `appearance: none` plus pseudo-elements on the input itself can cleanly
+ carry — so `.switch` is a deliberate, documented fallback class rather
+ than a first choice. */
+.switch {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ color: var(--ad-color-text-primary);
+}
+.switch input[role="switch"] { position: absolute; opacity: 0; width: 2.25rem; height: 1.25rem; margin: 0; }
+
+.switch span {
+ position: relative;
+ flex: none;
+ width: 2.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-border-default);
+ transition: background-color var(--ad-dur-base) var(--ad-ease);
+}
+.switch span::after {
+ content: "";
+ position: absolute;
+ inset-block-start: 2px;
+ inset-inline-start: 2px;
+ width: 1rem;
+ height: 1rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-fg);
+ box-shadow: var(--ad-shadow-sm);
+ transition: translate var(--ad-dur-base) var(--ad-ease);
+}
+.switch input[role="switch"]:checked + span { background: var(--ad-color-accent); }
+.switch input[role="switch"]:checked + span::after { translate: 1rem 0; }
+.switch input[role="switch"]:focus-visible + span { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+.switch:has(input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Rows of fields — used by the settings-form pattern. `<form>`/`<fieldset>`
+ are the elements that already mean this, so they are styled bare. A row
+ grouping two fields side by side has no element of its own, so it keeps
+ one plain class. */
+form {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-s);
+ max-width: 28rem;
+}
+.form-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
+ gap: var(--ad-space-xs);
+}
+fieldset {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ margin: 0;
+ padding: 0;
+ border: 0;
+}
+fieldset > legend {
+ padding: 0 0 var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Badge
+ Mono, uppercase, tracked — the catalog-label convention for status and
+ meta values (SAVED, OVERDUE, RECOMMENDED). A label, never a button.
+
+ A <span> is too generic to style globally — it is used all over running
+ prose for unrelated reasons — so Badge gets the one sanctioned class this
+ shape needs. Tone is an attribute, not a modifier class. */
+
+.badge {
+ --_bg: var(--ad-color-surface-fill);
+ --_fg: var(--ad-color-text-secondary);
+ --_border: var(--ad-color-border-hairline);
+
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.1875rem 0.5rem;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-sm);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ line-height: 1;
+ white-space: nowrap;
+}
+
+.badge[data-tone="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); --_border: transparent; }
+.badge[data-tone="success"] { --_bg: var(--ad-color-success-bg); --_fg: var(--ad-color-success); --_border: transparent; }
+.badge[data-tone="danger"] { --_bg: var(--ad-color-danger-bg); --_fg: var(--ad-color-danger); --_border: transparent; }
+
+.badge-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-2xs);
+}
+
+/* Tag
+ A removable mono chip for freeform tags — locations, categories.
+ `[data-dashed]` renders an "add tag" affordance instead of a value. Same
+ reasoning as Badge: <span> is too generic to select bare, so Tag gets one
+ class. The remove control is a real <button>, styled through the
+ descendant selector rather than a class of its own. */
+
+.tag {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.375rem;
+ padding: 0.25rem 0.625rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-default);
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ line-height: 1;
+}
+
+.tag[data-dashed] {
+ background: transparent;
+ border-style: dashed;
+ color: var(--ad-color-text-tertiary);
+ cursor: pointer;
+}
+
+.tag button {
+ all: unset;
+ cursor: pointer;
+ color: var(--ad-color-text-tertiary);
+ line-height: 1;
+ font-size: 0.75rem;
+}
+.tag button:hover { color: var(--ad-color-text-primary); }
+
+.tag-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.375rem;
+}
+
+/* Card
+ Flat surface, hairline border, radius-lg. `[data-elevated]` is opt-in and
+ reserved for the one focused/primary card in a view — a dialog, the
+ primary capture card — flat is the default everywhere else.
+
+ A <div>/<article> is too generic to select bare, so Card keeps one
+ sanctioned class. When Card's job is holding a list of rows — the common
+ case, entry lists, task lists, settings rows — it is a real <ul class="
+ card">, and CardRow is a bare <li> underneath it: no second class needed,
+ the row look comes from the `.card > li` descendant selector alone. The
+ first row's top border is removed automatically so callers never have to
+ override it by hand. */
+
+.card {
+ display: flex;
+ flex-direction: column;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: none;
+ overflow: hidden;
+}
+
+.card[data-elevated] { box-shadow: var(--ad-shadow-float); }
+
+.card > li {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+}
+.card > li:first-child { border-top: none; }
+
+/* A row whose ENTIRE content is wrapped in a real <button> or <a> — the
+ accessible way to make a row clickable — fills the row edge-to-edge and
+ picks up a hover fill and pointer cursor. `:only-child` is what tells this
+ apart from a row that merely contains a small button among other content
+ (a leading checkbox next to a title and a trailing value, say): only a
+ button/anchor that IS the row's one and only child means "the whole row
+ is the control." */
+.card > li:has(> button:only-child),
+.card > li:has(> a:only-child) {
+ padding: 0;
+}
+.card > li > button:only-child,
+.card > li > a:only-child {
+ all: unset;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ box-sizing: border-box;
+ color: inherit;
+ font: inherit;
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.card > li > button:only-child:hover,
+.card > li > a:only-child:hover { background: var(--ad-color-surface-fill); }
+
+/* Documentation-only extras: a body/footer pair and a responsive grid, used
+ by the prose pages (rationale.md, index.md) to show a Card as a plain
+ content tile rather than a data row. Neither is part of Card's props —
+ they exist because a content tile's inner layout has no element of its
+ own to hang a selector on. */
+.card-body {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-m);
+}
+.card-footer {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ margin-top: auto;
+ padding: var(--ad-space-xs) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+
+.card[data-link] { transition: border-color var(--ad-dur-fast) var(--ad-ease), box-shadow var(--ad-dur-fast) var(--ad-ease); }
+.card[data-link]:hover { border-color: var(--ad-color-border-default); box-shadow: var(--ad-shadow-sm); }
+.card[data-link]:focus-within { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+
+.card-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
+ gap: var(--ad-space-s);
+}
+
+/* Avatar — lives here because it almost never appears outside a card or a
+ row (the Loop dashboard's initials chip, Archive's file-type chip). A
+ <span> is too generic for a bare selector, so it keeps one class. */
+.avatar {
+ --_size: 1.875rem;
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ flex: none;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 600;
+ overflow: hidden;
+}
+.avatar[data-square] { border-radius: var(--ad-radius-md); background: var(--ad-color-surface-fill); color: var(--ad-color-text-secondary); border: 1px solid var(--ad-color-border-hairline); }
+.avatar[data-size="sm"] { --_size: 1.5rem; }
+.avatar[data-size="lg"] { --_size: 2.25rem; }
+
+/* Tooltip — a dark mono label on hover, positioned above the child. Pure CSS:
+ the trigger and bubble are siblings inside one relatively-positioned span,
+ shown on :hover/:focus-within so it works from the keyboard too.
+
+ `[role="tooltip"]` is the correct ARIA role for the bubble itself, so it
+ doubles as the styling hook — zero classes there. The wrapping span has no
+ attribute of its own, but it does not need one: `:has()` picks out "a span
+ that contains a [role=tooltip] bubble" precisely, without reaching for a
+ class on an otherwise-generic element. */
+span:has(> [role="tooltip"]) {
+ position: relative;
+ display: inline-flex;
+}
+
+[role="tooltip"] {
+ position: absolute;
+ bottom: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ margin-bottom: 0.375rem;
+ padding: 0.25rem 0.5rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-text-primary);
+ color: var(--ad-color-surface-page);
+ font-family: var(--ad-font-mono);
+ /* 11px is the source's own literal choice — smaller than --ad-step--2, the
+ smallest step on the scale, because a tooltip is meant to read as a
+ glance-only aside rather than body text. */
+ font-size: 11px;
+ white-space: nowrap;
+ line-height: 1.4;
+ z-index: 20;
+ opacity: 0;
+ visibility: hidden;
+ pointer-events: none;
+ transition: opacity var(--ad-dur-fast) var(--ad-ease);
+}
+span:has(> [role="tooltip"]):hover > [role="tooltip"],
+span:has(> [role="tooltip"]):focus-within > [role="tooltip"] {
+ opacity: 1;
+ visibility: visible;
+}
+
+/* Toast — the real pattern used for delete confirmations: a flat bar with an
+ optional destructive action and a dismiss. `[data-fixed]` pins it
+ bottom-right for real use; without it the toast stays in document flow for
+ documentation. `role="status"` is the correct live-region role for a
+ transient status message, so it is both the accessibility hook and the
+ style selector — no base class needed. The action and dismiss buttons are
+ plain <button>s told apart by `aria-label="Dismiss"`, an attribute they
+ need anyway for the icon-only close control. */
+[role="status"] {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ min-width: 20rem;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+}
+[role="status"][data-fixed] {
+ position: fixed;
+ inset-block-end: var(--ad-space-s);
+ inset-inline-end: var(--ad-space-s);
+ z-index: 60;
+}
+[role="status"] > span { flex: 1; }
+[role="status"] button {
+ all: unset;
+ cursor: pointer;
+ font-weight: 600;
+ color: var(--ad-color-danger);
+}
+[role="status"] button[aria-label="Dismiss"] {
+ color: var(--ad-color-text-tertiary);
+ font-size: 1rem;
+ font-weight: 400;
+ line-height: 1;
+}
+[role="status"] button[aria-label="Dismiss"]:hover { color: var(--ad-color-text-primary); }
+
+/* Dialog — the native <dialog> element: a centered modal, flat surface, no
+ colored header, hairline dividers between title/body/footer. A <header>
+ holds the title and a <footer> holds the actions, matching the elements
+ the tag already wants; everything else in between is the body, styled by
+ excluding those two rather than by a class of its own.
+
+ `.dialog-demo` scopes a static, always-open example to its own preview
+ container instead of the viewport, so it can sit safely inside a
+ documentation page — this is a doc-only affordance, not part of Dialog's
+ contract. It relies on the browser's own dialog centering: the UA
+ stylesheet already gives an open <dialog> `position: absolute; inset: 0;
+ margin: auto`, which centers it inside the nearest positioned ancestor —
+ here, `.dialog-demo` itself. A real `.showModal()` call gets the genuine
+ viewport-fixed overlay and `::backdrop` for free; a plain `open` attribute
+ (used here so the demo renders without JavaScript) does not paint a
+ backdrop at all, since `::backdrop` only exists for a dialog shown
+ modally — wire the real open/close state and call `.showModal()` in the
+ consuming app. */
+.dialog-demo {
+ position: relative;
+ min-height: 20rem;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-lg);
+}
+
+dialog {
+ width: 26rem;
+ max-width: calc(100% - (2 * var(--ad-space-s)));
+ height: fit-content;
+ padding: 0;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ overflow: hidden;
+}
+dialog::backdrop {
+ background: rgba(20, 18, 14, 0.35);
+}
+dialog > header {
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-sans);
+ font-weight: 600;
+ font-size: var(--ad-step-0);
+ color: var(--ad-color-text-primary);
+}
+dialog > footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+}
+dialog > *:not(header):not(footer) {
+ padding: var(--ad-space-m);
+ margin: 0;
+ color: var(--ad-color-text-primary);
+ font-size: var(--ad-step--1);
+}
+
+/* Tabs
+ Follows the real WAI-ARIA tabs pattern: a `[role="tablist"]` of
+ `[role="tab"]` buttons controlling `[role="tabpanel"]`s. Those roles are
+ required for the pattern to be accessible in the first place, so they
+ double as the entire styling contract — no classes at all for the base
+ shape. The keyboard wiring lives in assets/js/system.js and is
+ variant-agnostic — it only cares about `[data-tabs]` / `[role=tab]`.
+
+ Two real variants: `underline` (mono, accent underline — primary section
+ nav, the default) and `segmented` (pill track, active segment lifts to the
+ surface — filters like All · Overdue · Upcoming, never primary
+ navigation), chosen with `data-variant="segmented"` on the tablist since
+ neither has a natural attribute of its own. */
+
+/* Every rule below the list container pairs [role="tablist"] with
+ [role="tab"] rather than styling [role="tab"] alone — Tab buttons are
+ still literal <button> elements, so the compound selector's extra
+ specificity is what keeps Button's base styling (button.css) from
+ winning the cascade on shared properties like padding and background. */
+
+[role="tablist"] {
+ display: flex;
+ gap: var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+[role="tablist"] [role="tab"] {
+ padding: 0.5rem 0;
+ margin-bottom: -1px;
+ background: none;
+ border: 0;
+ border-bottom: 2px solid transparent;
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--1);
+ letter-spacing: 0.02em;
+ cursor: pointer;
+ transition: color var(--ad-dur-fast) var(--ad-ease), border-color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"] [role="tab"]:hover { color: var(--ad-color-text-primary); }
+[role="tablist"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ border-bottom-color: var(--ad-color-accent);
+}
+
+[role="tablist"][data-variant="segmented"] {
+ display: inline-flex;
+ gap: 0.125rem;
+ padding: 0.1875rem;
+ border-bottom: 0;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-md);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"] {
+ all: unset;
+ cursor: pointer;
+ padding: 0.375rem 0.875rem;
+ border-radius: var(--ad-radius-sm);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 550;
+ color: var(--ad-color-text-secondary);
+ transition: background-color var(--ad-dur-fast) var(--ad-ease), color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ background: var(--ad-color-surface-card);
+ box-shadow: var(--ad-shadow-sm);
+}
+
+[role="tabpanel"] { padding-top: var(--ad-space-s); }
+[role="tabpanel"][hidden] { display: none; }
+[role="tabpanel"]:focus-visible { outline-offset: 4px; }
+
+/* Documentation site chrome. Everything below the `.ad-doc-` prefix belongs
+ to this site rather than to the design system it documents — including the
+ width/height constants used directly below. The source design system
+ defines no container-width tokens at all, so there is nothing under
+ `--ad-*` for a doc shell to reach for; the Space foundation page's own
+ notes say to omit that group entirely rather than invent one. */
+
+.ad-doc-skip {
+ position: absolute;
+ inset-inline-start: var(--ad-space-xs);
+ inset-block-start: -4rem;
+ z-index: 40;
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ box-shadow: var(--ad-shadow-float);
+ transition: inset-block-start var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-skip:focus { inset-block-start: var(--ad-space-2xs); }
+
+.ad-doc-header {
+ position: sticky;
+ inset-block-start: 0;
+ z-index: 30;
+ height: 3.5rem;
+ background: color-mix(in srgb, var(--ad-color-surface-page) 92%, transparent);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-header__inner {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ height: 100%;
+ max-width: 72rem;
+ margin-inline: auto;
+ padding-inline: var(--ad-space-xs);
+}
+
+.ad-doc-wordmark {
+ display: flex;
+ align-items: baseline;
+ gap: var(--ad-space-2xs);
+ color: var(--ad-color-text-primary);
+ text-decoration: none;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ font-weight: 600;
+ letter-spacing: -0.01em;
+ white-space: nowrap;
+}
+.ad-doc-wordmark:hover { color: var(--ad-color-text-primary); }
+.ad-doc-wordmark__mark {
+ width: 1.375rem;
+ height: 1.375rem;
+ flex: none;
+ align-self: center;
+ color: var(--ad-color-accent);
+}
+.ad-doc-wordmark__version {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+
+.ad-doc-header__actions { margin-inline-start: auto; display: flex; align-items: center; gap: var(--ad-space-2xs); }
+
+.ad-doc-layout {
+ display: grid;
+ grid-template-columns: 16rem minmax(0, 1fr);
+ gap: var(--ad-space-l);
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-m) var(--ad-space-xs) var(--ad-space-2xl);
+}
+
+.ad-doc-sidebar {
+ position: sticky;
+ inset-block-start: calc(3.5rem + var(--ad-space-m));
+ align-self: start;
+ max-height: calc(100vh - 3.5rem - var(--ad-space-l));
+ overflow-y: auto;
+ padding-inline-end: var(--ad-space-2xs);
+}
+.ad-doc-nav__group + .ad-doc-nav__group { margin-top: var(--ad-space-s); }
+.ad-doc-nav__heading {
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-doc-nav__link {
+ display: block;
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ margin-inline-start: calc(var(--ad-space-xs) * -1);
+ border-inline-start: 2px solid transparent;
+ border-radius: 0 var(--ad-radius-sm) var(--ad-radius-sm) 0;
+ color: var(--ad-color-text-secondary);
+ font-size: var(--ad-step--1);
+ text-decoration: none;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-nav__link:hover { color: var(--ad-color-text-primary); background: var(--ad-color-surface-fill); }
+.ad-doc-nav__link[aria-current="page"] {
+ color: var(--ad-color-accent-ink);
+ border-inline-start-color: var(--ad-color-accent);
+ background: var(--ad-color-accent-soft);
+ font-weight: 500;
+}
+
+.ad-doc-main {
+ min-width: 0;
+ /* The design system's own body-text token (--ad-step-0, 18–20px) is right
+ for a product screen but too large for long-form documentation prose.
+ This site's own copy gets a plain, fixed 16px instead — the token above
+ is unchanged, and specimen tables/demos below still show its real
+ value. */
+ font-size: 1rem;
+}
+.ad-doc-main > * { max-width: 50rem; }
+
+.ad-doc-title { margin-bottom: var(--ad-space-xs); }
+.ad-doc-title__section {
+ display: block;
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-accent);
+}
+
+.ad-doc-main h2 {
+ margin-block: var(--ad-space-2xl) var(--ad-space-xs);
+ padding-bottom: var(--ad-space-2xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-main h3 { margin-block: var(--ad-space-l) var(--ad-space-2xs); }
+.ad-doc-main h4 { margin-block: var(--ad-space-s) var(--ad-space-2xs); }
+.ad-doc-main p,
+.ad-doc-main ul,
+.ad-doc-main ol { max-width: 68ch; }
+.ad-doc-main ul:not([class]),
+.ad-doc-main ol:not([class]) { padding-inline-start: var(--ad-space-s); }
+
+.ad-doc-footer {
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-s) var(--ad-space-xs) var(--ad-space-l);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-tertiary);
+ font-size: var(--ad-step--1);
+}
+.ad-doc-footer__inner { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); justify-content: space-between; }
+
+/* `button.ad-doc-nav-toggle` (element + class) rather than a bare class:
+ button.css's own base rule is `button:not(.icon-button)`, which is also
+ one element + one class in specificity and sets `display: inline-flex` —
+ a bare `.ad-doc-nav-toggle { display: none }` loses that fight and the
+ toggle stays visible at every width. Matching its specificity here (and
+ coming later in the concatenated stylesheet) settles it in source order. */
+button.ad-doc-nav-toggle { display: none; }
+
+@media (max-width: 60rem) {
+ .ad-doc-layout { grid-template-columns: minmax(0, 1fr); gap: var(--ad-space-s); }
+ .ad-doc-sidebar {
+ position: static;
+ max-height: none;
+ }
+ /* The toggle is a normal Button; the panel it opens gets its own card
+ treatment. Putting the same card styling on the <aside> wrapper too
+ nested a button-shaped box inside an outer box for no reason. */
+ .ad-doc-nav {
+ margin-top: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ }
+ .ad-doc-sidebar[data-collapsed="true"] > .ad-doc-nav { display: none; }
+ button.ad-doc-nav-toggle {
+ display: flex;
+ width: 100%;
+ justify-content: space-between;
+ }
+}
+
+@media (max-width: 34rem) {
+ .ad-doc-wordmark__version { display: none; }
+ .ad-doc-layout { padding-inline: var(--ad-space-2xs); }
+}
+
+/* The live-example figure used throughout the docs — see the "How the
+ example demos work" section on the Using the CSS page. One class on the
+ outer <figure>; everything inside is styled by plain element selectors,
+ since figcaption/div/details/summary/pre already say what they are.
+ `data-layout` and `data-surface` on the preview <div> distinguish the
+ handful of demos that need a non-default arrangement. */
+
+.example {
+ margin: var(--ad-space-s) 0 var(--ad-space-l);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+ background: var(--ad-color-surface-card);
+ overflow: hidden;
+}
+
+.example figcaption {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-secondary);
+}
+
+.example > div {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-s);
+}
+/* nowrap is load-bearing: a column-direction flex container with wrap enabled
+ sizes its single line against the container rather than the content, which
+ left every stacked example with a few hundred pixels of dead space. */
+.example > div[data-layout="stack"] { flex-direction: column; flex-wrap: nowrap; align-items: stretch; }
+.example > div[data-layout="grid"] {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
+ align-items: start;
+}
+.example > div[data-surface="sunken"] { background: var(--ad-color-surface-fill); }
+.example > div > * { min-width: 0; }
+
+.example details {
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+.example summary {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ color: var(--ad-color-text-secondary);
+ cursor: pointer;
+ list-style: none;
+ user-select: none;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+}
+.example summary::-webkit-details-marker { display: none; }
+.example summary::before {
+ content: "";
+ width: 0.5rem;
+ height: 0.5rem;
+ border-inline-end: 1.5px solid currentColor;
+ border-block-end: 1.5px solid currentColor;
+ rotate: -45deg;
+ transition: rotate var(--ad-dur-fast) var(--ad-ease);
+}
+.example details[open] > summary::before { rotate: 45deg; }
+.example summary:hover { color: var(--ad-color-text-primary); }
+
+.example pre {
+ margin: 0;
+ padding: var(--ad-space-xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+ tab-size: 2;
+}
+.example pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Standalone fenced code blocks in the prose (unhighlighted — there is no
+ syntax highlighter in this build any more, just Kramdown's plain
+ <pre><code class="language-*">). */
+.ad-doc-main > pre {
+ margin-block: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+}
+.ad-doc-main > pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Presentation for the token reference tables and swatch grids, plus a
+ generic data-table utility. All of this is documentation chrome — the
+ design system itself has no Table component (see the note on the removed
+ `table`/`progress`/`callout` pages in git history). */
+
+.ad-swatch-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs) var(--ad-space-s);
+ padding: 0;
+ list-style: none;
+}
+.ad-swatch {
+ display: flex;
+ flex-direction: column;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ overflow: hidden;
+ background: var(--ad-color-surface-card);
+}
+.ad-swatch__chip {
+ display: flex;
+ align-items: flex-end;
+ justify-content: flex-end;
+ height: 4rem;
+ padding: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+}
+.ad-swatch__meta {
+ display: flex;
+ flex-direction: column;
+ gap: 0.1rem;
+ padding: var(--ad-space-2xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+}
+.ad-swatch__hex { color: var(--ad-color-text-primary); text-transform: uppercase; }
+.ad-swatch__ratio { color: var(--ad-color-text-tertiary); }
+
+.ad-alias-row { display: flex; align-items: center; gap: var(--ad-space-2xs); }
+.ad-alias-chip {
+ width: 1.5rem;
+ height: 1.5rem;
+ flex: none;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+}
+.ad-alias-chip--pair { display: flex; }
+.ad-alias-chip--pair > span { flex: 1; }
+
+/* Type specimen rows. */
+.ad-specimen {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ gap: var(--ad-space-3xs);
+ padding-block: var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-specimen__meta {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-specimen__sample {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: var(--ad-color-text-primary);
+}
+
+/* Space and radius rulers. */
+.ad-ruler { display: flex; flex-direction: column; gap: var(--ad-space-2xs); }
+.ad-ruler__row { display: grid; grid-template-columns: 6.5rem 4.5rem 1fr; align-items: center; gap: var(--ad-space-xs); font-family: var(--ad-font-mono); font-size: 11px; }
+.ad-ruler__bar { height: 0.75rem; background: var(--ad-color-accent); border-radius: var(--ad-radius-sm); }
+.ad-ruler__num { color: var(--ad-color-text-tertiary); text-align: right; }
+
+.ad-radius-grid, .ad-elevation-grid, .ad-motion-grid, .ad-accent-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs);
+ padding: 0;
+ list-style: none;
+}
+.ad-radius-demo {
+ height: 4.5rem;
+ background: var(--ad-color-accent-soft);
+ border: 1px solid var(--ad-color-accent);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-elevation-demo {
+ height: 4.5rem;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-motion-demo {
+ height: 4.5rem;
+ display: grid;
+ place-items: center;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-md);
+ margin-bottom: var(--ad-space-2xs);
+ cursor: pointer;
+ overflow: hidden;
+}
+.ad-motion-demo__dot {
+ width: 1.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent);
+ translate: -3rem 0;
+ transition-property: translate;
+ transition-duration: var(--ad-dur-base);
+ transition-timing-function: var(--ad-ease);
+}
+.ad-motion-demo:hover .ad-motion-demo__dot,
+.ad-motion-demo:focus-visible .ad-motion-demo__dot { translate: 3rem 0; }
+
+.ad-accent-demo {
+ height: 4.5rem;
+ display: flex;
+ align-items: flex-end;
+ padding: var(--ad-space-2xs);
+ border-radius: var(--ad-radius-md);
+ color: #fff;
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ margin-bottom: var(--ad-space-2xs);
+}
+
+/* Token names read `<code>` sitewide, which already carries a mono chip
+ look for running prose (see :not(pre) > code in base/typography.css).
+ Inside these specimen/swatch/ruler rows the old `.ad-token-name` class was
+ plain mono text with no chip, so `<code>` is flattened back to that look
+ here the same way table cells are. */
+.ad-swatch code, .ad-specimen code, .ad-ruler code, .ad-alias-row code {
+ background: none;
+ border: 0;
+ padding: 0;
+ font-size: var(--ad-step--2);
+ color: var(--ad-color-text-primary);
+ white-space: nowrap;
+}
+
+/* A documentation aside. Like `<table>`, this is site chrome for the prose
+ itself, not a system component — the real system has no Callout. */
+.ad-callout {
+ margin-block: var(--ad-space-s);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-left: 3px solid var(--ad-color-accent);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+}
+/* Generic reference tables (component APIs, alias tables, the contrast
+ contract). `<table>` IS a table — no class needed to say so. Every table
+ on this site uses the one compact density, so there is no separate
+ modifier for it. `display: block` plus `overflow-x: auto` gives the table
+ its own horizontal scrollbar on narrow viewports without a wrapper div;
+ the anonymous table box the browser generates around `<thead>`/`<tbody>`
+ still does the row/column layout. `<code>` replaces the old
+ `.ad-token-name` / `.ad-table__code` cells — it is already styled
+ sitewide, just re-flattened here to a plain mono run instead of the
+ chip-with-border look prose code gets, to match this table's original
+ look. `.num` is the one sanctioned class left, for right-aligned numeric
+ columns — there is no attribute that means "this column is a number". */
+table { width: 100%; border-collapse: collapse; font-size: var(--ad-step--1); display: block; overflow-x: auto; }
+table th, table td {
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ text-align: left;
+ vertical-align: top;
+}
+table th { font-family: var(--ad-font-mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ad-color-text-secondary); font-weight: 500; }
+table code { background: none; border: 0; padding: 0; font-size: var(--ad-step--2); white-space: nowrap; }
+.num { text-align: right; font-variant-numeric: tabular-nums; }
+
+/* Home page. */
+.ad-hero { padding-block: var(--ad-space-m) var(--ad-space-l); }
+.ad-hero__title {
+ font-size: var(--ad-step-4);
+ margin-bottom: var(--ad-space-xs);
+}
+.ad-hero__actions { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); margin-top: var(--ad-space-s); }
+.ad-stat-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-l);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+}
+.ad-stat__value {
+ display: block;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-2);
+ font-weight: 600;
+ line-height: 1.16;
+ font-variant-numeric: tabular-nums;
+}
+.ad-stat__label {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+
+/* Card chrome. The pane supplies the frame; this just insets the page. */
+body { padding: var(--ad-space-l) var(--ad-space-m); }
+.ad-doc-main { max-width: 50rem; margin-inline: auto; }
+.ad-doc-main > * { max-width: none; }
+</style>
+</head>
+<body>
+<main class="ad-doc-main">
+<header class="ad-doc-title"><span class="ad-doc-title__section">Foundations</span>
+ <h1>Typography</h1>
+</header>
+<p class="ad-lead">Three families, eight fluid steps, eight compound styles. Sans for interface, mono for every label and technical value, serif as an opt-in reading variant.</p>
+<h2 id="families">Families</h2>
+
+<div class="ad-specimen">
+ <div class="ad-specimen__meta">
+ <code>--ad-font-sans</code>
+ <span>Interface text — every component, every screen. Web-safe; nothing is loaded for it.</span>
+ </div>
+ <div class="ad-specimen__sample" style="font-family: var(--ad-font-sans); font-size: 1.5rem;">
+ Arbitrary Definitions — 0123456789
+ </div>
+</div>
+<div class="ad-specimen">
+ <div class="ad-specimen__meta">
+ <code>--ad-font-mono</code>
+ <span>Every label, meta value, technical/catalog value, and code sample. The system's one loaded webfont.</span>
+ </div>
+ <div class="ad-specimen__sample" style="font-family: var(--ad-font-mono); font-size: 1.5rem;">
+ Arbitrary Definitions — 0123456789
+ </div>
+</div>
+<div class="ad-specimen">
+ <div class="ad-specimen__meta">
+ <code>--ad-font-serif</code>
+ <span>Opt-in reading variant for a project that wants an editorial, archival feel (domus-style). Not loaded by default; web-safe only.</span>
+ </div>
+ <div class="ad-specimen__sample" style="font-family: var(--ad-font-serif); font-size: 1.5rem;">
+ Arbitrary Definitions — 0123456789
+ </div>
+</div>
+
+<p>Only one webfont is loaded — IBM Plex Mono, at 400/500/600:</p>
+
+<pre><code class="language-css">@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&display=swap');
+</code></pre>
+
+<p>Sans is a web-safe system stack; serif is Georgia-based and opt-in, for a
+project that wants an editorial or archival feel. Neither is fetched.</p>
+
+<h2 id="fluid-scale">Fluid scale</h2>
+
+<p>Eight steps, <code>clamp()</code>-based (Utopia, 320→1240px viewport). The body sits at
+<code>--ad-step-0</code> — 18px on a phone, 20px on a wide screen — with no fixed
+breakpoint in between.</p>
+
+<div class="ad-specimen">
+ <div class="ad-specimen__meta">
+ <code>--ad-step--2</code>
+ <span>12.5 → 12.8px</span>
+ <span>type-label — eyebrows, catalog labels, meta.</span>
+ </div>
+ <div class="ad-specimen__sample" style="font-size: var(--ad-step--2);">
+ The quick brown fox jumps over the lazy dog
+ </div>
+</div>
+<div class="ad-specimen">
+ <div class="ad-specimen__meta">
+ <code>--ad-step--1</code>
+ <span>15 → 16px</span>
+ <span>type-body-sm and type-mono — secondary copy, technical values.</span>
+ </div>
+ <div class="ad-specimen__sample" style="font-size: var(--ad-step--1);">
+ The quick brown fox jumps over the lazy dog
+ </div>
+</div>
+<div class="ad-specimen">
+ <div class="ad-specimen__meta">
+ <code>--ad-step-0</code>
+ <span>18 → 20px</span>
+ <span>type-body — the base size, and the body element's own size.</span>
+ </div>
+ <div class="ad-specimen__sample" style="font-size: var(--ad-step-0);">
+ The quick brown fox jumps over the lazy dog
+ </div>
+</div>
+<div class="ad-specimen">
+ <div class="ad-specimen__meta">
+ <code>--ad-step-1</code>
+ <span>22 → 25px</span>
+ <span>type-h3 — card and subsection headings.</span>
+ </div>
+ <div class="ad-specimen__sample" style="font-size: var(--ad-step-1);">
+ The quick brown fox jumps over the lazy dog
+ </div>
+</div>
+<div class="ad-specimen">
+ <div class="ad-specimen__meta">
+ <code>--ad-step-2</code>
+ <span>26 → 31px</span>
+ <span>type-h2 — section headings.</span>
+ </div>
+ <div class="ad-specimen__sample" style="font-size: var(--ad-step-2);">
+ The quick brown fox jumps over the lazy dog
+ </div>
+</div>
+<div class="ad-specimen">
+ <div class="ad-specimen__meta">
+ <code>--ad-step-3</code>
+ <span>31 → 39px</span>
+ <span>type-h1 — screen titles.</span>
+ </div>
+ <div class="ad-specimen__sample" style="font-size: var(--ad-step-3);">
+ The quick brown fox jumps over the lazy dog
+ </div>
+</div>
+<div class="ad-specimen">
+ <div class="ad-specimen__meta">
+ <code>--ad-step-4</code>
+ <span>37 → 49px</span>
+ <span>type-display — one hero title per screen, at most.</span>
+ </div>
+ <div class="ad-specimen__sample" style="font-size: var(--ad-step-4);">
+ The quick brown fox jumps over the lazy dog
+ </div>
+</div>
+<div class="ad-specimen">
+ <div class="ad-specimen__meta">
+ <code>--ad-step-5</code>
+ <span>45 → 61px</span>
+ <span>Reserved headroom. No compound style claims it yet.</span>
+ </div>
+ <div class="ad-specimen__sample" style="font-size: var(--ad-step-5);">
+ The quick brown fox jumps over the lazy dog
+ </div>
+</div>
+
+<h2 id="the-eight-compound-styles">The eight compound styles</h2>
+
+<p>Real, specific numbers from the source — not a generic ramp compressed to
+fit. Each class sets family, size, weight, line-height, and (where the
+source specifies one) letter-spacing in a single declaration.</p>
+
+<table>
+ <thead><tr><th scope="col">Class</th><th scope="col">Weight</th><th scope="col">Size</th><th scope="col">Line-height</th><th scope="col">Tracking</th><th scope="col">Use for</th></tr></thead>
+ <tbody>
+ <tr>
+ <td><code>.type-display</code></td>
+ <td>600</td>
+ <td><code>--ad-step-4</code></td>
+ <td>1.08</td>
+ <td>-0.015em</td>
+ <td>Page-level hero title, once per screen.</td>
+ </tr>
+ <tr>
+ <td><code>.type-h1</code></td>
+ <td>600</td>
+ <td><code>--ad-step-3</code></td>
+ <td>1.12</td>
+ <td>-0.012em</td>
+ <td>Screen title.</td>
+ </tr>
+ <tr>
+ <td><code>.type-h2</code></td>
+ <td>600</td>
+ <td><code>--ad-step-2</code></td>
+ <td>1.16</td>
+ <td>-0.01em</td>
+ <td>Section heading.</td>
+ </tr>
+ <tr>
+ <td><code>.type-h3</code></td>
+ <td>600</td>
+ <td><code>--ad-step-1</code></td>
+ <td>1.25</td>
+ <td>normal</td>
+ <td>Card / subsection heading.</td>
+ </tr>
+ <tr>
+ <td><code>.type-body</code></td>
+ <td>400</td>
+ <td><code>--ad-step-0</code></td>
+ <td>1.55</td>
+ <td>normal</td>
+ <td>Body copy.</td>
+ </tr>
+ <tr>
+ <td><code>.type-body-sm</code></td>
+ <td>400</td>
+ <td><code>--ad-step--1</code></td>
+ <td>1.5</td>
+ <td>normal</td>
+ <td>Secondary copy, hints.</td>
+ </tr>
+ <tr>
+ <td><code>.type-label</code></td>
+ <td>500</td>
+ <td><code>--ad-step--2</code></td>
+ <td>1</td>
+ <td>0.08em</td>
+ <td>Eyebrows, catalog labels, meta — SAVED, RECOMMENDED, OVERDUE.</td>
+ </tr>
+ <tr>
+ <td><code>.type-mono</code></td>
+ <td>400</td>
+ <td><code>--ad-step--1</code></td>
+ <td>1.5</td>
+ <td>normal</td>
+ <td>Technical values, IDs, dates, code.</td>
+ </tr>
+ </tbody>
+ </table>
+
+<figure class="example">
+<figcaption>All eight, in order</figcaption>
+<div data-layout="stack">
+<div class="type-display">Display</div>
+<h1 class="type-h1">Heading one</h1>
+<h2 class="type-h2">Heading two</h2>
+<h3 class="type-h3">Heading three</h3>
+<p class="type-body">Body copy at the base size, 1.55 line-height.</p>
+<p class="type-body-sm">Secondary copy, one step down.</p>
+<span class="type-label">Catalog label</span>
+<span class="type-mono">technical.value / 2026-08-21 / #3a6a63</span>
+</div>
+<details><summary>Markup</summary>
+<pre><code><div class="type-display">Display</div>
+<h1 class="type-h1">Heading one</h1>
+<h2 class="type-h2">Heading two</h2>
+<h3 class="type-h3">Heading three</h3>
+<p class="type-body">Body copy at the base size, 1.55 line-height.</p>
+<p class="type-body-sm">Secondary copy, one step down.</p>
+<span class="type-label">Catalog label</span>
+<span class="type-mono">technical.value / 2026-08-21 / #3a6a63</span></code></pre>
+</details>
+</figure>
+
+<h2 id="rules">Rules</h2>
+
+<ul>
+ <li><strong>Three families, and no fourth.</strong> A new typeface needs a reason a weight
+or a size cannot supply.</li>
+ <li><strong>Mono is not decoration.</strong> Every label, meta value, ID, date, and code
+sample is mono — it is the one thread present in all three source projects.</li>
+ <li><strong><code>type-label</code> is always uppercase, mono, and tracked.</strong> It is the one
+place uppercase belongs in this system.</li>
+ <li><strong>Never set a size in <code>px</code>.</strong> The eight steps are fluid on purpose.</li>
+</ul>
+</main>
+<script>
+// Behavior for the pieces of the system that need it. Everything else is
+// CSS and semantic HTML on purpose — a design system that only works with
+// JavaScript running is a component library with extra steps.
+(function () {
+ "use strict";
+
+ var STORAGE_KEY = "ad-theme";
+
+ function readStored() {
+ try {
+ return localStorage.getItem(STORAGE_KEY);
+ } catch (error) {
+ return null;
+ }
+ }
+
+ function writeStored(value) {
+ try {
+ localStorage.setItem(STORAGE_KEY, value);
+ } catch (error) {
+ /* Nothing to do — the media query still gives a sensible default. */
+ }
+ }
+
+ function systemPrefersDark() {
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
+ }
+
+ function currentTheme() {
+ var explicit = document.documentElement.getAttribute("data-theme");
+ if (explicit) return explicit;
+ return systemPrefersDark() ? "dark" : "light";
+ }
+
+ function paintToggle(theme) {
+ document.querySelectorAll("[data-theme-toggle]").forEach(function (toggle) {
+ var next = theme === "dark" ? "light" : "dark";
+ toggle.querySelectorAll("[data-theme-icon]").forEach(function (icon) {
+ icon.hidden = icon.dataset.themeIcon !== theme;
+ });
+ toggle.setAttribute("aria-label", "Switch to the " + next + " theme");
+ toggle.setAttribute("title", "Switch to the " + next + " theme");
+ var label = toggle.querySelector("[data-theme-label]");
+ if (label) label.textContent = "Switch to the " + next + " theme";
+ });
+ }
+
+ function setUpTheme() {
+ paintToggle(currentTheme());
+
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-theme-toggle]");
+ if (!toggle) return;
+
+ var next = currentTheme() === "dark" ? "light" : "dark";
+ document.documentElement.setAttribute("data-theme", next);
+ writeStored(next);
+ paintToggle(next);
+ });
+
+ // Follow the OS while the reader has not made an explicit choice.
+ window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", function () {
+ if (!readStored()) paintToggle(currentTheme());
+ });
+ }
+
+ // Tabs, wired to the WAI-ARIA authoring practices: arrows move, Home/End
+ // jump, and only the selected tab stays in the tab order.
+ function setUpTabs() {
+ document.querySelectorAll("[data-tabs]").forEach(function (root) {
+ var tabs = Array.prototype.slice.call(root.querySelectorAll('[role="tab"]'));
+ if (!tabs.length) return;
+
+ function select(tab, focus) {
+ tabs.forEach(function (candidate) {
+ var selected = candidate === tab;
+ candidate.setAttribute("aria-selected", String(selected));
+ candidate.tabIndex = selected ? 0 : -1;
+ var panel = document.getElementById(candidate.getAttribute("aria-controls"));
+ if (panel) panel.hidden = !selected;
+ });
+ if (focus) tab.focus();
+ }
+
+ root.addEventListener("click", function (event) {
+ var tab = event.target.closest('[role="tab"]');
+ if (tab) select(tab, false);
+ });
+
+ root.addEventListener("keydown", function (event) {
+ var index = tabs.indexOf(document.activeElement);
+ if (index === -1) return;
+
+ var next = null;
+ if (event.key === "ArrowRight") next = tabs[(index + 1) % tabs.length];
+ else if (event.key === "ArrowLeft") next = tabs[(index - 1 + tabs.length) % tabs.length];
+ else if (event.key === "Home") next = tabs[0];
+ else if (event.key === "End") next = tabs[tabs.length - 1];
+
+ if (next) {
+ event.preventDefault();
+ select(next, true);
+ }
+ });
+
+ select(tabs.filter(function (tab) {
+ return tab.getAttribute("aria-selected") === "true";
+ })[0] || tabs[0], false);
+ });
+ }
+
+ function setUpNavToggle() {
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-nav-toggle]");
+ if (!toggle) return;
+
+ var sidebar = toggle.closest("[data-sidebar]");
+ var collapsed = sidebar.dataset.collapsed !== "false";
+ sidebar.dataset.collapsed = String(!collapsed);
+ toggle.setAttribute("aria-expanded", String(collapsed));
+ });
+ }
+
+ setUpTheme();
+ setUpTabs();
+ setUpNavToggle();
+})();
+
+</script>
+</body>
+</html>
diff --git a/design-bundle/patterns/archive.html b/design-bundle/patterns/archive.html
new file mode 100644
index 0000000..3ce15b8
--- /dev/null
+++ b/design-bundle/patterns/archive.html
@@ -0,0 +1,1860 @@
+<!-- @dsCard group="Patterns" -->
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Archive</title>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&display=swap">
+<style>
+/* Arbitrary Definitions — design tokens. Hand-maintained; edit values directly. */
+
+:root {
+ /* Palette ramps — reference only. Components use the aliases below. */
+ --ad-neutral-0: #faf9f7;
+ --ad-neutral-50: #fdfdfb;
+ --ad-neutral-100: #f1f0ed;
+ --ad-neutral-150: #e8e6e2;
+ --ad-neutral-200: #dcdad7;
+ --ad-neutral-300: #c0bdb9;
+ --ad-neutral-500: #888681;
+ --ad-neutral-600: #5f5d59;
+ --ad-neutral-800: #2a2926;
+ --ad-neutral-900: #100f0d;
+ --ad-neutral-dark-0: #0e0d0b;
+ --ad-neutral-dark-50: #151412;
+ --ad-neutral-dark-100: #1e1d1a;
+ --ad-neutral-dark-150: #252421;
+ --ad-neutral-dark-200: #2c2b29;
+ --ad-neutral-dark-300: #44423f;
+ --ad-neutral-dark-500: #7c7a76;
+ --ad-neutral-dark-600: #a09e9a;
+ --ad-neutral-dark-800: #d9d7d4;
+ --ad-neutral-dark-900: #e9e8e6;
+ --ad-accent-teal: #3a6a63;
+ --ad-accent-clay: #9a5a3c;
+ --ad-accent-ink-blue: #2b4a78;
+ --ad-accent-ochre: #93701f;
+ --ad-accent-plum: #6a3a5c;
+
+ /* Surfaces */
+ --ad-color-surface-page: #faf9f7;
+ --ad-color-surface-card: #fdfdfb;
+ --ad-color-surface-fill: #f1f0ed;
+ --ad-color-surface-fill-2: #e8e6e2;
+
+ /* Borders */
+ --ad-color-border-hairline: #dcdad7;
+ --ad-color-border-default: #c0bdb9;
+
+ /* Text */
+ --ad-color-text-primary: #100f0d;
+ --ad-color-text-secondary: #5f5d59;
+ --ad-color-text-tertiary: #888681;
+
+ /* Accent */
+ --ad-color-accent: #3a6a63;
+ --ad-color-accent-fg: #ffffff;
+ --ad-color-accent-ink: color-mix(in oklch, var(--ad-color-accent) 78%, black);
+ --ad-color-accent-soft: color-mix(in oklch, var(--ad-color-accent) 12%, white);
+
+ /* Status (fixed — never swapped like the accent) */
+ --ad-color-success: #3f7a4a;
+ --ad-color-success-bg: color-mix(in oklch, var(--ad-color-success) 12%, white);
+ --ad-color-danger: #ad3a2c;
+ --ad-color-danger-bg: color-mix(in oklch, var(--ad-color-danger) 10%, white);
+
+ /* Type families */
+ --ad-font-sans: -apple-system, "Helvetica Neue", Helvetica, Arial, system-ui, sans-serif;
+ --ad-font-mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, Consolas, "Courier New", monospace;
+ --ad-font-serif: Georgia, "Times New Roman", "Iowan Old Style", serif;
+
+ /* Type steps — fluid (Utopia scale), the design system's own body-text size */
+ --ad-step--2: clamp(0.7813rem, 0.7736rem + 0.0341vw, 0.8rem);
+ --ad-step--1: clamp(0.9375rem, 0.9119rem + 0.1136vw, 1rem);
+ --ad-step-0: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-step-1: clamp(1.35rem, 1.2631rem + 0.3864vw, 1.5625rem);
+ --ad-step-2: clamp(1.62rem, 1.4837rem + 0.6057vw, 1.9531rem);
+ --ad-step-3: clamp(1.944rem, 1.7405rem + 0.9044vw, 2.4414rem);
+ --ad-step-4: clamp(2.3328rem, 2.0387rem + 1.3072vw, 3.0518rem);
+ --ad-step-5: clamp(2.7994rem, 2.384rem + 1.8461vw, 3.8147rem);
+
+ /* Space */
+ --ad-space-3xs: clamp(0.3125rem, 0.3125rem + 0vw, 0.3125rem);
+ --ad-space-2xs: clamp(0.5625rem, 0.5369rem + 0.1136vw, 0.625rem);
+ --ad-space-xs: clamp(0.875rem, 0.8494rem + 0.1136vw, 0.9375rem);
+ --ad-space-s: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-space-m: clamp(1.6875rem, 1.6108rem + 0.3409vw, 1.875rem);
+ --ad-space-l: clamp(2.25rem, 2.1477rem + 0.4545vw, 2.5rem);
+ --ad-space-xl: clamp(3.375rem, 3.2216rem + 0.6818vw, 3.75rem);
+ --ad-space-2xl: clamp(4.5rem, 4.2955rem + 0.9091vw, 5rem);
+ --ad-space-3xl: clamp(6.75rem, 6.4432rem + 1.3636vw, 7.5rem);
+
+ /* Radius */
+ --ad-radius-sm: 4px;
+ --ad-radius-md: 6px;
+ --ad-radius-lg: 10px;
+ --ad-radius-full: 999px;
+ --ad-border-w: 1px;
+
+ /* Elevation */
+ --ad-shadow-sm: 0 1px 2px rgba(20, 18, 14, 0.05);
+ --ad-shadow-float: 0 2px 6px rgba(20, 18, 14, 0.06), 0 10px 24px -12px rgba(20, 18, 14, 0.18);
+
+ /* Motion */
+ --ad-dur-fast: 0.12s;
+ --ad-dur-base: 0.16s;
+ --ad-ease: cubic-bezier(0.2, 0, 0, 1);
+}
+
+@media (prefers-color-scheme: dark) {
+ :root:not([data-theme="light"]) {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+ }
+}
+
+:root[data-theme="dark"] {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ :root {
+ --ad-dur-fast: 0ms;
+ --ad-dur-base: 0ms;
+ }
+}
+
+/* A short reset. Everything it sets is something the system would otherwise
+ have to override in every component.
+
+ The rem values used for this documentation site's own chrome (measure,
+ content width, sidebar width, header height) belong to this site, not to
+ the design system it documents — the source design system defines no
+ container-width tokens at all, so there is nothing under `--ad-*` to reach
+ for here. See the header comment in site/shell.css. */
+
+*,
+*::before,
+*::after { box-sizing: border-box; }
+
+html {
+ -webkit-text-size-adjust: 100%;
+ scroll-behavior: smooth;
+ scroll-padding-top: calc(3.5rem + var(--ad-space-s));
+}
+
+@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;
+ }
+}
+
+body {
+ margin: 0;
+ background: var(--ad-color-surface-page);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ line-height: 1.55;
+ font-synthesis-weight: none;
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+}
+
+::selection {
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+}
+
+h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd { margin: 0; }
+ul[class], ol[class] { list-style: none; margin: 0; padding: 0; }
+
+img, svg, video { display: block; max-width: 100%; height: auto; }
+
+button, input, select, textarea {
+ font: inherit;
+ color: inherit;
+}
+
+hr {
+ height: 1px;
+ margin: var(--ad-space-l) 0;
+ border: 0;
+ background: var(--ad-color-border-hairline);
+}
+
+/* One focus ring, defined once. No component is allowed its own. */
+:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+ border-radius: var(--ad-radius-sm);
+}
+:focus:not(:focus-visible) { outline: none; }
+
+[hidden] { display: none !important; }
+
+.ad-visually-hidden {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip-path: inset(50%);
+ white-space: nowrap;
+ border: 0;
+}
+
+/* Every rule here reads a type token for family and size. Weight, line-height,
+ and letter-spacing are hardcoded to the exact numbers the source ships —
+ there is no `--ad-weight-*` / `--ad-leading-*` / `--ad-tracking-*` scale in
+ the real system, only eight fixed compound styles (see the Typography
+ foundation page, which this file implements verbatim). */
+
+.type-display { font: 600 var(--ad-step-4)/1.08 var(--ad-font-sans); letter-spacing: -0.015em; }
+.type-h1, h1 { font: 600 var(--ad-step-3)/1.12 var(--ad-font-sans); letter-spacing: -0.012em; }
+.type-h2, h2 { font: 600 var(--ad-step-2)/1.16 var(--ad-font-sans); letter-spacing: -0.01em; }
+.type-h3, h3 { font: 600 var(--ad-step-1)/1.25 var(--ad-font-sans); }
+.type-body { font: 400 var(--ad-step-0)/1.55 var(--ad-font-sans); }
+.type-body-sm { font: 400 var(--ad-step--1)/1.5 var(--ad-font-sans); }
+.type-label {
+ font: 500 var(--ad-step--2)/1 var(--ad-font-mono);
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: var(--ad-color-text-secondary);
+}
+.type-mono { font: 400 var(--ad-step--1)/1.5 var(--ad-font-mono); }
+
+/* Prose defaults for the documentation body copy. These read the same
+ tokens the compound classes above do; they are not a second scale. */
+h1, h2, h3, h4 { font-weight: 600; text-wrap: balance; }
+
+h4 { font: 600 var(--ad-step-0)/1.3 var(--ad-font-sans); }
+
+p, ul, ol { margin: 0 0 var(--ad-space-s); }
+li + li { margin-top: var(--ad-space-3xs); }
+
+a {
+ color: var(--ad-color-accent);
+ text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
+ text-underline-offset: 0.18em;
+ text-decoration-thickness: 1px;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ text-decoration-color var(--ad-dur-fast) var(--ad-ease);
+}
+a:hover {
+ color: var(--ad-color-accent-ink);
+ text-decoration-color: currentColor;
+}
+
+strong, b { font-weight: 600; }
+
+small { font-size: var(--ad-step--1); }
+
+.ad-lead {
+ font-size: var(--ad-step-1);
+ line-height: 1.5;
+ color: var(--ad-color-text-secondary);
+ max-width: 68ch;
+ text-wrap: pretty;
+}
+
+.ad-muted { color: var(--ad-color-text-secondary); }
+.ad-subtle { color: var(--ad-color-text-tertiary); }
+
+code, kbd, samp, pre {
+ font-family: var(--ad-font-mono);
+ font-feature-settings: "liga" 0;
+}
+
+:not(pre) > code {
+ padding: 0.15em 0.4em;
+ font-size: 0.875em;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-sm);
+ white-space: nowrap;
+}
+
+kbd {
+ display: inline-block;
+ padding: 0.1em 0.45em;
+ font-size: var(--ad-step--2);
+ line-height: 1.6;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-bottom-width: 2px;
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-secondary);
+}
+
+blockquote {
+ margin-block: var(--ad-space-s);
+ padding-left: var(--ad-space-s);
+ border-left: 2px solid var(--ad-color-accent);
+ color: var(--ad-color-text-secondary);
+}
+
+/* Button
+ A flat, quiet button that recedes rather than performs — no gradients, no
+ shine. Variants: default, primary, dark, ghost, danger. Sizes: sm, md
+ (default), lg — padding and font step only, weight and radius stay put.
+
+ The contract is the element itself: a bare <button> is styled directly, no
+ class required. An <a> that acts like a button (navigates rather than
+ changes something — see the Button page's "Links that act like buttons")
+ opts in with a single `.btn` class, since a bare `a` selector would catch
+ every ordinary prose link on the site. `:not(.icon-button)` keeps this
+ block from leaking onto IconButton, which shares the <button> tag but is
+ an entirely different shape. */
+
+button:not(.icon-button),
+a.btn {
+ --_bg: var(--ad-color-surface-card);
+ --_fg: var(--ad-color-text-primary);
+ --_border: var(--ad-color-border-default);
+ --_pad: var(--ad-space-xs) var(--ad-space-s);
+ --_font: var(--ad-step--1);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: var(--ad-space-2xs);
+ padding: var(--_pad);
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-sans);
+ font-size: var(--_font);
+ font-weight: 550;
+ letter-spacing: -0.01em;
+ line-height: 1.2;
+ text-align: center;
+ text-decoration: none;
+ white-space: nowrap;
+ cursor: pointer;
+ transition: filter var(--ad-dur-fast) var(--ad-ease),
+ border-color var(--ad-dur-fast) var(--ad-ease);
+}
+
+:is(button:not(.icon-button), a.btn):hover:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.96); }
+:is(button:not(.icon-button), a.btn):active:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.92); }
+
+button:not(.icon-button):disabled,
+:is(button:not(.icon-button), a.btn)[aria-disabled="true"] {
+ opacity: 0.5;
+ cursor: default;
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="primary"] {
+ --_bg: var(--ad-color-accent);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-accent);
+}
+
+/* Reserved for the single most-committing action in a view — a final Save,
+ never a whole row of them. */
+:is(button:not(.icon-button), a.btn)[data-variant="dark"] {
+ --_bg: var(--ad-color-text-primary);
+ --_fg: var(--ad-color-surface-page);
+ --_border: var(--ad-color-text-primary);
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="ghost"] {
+ --_bg: transparent;
+ --_fg: var(--ad-color-text-primary);
+ --_border: transparent;
+}
+
+/* The source hardcodes this label white rather than deriving it — danger
+ is not "on a filled accent surface", so --ad-color-accent-fg is reused
+ here only because its resolved value is the same literal white, not
+ because the two roles are the same thing. */
+:is(button:not(.icon-button), a.btn)[data-variant="danger"] {
+ --_bg: var(--ad-color-danger);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-danger);
+}
+
+:is(button:not(.icon-button), a.btn)[data-size="sm"] { --_pad: 0.375rem 0.75rem; --_font: 0.8125rem; }
+:is(button:not(.icon-button), a.btn)[data-size="lg"] { --_pad: 0.75rem 1.375rem; --_font: var(--ad-step-0); }
+
+:is(button:not(.icon-button), a.btn)[data-block] { width: 100%; }
+
+/* Button group — [role="group"] is already the correct ARIA role for a
+ toolbar of related buttons, so it doubles as the styling hook for free. */
+[role="group"] {
+ display: inline-flex;
+ isolation: isolate;
+}
+[role="group"] > button { border-radius: 0; }
+[role="group"] > button:first-child { border-start-start-radius: var(--ad-radius-md); border-end-start-radius: var(--ad-radius-md); }
+[role="group"] > button:last-child { border-start-end-radius: var(--ad-radius-md); border-end-end-radius: var(--ad-radius-md); }
+[role="group"] > button + button { margin-inline-start: -1px; }
+[role="group"] > button:hover,
+[role="group"] > button:focus-visible { z-index: 1; }
+
+/* IconButton — a square icon-only button. Always needs an aria-label; it
+ becomes both the accessible name and the native tooltip. There is no
+ attribute that distinguishes "a button that is only an icon" from a plain
+ Button, so this is the one sanctioned class in this file. */
+.icon-button {
+ --_size: 2.125rem;
+ --_bg: transparent;
+ --_border: transparent;
+ --_fg: var(--ad-color-text-secondary);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ padding: 0;
+ flex: none;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease),
+ color var(--ad-dur-fast) var(--ad-ease);
+}
+.icon-button:hover:not(:disabled) { background: var(--ad-color-surface-fill-2); }
+.icon-button:disabled { opacity: 0.5; cursor: default; }
+
+.icon-button[data-size="sm"] { --_size: 1.75rem; }
+.icon-button[data-size="lg"] { --_size: 2.5rem; }
+
+.icon-button[data-variant="outline"] { --_bg: var(--ad-color-surface-card); --_border: var(--ad-color-border-default); --_fg: var(--ad-color-text-primary); }
+.icon-button[data-variant="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); }
+
+/* Field
+ The labeled field wrapper — a <label> that wraps its own caption text
+ together with the control, so the association is native and needs no
+ `for`/`id` pair. The caption is mono, uppercase, tracked — the same
+ catalog-label convention as Badge and type-label.
+
+ A <label> is reused for three different shapes across this system (this
+ wrapper, Checkbox/Radio's row, Switch's row), so it cannot be styled bare
+ without one shape leaking into another. Field is the one of the three with
+ no distinguishing attribute of its own, so it gets the sanctioned class;
+ Checkbox/Radio tell themselves apart structurally instead (see below). */
+.field {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ min-width: 0;
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Hint and error read the native fine-print element. `role="alert"` is a
+ real live-region role for validation text, so it doubles as the selector
+ that turns a hint red. */
+.field > small {
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+.field > small[role="alert"] { color: var(--ad-color-danger); }
+
+/* Input / Select — bordered, matched to each other exactly. Both tags ARE
+ the component, so they are styled bare; nothing else on this site uses a
+ text input or a select. */
+input:not([type="checkbox"]):not([type="radio"]),
+select {
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ outline: none;
+ transition: border-color var(--ad-dur-fast) var(--ad-ease),
+ box-shadow var(--ad-dur-fast) var(--ad-ease);
+}
+input::placeholder { color: var(--ad-color-text-tertiary); }
+
+input:not([type="checkbox"]):not([type="radio"]):focus,
+select:focus {
+ border-color: var(--ad-color-accent);
+ box-shadow: 0 0 0 2px var(--ad-color-accent-soft);
+}
+
+input:not([type="checkbox"]):not([type="radio"]):disabled,
+select:disabled {
+ opacity: 0.5;
+ cursor: default;
+}
+
+/* The chevron is the one flagged color exception in this component set:
+ CSS cannot point a data-URI background-image at a custom property, so its
+ stroke is a literal hex rather than a var(--ad-*) reference. It is picked
+ to match --ad-color-text-secondary and commented here so it is not
+ mistaken for a stray color. */
+select {
+ appearance: none;
+ padding-inline-end: var(--ad-space-l);
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%235f5d59' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right var(--ad-space-xs) center;
+}
+
+input[aria-invalid="true"],
+select[aria-invalid="true"] {
+ border-color: var(--ad-color-danger);
+ background-color: var(--ad-color-danger-bg);
+}
+
+/* Checkbox / Radio — the real native controls, tinted with accent-color
+ instead of a hand-drawn box. This is not just a rename: it deletes the
+ custom SVG-checkmark markup entirely and lets the browser draw its own
+ (platform-appropriate) check and dot. `role="switch"` is excluded here so
+ Switch — a checkbox playing a different role — can style itself below
+ without this rule fighting it. A <label> wrapping a checkbox/radio is told
+ apart from Field's <label> structurally: it :has() one as a direct child,
+ no class required. */
+input[type="checkbox"]:not([role="switch"]),
+input[type="radio"] {
+ width: 1.125rem;
+ height: 1.125rem;
+ margin: 0;
+ accent-color: var(--ad-color-accent);
+ cursor: pointer;
+}
+input[type="checkbox"]:not([role="switch"]):focus-visible,
+input[type="radio"]:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+}
+
+label:has(> input[type="checkbox"]:not([role="switch"])),
+label:has(> input[type="radio"]) {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 400;
+ text-transform: none;
+ letter-spacing: normal;
+ color: var(--ad-color-text-primary);
+}
+label:has(> input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Switch — for on/off settings only, never a substitute for Checkbox.
+ `input[type="checkbox"][role="switch"]` is real ARIA (the correct role for
+ a toggle), not an invented hook, but the visual track+thumb needs a second
+ element for the thumb's independent position and transition — more than
+ `appearance: none` plus pseudo-elements on the input itself can cleanly
+ carry — so `.switch` is a deliberate, documented fallback class rather
+ than a first choice. */
+.switch {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ color: var(--ad-color-text-primary);
+}
+.switch input[role="switch"] { position: absolute; opacity: 0; width: 2.25rem; height: 1.25rem; margin: 0; }
+
+.switch span {
+ position: relative;
+ flex: none;
+ width: 2.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-border-default);
+ transition: background-color var(--ad-dur-base) var(--ad-ease);
+}
+.switch span::after {
+ content: "";
+ position: absolute;
+ inset-block-start: 2px;
+ inset-inline-start: 2px;
+ width: 1rem;
+ height: 1rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-fg);
+ box-shadow: var(--ad-shadow-sm);
+ transition: translate var(--ad-dur-base) var(--ad-ease);
+}
+.switch input[role="switch"]:checked + span { background: var(--ad-color-accent); }
+.switch input[role="switch"]:checked + span::after { translate: 1rem 0; }
+.switch input[role="switch"]:focus-visible + span { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+.switch:has(input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Rows of fields — used by the settings-form pattern. `<form>`/`<fieldset>`
+ are the elements that already mean this, so they are styled bare. A row
+ grouping two fields side by side has no element of its own, so it keeps
+ one plain class. */
+form {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-s);
+ max-width: 28rem;
+}
+.form-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
+ gap: var(--ad-space-xs);
+}
+fieldset {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ margin: 0;
+ padding: 0;
+ border: 0;
+}
+fieldset > legend {
+ padding: 0 0 var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Badge
+ Mono, uppercase, tracked — the catalog-label convention for status and
+ meta values (SAVED, OVERDUE, RECOMMENDED). A label, never a button.
+
+ A <span> is too generic to style globally — it is used all over running
+ prose for unrelated reasons — so Badge gets the one sanctioned class this
+ shape needs. Tone is an attribute, not a modifier class. */
+
+.badge {
+ --_bg: var(--ad-color-surface-fill);
+ --_fg: var(--ad-color-text-secondary);
+ --_border: var(--ad-color-border-hairline);
+
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.1875rem 0.5rem;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-sm);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ line-height: 1;
+ white-space: nowrap;
+}
+
+.badge[data-tone="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); --_border: transparent; }
+.badge[data-tone="success"] { --_bg: var(--ad-color-success-bg); --_fg: var(--ad-color-success); --_border: transparent; }
+.badge[data-tone="danger"] { --_bg: var(--ad-color-danger-bg); --_fg: var(--ad-color-danger); --_border: transparent; }
+
+.badge-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-2xs);
+}
+
+/* Tag
+ A removable mono chip for freeform tags — locations, categories.
+ `[data-dashed]` renders an "add tag" affordance instead of a value. Same
+ reasoning as Badge: <span> is too generic to select bare, so Tag gets one
+ class. The remove control is a real <button>, styled through the
+ descendant selector rather than a class of its own. */
+
+.tag {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.375rem;
+ padding: 0.25rem 0.625rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-default);
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ line-height: 1;
+}
+
+.tag[data-dashed] {
+ background: transparent;
+ border-style: dashed;
+ color: var(--ad-color-text-tertiary);
+ cursor: pointer;
+}
+
+.tag button {
+ all: unset;
+ cursor: pointer;
+ color: var(--ad-color-text-tertiary);
+ line-height: 1;
+ font-size: 0.75rem;
+}
+.tag button:hover { color: var(--ad-color-text-primary); }
+
+.tag-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.375rem;
+}
+
+/* Card
+ Flat surface, hairline border, radius-lg. `[data-elevated]` is opt-in and
+ reserved for the one focused/primary card in a view — a dialog, the
+ primary capture card — flat is the default everywhere else.
+
+ A <div>/<article> is too generic to select bare, so Card keeps one
+ sanctioned class. When Card's job is holding a list of rows — the common
+ case, entry lists, task lists, settings rows — it is a real <ul class="
+ card">, and CardRow is a bare <li> underneath it: no second class needed,
+ the row look comes from the `.card > li` descendant selector alone. The
+ first row's top border is removed automatically so callers never have to
+ override it by hand. */
+
+.card {
+ display: flex;
+ flex-direction: column;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: none;
+ overflow: hidden;
+}
+
+.card[data-elevated] { box-shadow: var(--ad-shadow-float); }
+
+.card > li {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+}
+.card > li:first-child { border-top: none; }
+
+/* A row whose ENTIRE content is wrapped in a real <button> or <a> — the
+ accessible way to make a row clickable — fills the row edge-to-edge and
+ picks up a hover fill and pointer cursor. `:only-child` is what tells this
+ apart from a row that merely contains a small button among other content
+ (a leading checkbox next to a title and a trailing value, say): only a
+ button/anchor that IS the row's one and only child means "the whole row
+ is the control." */
+.card > li:has(> button:only-child),
+.card > li:has(> a:only-child) {
+ padding: 0;
+}
+.card > li > button:only-child,
+.card > li > a:only-child {
+ all: unset;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ box-sizing: border-box;
+ color: inherit;
+ font: inherit;
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.card > li > button:only-child:hover,
+.card > li > a:only-child:hover { background: var(--ad-color-surface-fill); }
+
+/* Documentation-only extras: a body/footer pair and a responsive grid, used
+ by the prose pages (rationale.md, index.md) to show a Card as a plain
+ content tile rather than a data row. Neither is part of Card's props —
+ they exist because a content tile's inner layout has no element of its
+ own to hang a selector on. */
+.card-body {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-m);
+}
+.card-footer {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ margin-top: auto;
+ padding: var(--ad-space-xs) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+
+.card[data-link] { transition: border-color var(--ad-dur-fast) var(--ad-ease), box-shadow var(--ad-dur-fast) var(--ad-ease); }
+.card[data-link]:hover { border-color: var(--ad-color-border-default); box-shadow: var(--ad-shadow-sm); }
+.card[data-link]:focus-within { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+
+.card-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
+ gap: var(--ad-space-s);
+}
+
+/* Avatar — lives here because it almost never appears outside a card or a
+ row (the Loop dashboard's initials chip, Archive's file-type chip). A
+ <span> is too generic for a bare selector, so it keeps one class. */
+.avatar {
+ --_size: 1.875rem;
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ flex: none;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 600;
+ overflow: hidden;
+}
+.avatar[data-square] { border-radius: var(--ad-radius-md); background: var(--ad-color-surface-fill); color: var(--ad-color-text-secondary); border: 1px solid var(--ad-color-border-hairline); }
+.avatar[data-size="sm"] { --_size: 1.5rem; }
+.avatar[data-size="lg"] { --_size: 2.25rem; }
+
+/* Tooltip — a dark mono label on hover, positioned above the child. Pure CSS:
+ the trigger and bubble are siblings inside one relatively-positioned span,
+ shown on :hover/:focus-within so it works from the keyboard too.
+
+ `[role="tooltip"]` is the correct ARIA role for the bubble itself, so it
+ doubles as the styling hook — zero classes there. The wrapping span has no
+ attribute of its own, but it does not need one: `:has()` picks out "a span
+ that contains a [role=tooltip] bubble" precisely, without reaching for a
+ class on an otherwise-generic element. */
+span:has(> [role="tooltip"]) {
+ position: relative;
+ display: inline-flex;
+}
+
+[role="tooltip"] {
+ position: absolute;
+ bottom: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ margin-bottom: 0.375rem;
+ padding: 0.25rem 0.5rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-text-primary);
+ color: var(--ad-color-surface-page);
+ font-family: var(--ad-font-mono);
+ /* 11px is the source's own literal choice — smaller than --ad-step--2, the
+ smallest step on the scale, because a tooltip is meant to read as a
+ glance-only aside rather than body text. */
+ font-size: 11px;
+ white-space: nowrap;
+ line-height: 1.4;
+ z-index: 20;
+ opacity: 0;
+ visibility: hidden;
+ pointer-events: none;
+ transition: opacity var(--ad-dur-fast) var(--ad-ease);
+}
+span:has(> [role="tooltip"]):hover > [role="tooltip"],
+span:has(> [role="tooltip"]):focus-within > [role="tooltip"] {
+ opacity: 1;
+ visibility: visible;
+}
+
+/* Toast — the real pattern used for delete confirmations: a flat bar with an
+ optional destructive action and a dismiss. `[data-fixed]` pins it
+ bottom-right for real use; without it the toast stays in document flow for
+ documentation. `role="status"` is the correct live-region role for a
+ transient status message, so it is both the accessibility hook and the
+ style selector — no base class needed. The action and dismiss buttons are
+ plain <button>s told apart by `aria-label="Dismiss"`, an attribute they
+ need anyway for the icon-only close control. */
+[role="status"] {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ min-width: 20rem;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+}
+[role="status"][data-fixed] {
+ position: fixed;
+ inset-block-end: var(--ad-space-s);
+ inset-inline-end: var(--ad-space-s);
+ z-index: 60;
+}
+[role="status"] > span { flex: 1; }
+[role="status"] button {
+ all: unset;
+ cursor: pointer;
+ font-weight: 600;
+ color: var(--ad-color-danger);
+}
+[role="status"] button[aria-label="Dismiss"] {
+ color: var(--ad-color-text-tertiary);
+ font-size: 1rem;
+ font-weight: 400;
+ line-height: 1;
+}
+[role="status"] button[aria-label="Dismiss"]:hover { color: var(--ad-color-text-primary); }
+
+/* Dialog — the native <dialog> element: a centered modal, flat surface, no
+ colored header, hairline dividers between title/body/footer. A <header>
+ holds the title and a <footer> holds the actions, matching the elements
+ the tag already wants; everything else in between is the body, styled by
+ excluding those two rather than by a class of its own.
+
+ `.dialog-demo` scopes a static, always-open example to its own preview
+ container instead of the viewport, so it can sit safely inside a
+ documentation page — this is a doc-only affordance, not part of Dialog's
+ contract. It relies on the browser's own dialog centering: the UA
+ stylesheet already gives an open <dialog> `position: absolute; inset: 0;
+ margin: auto`, which centers it inside the nearest positioned ancestor —
+ here, `.dialog-demo` itself. A real `.showModal()` call gets the genuine
+ viewport-fixed overlay and `::backdrop` for free; a plain `open` attribute
+ (used here so the demo renders without JavaScript) does not paint a
+ backdrop at all, since `::backdrop` only exists for a dialog shown
+ modally — wire the real open/close state and call `.showModal()` in the
+ consuming app. */
+.dialog-demo {
+ position: relative;
+ min-height: 20rem;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-lg);
+}
+
+dialog {
+ width: 26rem;
+ max-width: calc(100% - (2 * var(--ad-space-s)));
+ height: fit-content;
+ padding: 0;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ overflow: hidden;
+}
+dialog::backdrop {
+ background: rgba(20, 18, 14, 0.35);
+}
+dialog > header {
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-sans);
+ font-weight: 600;
+ font-size: var(--ad-step-0);
+ color: var(--ad-color-text-primary);
+}
+dialog > footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+}
+dialog > *:not(header):not(footer) {
+ padding: var(--ad-space-m);
+ margin: 0;
+ color: var(--ad-color-text-primary);
+ font-size: var(--ad-step--1);
+}
+
+/* Tabs
+ Follows the real WAI-ARIA tabs pattern: a `[role="tablist"]` of
+ `[role="tab"]` buttons controlling `[role="tabpanel"]`s. Those roles are
+ required for the pattern to be accessible in the first place, so they
+ double as the entire styling contract — no classes at all for the base
+ shape. The keyboard wiring lives in assets/js/system.js and is
+ variant-agnostic — it only cares about `[data-tabs]` / `[role=tab]`.
+
+ Two real variants: `underline` (mono, accent underline — primary section
+ nav, the default) and `segmented` (pill track, active segment lifts to the
+ surface — filters like All · Overdue · Upcoming, never primary
+ navigation), chosen with `data-variant="segmented"` on the tablist since
+ neither has a natural attribute of its own. */
+
+/* Every rule below the list container pairs [role="tablist"] with
+ [role="tab"] rather than styling [role="tab"] alone — Tab buttons are
+ still literal <button> elements, so the compound selector's extra
+ specificity is what keeps Button's base styling (button.css) from
+ winning the cascade on shared properties like padding and background. */
+
+[role="tablist"] {
+ display: flex;
+ gap: var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+[role="tablist"] [role="tab"] {
+ padding: 0.5rem 0;
+ margin-bottom: -1px;
+ background: none;
+ border: 0;
+ border-bottom: 2px solid transparent;
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--1);
+ letter-spacing: 0.02em;
+ cursor: pointer;
+ transition: color var(--ad-dur-fast) var(--ad-ease), border-color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"] [role="tab"]:hover { color: var(--ad-color-text-primary); }
+[role="tablist"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ border-bottom-color: var(--ad-color-accent);
+}
+
+[role="tablist"][data-variant="segmented"] {
+ display: inline-flex;
+ gap: 0.125rem;
+ padding: 0.1875rem;
+ border-bottom: 0;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-md);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"] {
+ all: unset;
+ cursor: pointer;
+ padding: 0.375rem 0.875rem;
+ border-radius: var(--ad-radius-sm);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 550;
+ color: var(--ad-color-text-secondary);
+ transition: background-color var(--ad-dur-fast) var(--ad-ease), color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ background: var(--ad-color-surface-card);
+ box-shadow: var(--ad-shadow-sm);
+}
+
+[role="tabpanel"] { padding-top: var(--ad-space-s); }
+[role="tabpanel"][hidden] { display: none; }
+[role="tabpanel"]:focus-visible { outline-offset: 4px; }
+
+/* Documentation site chrome. Everything below the `.ad-doc-` prefix belongs
+ to this site rather than to the design system it documents — including the
+ width/height constants used directly below. The source design system
+ defines no container-width tokens at all, so there is nothing under
+ `--ad-*` for a doc shell to reach for; the Space foundation page's own
+ notes say to omit that group entirely rather than invent one. */
+
+.ad-doc-skip {
+ position: absolute;
+ inset-inline-start: var(--ad-space-xs);
+ inset-block-start: -4rem;
+ z-index: 40;
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ box-shadow: var(--ad-shadow-float);
+ transition: inset-block-start var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-skip:focus { inset-block-start: var(--ad-space-2xs); }
+
+.ad-doc-header {
+ position: sticky;
+ inset-block-start: 0;
+ z-index: 30;
+ height: 3.5rem;
+ background: color-mix(in srgb, var(--ad-color-surface-page) 92%, transparent);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-header__inner {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ height: 100%;
+ max-width: 72rem;
+ margin-inline: auto;
+ padding-inline: var(--ad-space-xs);
+}
+
+.ad-doc-wordmark {
+ display: flex;
+ align-items: baseline;
+ gap: var(--ad-space-2xs);
+ color: var(--ad-color-text-primary);
+ text-decoration: none;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ font-weight: 600;
+ letter-spacing: -0.01em;
+ white-space: nowrap;
+}
+.ad-doc-wordmark:hover { color: var(--ad-color-text-primary); }
+.ad-doc-wordmark__mark {
+ width: 1.375rem;
+ height: 1.375rem;
+ flex: none;
+ align-self: center;
+ color: var(--ad-color-accent);
+}
+.ad-doc-wordmark__version {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+
+.ad-doc-header__actions { margin-inline-start: auto; display: flex; align-items: center; gap: var(--ad-space-2xs); }
+
+.ad-doc-layout {
+ display: grid;
+ grid-template-columns: 16rem minmax(0, 1fr);
+ gap: var(--ad-space-l);
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-m) var(--ad-space-xs) var(--ad-space-2xl);
+}
+
+.ad-doc-sidebar {
+ position: sticky;
+ inset-block-start: calc(3.5rem + var(--ad-space-m));
+ align-self: start;
+ max-height: calc(100vh - 3.5rem - var(--ad-space-l));
+ overflow-y: auto;
+ padding-inline-end: var(--ad-space-2xs);
+}
+.ad-doc-nav__group + .ad-doc-nav__group { margin-top: var(--ad-space-s); }
+.ad-doc-nav__heading {
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-doc-nav__link {
+ display: block;
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ margin-inline-start: calc(var(--ad-space-xs) * -1);
+ border-inline-start: 2px solid transparent;
+ border-radius: 0 var(--ad-radius-sm) var(--ad-radius-sm) 0;
+ color: var(--ad-color-text-secondary);
+ font-size: var(--ad-step--1);
+ text-decoration: none;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-nav__link:hover { color: var(--ad-color-text-primary); background: var(--ad-color-surface-fill); }
+.ad-doc-nav__link[aria-current="page"] {
+ color: var(--ad-color-accent-ink);
+ border-inline-start-color: var(--ad-color-accent);
+ background: var(--ad-color-accent-soft);
+ font-weight: 500;
+}
+
+.ad-doc-main {
+ min-width: 0;
+ /* The design system's own body-text token (--ad-step-0, 18–20px) is right
+ for a product screen but too large for long-form documentation prose.
+ This site's own copy gets a plain, fixed 16px instead — the token above
+ is unchanged, and specimen tables/demos below still show its real
+ value. */
+ font-size: 1rem;
+}
+.ad-doc-main > * { max-width: 50rem; }
+
+.ad-doc-title { margin-bottom: var(--ad-space-xs); }
+.ad-doc-title__section {
+ display: block;
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-accent);
+}
+
+.ad-doc-main h2 {
+ margin-block: var(--ad-space-2xl) var(--ad-space-xs);
+ padding-bottom: var(--ad-space-2xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-main h3 { margin-block: var(--ad-space-l) var(--ad-space-2xs); }
+.ad-doc-main h4 { margin-block: var(--ad-space-s) var(--ad-space-2xs); }
+.ad-doc-main p,
+.ad-doc-main ul,
+.ad-doc-main ol { max-width: 68ch; }
+.ad-doc-main ul:not([class]),
+.ad-doc-main ol:not([class]) { padding-inline-start: var(--ad-space-s); }
+
+.ad-doc-footer {
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-s) var(--ad-space-xs) var(--ad-space-l);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-tertiary);
+ font-size: var(--ad-step--1);
+}
+.ad-doc-footer__inner { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); justify-content: space-between; }
+
+/* `button.ad-doc-nav-toggle` (element + class) rather than a bare class:
+ button.css's own base rule is `button:not(.icon-button)`, which is also
+ one element + one class in specificity and sets `display: inline-flex` —
+ a bare `.ad-doc-nav-toggle { display: none }` loses that fight and the
+ toggle stays visible at every width. Matching its specificity here (and
+ coming later in the concatenated stylesheet) settles it in source order. */
+button.ad-doc-nav-toggle { display: none; }
+
+@media (max-width: 60rem) {
+ .ad-doc-layout { grid-template-columns: minmax(0, 1fr); gap: var(--ad-space-s); }
+ .ad-doc-sidebar {
+ position: static;
+ max-height: none;
+ }
+ /* The toggle is a normal Button; the panel it opens gets its own card
+ treatment. Putting the same card styling on the <aside> wrapper too
+ nested a button-shaped box inside an outer box for no reason. */
+ .ad-doc-nav {
+ margin-top: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ }
+ .ad-doc-sidebar[data-collapsed="true"] > .ad-doc-nav { display: none; }
+ button.ad-doc-nav-toggle {
+ display: flex;
+ width: 100%;
+ justify-content: space-between;
+ }
+}
+
+@media (max-width: 34rem) {
+ .ad-doc-wordmark__version { display: none; }
+ .ad-doc-layout { padding-inline: var(--ad-space-2xs); }
+}
+
+/* The live-example figure used throughout the docs — see the "How the
+ example demos work" section on the Using the CSS page. One class on the
+ outer <figure>; everything inside is styled by plain element selectors,
+ since figcaption/div/details/summary/pre already say what they are.
+ `data-layout` and `data-surface` on the preview <div> distinguish the
+ handful of demos that need a non-default arrangement. */
+
+.example {
+ margin: var(--ad-space-s) 0 var(--ad-space-l);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+ background: var(--ad-color-surface-card);
+ overflow: hidden;
+}
+
+.example figcaption {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-secondary);
+}
+
+.example > div {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-s);
+}
+/* nowrap is load-bearing: a column-direction flex container with wrap enabled
+ sizes its single line against the container rather than the content, which
+ left every stacked example with a few hundred pixels of dead space. */
+.example > div[data-layout="stack"] { flex-direction: column; flex-wrap: nowrap; align-items: stretch; }
+.example > div[data-layout="grid"] {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
+ align-items: start;
+}
+.example > div[data-surface="sunken"] { background: var(--ad-color-surface-fill); }
+.example > div > * { min-width: 0; }
+
+.example details {
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+.example summary {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ color: var(--ad-color-text-secondary);
+ cursor: pointer;
+ list-style: none;
+ user-select: none;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+}
+.example summary::-webkit-details-marker { display: none; }
+.example summary::before {
+ content: "";
+ width: 0.5rem;
+ height: 0.5rem;
+ border-inline-end: 1.5px solid currentColor;
+ border-block-end: 1.5px solid currentColor;
+ rotate: -45deg;
+ transition: rotate var(--ad-dur-fast) var(--ad-ease);
+}
+.example details[open] > summary::before { rotate: 45deg; }
+.example summary:hover { color: var(--ad-color-text-primary); }
+
+.example pre {
+ margin: 0;
+ padding: var(--ad-space-xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+ tab-size: 2;
+}
+.example pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Standalone fenced code blocks in the prose (unhighlighted — there is no
+ syntax highlighter in this build any more, just Kramdown's plain
+ <pre><code class="language-*">). */
+.ad-doc-main > pre {
+ margin-block: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+}
+.ad-doc-main > pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Presentation for the token reference tables and swatch grids, plus a
+ generic data-table utility. All of this is documentation chrome — the
+ design system itself has no Table component (see the note on the removed
+ `table`/`progress`/`callout` pages in git history). */
+
+.ad-swatch-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs) var(--ad-space-s);
+ padding: 0;
+ list-style: none;
+}
+.ad-swatch {
+ display: flex;
+ flex-direction: column;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ overflow: hidden;
+ background: var(--ad-color-surface-card);
+}
+.ad-swatch__chip {
+ display: flex;
+ align-items: flex-end;
+ justify-content: flex-end;
+ height: 4rem;
+ padding: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+}
+.ad-swatch__meta {
+ display: flex;
+ flex-direction: column;
+ gap: 0.1rem;
+ padding: var(--ad-space-2xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+}
+.ad-swatch__hex { color: var(--ad-color-text-primary); text-transform: uppercase; }
+.ad-swatch__ratio { color: var(--ad-color-text-tertiary); }
+
+.ad-alias-row { display: flex; align-items: center; gap: var(--ad-space-2xs); }
+.ad-alias-chip {
+ width: 1.5rem;
+ height: 1.5rem;
+ flex: none;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+}
+.ad-alias-chip--pair { display: flex; }
+.ad-alias-chip--pair > span { flex: 1; }
+
+/* Type specimen rows. */
+.ad-specimen {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ gap: var(--ad-space-3xs);
+ padding-block: var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-specimen__meta {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-specimen__sample {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: var(--ad-color-text-primary);
+}
+
+/* Space and radius rulers. */
+.ad-ruler { display: flex; flex-direction: column; gap: var(--ad-space-2xs); }
+.ad-ruler__row { display: grid; grid-template-columns: 6.5rem 4.5rem 1fr; align-items: center; gap: var(--ad-space-xs); font-family: var(--ad-font-mono); font-size: 11px; }
+.ad-ruler__bar { height: 0.75rem; background: var(--ad-color-accent); border-radius: var(--ad-radius-sm); }
+.ad-ruler__num { color: var(--ad-color-text-tertiary); text-align: right; }
+
+.ad-radius-grid, .ad-elevation-grid, .ad-motion-grid, .ad-accent-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs);
+ padding: 0;
+ list-style: none;
+}
+.ad-radius-demo {
+ height: 4.5rem;
+ background: var(--ad-color-accent-soft);
+ border: 1px solid var(--ad-color-accent);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-elevation-demo {
+ height: 4.5rem;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-motion-demo {
+ height: 4.5rem;
+ display: grid;
+ place-items: center;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-md);
+ margin-bottom: var(--ad-space-2xs);
+ cursor: pointer;
+ overflow: hidden;
+}
+.ad-motion-demo__dot {
+ width: 1.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent);
+ translate: -3rem 0;
+ transition-property: translate;
+ transition-duration: var(--ad-dur-base);
+ transition-timing-function: var(--ad-ease);
+}
+.ad-motion-demo:hover .ad-motion-demo__dot,
+.ad-motion-demo:focus-visible .ad-motion-demo__dot { translate: 3rem 0; }
+
+.ad-accent-demo {
+ height: 4.5rem;
+ display: flex;
+ align-items: flex-end;
+ padding: var(--ad-space-2xs);
+ border-radius: var(--ad-radius-md);
+ color: #fff;
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ margin-bottom: var(--ad-space-2xs);
+}
+
+/* Token names read `<code>` sitewide, which already carries a mono chip
+ look for running prose (see :not(pre) > code in base/typography.css).
+ Inside these specimen/swatch/ruler rows the old `.ad-token-name` class was
+ plain mono text with no chip, so `<code>` is flattened back to that look
+ here the same way table cells are. */
+.ad-swatch code, .ad-specimen code, .ad-ruler code, .ad-alias-row code {
+ background: none;
+ border: 0;
+ padding: 0;
+ font-size: var(--ad-step--2);
+ color: var(--ad-color-text-primary);
+ white-space: nowrap;
+}
+
+/* A documentation aside. Like `<table>`, this is site chrome for the prose
+ itself, not a system component — the real system has no Callout. */
+.ad-callout {
+ margin-block: var(--ad-space-s);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-left: 3px solid var(--ad-color-accent);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+}
+/* Generic reference tables (component APIs, alias tables, the contrast
+ contract). `<table>` IS a table — no class needed to say so. Every table
+ on this site uses the one compact density, so there is no separate
+ modifier for it. `display: block` plus `overflow-x: auto` gives the table
+ its own horizontal scrollbar on narrow viewports without a wrapper div;
+ the anonymous table box the browser generates around `<thead>`/`<tbody>`
+ still does the row/column layout. `<code>` replaces the old
+ `.ad-token-name` / `.ad-table__code` cells — it is already styled
+ sitewide, just re-flattened here to a plain mono run instead of the
+ chip-with-border look prose code gets, to match this table's original
+ look. `.num` is the one sanctioned class left, for right-aligned numeric
+ columns — there is no attribute that means "this column is a number". */
+table { width: 100%; border-collapse: collapse; font-size: var(--ad-step--1); display: block; overflow-x: auto; }
+table th, table td {
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ text-align: left;
+ vertical-align: top;
+}
+table th { font-family: var(--ad-font-mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ad-color-text-secondary); font-weight: 500; }
+table code { background: none; border: 0; padding: 0; font-size: var(--ad-step--2); white-space: nowrap; }
+.num { text-align: right; font-variant-numeric: tabular-nums; }
+
+/* Home page. */
+.ad-hero { padding-block: var(--ad-space-m) var(--ad-space-l); }
+.ad-hero__title {
+ font-size: var(--ad-step-4);
+ margin-bottom: var(--ad-space-xs);
+}
+.ad-hero__actions { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); margin-top: var(--ad-space-s); }
+.ad-stat-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-l);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+}
+.ad-stat__value {
+ display: block;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-2);
+ font-weight: 600;
+ line-height: 1.16;
+ font-variant-numeric: tabular-nums;
+}
+.ad-stat__label {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+
+/* Card chrome. The pane supplies the frame; this just insets the page. */
+body { padding: var(--ad-space-l) var(--ad-space-m); }
+.ad-doc-main { max-width: 50rem; margin-inline: auto; }
+.ad-doc-main > * { max-width: none; }
+</style>
+</head>
+<body>
+<main class="ad-doc-main">
+<header class="ad-doc-title"><span class="ad-doc-title__section">Patterns</span>
+ <h1>Archive — capture app</h1>
+</header>
+<p class="ad-lead">A capture/document mobile app. Card list, bottom capture dock, photo slots with the diagonal-grain empty state. A generic demonstration of the system, not a recreation of any one source project's actual screens.</p>
+<p>Archive is not a real product — it is the system’s own reference layout for a
+mobile capture flow: the shape <code>kejadlen/domus</code> (a household inventory and
+document archive) would take if it were rebuilt on this token set, at its
+390×760 frame. Three screens, abbreviated to what each one adds.</p>
+
+<figure class="example">
+<figcaption>Home — recent captures</figcaption>
+<div>
+<div style="width: 300px; height: 560px; background: var(--ad-color-surface-page); border-radius: var(--ad-radius-lg); box-shadow: var(--ad-shadow-float); overflow:hidden; display:flex; flex-direction:column; position:relative;">
+ <header style="padding: 16px var(--ad-space-m) var(--ad-space-2xs);">
+ <span style="font-weight:600; font-size: var(--ad-step-1); letter-spacing:-0.01em;">Archive</span>
+ </header>
+ <div style="flex:1; overflow:hidden; padding: 0 var(--ad-space-m);">
+ <div style="display:flex; align-items:baseline; gap:8px; margin: var(--ad-space-s) 0 var(--ad-space-xs);">
+ <span style="font-weight:700; font-size: var(--ad-step-0);">Recent</span>
+ <span class="type-label" style="margin-left:auto;">3 tracked</span>
+ </div>
+ <ul class="card">
+ <li>
+ <span class="avatar" data-square=""><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 8 12 3 3 8v8l9 5 9-5V8Z" /><path d="M3 8l9 5 9-5M12 13v8" /></svg></span>
+ <div style="flex:1; min-width:0;">
+ <div style="font-weight:550; font-size: var(--ad-step--1); white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">Dishwasher manual</div>
+ <div class="type-label" style="margin-top:2px;">PDF · 2.1MB</div>
+ </div>
+ <span class="type-mono" style="color: var(--ad-color-text-tertiary); font-size: var(--ad-step--2);">2h</span>
+ </li>
+ <li>
+ <span class="avatar" data-square=""><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 8 12 3 3 8v8l9 5 9-5V8Z" /><path d="M3 8l9 5 9-5M12 13v8" /></svg></span>
+ <div style="flex:1; min-width:0;">
+ <div style="font-weight:550; font-size: var(--ad-step--1);">Sofa receipt</div>
+ <div class="type-label" style="margin-top:2px;">JPG · 640KB</div>
+ </div>
+ <span class="type-mono" style="color: var(--ad-color-text-tertiary); font-size: var(--ad-step--2);">1d</span>
+ </li>
+ <li>
+ <span class="avatar" data-square=""><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 8 12 3 3 8v8l9 5 9-5V8Z" /><path d="M3 8l9 5 9-5M12 13v8" /></svg></span>
+ <div style="flex:1; min-width:0;">
+ <div style="font-weight:550; font-size: var(--ad-step--1);">Passport scan</div>
+ <div class="type-label" style="margin-top:2px;">PDF · 890KB</div>
+ </div>
+ <span class="type-mono" style="color: var(--ad-color-text-tertiary); font-size: var(--ad-step--2);">4d</span>
+ </li>
+ </ul>
+ </div>
+ <div style="position:absolute; left:0; right:0; bottom:0; display:flex; justify-content:center; padding: 14px 20px 22px; background: linear-gradient(to top, var(--ad-color-surface-page) 65%, transparent);">
+ <button data-variant="primary" data-size="lg" data-block="">
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 8a2 2 0 0 1 2-2h1.5l1-1.5h9l1 1.5H20a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8Z" /><circle cx="12" cy="13" r="3.5" /></svg>
+ Take a photo
+ </button>
+ </div>
+</div>
+<div style="width: 300px; height: 560px; background: var(--ad-color-surface-page); border-radius: var(--ad-radius-lg); box-shadow: var(--ad-shadow-float); overflow:hidden; display:flex; flex-direction:column;">
+ <header style="display:flex; align-items:center; gap:8px; padding: 16px var(--ad-space-m) var(--ad-space-2xs);">
+ <button class="icon-button" data-size="sm" aria-label="Back"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 18l-6-6 6-6" /></svg></button>
+ <span style="font-weight:600; font-size: var(--ad-step-0);">Save</span>
+ </header>
+ <div style="flex:1; padding: 0 var(--ad-space-m);">
+ <div style="height: 180px; border-radius: var(--ad-radius-lg); border: 1px solid var(--ad-color-border-default); background: repeating-linear-gradient(45deg, transparent 0 8px, var(--ad-color-surface-fill-2) 8px 9px), var(--ad-color-surface-fill); display:flex; flex-direction:column; align-items:center; justify-content:center; gap:6px; margin-top: var(--ad-space-s);">
+ <svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 8 12 3 3 8v8l9 5 9-5V8Z" /><path d="M3 8l9 5 9-5M12 13v8" /></svg>
+ <span class="type-label">Photo preview</span>
+ </div>
+ <label class="field" style="margin-top: var(--ad-space-m);">
+ Name
+ <input placeholder="Dishwasher manual" />
+ </label>
+ </div>
+ <div style="padding: var(--ad-space-m); display:flex; gap: var(--ad-space-2xs);">
+ <button style="flex:1;">Retake</button>
+ <button data-variant="primary" style="flex:1;">Save</button>
+ </div>
+</div>
+<div style="width: 300px; height: 560px; background: var(--ad-color-surface-page); border-radius: var(--ad-radius-lg); box-shadow: var(--ad-shadow-float); overflow:hidden; display:flex; flex-direction:column;">
+ <header style="display:flex; align-items:center; gap:8px; padding: 16px var(--ad-space-m) var(--ad-space-2xs);">
+ <button class="icon-button" data-size="sm" aria-label="Back"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 18l-6-6 6-6" /></svg></button>
+ <span class="type-label">Archive</span>
+ </header>
+ <div style="flex:1; overflow:hidden; padding: 0 var(--ad-space-m) var(--ad-space-l);">
+ <h1 style="font-weight:700; font-size: var(--ad-step-2); letter-spacing:-0.01em; margin: var(--ad-space-xs) 0 var(--ad-space-2xs);">Dishwasher manual</h1>
+ <div style="display:flex; gap:6px; flex-wrap:wrap; margin-bottom: var(--ad-space-m);">
+ <span class="tag">location:kitchen</span>
+ <span class="tag" data-dashed="">+ add tag</span>
+ </div>
+ <div class="type-label" style="margin-bottom: var(--ad-space-2xs);">File</div>
+ <ul class="card">
+ <li>
+ <span class="avatar" data-square=""><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 8 12 3 3 8v8l9 5 9-5V8Z" /><path d="M3 8l9 5 9-5M12 13v8" /></svg></span>
+ <div style="flex:1; font-size: var(--ad-step--1);">PDF · 2.1MB</div>
+ </li>
+ </ul>
+ <div class="type-label" style="margin: var(--ad-space-m) 0 var(--ad-space-2xs);">Notes</div>
+ <p style="font-size: var(--ad-step--1); color: var(--ad-color-text-secondary); line-height:1.55; margin:0;">No notes yet.</p>
+ </div>
+</div>
+</div>
+</figure>
+
+<h2 id="what-this-screen-is-testing">What this screen is testing</h2>
+
+<ul>
+ <li><strong>The diagonal-grain empty state.</strong> The only patterned background this
+system permits, used only inside an empty photo slot.</li>
+ <li><strong>CardRow as a file row, twice.</strong> The same component holds a scannable
+list on Home and a single, focused row on Detail — no special “detail row”
+variant needed.</li>
+ <li><strong>Tag alongside its own dashed “add” affordance.</strong> The two live in one row,
+reading as one continuous, extendable list.</li>
+ <li><strong><code>type-label</code> as the workhorse.</strong> File size, tag count, and the back-button
+screen name all reach for the same mono catalog-label convention as Badge.</li>
+</ul>
+</main>
+<script>
+// Behavior for the pieces of the system that need it. Everything else is
+// CSS and semantic HTML on purpose — a design system that only works with
+// JavaScript running is a component library with extra steps.
+(function () {
+ "use strict";
+
+ var STORAGE_KEY = "ad-theme";
+
+ function readStored() {
+ try {
+ return localStorage.getItem(STORAGE_KEY);
+ } catch (error) {
+ return null;
+ }
+ }
+
+ function writeStored(value) {
+ try {
+ localStorage.setItem(STORAGE_KEY, value);
+ } catch (error) {
+ /* Nothing to do — the media query still gives a sensible default. */
+ }
+ }
+
+ function systemPrefersDark() {
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
+ }
+
+ function currentTheme() {
+ var explicit = document.documentElement.getAttribute("data-theme");
+ if (explicit) return explicit;
+ return systemPrefersDark() ? "dark" : "light";
+ }
+
+ function paintToggle(theme) {
+ document.querySelectorAll("[data-theme-toggle]").forEach(function (toggle) {
+ var next = theme === "dark" ? "light" : "dark";
+ toggle.querySelectorAll("[data-theme-icon]").forEach(function (icon) {
+ icon.hidden = icon.dataset.themeIcon !== theme;
+ });
+ toggle.setAttribute("aria-label", "Switch to the " + next + " theme");
+ toggle.setAttribute("title", "Switch to the " + next + " theme");
+ var label = toggle.querySelector("[data-theme-label]");
+ if (label) label.textContent = "Switch to the " + next + " theme";
+ });
+ }
+
+ function setUpTheme() {
+ paintToggle(currentTheme());
+
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-theme-toggle]");
+ if (!toggle) return;
+
+ var next = currentTheme() === "dark" ? "light" : "dark";
+ document.documentElement.setAttribute("data-theme", next);
+ writeStored(next);
+ paintToggle(next);
+ });
+
+ // Follow the OS while the reader has not made an explicit choice.
+ window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", function () {
+ if (!readStored()) paintToggle(currentTheme());
+ });
+ }
+
+ // Tabs, wired to the WAI-ARIA authoring practices: arrows move, Home/End
+ // jump, and only the selected tab stays in the tab order.
+ function setUpTabs() {
+ document.querySelectorAll("[data-tabs]").forEach(function (root) {
+ var tabs = Array.prototype.slice.call(root.querySelectorAll('[role="tab"]'));
+ if (!tabs.length) return;
+
+ function select(tab, focus) {
+ tabs.forEach(function (candidate) {
+ var selected = candidate === tab;
+ candidate.setAttribute("aria-selected", String(selected));
+ candidate.tabIndex = selected ? 0 : -1;
+ var panel = document.getElementById(candidate.getAttribute("aria-controls"));
+ if (panel) panel.hidden = !selected;
+ });
+ if (focus) tab.focus();
+ }
+
+ root.addEventListener("click", function (event) {
+ var tab = event.target.closest('[role="tab"]');
+ if (tab) select(tab, false);
+ });
+
+ root.addEventListener("keydown", function (event) {
+ var index = tabs.indexOf(document.activeElement);
+ if (index === -1) return;
+
+ var next = null;
+ if (event.key === "ArrowRight") next = tabs[(index + 1) % tabs.length];
+ else if (event.key === "ArrowLeft") next = tabs[(index - 1 + tabs.length) % tabs.length];
+ else if (event.key === "Home") next = tabs[0];
+ else if (event.key === "End") next = tabs[tabs.length - 1];
+
+ if (next) {
+ event.preventDefault();
+ select(next, true);
+ }
+ });
+
+ select(tabs.filter(function (tab) {
+ return tab.getAttribute("aria-selected") === "true";
+ })[0] || tabs[0], false);
+ });
+ }
+
+ function setUpNavToggle() {
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-nav-toggle]");
+ if (!toggle) return;
+
+ var sidebar = toggle.closest("[data-sidebar]");
+ var collapsed = sidebar.dataset.collapsed !== "false";
+ sidebar.dataset.collapsed = String(!collapsed);
+ toggle.setAttribute("aria-expanded", String(collapsed));
+ });
+ }
+
+ setUpTheme();
+ setUpTabs();
+ setUpNavToggle();
+})();
+
+</script>
+</body>
+</html>
diff --git a/design-bundle/patterns/loop.html b/design-bundle/patterns/loop.html
new file mode 100644
index 0000000..c5f4150
--- /dev/null
+++ b/design-bundle/patterns/loop.html
@@ -0,0 +1,1932 @@
+<!-- @dsCard group="Patterns" -->
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Loop</title>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&display=swap">
+<style>
+/* Arbitrary Definitions — design tokens. Hand-maintained; edit values directly. */
+
+:root {
+ /* Palette ramps — reference only. Components use the aliases below. */
+ --ad-neutral-0: #faf9f7;
+ --ad-neutral-50: #fdfdfb;
+ --ad-neutral-100: #f1f0ed;
+ --ad-neutral-150: #e8e6e2;
+ --ad-neutral-200: #dcdad7;
+ --ad-neutral-300: #c0bdb9;
+ --ad-neutral-500: #888681;
+ --ad-neutral-600: #5f5d59;
+ --ad-neutral-800: #2a2926;
+ --ad-neutral-900: #100f0d;
+ --ad-neutral-dark-0: #0e0d0b;
+ --ad-neutral-dark-50: #151412;
+ --ad-neutral-dark-100: #1e1d1a;
+ --ad-neutral-dark-150: #252421;
+ --ad-neutral-dark-200: #2c2b29;
+ --ad-neutral-dark-300: #44423f;
+ --ad-neutral-dark-500: #7c7a76;
+ --ad-neutral-dark-600: #a09e9a;
+ --ad-neutral-dark-800: #d9d7d4;
+ --ad-neutral-dark-900: #e9e8e6;
+ --ad-accent-teal: #3a6a63;
+ --ad-accent-clay: #9a5a3c;
+ --ad-accent-ink-blue: #2b4a78;
+ --ad-accent-ochre: #93701f;
+ --ad-accent-plum: #6a3a5c;
+
+ /* Surfaces */
+ --ad-color-surface-page: #faf9f7;
+ --ad-color-surface-card: #fdfdfb;
+ --ad-color-surface-fill: #f1f0ed;
+ --ad-color-surface-fill-2: #e8e6e2;
+
+ /* Borders */
+ --ad-color-border-hairline: #dcdad7;
+ --ad-color-border-default: #c0bdb9;
+
+ /* Text */
+ --ad-color-text-primary: #100f0d;
+ --ad-color-text-secondary: #5f5d59;
+ --ad-color-text-tertiary: #888681;
+
+ /* Accent */
+ --ad-color-accent: #3a6a63;
+ --ad-color-accent-fg: #ffffff;
+ --ad-color-accent-ink: color-mix(in oklch, var(--ad-color-accent) 78%, black);
+ --ad-color-accent-soft: color-mix(in oklch, var(--ad-color-accent) 12%, white);
+
+ /* Status (fixed — never swapped like the accent) */
+ --ad-color-success: #3f7a4a;
+ --ad-color-success-bg: color-mix(in oklch, var(--ad-color-success) 12%, white);
+ --ad-color-danger: #ad3a2c;
+ --ad-color-danger-bg: color-mix(in oklch, var(--ad-color-danger) 10%, white);
+
+ /* Type families */
+ --ad-font-sans: -apple-system, "Helvetica Neue", Helvetica, Arial, system-ui, sans-serif;
+ --ad-font-mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, Consolas, "Courier New", monospace;
+ --ad-font-serif: Georgia, "Times New Roman", "Iowan Old Style", serif;
+
+ /* Type steps — fluid (Utopia scale), the design system's own body-text size */
+ --ad-step--2: clamp(0.7813rem, 0.7736rem + 0.0341vw, 0.8rem);
+ --ad-step--1: clamp(0.9375rem, 0.9119rem + 0.1136vw, 1rem);
+ --ad-step-0: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-step-1: clamp(1.35rem, 1.2631rem + 0.3864vw, 1.5625rem);
+ --ad-step-2: clamp(1.62rem, 1.4837rem + 0.6057vw, 1.9531rem);
+ --ad-step-3: clamp(1.944rem, 1.7405rem + 0.9044vw, 2.4414rem);
+ --ad-step-4: clamp(2.3328rem, 2.0387rem + 1.3072vw, 3.0518rem);
+ --ad-step-5: clamp(2.7994rem, 2.384rem + 1.8461vw, 3.8147rem);
+
+ /* Space */
+ --ad-space-3xs: clamp(0.3125rem, 0.3125rem + 0vw, 0.3125rem);
+ --ad-space-2xs: clamp(0.5625rem, 0.5369rem + 0.1136vw, 0.625rem);
+ --ad-space-xs: clamp(0.875rem, 0.8494rem + 0.1136vw, 0.9375rem);
+ --ad-space-s: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem);
+ --ad-space-m: clamp(1.6875rem, 1.6108rem + 0.3409vw, 1.875rem);
+ --ad-space-l: clamp(2.25rem, 2.1477rem + 0.4545vw, 2.5rem);
+ --ad-space-xl: clamp(3.375rem, 3.2216rem + 0.6818vw, 3.75rem);
+ --ad-space-2xl: clamp(4.5rem, 4.2955rem + 0.9091vw, 5rem);
+ --ad-space-3xl: clamp(6.75rem, 6.4432rem + 1.3636vw, 7.5rem);
+
+ /* Radius */
+ --ad-radius-sm: 4px;
+ --ad-radius-md: 6px;
+ --ad-radius-lg: 10px;
+ --ad-radius-full: 999px;
+ --ad-border-w: 1px;
+
+ /* Elevation */
+ --ad-shadow-sm: 0 1px 2px rgba(20, 18, 14, 0.05);
+ --ad-shadow-float: 0 2px 6px rgba(20, 18, 14, 0.06), 0 10px 24px -12px rgba(20, 18, 14, 0.18);
+
+ /* Motion */
+ --ad-dur-fast: 0.12s;
+ --ad-dur-base: 0.16s;
+ --ad-ease: cubic-bezier(0.2, 0, 0, 1);
+}
+
+@media (prefers-color-scheme: dark) {
+ :root:not([data-theme="light"]) {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+ }
+}
+
+:root[data-theme="dark"] {
+ color-scheme: dark;
+ --ad-color-surface-page: #0e0d0b;
+ --ad-color-surface-card: #151412;
+ --ad-color-surface-fill: #1e1d1a;
+ --ad-color-surface-fill-2: #252421;
+ --ad-color-border-hairline: #2c2b29;
+ --ad-color-border-default: #44423f;
+ --ad-color-text-primary: #e9e8e6;
+ --ad-color-text-secondary: #a09e9a;
+ --ad-color-text-tertiary: #7c7a76;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ :root {
+ --ad-dur-fast: 0ms;
+ --ad-dur-base: 0ms;
+ }
+}
+
+/* A short reset. Everything it sets is something the system would otherwise
+ have to override in every component.
+
+ The rem values used for this documentation site's own chrome (measure,
+ content width, sidebar width, header height) belong to this site, not to
+ the design system it documents — the source design system defines no
+ container-width tokens at all, so there is nothing under `--ad-*` to reach
+ for here. See the header comment in site/shell.css. */
+
+*,
+*::before,
+*::after { box-sizing: border-box; }
+
+html {
+ -webkit-text-size-adjust: 100%;
+ scroll-behavior: smooth;
+ scroll-padding-top: calc(3.5rem + var(--ad-space-s));
+}
+
+@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;
+ }
+}
+
+body {
+ margin: 0;
+ background: var(--ad-color-surface-page);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ line-height: 1.55;
+ font-synthesis-weight: none;
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+}
+
+::selection {
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+}
+
+h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd { margin: 0; }
+ul[class], ol[class] { list-style: none; margin: 0; padding: 0; }
+
+img, svg, video { display: block; max-width: 100%; height: auto; }
+
+button, input, select, textarea {
+ font: inherit;
+ color: inherit;
+}
+
+hr {
+ height: 1px;
+ margin: var(--ad-space-l) 0;
+ border: 0;
+ background: var(--ad-color-border-hairline);
+}
+
+/* One focus ring, defined once. No component is allowed its own. */
+:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+ border-radius: var(--ad-radius-sm);
+}
+:focus:not(:focus-visible) { outline: none; }
+
+[hidden] { display: none !important; }
+
+.ad-visually-hidden {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip-path: inset(50%);
+ white-space: nowrap;
+ border: 0;
+}
+
+/* Every rule here reads a type token for family and size. Weight, line-height,
+ and letter-spacing are hardcoded to the exact numbers the source ships —
+ there is no `--ad-weight-*` / `--ad-leading-*` / `--ad-tracking-*` scale in
+ the real system, only eight fixed compound styles (see the Typography
+ foundation page, which this file implements verbatim). */
+
+.type-display { font: 600 var(--ad-step-4)/1.08 var(--ad-font-sans); letter-spacing: -0.015em; }
+.type-h1, h1 { font: 600 var(--ad-step-3)/1.12 var(--ad-font-sans); letter-spacing: -0.012em; }
+.type-h2, h2 { font: 600 var(--ad-step-2)/1.16 var(--ad-font-sans); letter-spacing: -0.01em; }
+.type-h3, h3 { font: 600 var(--ad-step-1)/1.25 var(--ad-font-sans); }
+.type-body { font: 400 var(--ad-step-0)/1.55 var(--ad-font-sans); }
+.type-body-sm { font: 400 var(--ad-step--1)/1.5 var(--ad-font-sans); }
+.type-label {
+ font: 500 var(--ad-step--2)/1 var(--ad-font-mono);
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: var(--ad-color-text-secondary);
+}
+.type-mono { font: 400 var(--ad-step--1)/1.5 var(--ad-font-mono); }
+
+/* Prose defaults for the documentation body copy. These read the same
+ tokens the compound classes above do; they are not a second scale. */
+h1, h2, h3, h4 { font-weight: 600; text-wrap: balance; }
+
+h4 { font: 600 var(--ad-step-0)/1.3 var(--ad-font-sans); }
+
+p, ul, ol { margin: 0 0 var(--ad-space-s); }
+li + li { margin-top: var(--ad-space-3xs); }
+
+a {
+ color: var(--ad-color-accent);
+ text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
+ text-underline-offset: 0.18em;
+ text-decoration-thickness: 1px;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ text-decoration-color var(--ad-dur-fast) var(--ad-ease);
+}
+a:hover {
+ color: var(--ad-color-accent-ink);
+ text-decoration-color: currentColor;
+}
+
+strong, b { font-weight: 600; }
+
+small { font-size: var(--ad-step--1); }
+
+.ad-lead {
+ font-size: var(--ad-step-1);
+ line-height: 1.5;
+ color: var(--ad-color-text-secondary);
+ max-width: 68ch;
+ text-wrap: pretty;
+}
+
+.ad-muted { color: var(--ad-color-text-secondary); }
+.ad-subtle { color: var(--ad-color-text-tertiary); }
+
+code, kbd, samp, pre {
+ font-family: var(--ad-font-mono);
+ font-feature-settings: "liga" 0;
+}
+
+:not(pre) > code {
+ padding: 0.15em 0.4em;
+ font-size: 0.875em;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-sm);
+ white-space: nowrap;
+}
+
+kbd {
+ display: inline-block;
+ padding: 0.1em 0.45em;
+ font-size: var(--ad-step--2);
+ line-height: 1.6;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-bottom-width: 2px;
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-secondary);
+}
+
+blockquote {
+ margin-block: var(--ad-space-s);
+ padding-left: var(--ad-space-s);
+ border-left: 2px solid var(--ad-color-accent);
+ color: var(--ad-color-text-secondary);
+}
+
+/* Button
+ A flat, quiet button that recedes rather than performs — no gradients, no
+ shine. Variants: default, primary, dark, ghost, danger. Sizes: sm, md
+ (default), lg — padding and font step only, weight and radius stay put.
+
+ The contract is the element itself: a bare <button> is styled directly, no
+ class required. An <a> that acts like a button (navigates rather than
+ changes something — see the Button page's "Links that act like buttons")
+ opts in with a single `.btn` class, since a bare `a` selector would catch
+ every ordinary prose link on the site. `:not(.icon-button)` keeps this
+ block from leaking onto IconButton, which shares the <button> tag but is
+ an entirely different shape. */
+
+button:not(.icon-button),
+a.btn {
+ --_bg: var(--ad-color-surface-card);
+ --_fg: var(--ad-color-text-primary);
+ --_border: var(--ad-color-border-default);
+ --_pad: var(--ad-space-xs) var(--ad-space-s);
+ --_font: var(--ad-step--1);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: var(--ad-space-2xs);
+ padding: var(--_pad);
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-sans);
+ font-size: var(--_font);
+ font-weight: 550;
+ letter-spacing: -0.01em;
+ line-height: 1.2;
+ text-align: center;
+ text-decoration: none;
+ white-space: nowrap;
+ cursor: pointer;
+ transition: filter var(--ad-dur-fast) var(--ad-ease),
+ border-color var(--ad-dur-fast) var(--ad-ease);
+}
+
+:is(button:not(.icon-button), a.btn):hover:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.96); }
+:is(button:not(.icon-button), a.btn):active:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.92); }
+
+button:not(.icon-button):disabled,
+:is(button:not(.icon-button), a.btn)[aria-disabled="true"] {
+ opacity: 0.5;
+ cursor: default;
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="primary"] {
+ --_bg: var(--ad-color-accent);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-accent);
+}
+
+/* Reserved for the single most-committing action in a view — a final Save,
+ never a whole row of them. */
+:is(button:not(.icon-button), a.btn)[data-variant="dark"] {
+ --_bg: var(--ad-color-text-primary);
+ --_fg: var(--ad-color-surface-page);
+ --_border: var(--ad-color-text-primary);
+}
+
+:is(button:not(.icon-button), a.btn)[data-variant="ghost"] {
+ --_bg: transparent;
+ --_fg: var(--ad-color-text-primary);
+ --_border: transparent;
+}
+
+/* The source hardcodes this label white rather than deriving it — danger
+ is not "on a filled accent surface", so --ad-color-accent-fg is reused
+ here only because its resolved value is the same literal white, not
+ because the two roles are the same thing. */
+:is(button:not(.icon-button), a.btn)[data-variant="danger"] {
+ --_bg: var(--ad-color-danger);
+ --_fg: var(--ad-color-accent-fg);
+ --_border: var(--ad-color-danger);
+}
+
+:is(button:not(.icon-button), a.btn)[data-size="sm"] { --_pad: 0.375rem 0.75rem; --_font: 0.8125rem; }
+:is(button:not(.icon-button), a.btn)[data-size="lg"] { --_pad: 0.75rem 1.375rem; --_font: var(--ad-step-0); }
+
+:is(button:not(.icon-button), a.btn)[data-block] { width: 100%; }
+
+/* Button group — [role="group"] is already the correct ARIA role for a
+ toolbar of related buttons, so it doubles as the styling hook for free. */
+[role="group"] {
+ display: inline-flex;
+ isolation: isolate;
+}
+[role="group"] > button { border-radius: 0; }
+[role="group"] > button:first-child { border-start-start-radius: var(--ad-radius-md); border-end-start-radius: var(--ad-radius-md); }
+[role="group"] > button:last-child { border-start-end-radius: var(--ad-radius-md); border-end-end-radius: var(--ad-radius-md); }
+[role="group"] > button + button { margin-inline-start: -1px; }
+[role="group"] > button:hover,
+[role="group"] > button:focus-visible { z-index: 1; }
+
+/* IconButton — a square icon-only button. Always needs an aria-label; it
+ becomes both the accessible name and the native tooltip. There is no
+ attribute that distinguishes "a button that is only an icon" from a plain
+ Button, so this is the one sanctioned class in this file. */
+.icon-button {
+ --_size: 2.125rem;
+ --_bg: transparent;
+ --_border: transparent;
+ --_fg: var(--ad-color-text-secondary);
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ padding: 0;
+ flex: none;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-md);
+ background: var(--_bg);
+ color: var(--_fg);
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease),
+ color var(--ad-dur-fast) var(--ad-ease);
+}
+.icon-button:hover:not(:disabled) { background: var(--ad-color-surface-fill-2); }
+.icon-button:disabled { opacity: 0.5; cursor: default; }
+
+.icon-button[data-size="sm"] { --_size: 1.75rem; }
+.icon-button[data-size="lg"] { --_size: 2.5rem; }
+
+.icon-button[data-variant="outline"] { --_bg: var(--ad-color-surface-card); --_border: var(--ad-color-border-default); --_fg: var(--ad-color-text-primary); }
+.icon-button[data-variant="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); }
+
+/* Field
+ The labeled field wrapper — a <label> that wraps its own caption text
+ together with the control, so the association is native and needs no
+ `for`/`id` pair. The caption is mono, uppercase, tracked — the same
+ catalog-label convention as Badge and type-label.
+
+ A <label> is reused for three different shapes across this system (this
+ wrapper, Checkbox/Radio's row, Switch's row), so it cannot be styled bare
+ without one shape leaking into another. Field is the one of the three with
+ no distinguishing attribute of its own, so it gets the sanctioned class;
+ Checkbox/Radio tell themselves apart structurally instead (see below). */
+.field {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ min-width: 0;
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Hint and error read the native fine-print element. `role="alert"` is a
+ real live-region role for validation text, so it doubles as the selector
+ that turns a hint red. */
+.field > small {
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+.field > small[role="alert"] { color: var(--ad-color-danger); }
+
+/* Input / Select — bordered, matched to each other exactly. Both tags ARE
+ the component, so they are styled bare; nothing else on this site uses a
+ text input or a select. */
+input:not([type="checkbox"]):not([type="radio"]),
+select {
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
+ outline: none;
+ transition: border-color var(--ad-dur-fast) var(--ad-ease),
+ box-shadow var(--ad-dur-fast) var(--ad-ease);
+}
+input::placeholder { color: var(--ad-color-text-tertiary); }
+
+input:not([type="checkbox"]):not([type="radio"]):focus,
+select:focus {
+ border-color: var(--ad-color-accent);
+ box-shadow: 0 0 0 2px var(--ad-color-accent-soft);
+}
+
+input:not([type="checkbox"]):not([type="radio"]):disabled,
+select:disabled {
+ opacity: 0.5;
+ cursor: default;
+}
+
+/* The chevron is the one flagged color exception in this component set:
+ CSS cannot point a data-URI background-image at a custom property, so its
+ stroke is a literal hex rather than a var(--ad-*) reference. It is picked
+ to match --ad-color-text-secondary and commented here so it is not
+ mistaken for a stray color. */
+select {
+ appearance: none;
+ padding-inline-end: var(--ad-space-l);
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%235f5d59' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right var(--ad-space-xs) center;
+}
+
+input[aria-invalid="true"],
+select[aria-invalid="true"] {
+ border-color: var(--ad-color-danger);
+ background-color: var(--ad-color-danger-bg);
+}
+
+/* Checkbox / Radio — the real native controls, tinted with accent-color
+ instead of a hand-drawn box. This is not just a rename: it deletes the
+ custom SVG-checkmark markup entirely and lets the browser draw its own
+ (platform-appropriate) check and dot. `role="switch"` is excluded here so
+ Switch — a checkbox playing a different role — can style itself below
+ without this rule fighting it. A <label> wrapping a checkbox/radio is told
+ apart from Field's <label> structurally: it :has() one as a direct child,
+ no class required. */
+input[type="checkbox"]:not([role="switch"]),
+input[type="radio"] {
+ width: 1.125rem;
+ height: 1.125rem;
+ margin: 0;
+ accent-color: var(--ad-color-accent);
+ cursor: pointer;
+}
+input[type="checkbox"]:not([role="switch"]):focus-visible,
+input[type="radio"]:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
+}
+
+label:has(> input[type="checkbox"]:not([role="switch"])),
+label:has(> input[type="radio"]) {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 400;
+ text-transform: none;
+ letter-spacing: normal;
+ color: var(--ad-color-text-primary);
+}
+label:has(> input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Switch — for on/off settings only, never a substitute for Checkbox.
+ `input[type="checkbox"][role="switch"]` is real ARIA (the correct role for
+ a toggle), not an invented hook, but the visual track+thumb needs a second
+ element for the thumb's independent position and transition — more than
+ `appearance: none` plus pseudo-elements on the input itself can cleanly
+ carry — so `.switch` is a deliberate, documented fallback class rather
+ than a first choice. */
+.switch {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+ cursor: pointer;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ color: var(--ad-color-text-primary);
+}
+.switch input[role="switch"] { position: absolute; opacity: 0; width: 2.25rem; height: 1.25rem; margin: 0; }
+
+.switch span {
+ position: relative;
+ flex: none;
+ width: 2.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-border-default);
+ transition: background-color var(--ad-dur-base) var(--ad-ease);
+}
+.switch span::after {
+ content: "";
+ position: absolute;
+ inset-block-start: 2px;
+ inset-inline-start: 2px;
+ width: 1rem;
+ height: 1rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-fg);
+ box-shadow: var(--ad-shadow-sm);
+ transition: translate var(--ad-dur-base) var(--ad-ease);
+}
+.switch input[role="switch"]:checked + span { background: var(--ad-color-accent); }
+.switch input[role="switch"]:checked + span::after { translate: 1rem 0; }
+.switch input[role="switch"]:focus-visible + span { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+.switch:has(input:disabled) { cursor: default; opacity: 0.5; }
+
+/* Rows of fields — used by the settings-form pattern. `<form>`/`<fieldset>`
+ are the elements that already mean this, so they are styled bare. A row
+ grouping two fields side by side has no element of its own, so it keeps
+ one plain class. */
+form {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-s);
+ max-width: 28rem;
+}
+.form-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
+ gap: var(--ad-space-xs);
+}
+fieldset {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ margin: 0;
+ padding: 0;
+ border: 0;
+}
+fieldset > legend {
+ padding: 0 0 var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--ad-color-text-secondary);
+}
+
+/* Badge
+ Mono, uppercase, tracked — the catalog-label convention for status and
+ meta values (SAVED, OVERDUE, RECOMMENDED). A label, never a button.
+
+ A <span> is too generic to style globally — it is used all over running
+ prose for unrelated reasons — so Badge gets the one sanctioned class this
+ shape needs. Tone is an attribute, not a modifier class. */
+
+.badge {
+ --_bg: var(--ad-color-surface-fill);
+ --_fg: var(--ad-color-text-secondary);
+ --_border: var(--ad-color-border-hairline);
+
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.1875rem 0.5rem;
+ border: 1px solid var(--_border);
+ border-radius: var(--ad-radius-sm);
+ background: var(--_bg);
+ color: var(--_fg);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ line-height: 1;
+ white-space: nowrap;
+}
+
+.badge[data-tone="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); --_border: transparent; }
+.badge[data-tone="success"] { --_bg: var(--ad-color-success-bg); --_fg: var(--ad-color-success); --_border: transparent; }
+.badge[data-tone="danger"] { --_bg: var(--ad-color-danger-bg); --_fg: var(--ad-color-danger); --_border: transparent; }
+
+.badge-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-2xs);
+}
+
+/* Tag
+ A removable mono chip for freeform tags — locations, categories.
+ `[data-dashed]` renders an "add tag" affordance instead of a value. Same
+ reasoning as Badge: <span> is too generic to select bare, so Tag gets one
+ class. The remove control is a real <button>, styled through the
+ descendant selector rather than a class of its own. */
+
+.tag {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.375rem;
+ padding: 0.25rem 0.625rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-default);
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ line-height: 1;
+}
+
+.tag[data-dashed] {
+ background: transparent;
+ border-style: dashed;
+ color: var(--ad-color-text-tertiary);
+ cursor: pointer;
+}
+
+.tag button {
+ all: unset;
+ cursor: pointer;
+ color: var(--ad-color-text-tertiary);
+ line-height: 1;
+ font-size: 0.75rem;
+}
+.tag button:hover { color: var(--ad-color-text-primary); }
+
+.tag-set {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.375rem;
+}
+
+/* Card
+ Flat surface, hairline border, radius-lg. `[data-elevated]` is opt-in and
+ reserved for the one focused/primary card in a view — a dialog, the
+ primary capture card — flat is the default everywhere else.
+
+ A <div>/<article> is too generic to select bare, so Card keeps one
+ sanctioned class. When Card's job is holding a list of rows — the common
+ case, entry lists, task lists, settings rows — it is a real <ul class="
+ card">, and CardRow is a bare <li> underneath it: no second class needed,
+ the row look comes from the `.card > li` descendant selector alone. The
+ first row's top border is removed automatically so callers never have to
+ override it by hand. */
+
+.card {
+ display: flex;
+ flex-direction: column;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: none;
+ overflow: hidden;
+}
+
+.card[data-elevated] { box-shadow: var(--ad-shadow-float); }
+
+.card > li {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+}
+.card > li:first-child { border-top: none; }
+
+/* A row whose ENTIRE content is wrapped in a real <button> or <a> — the
+ accessible way to make a row clickable — fills the row edge-to-edge and
+ picks up a hover fill and pointer cursor. `:only-child` is what tells this
+ apart from a row that merely contains a small button among other content
+ (a leading checkbox next to a title and a trailing value, say): only a
+ button/anchor that IS the row's one and only child means "the whole row
+ is the control." */
+.card > li:has(> button:only-child),
+.card > li:has(> a:only-child) {
+ padding: 0;
+}
+.card > li > button:only-child,
+.card > li > a:only-child {
+ all: unset;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ width: 100%;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ box-sizing: border-box;
+ color: inherit;
+ font: inherit;
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.card > li > button:only-child:hover,
+.card > li > a:only-child:hover { background: var(--ad-color-surface-fill); }
+
+/* Documentation-only extras: a body/footer pair and a responsive grid, used
+ by the prose pages (rationale.md, index.md) to show a Card as a plain
+ content tile rather than a data row. Neither is part of Card's props —
+ they exist because a content tile's inner layout has no element of its
+ own to hang a selector on. */
+.card-body {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-m);
+}
+.card-footer {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ margin-top: auto;
+ padding: var(--ad-space-xs) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+
+.card[data-link] { transition: border-color var(--ad-dur-fast) var(--ad-ease), box-shadow var(--ad-dur-fast) var(--ad-ease); }
+.card[data-link]:hover { border-color: var(--ad-color-border-default); box-shadow: var(--ad-shadow-sm); }
+.card[data-link]:focus-within { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+
+.card-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
+ gap: var(--ad-space-s);
+}
+
+/* Avatar — lives here because it almost never appears outside a card or a
+ row (the Loop dashboard's initials chip, Archive's file-type chip). A
+ <span> is too generic for a bare selector, so it keeps one class. */
+.avatar {
+ --_size: 1.875rem;
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--_size);
+ height: var(--_size);
+ flex: none;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent-soft);
+ color: var(--ad-color-accent-ink);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--2);
+ font-weight: 600;
+ overflow: hidden;
+}
+.avatar[data-square] { border-radius: var(--ad-radius-md); background: var(--ad-color-surface-fill); color: var(--ad-color-text-secondary); border: 1px solid var(--ad-color-border-hairline); }
+.avatar[data-size="sm"] { --_size: 1.5rem; }
+.avatar[data-size="lg"] { --_size: 2.25rem; }
+
+/* Tooltip — a dark mono label on hover, positioned above the child. Pure CSS:
+ the trigger and bubble are siblings inside one relatively-positioned span,
+ shown on :hover/:focus-within so it works from the keyboard too.
+
+ `[role="tooltip"]` is the correct ARIA role for the bubble itself, so it
+ doubles as the styling hook — zero classes there. The wrapping span has no
+ attribute of its own, but it does not need one: `:has()` picks out "a span
+ that contains a [role=tooltip] bubble" precisely, without reaching for a
+ class on an otherwise-generic element. */
+span:has(> [role="tooltip"]) {
+ position: relative;
+ display: inline-flex;
+}
+
+[role="tooltip"] {
+ position: absolute;
+ bottom: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ margin-bottom: 0.375rem;
+ padding: 0.25rem 0.5rem;
+ border-radius: var(--ad-radius-sm);
+ background: var(--ad-color-text-primary);
+ color: var(--ad-color-surface-page);
+ font-family: var(--ad-font-mono);
+ /* 11px is the source's own literal choice — smaller than --ad-step--2, the
+ smallest step on the scale, because a tooltip is meant to read as a
+ glance-only aside rather than body text. */
+ font-size: 11px;
+ white-space: nowrap;
+ line-height: 1.4;
+ z-index: 20;
+ opacity: 0;
+ visibility: hidden;
+ pointer-events: none;
+ transition: opacity var(--ad-dur-fast) var(--ad-ease);
+}
+span:has(> [role="tooltip"]):hover > [role="tooltip"],
+span:has(> [role="tooltip"]):focus-within > [role="tooltip"] {
+ opacity: 1;
+ visibility: visible;
+}
+
+/* Toast — the real pattern used for delete confirmations: a flat bar with an
+ optional destructive action and a dismiss. `[data-fixed]` pins it
+ bottom-right for real use; without it the toast stays in document flow for
+ documentation. `role="status"` is the correct live-region role for a
+ transient status message, so it is both the accessibility hook and the
+ style selector — no base class needed. The action and dismiss buttons are
+ plain <button>s told apart by `aria-label="Dismiss"`, an attribute they
+ need anyway for the icon-only close control. */
+[role="status"] {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ min-width: 20rem;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--2);
+}
+[role="status"][data-fixed] {
+ position: fixed;
+ inset-block-end: var(--ad-space-s);
+ inset-inline-end: var(--ad-space-s);
+ z-index: 60;
+}
+[role="status"] > span { flex: 1; }
+[role="status"] button {
+ all: unset;
+ cursor: pointer;
+ font-weight: 600;
+ color: var(--ad-color-danger);
+}
+[role="status"] button[aria-label="Dismiss"] {
+ color: var(--ad-color-text-tertiary);
+ font-size: 1rem;
+ font-weight: 400;
+ line-height: 1;
+}
+[role="status"] button[aria-label="Dismiss"]:hover { color: var(--ad-color-text-primary); }
+
+/* Dialog — the native <dialog> element: a centered modal, flat surface, no
+ colored header, hairline dividers between title/body/footer. A <header>
+ holds the title and a <footer> holds the actions, matching the elements
+ the tag already wants; everything else in between is the body, styled by
+ excluding those two rather than by a class of its own.
+
+ `.dialog-demo` scopes a static, always-open example to its own preview
+ container instead of the viewport, so it can sit safely inside a
+ documentation page — this is a doc-only affordance, not part of Dialog's
+ contract. It relies on the browser's own dialog centering: the UA
+ stylesheet already gives an open <dialog> `position: absolute; inset: 0;
+ margin: auto`, which centers it inside the nearest positioned ancestor —
+ here, `.dialog-demo` itself. A real `.showModal()` call gets the genuine
+ viewport-fixed overlay and `::backdrop` for free; a plain `open` attribute
+ (used here so the demo renders without JavaScript) does not paint a
+ backdrop at all, since `::backdrop` only exists for a dialog shown
+ modally — wire the real open/close state and call `.showModal()` in the
+ consuming app. */
+.dialog-demo {
+ position: relative;
+ min-height: 20rem;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-lg);
+}
+
+dialog {
+ width: 26rem;
+ max-width: calc(100% - (2 * var(--ad-space-s)));
+ height: fit-content;
+ padding: 0;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
+ overflow: hidden;
+}
+dialog::backdrop {
+ background: rgba(20, 18, 14, 0.35);
+}
+dialog > header {
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-sans);
+ font-weight: 600;
+ font-size: var(--ad-step-0);
+ color: var(--ad-color-text-primary);
+}
+dialog > footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: var(--ad-space-2xs);
+ padding: var(--ad-space-s) var(--ad-space-m);
+ border-top: 1px solid var(--ad-color-border-hairline);
+}
+dialog > *:not(header):not(footer) {
+ padding: var(--ad-space-m);
+ margin: 0;
+ color: var(--ad-color-text-primary);
+ font-size: var(--ad-step--1);
+}
+
+/* Tabs
+ Follows the real WAI-ARIA tabs pattern: a `[role="tablist"]` of
+ `[role="tab"]` buttons controlling `[role="tabpanel"]`s. Those roles are
+ required for the pattern to be accessible in the first place, so they
+ double as the entire styling contract — no classes at all for the base
+ shape. The keyboard wiring lives in assets/js/system.js and is
+ variant-agnostic — it only cares about `[data-tabs]` / `[role=tab]`.
+
+ Two real variants: `underline` (mono, accent underline — primary section
+ nav, the default) and `segmented` (pill track, active segment lifts to the
+ surface — filters like All · Overdue · Upcoming, never primary
+ navigation), chosen with `data-variant="segmented"` on the tablist since
+ neither has a natural attribute of its own. */
+
+/* Every rule below the list container pairs [role="tablist"] with
+ [role="tab"] rather than styling [role="tab"] alone — Tab buttons are
+ still literal <button> elements, so the compound selector's extra
+ specificity is what keeps Button's base styling (button.css) from
+ winning the cascade on shared properties like padding and background. */
+
+[role="tablist"] {
+ display: flex;
+ gap: var(--ad-space-m);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+[role="tablist"] [role="tab"] {
+ padding: 0.5rem 0;
+ margin-bottom: -1px;
+ background: none;
+ border: 0;
+ border-bottom: 2px solid transparent;
+ color: var(--ad-color-text-secondary);
+ font-family: var(--ad-font-mono);
+ font-size: var(--ad-step--1);
+ letter-spacing: 0.02em;
+ cursor: pointer;
+ transition: color var(--ad-dur-fast) var(--ad-ease), border-color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"] [role="tab"]:hover { color: var(--ad-color-text-primary); }
+[role="tablist"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ border-bottom-color: var(--ad-color-accent);
+}
+
+[role="tablist"][data-variant="segmented"] {
+ display: inline-flex;
+ gap: 0.125rem;
+ padding: 0.1875rem;
+ border-bottom: 0;
+ background: var(--ad-color-surface-fill);
+ border-radius: var(--ad-radius-md);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"] {
+ all: unset;
+ cursor: pointer;
+ padding: 0.375rem 0.875rem;
+ border-radius: var(--ad-radius-sm);
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step--1);
+ font-weight: 550;
+ color: var(--ad-color-text-secondary);
+ transition: background-color var(--ad-dur-fast) var(--ad-ease), color var(--ad-dur-fast) var(--ad-ease);
+}
+[role="tablist"][data-variant="segmented"] [role="tab"][aria-selected="true"] {
+ color: var(--ad-color-text-primary);
+ background: var(--ad-color-surface-card);
+ box-shadow: var(--ad-shadow-sm);
+}
+
+[role="tabpanel"] { padding-top: var(--ad-space-s); }
+[role="tabpanel"][hidden] { display: none; }
+[role="tabpanel"]:focus-visible { outline-offset: 4px; }
+
+/* Documentation site chrome. Everything below the `.ad-doc-` prefix belongs
+ to this site rather than to the design system it documents — including the
+ width/height constants used directly below. The source design system
+ defines no container-width tokens at all, so there is nothing under
+ `--ad-*` for a doc shell to reach for; the Space foundation page's own
+ notes say to omit that group entirely rather than invent one. */
+
+.ad-doc-skip {
+ position: absolute;
+ inset-inline-start: var(--ad-space-xs);
+ inset-block-start: -4rem;
+ z-index: 40;
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ box-shadow: var(--ad-shadow-float);
+ transition: inset-block-start var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-skip:focus { inset-block-start: var(--ad-space-2xs); }
+
+.ad-doc-header {
+ position: sticky;
+ inset-block-start: 0;
+ z-index: 30;
+ height: 3.5rem;
+ background: color-mix(in srgb, var(--ad-color-surface-page) 92%, transparent);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-header__inner {
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ height: 100%;
+ max-width: 72rem;
+ margin-inline: auto;
+ padding-inline: var(--ad-space-xs);
+}
+
+.ad-doc-wordmark {
+ display: flex;
+ align-items: baseline;
+ gap: var(--ad-space-2xs);
+ color: var(--ad-color-text-primary);
+ text-decoration: none;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-0);
+ font-weight: 600;
+ letter-spacing: -0.01em;
+ white-space: nowrap;
+}
+.ad-doc-wordmark:hover { color: var(--ad-color-text-primary); }
+.ad-doc-wordmark__mark {
+ width: 1.375rem;
+ height: 1.375rem;
+ flex: none;
+ align-self: center;
+ color: var(--ad-color-accent);
+}
+.ad-doc-wordmark__version {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 400;
+ color: var(--ad-color-text-tertiary);
+}
+
+.ad-doc-header__actions { margin-inline-start: auto; display: flex; align-items: center; gap: var(--ad-space-2xs); }
+
+.ad-doc-layout {
+ display: grid;
+ grid-template-columns: 16rem minmax(0, 1fr);
+ gap: var(--ad-space-l);
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-m) var(--ad-space-xs) var(--ad-space-2xl);
+}
+
+.ad-doc-sidebar {
+ position: sticky;
+ inset-block-start: calc(3.5rem + var(--ad-space-m));
+ align-self: start;
+ max-height: calc(100vh - 3.5rem - var(--ad-space-l));
+ overflow-y: auto;
+ padding-inline-end: var(--ad-space-2xs);
+}
+.ad-doc-nav__group + .ad-doc-nav__group { margin-top: var(--ad-space-s); }
+.ad-doc-nav__heading {
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-doc-nav__link {
+ display: block;
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ margin-inline-start: calc(var(--ad-space-xs) * -1);
+ border-inline-start: 2px solid transparent;
+ border-radius: 0 var(--ad-radius-sm) var(--ad-radius-sm) 0;
+ color: var(--ad-color-text-secondary);
+ font-size: var(--ad-step--1);
+ text-decoration: none;
+ transition: color var(--ad-dur-fast) var(--ad-ease),
+ background-color var(--ad-dur-fast) var(--ad-ease);
+}
+.ad-doc-nav__link:hover { color: var(--ad-color-text-primary); background: var(--ad-color-surface-fill); }
+.ad-doc-nav__link[aria-current="page"] {
+ color: var(--ad-color-accent-ink);
+ border-inline-start-color: var(--ad-color-accent);
+ background: var(--ad-color-accent-soft);
+ font-weight: 500;
+}
+
+.ad-doc-main {
+ min-width: 0;
+ /* The design system's own body-text token (--ad-step-0, 18–20px) is right
+ for a product screen but too large for long-form documentation prose.
+ This site's own copy gets a plain, fixed 16px instead — the token above
+ is unchanged, and specimen tables/demos below still show its real
+ value. */
+ font-size: 1rem;
+}
+.ad-doc-main > * { max-width: 50rem; }
+
+.ad-doc-title { margin-bottom: var(--ad-space-xs); }
+.ad-doc-title__section {
+ display: block;
+ margin-bottom: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ad-color-accent);
+}
+
+.ad-doc-main h2 {
+ margin-block: var(--ad-space-2xl) var(--ad-space-xs);
+ padding-bottom: var(--ad-space-2xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-doc-main h3 { margin-block: var(--ad-space-l) var(--ad-space-2xs); }
+.ad-doc-main h4 { margin-block: var(--ad-space-s) var(--ad-space-2xs); }
+.ad-doc-main p,
+.ad-doc-main ul,
+.ad-doc-main ol { max-width: 68ch; }
+.ad-doc-main ul:not([class]),
+.ad-doc-main ol:not([class]) { padding-inline-start: var(--ad-space-s); }
+
+.ad-doc-footer {
+ max-width: 72rem;
+ margin-inline: auto;
+ padding: var(--ad-space-s) var(--ad-space-xs) var(--ad-space-l);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ color: var(--ad-color-text-tertiary);
+ font-size: var(--ad-step--1);
+}
+.ad-doc-footer__inner { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); justify-content: space-between; }
+
+/* `button.ad-doc-nav-toggle` (element + class) rather than a bare class:
+ button.css's own base rule is `button:not(.icon-button)`, which is also
+ one element + one class in specificity and sets `display: inline-flex` —
+ a bare `.ad-doc-nav-toggle { display: none }` loses that fight and the
+ toggle stays visible at every width. Matching its specificity here (and
+ coming later in the concatenated stylesheet) settles it in source order. */
+button.ad-doc-nav-toggle { display: none; }
+
+@media (max-width: 60rem) {
+ .ad-doc-layout { grid-template-columns: minmax(0, 1fr); gap: var(--ad-space-s); }
+ .ad-doc-sidebar {
+ position: static;
+ max-height: none;
+ }
+ /* The toggle is a normal Button; the panel it opens gets its own card
+ treatment. Putting the same card styling on the <aside> wrapper too
+ nested a button-shaped box inside an outer box for no reason. */
+ .ad-doc-nav {
+ margin-top: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ }
+ .ad-doc-sidebar[data-collapsed="true"] > .ad-doc-nav { display: none; }
+ button.ad-doc-nav-toggle {
+ display: flex;
+ width: 100%;
+ justify-content: space-between;
+ }
+}
+
+@media (max-width: 34rem) {
+ .ad-doc-wordmark__version { display: none; }
+ .ad-doc-layout { padding-inline: var(--ad-space-2xs); }
+}
+
+/* The live-example figure used throughout the docs — see the "How the
+ example demos work" section on the Using the CSS page. One class on the
+ outer <figure>; everything inside is styled by plain element selectors,
+ since figcaption/div/details/summary/pre already say what they are.
+ `data-layout` and `data-surface` on the preview <div> distinguish the
+ handful of demos that need a non-default arrangement. */
+
+.example {
+ margin: var(--ad-space-s) 0 var(--ad-space-l);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+ background: var(--ad-color-surface-card);
+ overflow: hidden;
+}
+
+.example figcaption {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-secondary);
+}
+
+.example > div {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--ad-space-xs);
+ padding: var(--ad-space-s);
+}
+/* nowrap is load-bearing: a column-direction flex container with wrap enabled
+ sizes its single line against the container rather than the content, which
+ left every stacked example with a few hundred pixels of dead space. */
+.example > div[data-layout="stack"] { flex-direction: column; flex-wrap: nowrap; align-items: stretch; }
+.example > div[data-layout="grid"] {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
+ align-items: start;
+}
+.example > div[data-surface="sunken"] { background: var(--ad-color-surface-fill); }
+.example > div > * { min-width: 0; }
+
+.example details {
+ border-top: 1px solid var(--ad-color-border-hairline);
+ background: var(--ad-color-surface-fill);
+}
+.example summary {
+ padding: var(--ad-space-2xs) var(--ad-space-xs);
+ font-size: var(--ad-step--2);
+ font-weight: 500;
+ color: var(--ad-color-text-secondary);
+ cursor: pointer;
+ list-style: none;
+ user-select: none;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-2xs);
+}
+.example summary::-webkit-details-marker { display: none; }
+.example summary::before {
+ content: "";
+ width: 0.5rem;
+ height: 0.5rem;
+ border-inline-end: 1.5px solid currentColor;
+ border-block-end: 1.5px solid currentColor;
+ rotate: -45deg;
+ transition: rotate var(--ad-dur-fast) var(--ad-ease);
+}
+.example details[open] > summary::before { rotate: 45deg; }
+.example summary:hover { color: var(--ad-color-text-primary); }
+
+.example pre {
+ margin: 0;
+ padding: var(--ad-space-xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+ tab-size: 2;
+}
+.example pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Standalone fenced code blocks in the prose (unhighlighted — there is no
+ syntax highlighter in this build any more, just Kramdown's plain
+ <pre><code class="language-*">). */
+.ad-doc-main > pre {
+ margin-block: var(--ad-space-xs);
+ padding: var(--ad-space-xs);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+ overflow-x: auto;
+ font-size: var(--ad-step--2);
+ line-height: 1.5;
+}
+.ad-doc-main > pre code { background: none; border: 0; padding: 0; white-space: pre; }
+
+/* Presentation for the token reference tables and swatch grids, plus a
+ generic data-table utility. All of this is documentation chrome — the
+ design system itself has no Table component (see the note on the removed
+ `table`/`progress`/`callout` pages in git history). */
+
+.ad-swatch-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs) var(--ad-space-s);
+ padding: 0;
+ list-style: none;
+}
+.ad-swatch {
+ display: flex;
+ flex-direction: column;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+ overflow: hidden;
+ background: var(--ad-color-surface-card);
+}
+.ad-swatch__chip {
+ display: flex;
+ align-items: flex-end;
+ justify-content: flex-end;
+ height: 4rem;
+ padding: var(--ad-space-2xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ font-weight: 600;
+}
+.ad-swatch__meta {
+ display: flex;
+ flex-direction: column;
+ gap: 0.1rem;
+ padding: var(--ad-space-2xs);
+ border-top: 1px solid var(--ad-color-border-hairline);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+}
+.ad-swatch__hex { color: var(--ad-color-text-primary); text-transform: uppercase; }
+.ad-swatch__ratio { color: var(--ad-color-text-tertiary); }
+
+.ad-alias-row { display: flex; align-items: center; gap: var(--ad-space-2xs); }
+.ad-alias-chip {
+ width: 1.5rem;
+ height: 1.5rem;
+ flex: none;
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-sm);
+}
+.ad-alias-chip--pair { display: flex; }
+.ad-alias-chip--pair > span { flex: 1; }
+
+/* Type specimen rows. */
+.ad-specimen {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ gap: var(--ad-space-3xs);
+ padding-block: var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+}
+.ad-specimen__meta {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--ad-space-xs);
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ color: var(--ad-color-text-tertiary);
+}
+.ad-specimen__sample {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: var(--ad-color-text-primary);
+}
+
+/* Space and radius rulers. */
+.ad-ruler { display: flex; flex-direction: column; gap: var(--ad-space-2xs); }
+.ad-ruler__row { display: grid; grid-template-columns: 6.5rem 4.5rem 1fr; align-items: center; gap: var(--ad-space-xs); font-family: var(--ad-font-mono); font-size: 11px; }
+.ad-ruler__bar { height: 0.75rem; background: var(--ad-color-accent); border-radius: var(--ad-radius-sm); }
+.ad-ruler__num { color: var(--ad-color-text-tertiary); text-align: right; }
+
+.ad-radius-grid, .ad-elevation-grid, .ad-motion-grid, .ad-accent-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-xs);
+ padding: 0;
+ list-style: none;
+}
+.ad-radius-demo {
+ height: 4.5rem;
+ background: var(--ad-color-accent-soft);
+ border: 1px solid var(--ad-color-accent);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-elevation-demo {
+ height: 4.5rem;
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-lg);
+ margin-bottom: var(--ad-space-2xs);
+}
+.ad-motion-demo {
+ height: 4.5rem;
+ display: grid;
+ place-items: center;
+ background: var(--ad-color-surface-fill);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-radius: var(--ad-radius-md);
+ margin-bottom: var(--ad-space-2xs);
+ cursor: pointer;
+ overflow: hidden;
+}
+.ad-motion-demo__dot {
+ width: 1.25rem;
+ height: 1.25rem;
+ border-radius: var(--ad-radius-full);
+ background: var(--ad-color-accent);
+ translate: -3rem 0;
+ transition-property: translate;
+ transition-duration: var(--ad-dur-base);
+ transition-timing-function: var(--ad-ease);
+}
+.ad-motion-demo:hover .ad-motion-demo__dot,
+.ad-motion-demo:focus-visible .ad-motion-demo__dot { translate: 3rem 0; }
+
+.ad-accent-demo {
+ height: 4.5rem;
+ display: flex;
+ align-items: flex-end;
+ padding: var(--ad-space-2xs);
+ border-radius: var(--ad-radius-md);
+ color: #fff;
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ margin-bottom: var(--ad-space-2xs);
+}
+
+/* Token names read `<code>` sitewide, which already carries a mono chip
+ look for running prose (see :not(pre) > code in base/typography.css).
+ Inside these specimen/swatch/ruler rows the old `.ad-token-name` class was
+ plain mono text with no chip, so `<code>` is flattened back to that look
+ here the same way table cells are. */
+.ad-swatch code, .ad-specimen code, .ad-ruler code, .ad-alias-row code {
+ background: none;
+ border: 0;
+ padding: 0;
+ font-size: var(--ad-step--2);
+ color: var(--ad-color-text-primary);
+ white-space: nowrap;
+}
+
+/* A documentation aside. Like `<table>`, this is site chrome for the prose
+ itself, not a system component — the real system has no Callout. */
+.ad-callout {
+ margin-block: var(--ad-space-s);
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ border: 1px solid var(--ad-color-border-hairline);
+ border-left: 3px solid var(--ad-color-accent);
+ border-radius: var(--ad-radius-md);
+ background: var(--ad-color-surface-fill);
+}
+/* Generic reference tables (component APIs, alias tables, the contrast
+ contract). `<table>` IS a table — no class needed to say so. Every table
+ on this site uses the one compact density, so there is no separate
+ modifier for it. `display: block` plus `overflow-x: auto` gives the table
+ its own horizontal scrollbar on narrow viewports without a wrapper div;
+ the anonymous table box the browser generates around `<thead>`/`<tbody>`
+ still does the row/column layout. `<code>` replaces the old
+ `.ad-token-name` / `.ad-table__code` cells — it is already styled
+ sitewide, just re-flattened here to a plain mono run instead of the
+ chip-with-border look prose code gets, to match this table's original
+ look. `.num` is the one sanctioned class left, for right-aligned numeric
+ columns — there is no attribute that means "this column is a number". */
+table { width: 100%; border-collapse: collapse; font-size: var(--ad-step--1); display: block; overflow-x: auto; }
+table th, table td {
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
+ border-bottom: 1px solid var(--ad-color-border-hairline);
+ text-align: left;
+ vertical-align: top;
+}
+table th { font-family: var(--ad-font-mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ad-color-text-secondary); font-weight: 500; }
+table code { background: none; border: 0; padding: 0; font-size: var(--ad-step--2); white-space: nowrap; }
+.num { text-align: right; font-variant-numeric: tabular-nums; }
+
+/* Home page. */
+.ad-hero { padding-block: var(--ad-space-m) var(--ad-space-l); }
+.ad-hero__title {
+ font-size: var(--ad-step-4);
+ margin-bottom: var(--ad-space-xs);
+}
+.ad-hero__actions { display: flex; flex-wrap: wrap; gap: var(--ad-space-xs); margin-top: var(--ad-space-s); }
+.ad-stat-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
+ gap: var(--ad-space-xs);
+ margin-block: var(--ad-space-l);
+ padding: var(--ad-space-xs);
+ background: var(--ad-color-surface-card);
+ border: 1px solid var(--ad-color-border-default);
+ border-radius: var(--ad-radius-lg);
+}
+.ad-stat__value {
+ display: block;
+ font-family: var(--ad-font-sans);
+ font-size: var(--ad-step-2);
+ font-weight: 600;
+ line-height: 1.16;
+ font-variant-numeric: tabular-nums;
+}
+.ad-stat__label {
+ font-family: var(--ad-font-mono);
+ font-size: 11px;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ad-color-text-tertiary);
+}
+
+/* Card chrome. The pane supplies the frame; this just insets the page. */
+body { padding: var(--ad-space-l) var(--ad-space-m); }
+.ad-doc-main { max-width: 50rem; margin-inline: auto; }
+.ad-doc-main > * { max-width: none; }
+</style>
+</head>
+<body>
+<main class="ad-doc-main">
+<header class="ad-doc-title"><span class="ad-doc-title__section">Patterns</span>
+ <h1>Loop — task dashboard</h1>
+</header>
+<p class="ad-lead">A recurring-task web dashboard. Dense rows, mono meta columns, overdue in danger red, one primary action. A generic demonstration of the system, not a recreation of any one source project's actual screens.</p>
+<p>Loop is not a real product — it is the system’s own reference layout for a
+dense, table-ish list screen: the shape <code>kejadlen/ketchup</code> (a recurring-task
+tracker) would take if it were rebuilt on this token set. Every visible value
+below is a component from this system; nothing here is page-specific CSS
+beyond a couple of inline widths.</p>
+
+<figure class="example">
+<figcaption>Loop</figcaption>
+<div data-layout="stack">
+<div style="border: 1px solid var(--ad-color-border-hairline); border-radius: var(--ad-radius-lg); overflow: hidden; background: var(--ad-color-surface-page);">
+ <header style="display:flex; align-items:center; gap: var(--ad-space-s); padding: var(--ad-space-s) var(--ad-space-l); border-bottom: 1px solid var(--ad-color-border-hairline);">
+ <span style="font-family: var(--ad-font-mono); font-weight:600; letter-spacing:0.02em; font-size: var(--ad-step-0);">Loop</span>
+ <div style="flex:1;"></div>
+ <button data-variant="primary" data-size="sm">
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 5v14M5 12h14" /></svg>
+ New
+ </button>
+ <span class="avatar">km</span>
+ </header>
+ <div style="display:flex;">
+ <div style="flex:1; min-width:0; max-width: 34rem; padding: var(--ad-space-l);">
+ <section style="margin-bottom: var(--ad-space-l);">
+ <div style="display:flex; align-items:baseline; gap: var(--ad-space-2xs); margin-bottom: var(--ad-space-s);">
+ <span class="type-label" style="color: var(--ad-color-danger);">Overdue</span>
+ <span class="badge" data-tone="danger">2</span>
+ </div>
+ <ul class="card" style="border:none; border-radius:0; background:none;">
+ <li style="border-top: 1px solid var(--ad-color-border-hairline);">
+ <button aria-label="Complete" style="all:unset; width:18px; height:18px; border-radius:4px; flex-shrink:0; cursor:pointer; border: 1.5px solid var(--ad-color-danger);"></button>
+ <div style="flex:1; min-width:0;">
+ <div style="font-weight:500; color: var(--ad-color-danger);">Call Mom</div>
+ <div class="type-mono" style="color: var(--ad-color-text-tertiary); font-size: var(--ad-step--2);">every 2 weeks</div>
+ </div>
+ <span class="type-mono" style="color: var(--ad-color-danger); font-size: var(--ad-step--2); white-space:nowrap;">3d overdue</span>
+ </li>
+ <li>
+ <button aria-label="Complete" style="all:unset; width:18px; height:18px; border-radius:4px; flex-shrink:0; cursor:pointer; border: 1.5px solid var(--ad-color-danger);"></button>
+ <div style="flex:1; min-width:0;">
+ <div style="font-weight:500; color: var(--ad-color-danger);">Water the ferns</div>
+ <div class="type-mono" style="color: var(--ad-color-text-tertiary); font-size: var(--ad-step--2);">every 5 days</div>
+ </div>
+ <span class="type-mono" style="color: var(--ad-color-danger); font-size: var(--ad-step--2); white-space:nowrap;">1d overdue</span>
+ </li>
+ </ul>
+ </section>
+ <section>
+ <div class="type-label" style="margin-bottom: var(--ad-space-s);">Upcoming</div>
+ <ul class="card" style="border:none; border-radius:0; background:none;">
+ <li style="border-top: 1px solid var(--ad-color-border-hairline);">
+ <button aria-label="Complete" style="all:unset; width:18px; height:18px; border-radius:4px; flex-shrink:0; cursor:pointer; border: 1.5px solid var(--ad-color-text-secondary);"></button>
+ <div style="flex:1; min-width:0;">
+ <div style="font-weight:500;">Renew passport</div>
+ <div class="type-mono" style="color: var(--ad-color-text-tertiary); font-size: var(--ad-step--2);">every 10 years</div>
+ </div>
+ <span class="type-mono" style="color: var(--ad-color-text-secondary); font-size: var(--ad-step--2); white-space:nowrap;">in 3 months</span>
+ </li>
+ <li>
+ <button aria-label="Complete" style="all:unset; width:18px; height:18px; border-radius:4px; flex-shrink:0; cursor:pointer; border: 1.5px solid var(--ad-color-text-secondary);"></button>
+ <div style="flex:1; min-width:0;">
+ <div style="font-weight:500;">Dentist checkup</div>
+ <div class="type-mono" style="color: var(--ad-color-text-tertiary); font-size: var(--ad-step--2);">every 6 months</div>
+ </div>
+ <span class="type-mono" style="color: var(--ad-color-text-secondary); font-size: var(--ad-step--2); white-space:nowrap;">in 2 weeks</span>
+ </li>
+ <li>
+ <button aria-label="Complete" style="all:unset; width:18px; height:18px; border-radius:4px; flex-shrink:0; cursor:pointer; border: 1.5px solid var(--ad-color-text-secondary);"></button>
+ <div style="flex:1; min-width:0;">
+ <div style="font-weight:500;">Rotate mattress</div>
+ <div class="type-mono" style="color: var(--ad-color-text-tertiary); font-size: var(--ad-step--2);">every 6 months</div>
+ </div>
+ <span class="type-mono" style="color: var(--ad-color-text-secondary); font-size: var(--ad-step--2); white-space:nowrap;">in 5 weeks</span>
+ </li>
+ </ul>
+ </section>
+ </div>
+ <aside style="width: 15rem; flex-shrink:0; background: var(--ad-color-surface-fill); border-left: 1px solid var(--ad-color-border-hairline); padding: var(--ad-space-l);">
+ <div class="type-label" style="margin-bottom: var(--ad-space-s);">This week</div>
+ <div style="display:flex; background: var(--ad-color-surface-card); border: 1px solid var(--ad-color-border-hairline); border-radius: var(--ad-radius-md); overflow:hidden; margin-bottom: var(--ad-space-l);">
+ <div style="flex:1; display:flex; flex-direction:column; align-items:center; gap:4px; padding: 8px 0; border-right: 1px solid var(--ad-color-border-hairline);">
+ <span class="type-mono" style="font-size:9px; color: var(--ad-color-text-tertiary); text-transform:uppercase;">S</span>
+ <span style="font-size:13px; font-weight:600; color: var(--ad-color-text-primary);">0</span>
+ </div>
+ <div style="flex:1; display:flex; flex-direction:column; align-items:center; gap:4px; padding: 8px 0; border-right: 1px solid var(--ad-color-border-hairline);">
+ <span class="type-mono" style="font-size:9px; color: var(--ad-color-text-tertiary); text-transform:uppercase;">M</span>
+ <span style="font-size:13px; font-weight:600; color: var(--ad-color-text-primary);">1</span>
+ </div>
+ <div style="flex:1; display:flex; flex-direction:column; align-items:center; gap:4px; padding: 8px 0; border-right: 1px solid var(--ad-color-border-hairline);">
+ <span class="type-mono" style="font-size:9px; color: var(--ad-color-text-tertiary); text-transform:uppercase;">T</span>
+ <span style="font-size:13px; font-weight:600; color: var(--ad-color-text-primary);">0</span>
+ </div>
+ <div style="flex:1; display:flex; flex-direction:column; align-items:center; gap:4px; padding: 8px 0; background: var(--ad-color-accent-soft); border-right: 1px solid var(--ad-color-border-hairline);">
+ <span class="type-mono" style="font-size:9px; color: var(--ad-color-text-tertiary); text-transform:uppercase;">W</span>
+ <span style="font-size:13px; font-weight:600; color: var(--ad-color-accent-ink);">2</span>
+ </div>
+ <div style="flex:1; display:flex; flex-direction:column; align-items:center; gap:4px; padding: 8px 0; border-right: 1px solid var(--ad-color-border-hairline);">
+ <span class="type-mono" style="font-size:9px; color: var(--ad-color-text-tertiary); text-transform:uppercase;">T</span>
+ <span style="font-size:13px; font-weight:600; color: var(--ad-color-text-primary);">0</span>
+ </div>
+ <div style="flex:1; display:flex; flex-direction:column; align-items:center; gap:4px; padding: 8px 0; border-right: 1px solid var(--ad-color-border-hairline);">
+ <span class="type-mono" style="font-size:9px; color: var(--ad-color-text-tertiary); text-transform:uppercase;">F</span>
+ <span style="font-size:13px; font-weight:600; color: var(--ad-color-text-primary);">1</span>
+ </div>
+ <div style="flex:1; display:flex; flex-direction:column; align-items:center; gap:4px; padding: 8px 0; ">
+ <span class="type-mono" style="font-size:9px; color: var(--ad-color-text-tertiary); text-transform:uppercase;">S</span>
+ <span style="font-size:13px; font-weight:600; color: var(--ad-color-text-primary);">0</span>
+ </div>
+ </div>
+ <div class="type-label" style="margin-bottom: var(--ad-space-2xs);">Series</div>
+ <div>
+ <div style="padding: var(--ad-space-2xs) 0; font-size: var(--ad-step--1); color: var(--ad-color-text-secondary);">Call Mom</div>
+ <div style="padding: var(--ad-space-2xs) 0; border-top: 1px solid var(--ad-color-border-hairline); font-size: var(--ad-step--1); color: var(--ad-color-text-secondary);">Water the ferns</div>
+ <div style="padding: var(--ad-space-2xs) 0; border-top: 1px solid var(--ad-color-border-hairline); font-size: var(--ad-step--1); color: var(--ad-color-text-secondary);">Renew passport</div>
+ </div>
+ </aside>
+ </div>
+</div>
+</div>
+</figure>
+
+<h2 id="the-new-series-dialog">The New series dialog</h2>
+
+<figure class="example">
+<figcaption>New series</figcaption>
+<div data-layout="stack">
+<div class="dialog-demo" style="min-height: 36rem;">
+ <dialog open="" style="width: 22rem;">
+ <header>New series</header>
+ <div>
+ <div style="display:flex; flex-direction:column; gap: var(--ad-space-s);">
+ <label class="field">
+ Note
+ <input placeholder="Call Mom" />
+ <small>First line becomes the display name.</small>
+ </label>
+ <div class="form-row">
+ <label class="field">
+ Every
+ <input type="number" placeholder="2" />
+ </label>
+ <label class="field">
+ Unit
+ <select><option>Day</option><option selected="">Week</option><option>Month</option><option>Year</option></select>
+ </label>
+ </div>
+ </div>
+ </div>
+ <footer>
+ <button>Cancel</button>
+ <button data-variant="primary">Create</button>
+ </footer>
+ </dialog>
+</div>
+</div>
+</figure>
+
+<h2 id="after-completing-a-task">After completing a task</h2>
+
+<figure class="example">
+<figcaption>Toast</figcaption>
+<div>
+<div role="status">
+ <span>Completed "Call Mom"</span>
+ <button aria-label="Dismiss">×</button>
+</div>
+</div>
+</figure>
+
+<h2 id="what-this-screen-is-testing">What this screen is testing</h2>
+
+<ul>
+ <li><strong>Dense rows next to a mono meta column.</strong> A recurring-task list is
+fundamentally tabular even though it never touches an actual <code><table></code>.</li>
+ <li><strong>Overdue in <code>--ad-color-danger</code>, everywhere at once.</strong> Row text, the
+complete-button border, and the trailing meta all pick it up from the same
+<code>overdue</code> state — color never drifts row to row.</li>
+ <li><strong>One primary action.</strong> <code>New</code> is the only accent-filled control on screen;
+everything else is neutral or bordered.</li>
+ <li><strong>Accent used exactly once, decoratively.</strong> The “km” avatar and the
+highlighted day both use <code>--ad-color-accent-soft</code> / <code>-ink</code> — never the raw
+accent fill outside of <code>New</code>.</li>
+</ul>
+</main>
+<script>
+// Behavior for the pieces of the system that need it. Everything else is
+// CSS and semantic HTML on purpose — a design system that only works with
+// JavaScript running is a component library with extra steps.
+(function () {
+ "use strict";
+
+ var STORAGE_KEY = "ad-theme";
+
+ function readStored() {
+ try {
+ return localStorage.getItem(STORAGE_KEY);
+ } catch (error) {
+ return null;
+ }
+ }
+
+ function writeStored(value) {
+ try {
+ localStorage.setItem(STORAGE_KEY, value);
+ } catch (error) {
+ /* Nothing to do — the media query still gives a sensible default. */
+ }
+ }
+
+ function systemPrefersDark() {
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
+ }
+
+ function currentTheme() {
+ var explicit = document.documentElement.getAttribute("data-theme");
+ if (explicit) return explicit;
+ return systemPrefersDark() ? "dark" : "light";
+ }
+
+ function paintToggle(theme) {
+ document.querySelectorAll("[data-theme-toggle]").forEach(function (toggle) {
+ var next = theme === "dark" ? "light" : "dark";
+ toggle.querySelectorAll("[data-theme-icon]").forEach(function (icon) {
+ icon.hidden = icon.dataset.themeIcon !== theme;
+ });
+ toggle.setAttribute("aria-label", "Switch to the " + next + " theme");
+ toggle.setAttribute("title", "Switch to the " + next + " theme");
+ var label = toggle.querySelector("[data-theme-label]");
+ if (label) label.textContent = "Switch to the " + next + " theme";
+ });
+ }
+
+ function setUpTheme() {
+ paintToggle(currentTheme());
+
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-theme-toggle]");
+ if (!toggle) return;
+
+ var next = currentTheme() === "dark" ? "light" : "dark";
+ document.documentElement.setAttribute("data-theme", next);
+ writeStored(next);
+ paintToggle(next);
+ });
+
+ // Follow the OS while the reader has not made an explicit choice.
+ window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", function () {
+ if (!readStored()) paintToggle(currentTheme());
+ });
+ }
+
+ // Tabs, wired to the WAI-ARIA authoring practices: arrows move, Home/End
+ // jump, and only the selected tab stays in the tab order.
+ function setUpTabs() {
+ document.querySelectorAll("[data-tabs]").forEach(function (root) {
+ var tabs = Array.prototype.slice.call(root.querySelectorAll('[role="tab"]'));
+ if (!tabs.length) return;
+
+ function select(tab, focus) {
+ tabs.forEach(function (candidate) {
+ var selected = candidate === tab;
+ candidate.setAttribute("aria-selected", String(selected));
+ candidate.tabIndex = selected ? 0 : -1;
+ var panel = document.getElementById(candidate.getAttribute("aria-controls"));
+ if (panel) panel.hidden = !selected;
+ });
+ if (focus) tab.focus();
+ }
+
+ root.addEventListener("click", function (event) {
+ var tab = event.target.closest('[role="tab"]');
+ if (tab) select(tab, false);
+ });
+
+ root.addEventListener("keydown", function (event) {
+ var index = tabs.indexOf(document.activeElement);
+ if (index === -1) return;
+
+ var next = null;
+ if (event.key === "ArrowRight") next = tabs[(index + 1) % tabs.length];
+ else if (event.key === "ArrowLeft") next = tabs[(index - 1 + tabs.length) % tabs.length];
+ else if (event.key === "Home") next = tabs[0];
+ else if (event.key === "End") next = tabs[tabs.length - 1];
+
+ if (next) {
+ event.preventDefault();
+ select(next, true);
+ }
+ });
+
+ select(tabs.filter(function (tab) {
+ return tab.getAttribute("aria-selected") === "true";
+ })[0] || tabs[0], false);
+ });
+ }
+
+ function setUpNavToggle() {
+ document.addEventListener("click", function (event) {
+ var toggle = event.target.closest("[data-nav-toggle]");
+ if (!toggle) return;
+
+ var sidebar = toggle.closest("[data-sidebar]");
+ var collapsed = sidebar.dataset.collapsed !== "false";
+ sidebar.dataset.collapsed = String(!collapsed);
+ toggle.setAttribute("aria-expanded", String(collapsed));
+ });
+ }
+
+ setUpTheme();
+ setUpTabs();
+ setUpNavToggle();
+})();
+
+</script>
+</body>
+</html>
diff --git a/script/build_design_bundle.rb b/script/build_design_bundle.rb
deleted file mode 100644
index 38ff581..0000000
--- a/script/build_design_bundle.rb
+++ /dev/null
@@ -1,129 +0,0 @@
-#!/usr/bin/env ruby
-# frozen_string_literal: true
-
-# Builds the Claude Design upload bundle from the already-built _site.
-#
-# One card per documentation page: the page's <main> lifted out of the site
-# chrome and dropped into a standalone HTML file with the compiled tokens and
-# component CSS inlined. Cards are generated rather than hand-written for the
-# same reason the docs are — a second, hand-maintained copy of the system would
-# start disagreeing with the first one immediately.
-#
-# bundle exec rake build && ruby script/build_design_bundle.rb
-#
-# Output lands in design-bundle/, which is gitignored: it is a build artifact.
-
-require "fileutils"
-
-# The site is UTF-8 regardless of the locale the build happens to run under,
-# and a runner with LANG unset would otherwise read it as US-ASCII.
-Encoding.default_external = Encoding::UTF_8
-Encoding.default_internal = Encoding::UTF_8
-
-ROOT = File.expand_path("..", __dir__)
-SITE = File.join(ROOT, "_site")
-OUT = File.join(ROOT, "design-bundle")
-BASE = "/arbitrary-definitions-design-system"
-CANON = "https://kejadlen.github.io#{BASE}"
-
-# page path in _site => [bundle path, Design System pane group, card title]
-CARDS = [
- ["foundations/color", "foundations/color.html", "Foundations", "Color"],
- ["foundations/typography", "foundations/typography.html", "Foundations", "Typography"],
- ["foundations/space", "foundations/space.html", "Foundations", "Space"],
- ["foundations/elevation", "foundations/elevation.html", "Foundations", "Elevation"],
- ["foundations/motion", "foundations/motion.html", "Foundations", "Motion"],
- ["components/button", "components/button.html", "Core", "Button"],
- ["components/icon-button", "components/icon-button.html", "Core", "IconButton"],
- ["components/badge", "components/badge.html", "Core", "Badge"],
- ["components/tag", "components/tag.html", "Core", "Tag"],
- ["components/card", "components/card.html", "Core", "Card"],
- ["components/card-row", "components/card-row.html", "Core", "CardRow"],
- ["components/field", "components/field.html", "Forms", "Field"],
- ["components/input", "components/input.html", "Forms", "Input"],
- ["components/select", "components/select.html", "Forms", "Select"],
- ["components/checkbox", "components/checkbox.html", "Forms", "Checkbox"],
- ["components/radio", "components/radio.html", "Forms", "Radio"],
- ["components/switch", "components/switch.html", "Forms", "Switch"],
- ["components/tooltip", "components/tooltip.html", "Feedback", "Tooltip"],
- ["components/toast", "components/toast.html", "Feedback", "Toast"],
- ["components/dialog", "components/dialog.html", "Feedback", "Dialog"],
- ["components/tabs", "components/tabs.html", "Navigation", "Tabs"],
- ["patterns/loop", "patterns/loop.html", "Patterns", "Loop"],
- ["patterns/archive", "patterns/archive.html", "Patterns", "Archive"],
-].freeze
-
-def read_site(path)
- full = File.join(SITE, path)
- abort "missing #{full} — run `bundle exec rake build` first" unless File.file?(full)
- File.read(full, encoding: "UTF-8")
-end
-
-# The site emits exactly one <main>, with no nesting, so this is unambiguous.
-def extract_main(html, page)
- body = html[%r{<main[^>]*id="main"[^>]*>(.*?)</main>}m, 1]
- abort "no <main> found in #{page}" unless body
-
- body
- # Site-relative links would dead-end inside the Design System pane.
- .gsub(%r{(href|src)="#{Regexp.escape(BASE)}/}, "\\1=\"#{CANON}/")
- .strip
-end
-
-def card_html(group:, title:, body:, tokens:, styles:, script:)
- <<~HTML
- <!-- @dsCard group="#{group}" -->
- <!doctype html>
- <html lang="en">
- <head>
- <meta charset="utf-8">
- <meta name="viewport" content="width=device-width, initial-scale=1">
- <title>#{title}</title>
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
- <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&display=swap">
- <style>
- #{tokens}
- #{styles}
- /* Card chrome. The pane supplies the frame; this just insets the page. */
- body { padding: var(--ad-space-l) var(--ad-space-m); }
- .ad-doc-main { max-width: 50rem; margin-inline: auto; }
- .ad-doc-main > * { max-width: none; }
- </style>
- </head>
- <body>
- <main class="ad-doc-main">
- #{body}
- </main>
- <script>
- #{script}
- </script>
- </body>
- </html>
- HTML
-end
-
-FileUtils.rm_rf(OUT)
-FileUtils.mkdir_p(OUT)
-
-tokens = read_site("assets/css/tokens.css")
-styles = read_site("assets/css/style.css")
-script = read_site("assets/js/system.js")
-
-# The compiled CSS goes in as-is too, so the project carries the artifact a
-# consumer would actually link, not only the rendered previews.
-FileUtils.mkdir_p(File.join(OUT, "css"))
-File.write(File.join(OUT, "css", "tokens.css"), tokens)
-File.write(File.join(OUT, "css", "style.css"), styles)
-
-CARDS.each do |page, out_path, group, title|
- body = extract_main(read_site(File.join(page, "index.html")), page)
- target = File.join(OUT, out_path)
- FileUtils.mkdir_p(File.dirname(target))
- File.write(target, card_html(group: group, title: title, body: body,
- tokens: tokens, styles: styles, script: script))
-end
-
-total = Dir.glob(File.join(OUT, "**", "*")).count { |f| File.file?(f) }
-bytes = Dir.glob(File.join(OUT, "**", "*")).sum { |f| File.file?(f) ? File.size(f) : 0 }
-puts "design-bundle: #{CARDS.size} cards, #{total} files, #{(bytes / 1024.0).round} KiB"