{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "aesthetic", "type": "registry:item", "title": "aesthetic", "description": "The Misty Step design system, v0.25.0: one stylesheet — tokens, base, and the primitive catalog. Hierarchy from ink and weight, hairlines, radius 0, motion as feedback.", "meta": { "version": "0.25.0", "tier": "whole-kit", "classes": ["*"], "tokens": [ "--ae-surface", "--ae-wash", "--ae-ink", "--ae-ink-muted", "--ae-ink-faint", "--ae-line", "--ae-accent", "--ae-ok", "--ae-warn", "--ae-err", "--ae-cat-0", "--ae-cat-1", "--ae-cat-2", "--ae-cat-3", "--ae-cat-4", "--ae-cat-5", "--ae-cat-6", "--ae-cat-7", "--ae-surface-dark", "--ae-wash-dark", "--ae-ink-dark", "--ae-ink-muted-dark", "--ae-ink-faint-dark", "--ae-line-dark", "--ae-accent-dark", "--ae-ok-dark", "--ae-warn-dark", "--ae-err-dark", "--ae-cat-0-dark", "--ae-cat-1-dark", "--ae-cat-2-dark", "--ae-cat-3-dark", "--ae-cat-4-dark", "--ae-cat-5-dark", "--ae-cat-6-dark", "--ae-cat-7-dark", "--ae-font", "--ae-font-mono", "--ae-w-regular", "--ae-w-medium", "--ae-w-black", "--ae-measure", "--ae-measure-wide", "--ae-radius", "--ae-breakpoint-sm", "--ae-breakpoint-md", "--ae-breakpoint-lg", "--ae-space-0", "--ae-space-1", "--ae-space-2", "--ae-space-3", "--ae-space-4", "--ae-space-5", "--ae-space-6", "--ae-space-7", "--ae-space-8", "--ae-z-backdrop", "--ae-z-dialog", "--ae-z-pop", "--ae-z-tip", "--ae-z-toast", "--ae-ease", "--ae-quick", "--ae-soft", "--ae-gentle" ], "recipes": [] }, "files": [ { "path": "aesthetic.css", "type": "registry:file", "target": "~/aesthetic.css", "content": "/* ═══════════════════════════════════════════════════════════════════\n aesthetic v0.25.0\n The Misty Step design system. https://github.com/misty-step/aesthetic\n\n The law:\n · One size. Exactly one font size on every surface; the headline is\n heavier and blacker, never larger.\n · Three inks by three weights (400 / 550 / 800): nine registers.\n · The accent is yours. Define your scheme and use it with judgment;\n identity comes from ink, hairlines, type, and motion — never from\n a color-counting rule. Color earns its place; it is not ambient.\n · Status rides the glyph: danger, success, and warning color the\n Lucide icon, never the sentence, never a filled pill.\n · Every view fits the viewport. 100dvh screens, no page scrolling;\n when content exceeds one screen, a quiet nav swaps views.\n · Motion is feedback, never decoration: small, brief, eased, and\n gone when the user prefers reduced motion. State resolutions are\n gentler (--ae-gentle) and resolve once — success persists, it is\n never rewound. Nothing changes size while it animates.\n · Buttons are not links: contained ink shapes. Links navigate,\n buttons act.\n · Light and dark, defaulting to the system, with a user toggle.\n\n Steering: every token is a CSS custom property; downstream projects\n pin a named theme or steer the scheme — --ae-accent /\n --ae-accent-dark, and the status triplet when their domain needs\n different hues. Everything else is the shared identity.\n ═══════════════════════════════════════════════════════════════════ */\n\n/* ── tokens ───────────────────────────────────────────────────────── */\n\n:root {\n --ae-surface: #fcfcfc;\n --ae-wash: #f3f3f3;\n --ae-ink: #151515;\n --ae-ink-muted: #737373;\n --ae-ink-faint: #a3a3a3;\n --ae-line: #e9e9e9;\n --ae-accent: #2643d0;\n\n /* status inks: hue rides the glyph, words stay ink */\n --ae-ok: #15714b;\n --ae-warn: #8a5f32;\n --ae-err: #a84138;\n\n /* categorical inks: identity, not judgment — N agents, N series, N\n actors need N distinguishable hues with no severity meaning. Ride\n the word on .ae-chip (see below); this is the one place a hue is\n allowed to color text, because there is no glyph to carry it and\n nothing is being judged. */\n --ae-cat-0: #8c2f1f;\n --ae-cat-1: #2c6e62;\n --ae-cat-2: #a67c1e;\n --ae-cat-3: #6b4c86;\n --ae-cat-4: #46586b;\n --ae-cat-5: #5b7444;\n --ae-cat-6: #3d5a80;\n --ae-cat-7: #a1452e;\n\n /* dark counterparts, resolved by the mode blocks below */\n --ae-surface-dark: #121212;\n --ae-wash-dark: #1b1b1b;\n --ae-ink-dark: #ededed;\n --ae-ink-muted-dark: #8f8f8f;\n --ae-ink-faint-dark: #5c5c5c;\n --ae-line-dark: #262626;\n --ae-accent-dark: #8c9eff;\n --ae-ok-dark: #6fd2a8;\n --ae-warn-dark: #c49d72;\n --ae-err-dark: #e58379;\n --ae-cat-0-dark: #d06a4f;\n --ae-cat-1-dark: #54bba4;\n --ae-cat-2-dark: #d8b25a;\n --ae-cat-3-dark: #b98fe0;\n --ae-cat-4-dark: #93a8c2;\n --ae-cat-5-dark: #9bc47a;\n --ae-cat-6-dark: #7ea1d1;\n --ae-cat-7-dark: #e08a6a;\n\n --ae-font: 'Geist', 'Helvetica Neue', Helvetica, Arial, sans-serif;\n --ae-font-mono: 'Geist Mono', ui-monospace, SFMono-Regular, Menlo, monospace;\n\n --ae-w-regular: 400;\n --ae-w-medium: 550;\n --ae-w-black: 800;\n\n --ae-measure: 38rem; /* rem: bars in the 13px chrome register must not shrink the measure */\n --ae-measure-wide: 68rem;\n --ae-radius: 0;\n --ae-breakpoint-sm: 40rem; /* phones and narrow split-view: one-column data */\n --ae-breakpoint-md: 48rem; /* tablet portrait: shell chrome leaves the side */\n --ae-breakpoint-lg: 64rem; /* small laptop: boards can hold columns */\n\n /* the spacing scale: consumers use these instead of ad-hoc em values.\n The token-guard (015) will enforce them. Off-scale internal values\n are left as literals — the scale is the vocabulary going forward. */\n --ae-space-0: 0.1em; /* micro — tag padding, chrome adjustments */\n --ae-space-1: 0.25em; /* tight — icon gaps, control insets */\n --ae-space-2: 0.5em; /* small — button padding, small gaps */\n --ae-space-3: 0.75em; /* medium — control padding, row gaps */\n --ae-space-4: 1em; /* base — group padding, standard gap */\n --ae-space-5: 1.5em; /* comfortable — section internals */\n --ae-space-6: 2em; /* spacious — section gaps, stage padding */\n --ae-space-7: 2.5em; /* large — document section spacing */\n --ae-space-8: 3em; /* section — top-level margins */\n\n /* z-index scale: named layers so consumers don't guess at magic numbers */\n --ae-z-backdrop: 40; /* modal backdrop below its decision panel */\n --ae-z-dialog: 41; /* modal decision panel */\n --ae-z-pop: 50; /* portaled menus and popovers above modal decisions */\n --ae-z-tip: 60; /* portaled tooltips above popovers and dialogs */\n --ae-z-toast: 100; /* toasts, persistent notifications */\n\n /* motion: feedback, never decoration */\n --ae-ease: cubic-bezier(0.23, 1, 0.32, 1);\n --ae-quick: 160ms;\n --ae-soft: 240ms;\n --ae-gentle: 480ms; /* state resolutions: slower than feedback, never abrupt */\n\n color-scheme: light dark;\n}\n\n/* named themes: opt-in flavor presets over the same token contract.\n Projects pin one on , or include\n recipes/theme.js to let users switch. Theme presets steer accent and\n categorical inks; status semantics stay the house triplet unless a\n project deliberately overrides them. */\n:root[data-ae-theme='ultramarine'] {\n --ae-accent: #2643d0;\n --ae-accent-dark: #8c9eff;\n --ae-cat-0: #8c2f1f;\n --ae-cat-1: #2c6e62;\n --ae-cat-2: #a67c1e;\n --ae-cat-3: #6b4c86;\n --ae-cat-4: #46586b;\n --ae-cat-5: #5b7444;\n --ae-cat-6: #3d5a80;\n --ae-cat-7: #a1452e;\n --ae-cat-0-dark: #d06a4f;\n --ae-cat-1-dark: #54bba4;\n --ae-cat-2-dark: #d8b25a;\n --ae-cat-3-dark: #b98fe0;\n --ae-cat-4-dark: #93a8c2;\n --ae-cat-5-dark: #9bc47a;\n --ae-cat-6-dark: #7ea1d1;\n --ae-cat-7-dark: #e08a6a;\n}\n\n:root[data-ae-theme='moss'] {\n --ae-accent: #2f6b3f;\n --ae-accent-dark: #80d98e;\n --ae-cat-0: #6f3f1f;\n --ae-cat-1: #2f6b3f;\n --ae-cat-2: #8a6a1f;\n --ae-cat-3: #5d5686;\n --ae-cat-4: #4b6470;\n --ae-cat-5: #55743d;\n --ae-cat-6: #3d6472;\n --ae-cat-7: #934b2f;\n --ae-cat-0-dark: #d28a5f;\n --ae-cat-1-dark: #80d98e;\n --ae-cat-2-dark: #d6b65c;\n --ae-cat-3-dark: #b8a8e8;\n --ae-cat-4-dark: #9ab8c4;\n --ae-cat-5-dark: #a8d68a;\n --ae-cat-6-dark: #88c4d2;\n --ae-cat-7-dark: #df8a62;\n}\n\n:root[data-ae-theme='ember'] {\n --ae-accent: #c2410c;\n --ae-accent-dark: #ff8a5c;\n --ae-cat-0: #9b2f1f;\n --ae-cat-1: #6b5a2f;\n --ae-cat-2: #8a5f32;\n --ae-cat-3: #6d4c86;\n --ae-cat-4: #5a6172;\n --ae-cat-5: #5b7444;\n --ae-cat-6: #3d5a80;\n --ae-cat-7: #a1452e;\n --ae-cat-0-dark: #f08a70;\n --ae-cat-1-dark: #c49d72;\n --ae-cat-2-dark: #ffb45c;\n --ae-cat-3-dark: #b98fe0;\n --ae-cat-4-dark: #93a8c2;\n --ae-cat-5-dark: #9bc47a;\n --ae-cat-6-dark: #7ea1d1;\n --ae-cat-7-dark: #ff8a5c;\n}\n\n:root[data-ae-theme='violet'] {\n --ae-accent: #6d28d9;\n --ae-accent-dark: #c4a3ff;\n --ae-cat-0: #8c2f5a;\n --ae-cat-1: #2c6e62;\n --ae-cat-2: #8a721e;\n --ae-cat-3: #6d28d9;\n --ae-cat-4: #46586b;\n --ae-cat-5: #5b7444;\n --ae-cat-6: #3d5a80;\n --ae-cat-7: #9945a1;\n --ae-cat-0-dark: #e08ab8;\n --ae-cat-1-dark: #54bba4;\n --ae-cat-2-dark: #d8b25a;\n --ae-cat-3-dark: #c4a3ff;\n --ae-cat-4-dark: #93a8c2;\n --ae-cat-5-dark: #9bc47a;\n --ae-cat-6-dark: #7ea1d1;\n --ae-cat-7-dark: #df8ae8;\n}\n\n/* auto dark: follows the OS unless the page pins a mode */\n@media (prefers-color-scheme: dark) {\n :root:not(.light):not([data-ae-mode='light']) {\n --ae-surface: var(--ae-surface-dark);\n --ae-wash: var(--ae-wash-dark);\n --ae-ink: var(--ae-ink-dark);\n --ae-ink-muted: var(--ae-ink-muted-dark);\n --ae-ink-faint: var(--ae-ink-faint-dark);\n --ae-line: var(--ae-line-dark);\n --ae-accent: var(--ae-accent-dark);\n --ae-ok: var(--ae-ok-dark);\n --ae-warn: var(--ae-warn-dark);\n --ae-err: var(--ae-err-dark);\n --ae-cat-0: var(--ae-cat-0-dark);\n --ae-cat-1: var(--ae-cat-1-dark);\n --ae-cat-2: var(--ae-cat-2-dark);\n --ae-cat-3: var(--ae-cat-3-dark);\n --ae-cat-4: var(--ae-cat-4-dark);\n --ae-cat-5: var(--ae-cat-5-dark);\n --ae-cat-6: var(--ae-cat-6-dark);\n --ae-cat-7: var(--ae-cat-7-dark);\n }\n}\n\n/* pinned modes narrow color-scheme so UA widgets (scrollbars, form\n controls) match the pinned mode instead of following the OS */\n.light,\n[data-ae-mode='light'] {\n color-scheme: light;\n}\n\n/* pinned dark: class or attribute set by the site's mode toggle */\n:root.dark,\n:root[data-ae-mode='dark'] {\n color-scheme: dark;\n --ae-surface: var(--ae-surface-dark);\n --ae-wash: var(--ae-wash-dark);\n --ae-ink: var(--ae-ink-dark);\n --ae-ink-muted: var(--ae-ink-muted-dark);\n --ae-ink-faint: var(--ae-ink-faint-dark);\n --ae-line: var(--ae-line-dark);\n --ae-accent: var(--ae-accent-dark);\n --ae-ok: var(--ae-ok-dark);\n --ae-warn: var(--ae-warn-dark);\n --ae-err: var(--ae-err-dark);\n --ae-cat-0: var(--ae-cat-0-dark);\n --ae-cat-1: var(--ae-cat-1-dark);\n --ae-cat-2: var(--ae-cat-2-dark);\n --ae-cat-3: var(--ae-cat-3-dark);\n --ae-cat-4: var(--ae-cat-4-dark);\n --ae-cat-5: var(--ae-cat-5-dark);\n --ae-cat-6: var(--ae-cat-6-dark);\n --ae-cat-7: var(--ae-cat-7-dark);\n}\n\n/* ── base ─────────────────────────────────────────────────────────── */\n\nhtml {\n scroll-behavior: auto;\n}\n\nbody {\n margin: 0;\n background: var(--ae-surface);\n color: var(--ae-ink);\n font-family: var(--ae-font);\n font-size: 16px;\n font-weight: var(--ae-w-regular);\n line-height: 1.8;\n cursor: default; /* no I-beam on static text; pointer/text are earned */\n -webkit-font-smoothing: antialiased;\n text-rendering: optimizeLegibility;\n}\n\n/* one size: nothing earns scale, only ink and weight */\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\np,\nli,\ndt,\ndd,\nblockquote,\nfigcaption,\nlabel,\ninput,\ntextarea,\nbutton,\ntable {\n font-size: 1em;\n font-family: inherit;\n line-height: inherit;\n margin: 0;\n}\n\ncode,\nkbd,\npre,\nsamp {\n font-family: var(--ae-font-mono);\n font-size: 0.92em;\n}\n\n::selection {\n background: var(--ae-wash);\n color: var(--ae-ink);\n}\n\n:focus-visible {\n outline: 2px solid var(--ae-ink);\n outline-offset: 3px;\n}\n\n/* links: ink at rest, warm to accent on hover (the locked treatment).\n Approved alternates, documented for easy change, live in the README:\n marker sweep and draw-on-hover. */\na {\n color: inherit;\n cursor: pointer;\n text-decoration: underline;\n text-underline-offset: 0.2em;\n text-decoration-color: var(--ae-ink-faint);\n padding-block: 0.35em;\n margin-block: -0.35em;\n transition:\n color var(--ae-quick) var(--ae-ease),\n text-decoration-color var(--ae-quick) var(--ae-ease);\n}\n\na:hover {\n color: var(--ae-accent);\n text-decoration-color: currentColor;\n}\n\n/* list rows: the whole row is the link, so the cursor never crosses a\n dead band between rows */\n.ae-row-link {\n display: block;\n text-decoration: none;\n padding: 0.42em 0;\n margin-block: 0;\n color: inherit;\n}\n\n.ae-row-link:hover {\n color: inherit;\n}\n\n.ae-row-link .ae-item {\n transition: color var(--ae-quick) var(--ae-ease);\n}\n\n.ae-row-link:hover .ae-item {\n color: var(--ae-accent);\n}\n\n/* ── screens: every view fits the viewport ────────────────────────── */\n\n/* the screen: one viewport, no page scroll */\n.ae-screen {\n height: 100dvh;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n}\n\n/* a bar: quiet chrome at the top or bottom of a screen */\n.ae-bar {\n flex: none;\n width: min(var(--ae-measure), 100% - 5rem);\n margin-inline: auto;\n padding: 1.4em 0;\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n justify-content: space-between;\n gap: 0.4em 1.5em;\n}\n\n/* the stage: the screen's content, vertically centered; a view that\n cannot fit scrolls inside the stage, never the page */\n.ae-stage {\n flex: 1;\n min-height: 0;\n width: min(var(--ae-measure), 100% - 5rem);\n margin-inline: auto;\n display: flex;\n flex-direction: column;\n overflow-y: auto;\n}\n\n.ae-stage > * {\n margin-block: auto;\n padding-block: var(--ae-space-5);\n}\n\n/* long documents: top-aligned instead of centered */\n.ae-stage-scroll > * {\n margin-block: 0;\n padding-block: var(--ae-space-6);\n}\n\n/* a wide screen: catalog, specimen, and dashboard shapes that cannot\n live in 38rem. One decision per screen — set it on .ae-screen. */\n.ae-wide .ae-bar,\n.ae-wide .ae-stage {\n width: min(var(--ae-measure-wide), 100% - 5rem);\n}\n\n/* a view: one screenful of content; remount to replay the entrance */\n.ae-view {\n animation: ae-enter var(--ae-soft) var(--ae-ease) both;\n}\n\n/* hash-routed views: one mounts at a time; the display toggle remounts\n the view and replays its entrance (behavior: recipes/views.js) */\n[data-route] {\n display: none;\n}\n\n[data-route].is-on {\n display: block;\n}\n\n@keyframes ae-enter {\n from {\n opacity: 0;\n transform: translateY(6px);\n }\n}\n\n/* the nav: quiet words with a sliding ink underline. The site positions\n .ae-nav-ind under the active item (left/width) on view change.\n .ae-tabs is the same instrument pointed at panels instead of views:\n role=\"tablist\" buttons, aria-selected marks the open one. */\n.ae-nav,\n.ae-tabs {\n position: relative;\n display: flex;\n gap: var(--ae-space-6);\n}\n\n.ae-nav a,\n.ae-nav button,\n.ae-tabs a,\n.ae-tabs button {\n background: transparent;\n border: 0;\n padding: 0 0 0.6em;\n margin-block: 0;\n cursor: pointer;\n color: var(--ae-ink-muted);\n font-weight: var(--ae-w-medium);\n text-decoration: none;\n transition: color var(--ae-quick) var(--ae-ease);\n}\n\n.ae-nav a:hover,\n.ae-nav button:hover,\n.ae-tabs a:hover,\n.ae-tabs button:hover {\n color: var(--ae-ink);\n}\n\n.ae-nav [aria-current],\n.ae-nav .is-active,\n.ae-tabs [aria-current],\n.ae-tabs [aria-selected='true'],\n.ae-tabs .is-active {\n color: var(--ae-ink);\n}\n\n.ae-nav-ind {\n position: absolute;\n bottom: 0;\n height: 2px;\n background: var(--ae-ink);\n transition:\n left var(--ae-soft) var(--ae-ease),\n width var(--ae-soft) var(--ae-ease);\n}\n\n/* chrome: the one sanctioned second size. Content gets 16px; chrome\n (footers, utility links, metadata) gets 13px and muted ink. */\n.ae-chrome {\n font-size: 13px;\n color: var(--ae-ink-muted);\n}\n\n.ae-chrome a {\n color: var(--ae-ink-muted);\n text-decoration: none;\n}\n\n.ae-chrome a:hover {\n color: var(--ae-ink);\n}\n\n/* the footer: hairline-topped, two ends, chrome register */\n.ae-foot {\n font-size: 13px;\n color: var(--ae-ink-muted);\n border-top: 1px solid var(--ae-line);\n padding-top: var(--ae-space-4);\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: var(--ae-space-4) var(--ae-space-6);\n flex-wrap: wrap;\n}\n\n.ae-foot a {\n color: var(--ae-ink-muted);\n text-decoration: none;\n}\n\n.ae-foot a:hover {\n color: var(--ae-ink);\n}\n\n.ae-foot-links {\n display: inline-flex;\n gap: var(--ae-space-5);\n align-items: center;\n}\n\n/* icons: Lucide (ISC), inlined as SVG symbols. 1.5px stroke, round\n caps, sized to ride alongside text. */\n.ae-icon {\n width: 1.2em;\n height: 1.2em;\n stroke: currentColor;\n stroke-width: 1.5;\n stroke-linecap: round;\n stroke-linejoin: round;\n fill: none;\n vertical-align: -0.2em;\n}\n\n/* icon-forward rows: Lucide gives the eye a scan target, but the law\n stays unchanged — status hue rides the SVG, words stay ink. */\n.ae-status {\n display: inline-flex;\n align-items: baseline;\n gap: var(--ae-space-2);\n color: var(--ae-ink);\n}\n\n.ae-status .ae-icon {\n flex: none;\n}\n\n.ae-status-label {\n color: var(--ae-ink);\n}\n\n/* Headless triggers render real buttons; remove UA chrome when a status\n mark is used as the trigger without turning its words into a button. */\nbutton.ae-status {\n background: none;\n border: 0;\n padding: 0;\n margin: 0;\n font: inherit;\n color: inherit;\n text-align: left;\n}\n.ae-icon-row {\n display: grid;\n grid-template-columns: auto minmax(0, 1fr);\n align-items: baseline;\n gap: var(--ae-space-3);\n color: inherit;\n text-decoration: none;\n}\n\n.ae-list-icon {\n flex: none;\n color: var(--ae-ink-muted);\n}\n\n.ae-icon-row:hover .ae-list-icon {\n color: var(--ae-ink);\n}\n\n.ae-icon-row-main {\n min-width: 0;\n display: flex;\n flex-direction: column;\n}\n\n.ae-icon-row-meta {\n font-size: 13px;\n color: var(--ae-ink-muted);\n}\n\n/* decomposed list rows: incident feeds and dense activity lists should not\n collapse into \"time · app · type · status\" prose. Each field gets a\n label, a value, and an optional Lucide/app mark; status hue still rides\n only the glyph. Compose .ae-row-link when the whole row navigates. */\n.ae-list-rows {\n display: grid;\n border-block: 1px solid var(--ae-line);\n}\n\n.ae-list-row {\n display: grid;\n grid-template-columns:\n minmax(5.5em, 0.8fr) minmax(9em, 1.25fr) minmax(9em, 1fr)\n minmax(7em, 0.9fr);\n align-items: center;\n gap: var(--ae-space-4);\n padding: 0.68em 0;\n border-bottom: 1px solid var(--ae-line);\n color: inherit;\n text-decoration: none;\n}\n\n.ae-list-row:last-child {\n border-bottom: 0;\n}\n\n.ae-list-row:hover {\n color: inherit;\n}\n\n.ae-list-cell {\n min-width: 0;\n display: grid;\n grid-template-columns: minmax(0, 1fr);\n column-gap: var(--ae-space-2);\n align-items: center;\n}\n\n.ae-list-cell:has(> .ae-icon),\n.ae-list-cell:has(> .ae-app-mark) {\n grid-template-columns: auto minmax(0, 1fr);\n}\n\n.ae-list-cell > .ae-icon,\n.ae-list-cell > .ae-app-mark {\n grid-column: 1;\n grid-row: 1 / span 2;\n align-self: center;\n}\n\n.ae-list-cell > .ae-icon:not(.ae-ok):not(.ae-warn):not(.ae-err) {\n flex: none;\n color: var(--ae-ink-muted);\n}\n\n.ae-list-label {\n grid-column: 1;\n font-family: var(--ae-font-mono);\n font-size: 11px;\n line-height: 1.2;\n letter-spacing: 0.12em;\n color: var(--ae-ink-faint);\n text-transform: uppercase;\n white-space: nowrap;\n}\n\n.ae-list-value {\n grid-column: 1;\n min-width: 0;\n color: var(--ae-ink);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.ae-list-cell:has(> .ae-icon) > .ae-list-label,\n.ae-list-cell:has(> .ae-icon) > .ae-list-value,\n.ae-list-cell:has(> .ae-app-mark) > .ae-list-label,\n.ae-list-cell:has(> .ae-app-mark) > .ae-list-value {\n grid-column: 2;\n}\n\n.ae-list-time .ae-list-value {\n font-family: var(--ae-font-mono);\n font-size: 13px;\n font-variant-numeric: tabular-nums;\n color: var(--ae-ink-muted);\n}\n\n.ae-list-status {\n justify-self: end;\n}\n\n.ae-list-status .ae-list-value {\n font-weight: var(--ae-w-medium);\n}\n\n/* app identity: a Lucide project mark sized by the register it lives\n in. The same mark works in a 13px rail and a 16px header. */\n.ae-logo {\n display: inline-flex;\n align-items: center;\n gap: var(--ae-space-3);\n color: inherit;\n text-decoration: none;\n padding-block: 0.2em;\n margin-block: 0;\n}\n\n.ae-logo:hover {\n color: inherit;\n}\n\n.ae-logo .ae-name {\n transition: color var(--ae-quick) var(--ae-ease);\n}\n\n.ae-logo:hover .ae-name {\n color: var(--ae-accent);\n}\n\n.ae-logo-compact {\n gap: var(--ae-space-2);\n font-size: 13px;\n}\n\n.ae-app-mark {\n width: 2em;\n height: 2em;\n border: 1px solid var(--ae-line);\n color: var(--ae-ink);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n}\n\n.ae-app-mark .ae-icon {\n width: 1.2em;\n height: 1.2em;\n vertical-align: 0;\n}\n\n.ae-logo-compact .ae-app-mark {\n width: 1.8em;\n height: 1.8em;\n}\n\n/* the mode toggle: an icon button; sun and moon rotate-crossfade.\n Markup: button.ae-mode > svg.ae-icon.ae-sun + svg.ae-icon.ae-moon */\n.ae-mode {\n position: relative;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 2.2em;\n height: 2.2em;\n background: transparent;\n border: 0;\n padding: 0;\n cursor: pointer;\n color: var(--ae-ink-muted);\n transition: color var(--ae-quick) var(--ae-ease);\n}\n\n/* the icons are absolutely stacked, so the button has no in-flow text;\n this zero-width space gives it a real baseline so bars can\n baseline-align it with neighboring links */\n.ae-mode::before {\n content: '\\200b';\n}\n\n.ae-mode:hover {\n color: var(--ae-ink);\n}\n\n.ae-mode .ae-icon {\n position: absolute;\n inset: 0;\n margin: auto;\n width: 1.25em;\n height: 1.25em;\n transition:\n opacity var(--ae-quick) var(--ae-ease),\n transform var(--ae-quick) var(--ae-ease);\n}\n\n.ae-mode .ae-sun {\n opacity: 1;\n transform: rotate(0) scale(1);\n}\n\n.ae-mode .ae-moon {\n opacity: 0;\n transform: rotate(-90deg) scale(0.6);\n}\n\n.dark .ae-mode .ae-sun,\n[data-ae-mode='dark'] .ae-mode .ae-sun {\n opacity: 0;\n transform: rotate(90deg) scale(0.6);\n}\n\n.dark .ae-mode .ae-moon,\n[data-ae-mode='dark'] .ae-mode .ae-moon {\n opacity: 1;\n transform: rotate(0) scale(1);\n}\n\n/* a panel: soft depth for dense content (forms). Radius stays 0. */\n.ae-panel {\n background: var(--ae-surface);\n padding: var(--ae-space-6);\n box-shadow:\n 0 1px 2px rgba(0, 0, 0, 0.04),\n 0 8px 28px rgba(0, 0, 0, 0.06);\n}\n\n.dark .ae-panel,\n[data-ae-mode='dark'] .ae-panel {\n background: var(--ae-wash);\n box-shadow: none;\n}\n\n/* auto dark (no pinned mode): panel and mode icon follow the OS */\n@media (prefers-color-scheme: dark) {\n :root:not(.light):not([data-ae-mode='light']) .ae-panel {\n background: var(--ae-wash);\n box-shadow: none;\n }\n\n :root:not(.light):not([data-ae-mode='light']) .ae-mode .ae-sun {\n opacity: 0;\n transform: rotate(90deg) scale(0.6);\n }\n\n :root:not(.light):not([data-ae-mode='light']) .ae-mode .ae-moon {\n opacity: 1;\n transform: rotate(0) scale(1);\n }\n\n :root:not(.light):not([data-ae-mode='light']) dialog.ae-dialog,\n :root:not(.light):not([data-ae-mode='light']) .ae-dialog-panel {\n background: var(--ae-wash);\n box-shadow: none;\n }\n\n :root:not(.light):not([data-ae-mode='light']) dialog.ae-dialog::backdrop,\n :root:not(.light):not([data-ae-mode='light']) .ae-dialog-backdrop {\n background: rgba(18, 18, 18, 0.4);\n }\n\n :root:not(.light):not([data-ae-mode='light']) .ae-pop,\n :root:not(.light):not([data-ae-mode='light']) .ae-pop-surface,\n :root:not(.light):not([data-ae-mode='light']) .ae-toast {\n background: var(--ae-wash);\n box-shadow: none;\n }\n\n :root:not(.light):not([data-ae-mode='light']) [data-ae-tip]::after,\n :root:not(.light):not([data-ae-mode='light']) .ae-tip-surface {\n background: var(--ae-wash);\n }\n}\n\n/* the dialog: the panel costume in the top layer. A decision is asked\n in a soft-depth panel over a whisper of paper dim; Escape and the\n quiet button decline it. Pair with + showModal(). */\ndialog.ae-dialog {\n border: 0;\n border-radius: var(--ae-radius);\n background: var(--ae-surface);\n color: var(--ae-ink);\n width: min(26em, calc(100vw - 3em));\n box-sizing: border-box;\n padding: var(--ae-space-6);\n box-shadow:\n 0 1px 2px rgba(0, 0, 0, 0.05),\n 0 12px 36px rgba(0, 0, 0, 0.09);\n}\n\ndialog.ae-dialog[open] {\n animation: ae-enter var(--ae-soft) var(--ae-ease) both;\n}\n\ndialog.ae-dialog::backdrop {\n background: rgba(252, 252, 252, 0.4);\n}\n\n.dark dialog.ae-dialog,\n[data-ae-mode='dark'] dialog.ae-dialog {\n background: var(--ae-wash);\n box-shadow: none;\n}\n\n.dark dialog.ae-dialog::backdrop,\n[data-ae-mode='dark'] dialog.ae-dialog::backdrop {\n background: rgba(18, 18, 18, 0.4);\n}\n\n/* Headless dialog anatomy: Base UI renders separate popup and backdrop\n elements rather than a native . Behavior stays with Base UI;\n these classes carry the same Aesthetic costume. */\n.ae-dialog-panel {\n position: fixed;\n top: 50%;\n left: 50%;\n translate: -50% -50%;\n z-index: var(--ae-z-dialog);\n border: 0;\n border-radius: var(--ae-radius);\n background: var(--ae-surface);\n color: var(--ae-ink);\n width: min(26em, calc(100vw - 3em));\n box-sizing: border-box;\n padding: var(--ae-space-6);\n box-shadow:\n 0 1px 2px rgba(0, 0, 0, 0.05),\n 0 12px 36px rgba(0, 0, 0, 0.09);\n outline: none;\n}\n\n.ae-dialog-panel[data-open] {\n animation: ae-enter var(--ae-soft) var(--ae-ease) both;\n}\n\n.ae-dialog-backdrop {\n position: fixed;\n inset: 0;\n z-index: var(--ae-z-backdrop);\n background: rgba(252, 252, 252, 0.4);\n}\n\n.dark .ae-dialog-panel,\n[data-ae-mode='dark'] .ae-dialog-panel {\n background: var(--ae-wash);\n box-shadow: none;\n}\n\n.dark .ae-dialog-backdrop,\n[data-ae-mode='dark'] .ae-dialog-backdrop {\n background: rgba(18, 18, 18, 0.4);\n}\n\n/* the dialog's title and action row */\n.ae-dialog-title {\n font-weight: var(--ae-w-black);\n margin-bottom: var(--ae-space-2);\n}\n\n.ae-dialog-acts {\n display: flex;\n gap: var(--ae-space-4);\n justify-content: flex-end;\n margin-top: 1.8em;\n}\n\n/* the popover: a slip of surface anchored to its invoker — hairline\n frame, soft depth, no dim. Pair with the native popover attribute\n (or popovertarget); the recipe places it by the anchor. */\n.ae-pop {\n position: fixed;\n inset: auto;\n margin: 0;\n border: 1px solid var(--ae-line);\n background: var(--ae-surface);\n color: var(--ae-ink);\n width: max-content;\n max-width: 22em;\n padding: 1em 1.2em;\n box-shadow:\n 0 1px 2px rgba(0, 0, 0, 0.04),\n 0 8px 28px rgba(0, 0, 0, 0.06);\n}\n\n.ae-pop:popover-open {\n animation: ae-enter var(--ae-quick) var(--ae-ease) both;\n}\n\n.ae-pop::backdrop {\n background: transparent;\n}\n\n/* a popover holding only a menu drops the padding; the menu's own\n inset carries it */\n.ae-pop > .ae-menu:only-child {\n margin: -1em -1.2em;\n border: 0;\n}\n\n.dark .ae-pop,\n[data-ae-mode='dark'] .ae-pop {\n background: var(--ae-wash);\n box-shadow: none;\n}\n\n/* Headless menu/popover popup: its Positioner owns geometry. */\n.ae-pop-positioner {\n z-index: var(--ae-z-pop);\n}\n\n.ae-pop-surface {\n position: relative;\n z-index: var(--ae-z-pop);\n border: 1px solid var(--ae-line);\n background: var(--ae-surface);\n color: var(--ae-ink);\n width: max-content;\n max-width: 22em;\n padding: 1em 1.2em;\n box-shadow:\n 0 1px 2px rgba(0, 0, 0, 0.04),\n 0 8px 28px rgba(0, 0, 0, 0.06);\n outline: none;\n}\n\n.ae-pop-surface[data-open] {\n animation: ae-enter var(--ae-quick) var(--ae-ease) both;\n}\n\n.ae-pop-surface > .ae-menu:only-child {\n margin: -1em -1.2em;\n border: 0;\n}\n\n.dark .ae-pop-surface,\n[data-ae-mode='dark'] .ae-pop-surface {\n background: var(--ae-wash);\n box-shadow: none;\n}\n\n/* the tip: a whisper that answers hover — chrome-register words in a\n hairline slip. Patience built in: it waits a beat on hover, answers\n focus at once. Set the words in data-ae-tip; keep them short. */\n[data-ae-tip] {\n position: relative;\n}\n\n[data-ae-tip]::after {\n content: attr(data-ae-tip);\n position: absolute;\n bottom: calc(100% + 0.6em);\n left: 50%;\n transform: translateX(-50%);\n background: var(--ae-surface);\n color: var(--ae-ink-muted);\n border: 1px solid var(--ae-line);\n font-family: var(--ae-font);\n font-size: 13px;\n font-weight: var(--ae-w-regular);\n letter-spacing: normal;\n line-height: 1.5;\n padding: 0.25em 0.7em;\n white-space: nowrap;\n pointer-events: none;\n opacity: 0;\n z-index: var(--ae-z-tip);\n transition: opacity var(--ae-quick) var(--ae-ease);\n}\n[data-ae-tip]:hover::after {\n opacity: 1;\n transition-delay: 450ms;\n}\n\n[data-ae-tip]:focus-visible::after {\n opacity: 1;\n}\n\n.dark [data-ae-tip]::after,\n[data-ae-mode='dark'] [data-ae-tip]::after {\n background: var(--ae-wash);\n}\n\n/* Headless tooltip popup: a real element instead of [data-ae-tip]'s\n generated pseudo-element. Base UI owns delay and positioning. */\n.ae-tip-positioner {\n z-index: var(--ae-z-tip);\n}\n\n.ae-tip-surface {\n z-index: var(--ae-z-tip);\n background: var(--ae-surface);\n color: var(--ae-ink-muted);\n border: 1px solid var(--ae-line);\n font-family: var(--ae-font);\n font-size: 13px;\n font-weight: var(--ae-w-regular);\n letter-spacing: normal;\n line-height: 1.5;\n padding: 0.25em 0.7em;\n white-space: nowrap;\n pointer-events: none;\n}\n\n.ae-tip-surface[data-open] {\n animation: ae-enter var(--ae-quick) var(--ae-ease) both;\n}\n\n.dark .ae-tip-surface,\n[data-ae-mode='dark'] .ae-tip-surface {\n background: var(--ae-wash);\n}\n\n/* the toast: news arrives at the edge and waits to be read. The tray\n stacks bottom-right; each toast is a slip with the status on its\n glyph. It persists until dismissed or replaced — never an anxious\n timer by default. Behavior glue: recipes (aeToast). */\n.ae-toasts {\n position: fixed;\n right: 1.4em;\n bottom: 1.4em;\n z-index: var(--ae-z-toast);\n display: flex;\n flex-direction: column;\n align-items: flex-end;\n gap: 0.6em;\n}\n\n.ae-toast {\n display: flex;\n align-items: baseline;\n gap: 0.6em;\n background: var(--ae-surface);\n border: 1px solid var(--ae-line);\n color: var(--ae-ink);\n font-size: 13px;\n line-height: 1.6;\n padding: 0.7em 1.1em;\n max-width: 26em;\n box-shadow:\n 0 1px 2px rgba(0, 0, 0, 0.04),\n 0 8px 28px rgba(0, 0, 0, 0.06);\n animation: ae-enter var(--ae-soft) var(--ae-ease) both;\n}\n\n.ae-toast.is-leaving {\n opacity: 0;\n transition: opacity var(--ae-quick) var(--ae-ease);\n}\n\n.ae-toast-x {\n background: transparent;\n border: 0;\n padding: 0;\n margin-left: 0.4em;\n min-width: 24px;\n min-height: 24px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-size: 13px;\n line-height: inherit;\n color: var(--ae-ink-faint);\n cursor: pointer;\n transition: color var(--ae-quick) var(--ae-ease);\n}\n\n.ae-toast-x:hover {\n color: var(--ae-ink);\n}\n\n.dark .ae-toast,\n[data-ae-mode='dark'] .ae-toast {\n background: var(--ae-wash);\n box-shadow: none;\n}\n\n/* ── registers ────────────────────────────────────────────────────── */\n\n/* the name: heavy and spaced, never large */\n.ae-name {\n font-weight: var(--ae-w-black);\n letter-spacing: 0.38em;\n}\n\na.ae-name {\n text-decoration: none;\n}\n\n/* the lede: the page's whole argument, plainly */\n.ae-lede {\n max-width: 34em;\n text-wrap: pretty;\n}\n\n/* a group: whitespace is the only divider */\n.ae-group {\n margin-bottom: 2.6em;\n}\n\n.ae-group:last-child {\n margin-bottom: 0;\n}\n\n.ae-group p {\n margin-bottom: 0.15em;\n}\n\n/* a group heading: muted, spaced, medium */\n.ae-h {\n font-weight: var(--ae-w-medium);\n color: var(--ae-ink-muted);\n letter-spacing: 0.18em;\n margin-bottom: 0.7em;\n}\n\n/* an item that matters: medium ink */\n.ae-item {\n font-weight: var(--ae-w-medium);\n}\n\n/* supporting matter: muted ink */\n.ae-dim {\n color: var(--ae-ink-muted);\n}\n\n/* the accent: yours to define and spend with judgment */\n.ae-accent {\n color: var(--ae-accent);\n font-weight: var(--ae-w-medium);\n text-decoration: underline;\n text-decoration-color: currentColor;\n text-underline-offset: 0.2em;\n}\n\n/* status inks: put these on the glyph (svg.ae-icon), never the\n sentence — the word stays ink, the icon carries the hue */\n.ae-ok {\n color: var(--ae-ok);\n}\n\n.ae-warn {\n color: var(--ae-warn);\n}\n\n.ae-err {\n color: var(--ae-err);\n}\n\n/* the tag: a word in the plate-caption voice — mono, spaced, hairline\n at most. This is the badge, refused: no fill, no pill; status comes\n as a glyph beside the word, never as a colored box. */\n.ae-tag {\n display: inline-block;\n font-family: var(--ae-font-mono);\n font-size: 11px;\n line-height: 1.6;\n letter-spacing: 0.14em;\n color: var(--ae-ink-muted);\n border: 1px solid var(--ae-line);\n padding: 0.1em 0.6em;\n vertical-align: 0.1em;\n}\n\n.ae-tag-bare {\n border: 0;\n padding: 0;\n}\n\n/* the chip: a categorical tag — N agents, N series, N actors need N\n distinguishable hues with no severity meaning. This is not status\n (.ae-ok/.ae-warn/.ae-err never apply here); the hue identifies, it\n doesn't judge, so it's the one place the law lets a hue ride the\n word instead of a glyph. Same grammar as .ae-tag — mono, hairline,\n no fill — with the category's hue on both the border and the word\n (currentColor ties them). Compose with .ae-cat-0 through .ae-cat-7;\n .ae-chip-sub holds an optional muted trailing detail. */\n.ae-chip {\n display: inline-flex;\n align-items: baseline;\n gap: 0.5em;\n font-family: var(--ae-font-mono);\n font-size: 11px;\n line-height: 1.6;\n letter-spacing: 0.06em;\n border: 1px solid currentColor;\n padding: 0.12em 0.55em;\n color: var(--ae-ink-muted);\n}\n\n.ae-chip-sub {\n opacity: 0.75;\n}\n\n/* stat badges: a compact header summary, not a prose run. Values are\n tabular and strong; labels are quiet; status, when needed, rides the\n optional mark slot with .ae-ok/.ae-warn/.ae-err. Compose .ae-chip\n inside only for categorical side-detail, never severity. */\n.ae-stat-badges {\n display: flex;\n flex-wrap: wrap;\n align-items: stretch;\n gap: var(--ae-space-2);\n}\n\n.ae-stat-badge {\n display: inline-flex;\n align-items: baseline;\n gap: var(--ae-space-2);\n border: 1px solid var(--ae-line);\n padding: 0.34em 0.65em;\n color: var(--ae-ink);\n white-space: nowrap;\n}\n\n.ae-stat-mark {\n font-family: var(--ae-font-mono);\n font-size: 13px;\n font-weight: var(--ae-w-black);\n}\n\n.ae-stat-value {\n font-family: var(--ae-font-mono);\n font-weight: var(--ae-w-medium);\n font-variant-numeric: tabular-nums;\n}\n\n.ae-stat-label {\n color: var(--ae-ink-muted);\n}\n\n.ae-stat-badge .ae-chip {\n margin-left: var(--ae-space-1);\n}\n\n.ae-cat-0 {\n color: var(--ae-cat-0);\n}\n\n.ae-cat-1 {\n color: var(--ae-cat-1);\n}\n\n.ae-cat-2 {\n color: var(--ae-cat-2);\n}\n\n.ae-cat-3 {\n color: var(--ae-cat-3);\n}\n\n.ae-cat-4 {\n color: var(--ae-cat-4);\n}\n\n.ae-cat-5 {\n color: var(--ae-cat-5);\n}\n\n.ae-cat-6 {\n color: var(--ae-cat-6);\n}\n\n.ae-cat-7 {\n color: var(--ae-cat-7);\n}\n\n/* the keycap: a quiet square of wash around the key's name */\nkbd {\n background: var(--ae-wash);\n border: 1px solid var(--ae-line);\n padding: 0.08em 0.5em;\n}\n\n/* ── forms: a line, not a box ─────────────────────────────────────── */\n\n.ae-label {\n display: block;\n font-weight: var(--ae-w-medium);\n margin-bottom: var(--ae-space-0);\n}\n\n.ae-input {\n display: block;\n width: 100%;\n box-sizing: border-box;\n background: transparent;\n color: var(--ae-ink);\n cursor: text;\n border: 0;\n border-bottom: 1px solid var(--ae-line);\n border-radius: var(--ae-radius);\n padding: 0.4em 0 calc(0.4em - 1px);\n transition: border-color var(--ae-quick) var(--ae-ease);\n}\n\n.ae-input::placeholder {\n color: var(--ae-ink-faint);\n}\n\n/* the focus ring: a 2px underline (not 1px) so the state reads at a\n glance, never a glow. padding-bottom compensates the extra px so\n the field doesn't shift height on focus. */\n.ae-input:focus-visible {\n outline: none;\n border-bottom-width: 2px;\n border-bottom-color: var(--ae-ink);\n}\n\ntextarea.ae-input {\n resize: none;\n min-height: 5.5em;\n}\n\n/* field validation: the line confesses, the glyph carries the hue,\n the sentence stays ink. Set aria-invalid on the field; put the\n message in a field note with a status icon. */\n.ae-input[aria-invalid='true'] {\n border-bottom-color: var(--ae-err);\n}\n\n.ae-field-note {\n display: flex;\n align-items: baseline;\n gap: 0.45em;\n font-size: 13px;\n color: var(--ae-ink-muted);\n margin-top: 0.4em;\n}\n\n/* buttons are not links: contained shape, ink, never resizing.\n Primary is solid ink by default; .ae-button-quiet is the hairline\n secondary. Spend the accent on a button when the project's scheme calls\n for it — where the accent goes is the consumer's judgment, not a rule. */\n.ae-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--ae-space-2);\n background: var(--ae-ink);\n border: 1px solid var(--ae-ink);\n border-radius: var(--ae-radius);\n color: var(--ae-surface);\n font-weight: var(--ae-w-medium);\n padding: 0.55em 1.5em;\n min-height: 44px;\n cursor: pointer;\n transition:\n opacity var(--ae-quick) var(--ae-ease),\n background-color var(--ae-quick) var(--ae-ease),\n transform var(--ae-quick) var(--ae-ease);\n}\n\n.ae-button:hover {\n opacity: 0.9;\n}\n\n.ae-button:active {\n transform: translateY(1px) scale(0.99);\n}\n\n.ae-button:disabled {\n cursor: default;\n opacity: 0.7;\n transform: none;\n}\n\n.ae-button-quiet {\n background: transparent;\n color: var(--ae-ink);\n}\n\n.ae-button-quiet:hover {\n background: var(--ae-wash);\n opacity: 1;\n}\n\n/* the compact scale: a button living in the 13px chrome register —\n toolbars, table rows, rails. Same shapes, smaller breath. */\n.ae-button-compact {\n font-size: 13px;\n padding: 0.35em 1em;\n min-height: 32px;\n}\n\n/* the send moment: the label gives way to the resolved state, gently,\n once. Success persists — disable the button instead of resetting it.\n Markup: \n Add .is-sent to resolve. The status icon sits AFTER the word.\n The button never changes size: the resting label defines the box and\n the resolved layer is absolutely stacked over it. */\n.ae-send {\n position: relative;\n overflow: hidden;\n}\n\n.ae-send .ae-send-label,\n.ae-send .ae-send-done {\n display: inline-flex;\n align-items: center;\n gap: var(--ae-space-2);\n transition:\n opacity var(--ae-gentle) var(--ae-ease),\n transform var(--ae-gentle) var(--ae-ease);\n}\n\n.ae-send .ae-send-done {\n position: absolute;\n inset: 0;\n justify-content: center;\n opacity: 0;\n transform: translateY(45%);\n}\n\n.ae-send.is-sent .ae-send-label {\n opacity: 0;\n transform: translateY(-55%);\n}\n\n.ae-send.is-sent .ae-send-done {\n opacity: 1;\n transform: translateY(0);\n}\n\n.ae-send.is-sent,\n.ae-send.is-sent:disabled {\n opacity: 1;\n}\n\n/* ── choice marks: squares, since radius is 0 everywhere ──────────── */\n\n/* a choice row: the mark and its words share a line and one cursor */\n.ae-choice {\n display: flex;\n align-items: center;\n gap: 0.65em;\n cursor: pointer;\n}\n\n.ae-choice + .ae-choice {\n margin-top: 0.3em;\n}\n\n/* the checkbox: a pen-stroke square that fills with ink; the check is\n drawn in surface. The radio is the same square holding an ink core —\n a circle would be the only curve in the system, so there isn't one. */\ninput.ae-check,\ninput.ae-radio {\n appearance: none;\n width: 1em;\n height: 1em;\n flex: none;\n margin: 0;\n display: inline-grid;\n place-content: center;\n background: transparent;\n border: 1px solid var(--ae-ink-muted);\n border-radius: var(--ae-radius);\n cursor: pointer;\n vertical-align: -0.12em;\n transition:\n border-color var(--ae-quick) var(--ae-ease),\n background-color var(--ae-quick) var(--ae-ease);\n}\n\ninput.ae-check:hover,\ninput.ae-radio:hover,\n.ae-choice:hover input.ae-check,\n.ae-choice:hover input.ae-radio {\n border-color: var(--ae-ink);\n}\n\ninput.ae-check::before {\n content: '';\n width: 0.55em;\n height: 0.3em;\n margin-top: -0.12em;\n border-left: 2px solid var(--ae-surface);\n border-bottom: 2px solid var(--ae-surface);\n transform: rotate(-45deg);\n opacity: 0;\n transition: opacity var(--ae-quick) var(--ae-ease);\n}\n\ninput.ae-check:checked {\n background: var(--ae-ink);\n border-color: var(--ae-ink);\n}\n\ninput.ae-check:checked::before {\n opacity: 1;\n}\n\ninput.ae-radio::before {\n content: '';\n width: 0.42em;\n height: 0.42em;\n background: var(--ae-ink);\n opacity: 0;\n transition: opacity var(--ae-quick) var(--ae-ease);\n}\n\ninput.ae-radio:checked {\n border-color: var(--ae-ink);\n}\n\ninput.ae-radio:checked::before {\n opacity: 1;\n}\n\ninput.ae-check:disabled,\ninput.ae-radio:disabled,\ninput.ae-switch:disabled {\n opacity: 0.45;\n cursor: default;\n}\n\n/* the switch: a hairline channel and an ink slide — state reads from\n position and fill, never from a green pill. The slide is the\n feedback; it resolves once per flip. */\ninput.ae-switch {\n appearance: none;\n position: relative;\n width: 2.1em;\n height: 1.15em;\n flex: none;\n margin: 0;\n background: transparent;\n border: 1px solid var(--ae-ink-muted);\n border-radius: var(--ae-radius);\n cursor: pointer;\n vertical-align: -0.18em;\n transition: border-color var(--ae-quick) var(--ae-ease);\n}\n\ninput.ae-switch::before {\n content: '';\n position: absolute;\n top: 0.14em;\n bottom: 0.14em;\n left: 0.14em;\n width: 0.68em;\n background: var(--ae-ink-muted);\n transition:\n left var(--ae-quick) var(--ae-ease),\n background-color var(--ae-quick) var(--ae-ease);\n}\n\ninput.ae-switch:hover {\n border-color: var(--ae-ink);\n}\n\ninput.ae-switch:checked {\n border-color: var(--ae-ink);\n}\n\ninput.ae-switch:checked::before {\n left: calc(100% - 0.82em);\n background: var(--ae-ink);\n}\n\n/* ── data: the table is an instrument, framed as a plate ──────────── */\n\n/* the table: terminal density — 13px mono, lowercase headers on a\n wash band, hairline row rules. A column header shares its column's\n alignment: put .num on the th AND its tds. */\n.ae-table {\n width: 100%;\n border-collapse: collapse;\n font-family: var(--ae-font-mono);\n font-size: 13px;\n line-height: 1.7;\n}\n\n.ae-table th {\n text-align: left;\n font-weight: var(--ae-w-regular);\n letter-spacing: 0.08em;\n color: var(--ae-ink);\n background: var(--ae-wash);\n padding: 0.45em 1.4em 0.45em 0.7em;\n}\n\n.ae-table td {\n border-top: 1px solid var(--ae-line);\n padding: 0.42em 1.4em 0.42em 0.7em;\n}\n\n.ae-table tbody tr:first-child td {\n border-top: 0;\n}\n\n.ae-table .num {\n text-align: right;\n}\n\n.ae-table .ae-item {\n font-weight: var(--ae-w-medium);\n}\n\n/* sm table stack: add data-label to each td when a dense instrument must\n survive phone width. The row becomes a ruled list of label/value pairs\n instead of shrinking columns until they are unreadable. */\n@media (max-width: 40rem) {\n .ae-table:has(td[data-label]) {\n display: block;\n }\n\n .ae-table:has(td[data-label]) thead {\n display: none;\n }\n\n .ae-table:has(td[data-label]) tbody,\n .ae-table:has(td[data-label]) tr,\n .ae-table:has(td[data-label]) td {\n display: block;\n }\n\n .ae-table:has(td[data-label]) tbody tr {\n border-top: 1px solid var(--ae-line);\n padding: 0.55em 0;\n }\n\n .ae-table:has(td[data-label]) tbody tr:first-child {\n border-top: 0;\n }\n\n .ae-table:has(td[data-label]) td {\n border-top: 0;\n padding: 0.16em 0;\n display: grid;\n grid-template-columns: minmax(7em, 0.45fr) minmax(0, 1fr);\n gap: var(--ae-space-3);\n text-align: left;\n white-space: normal;\n overflow-wrap: anywhere;\n }\n\n .ae-table:has(td[data-label]) td::before {\n content: attr(data-label);\n color: var(--ae-ink-faint);\n font-weight: var(--ae-w-regular);\n letter-spacing: 0.08em;\n text-align: left;\n }\n\n .ae-table:has(td[data-label]) td.num {\n text-align: right;\n }\n}\n\n/* the plate: a numbered figure frame around a table or specimen —\n mono caption above, hairline frame, footnote rule below */\n.ae-plate {\n border: 1px solid var(--ae-line);\n padding: 1.4em 1.6em 1.2em;\n}\n\n.ae-plate-cap {\n font-family: var(--ae-font-mono);\n font-size: 11px;\n letter-spacing: 0.14em;\n color: var(--ae-ink-muted);\n margin-bottom: var(--ae-space-4);\n}\n\n.ae-plate-note {\n font-size: 13px;\n color: var(--ae-ink-faint);\n border-top: 1px solid var(--ae-line);\n padding-top: 0.8em;\n margin-top: 1.1em;\n}\n\n/* the trail: a chronological run of events as the conversation it is —\n a hairline spine, an ink tick per entry, time and actor in the\n instrument register, the body in prose. Not a table: entries carry\n variable-length bodies and states, so alignment comes from the spine,\n not columns. The tick is a square, never a circle — the choice mark's\n rule holds here too. Status, if an entry carries one, rides a glyph\n in the body, never the tick or a filled pill. */\n.ae-trail {\n list-style: none;\n margin: 0 0 0 0.25em;\n padding: 0;\n border-left: 1px solid var(--ae-line);\n}\n\n.ae-trail-item {\n position: relative;\n padding: 0 0 1.3em 1.5em;\n}\n\n.ae-trail-item:last-child {\n padding-bottom: 0;\n}\n\n.ae-trail-item::before {\n content: '';\n position: absolute;\n left: -0.31em;\n top: 0.5em;\n width: 0.5em;\n height: 0.5em;\n background: var(--ae-ink-faint);\n}\n\n.ae-trail-item.is-active::before {\n background: var(--ae-accent);\n}\n\n.ae-trail-head {\n display: flex;\n align-items: baseline;\n gap: 0.7em;\n flex-wrap: wrap;\n margin-bottom: 0.25em;\n}\n\n.ae-trail-time {\n font-family: var(--ae-font-mono);\n font-size: 11px;\n letter-spacing: 0.06em;\n color: var(--ae-ink-muted);\n}\n\n.ae-trail-who {\n font-family: var(--ae-font-mono);\n font-size: 11px;\n letter-spacing: 0.04em;\n color: var(--ae-ink-muted);\n border: 1px solid currentColor;\n padding: 0.1em 0.55em;\n}\n\n.ae-trail-body {\n font-size: 13px;\n color: var(--ae-ink);\n}\n\n.ae-trail-body .ae-dim {\n color: var(--ae-ink-muted);\n}\n\n/* the wall: a grid of instrument cards for many-of-a-kind status —\n services, agents, targets. Each card is a hairline plate; status\n rides the glyph (.ae-ok/.ae-warn/.ae-err on .ae-wall-mark), never a\n filled tile or a colored border. The right column is optional: a\n figure, a timestamp, a trace of recent readings. */\n.ae-wall {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr));\n gap: var(--ae-space-6);\n}\n\n.ae-wall-card {\n border: 1px solid var(--ae-line);\n padding: var(--ae-space-4) var(--ae-space-5);\n display: grid;\n grid-template-columns: 1fr auto;\n gap: var(--ae-space-4);\n}\n\n.ae-wall-head {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--ae-space-2) var(--ae-space-3);\n}\n\n.ae-wall-mark {\n font-family: var(--ae-font-mono);\n font-weight: var(--ae-w-black);\n flex: none;\n}\n\n.ae-wall-mark.ae-icon {\n width: 1.1em;\n height: 1.1em;\n vertical-align: -0.18em;\n}\n\n.ae-wall-meta {\n font-size: 13px;\n color: var(--ae-ink-muted);\n margin-top: 0.2em;\n}\n\n.ae-wall-figure {\n text-align: right;\n}\n\n.ae-wall-figure .ae-num {\n display: block;\n}\n\n.ae-wall-time {\n font-size: 13px;\n color: var(--ae-ink-muted);\n}\n\n/* a trace: the wall figure's recent-readings strip — glyphs, not a\n sparkline; the discrete cousin of .ae-spark */\n.ae-wall-trace {\n display: inline-flex;\n align-items: center;\n justify-content: flex-end;\n gap: 0.18em;\n color: var(--ae-ink-muted);\n white-space: nowrap;\n}\n\n.ae-wall-trace .ae-icon {\n width: 1em;\n height: 1em;\n vertical-align: 0;\n}\n\n/* the board: several columns of comparable work. Columns are hairline\n lanes, not cards inside cards; at narrow widths they stack before their\n contents are squeezed. */\n.ae-board {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));\n gap: var(--ae-space-6);\n align-items: start;\n}\n\n.ae-column {\n min-width: 0;\n border-top: 1px solid var(--ae-line);\n padding-top: var(--ae-space-4);\n}\n\n.ae-column-head {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: var(--ae-space-4);\n margin-bottom: var(--ae-space-4);\n}\n\n.ae-column-title {\n font-weight: var(--ae-w-medium);\n}\n\n.ae-column-meta {\n font-family: var(--ae-font-mono);\n font-size: 13px;\n color: var(--ae-ink-muted);\n white-space: nowrap;\n}\n\n.ae-column-body {\n display: grid;\n gap: var(--ae-space-3);\n}\n\n.ae-board-card {\n border-top: 1px solid var(--ae-line);\n padding-top: var(--ae-space-3);\n}\n\n.ae-board-card:first-child {\n border-top: 0;\n padding-top: 0;\n}\n\n/* ── instruments: ink on a ruled line ─────────────────────────────── */\n\n/* the meter: a wash channel and an ink fill — a ruled line, never a\n glowing pill. The fill takes a status ink only when the level IS\n the signal (thresholds); ticks are hairline marks. Width changes\n are state resolutions: gentle, once. */\n.ae-meter {\n position: relative;\n height: 4px;\n background: var(--ae-wash);\n border: 1px solid var(--ae-line);\n}\n\n.ae-meter-fill {\n position: absolute;\n inset: 0 auto 0 0;\n background: var(--ae-ink);\n transition: width var(--ae-gentle) var(--ae-ease);\n}\n\n.ae-meter-fill.ae-ok {\n background: var(--ae-ok);\n}\n\n.ae-meter-fill.ae-warn {\n background: var(--ae-warn);\n}\n\n.ae-meter-fill.ae-err {\n background: var(--ae-err);\n}\n\n.ae-meter-mark {\n position: absolute;\n top: -3px;\n bottom: -3px;\n width: 1px;\n background: var(--ae-ink-muted);\n}\n\n/* figures: tabular numerals that tick without jitter. Compose with the\n weights — a hero figure is 800 and isolated, never larger. */\n.ae-num {\n font-family: var(--ae-font-mono);\n font-variant-numeric: tabular-nums;\n}\n\n/* the strong register: the one loud thing, by weight alone */\n.ae-strong {\n font-weight: var(--ae-w-black);\n}\n\n/* a delta: the arrow carries the hue, the figure stays ink */\n.ae-delta {\n display: inline-flex;\n align-items: baseline;\n gap: 0.35em;\n}\n\n/* the spark: a pen-stroke trend — no axes, no area fill. Inline SVG:\n \n */\n.ae-spark {\n width: 7em;\n height: 1.1em;\n stroke: currentColor;\n stroke-width: 1.5;\n stroke-linecap: round;\n stroke-linejoin: round;\n fill: none;\n vertical-align: -0.15em;\n}\n\n/* the interval: the meter with a spread. A ruled track carrying a\n [lo,hi] range band and a mean tick, so a score reads as a level AND\n its confidence — and where two bands share a column, the lead is\n inside the spread (the certification read). Marks are positioned by\n inline left/width %, exactly like the meter. Every mark is a\n rectangle, so radius 0 holds (round marks are the scatter's pen). */\n.ae-ci {\n position: relative;\n height: 1.6em;\n}\n\n.ae-ci::before {\n /* the shared baseline rule the interval sits on */\n content: '';\n position: absolute;\n inset: 50% 0 auto;\n height: 1px;\n background: var(--ae-line);\n}\n\n/* the [lo,hi] range band: a bounded mass with a hairline edge — never a\n filled status pill. It draws in once from the mean (the meter's\n width-as-resolution), gentle and only on first render. */\n.ae-ci-band {\n position: absolute;\n top: 50%;\n height: 10px;\n transform: translateY(-50%);\n transform-origin: center;\n background: color-mix(in oklab, var(--ae-ink) 9%, var(--ae-surface));\n border: 1px solid var(--ae-ink-faint);\n animation: ae-ci-draw var(--ae-soft) var(--ae-ease) both;\n}\n\n@keyframes ae-ci-draw {\n from {\n transform: translateY(-50%) scaleX(0.04);\n }\n}\n\n/* the mean: a single ink tick, like .ae-meter-mark. is-lead spends the\n accent on it — one accent role per view, the consumer's to assign. */\n.ae-ci-mean {\n position: absolute;\n top: 50%;\n width: 2px;\n height: 1.15em;\n transform: translate(-50%, -50%);\n background: var(--ae-ink);\n}\n\n.ae-ci.is-lead .ae-ci-mean {\n background: var(--ae-accent);\n}\n\n/* the no-fill variant: a thin lo→hi rule with square end caps, for a\n field that should read as points rather than mass */\n.ae-ci-whisker {\n position: absolute;\n top: 50%;\n height: 1px;\n transform: translateY(-50%);\n background: var(--ae-ink-muted);\n}\n\n.ae-ci-cap {\n position: absolute;\n top: 50%;\n width: 1px;\n height: 0.8em;\n transform: translate(-50%, -50%);\n background: var(--ae-ink-muted);\n}\n\n/* hover (or keyboard focus) reveals the exact figures at the track's\n right edge — surface-backed so it rides over the baseline rule with\n no layout shift. The mark carries the field; the numbers stay exact\n on inspection. */\n.ae-ci-detail {\n position: absolute;\n right: 0;\n top: 50%;\n transform: translateY(-50%);\n display: inline-flex;\n align-items: baseline;\n gap: var(--ae-space-2);\n padding: 0.1em 0.2em 0.1em 0.7em;\n background: var(--ae-surface);\n font-family: var(--ae-font-mono);\n font-size: 11px;\n font-variant-numeric: tabular-nums;\n color: var(--ae-ink);\n white-space: nowrap;\n opacity: 0;\n pointer-events: none;\n transition: opacity var(--ae-quick) var(--ae-ease);\n}\n\n.ae-ci-detail .ae-ci-bound {\n color: var(--ae-ink-faint);\n}\n\n.ae-ci:hover .ae-ci-detail,\n.ae-ci:focus-within .ae-ci-detail {\n opacity: 1;\n}\n\n/* touch has no hover: show the exact figures by default there, so the\n precise read is never pointer-only. Screen readers get them from the\n element's aria-label (role=\"img\"). */\n@media (hover: none) {\n .ae-ci-detail {\n opacity: 1;\n }\n}\n\n/* the plane: an XY field — cost against quality — with a Pareto\n frontier. Like .ae-spark, the kit dresses a consumer-authored \n (the consumer owns the data and the projection); round dots are the\n pen (SVG , never a CSS radius), the frame/axes/ticks stay\n radius 0. Default composition = frontier: a dashed guide threads the\n non-dominated points, the pick is the lone accent dot, the rest drop\n to faint. */\n.ae-plot {\n display: block;\n width: 100%;\n height: auto;\n font-family: var(--ae-font-mono);\n}\n.ae-plot-axis {\n fill: none;\n stroke: var(--ae-line);\n stroke-width: 1;\n}\n.ae-plot-tick {\n stroke: var(--ae-line);\n stroke-width: 1;\n}\n.ae-plot-ticklabel {\n fill: var(--ae-ink-faint);\n font-size: 11px;\n letter-spacing: 0.04em;\n}\n.ae-plot-axislabel {\n fill: var(--ae-ink-muted);\n font-size: 11px;\n letter-spacing: 0.12em;\n}\n.ae-plot-frontier {\n fill: none;\n stroke: var(--ae-ink);\n stroke-width: 1.5;\n stroke-dasharray: 5 4;\n stroke-linecap: round;\n stroke-linejoin: round;\n}\n.ae-plot-crosshair {\n stroke: var(--ae-ink-faint);\n stroke-width: 1;\n stroke-dasharray: 2 3;\n}\n.ae-plot-dot {\n fill: var(--ae-ink);\n transition: fill var(--ae-soft) var(--ae-ease);\n}\n.ae-plot-dot.is-dominated {\n fill: var(--ae-ink-faint);\n}\n.ae-plot-dot.is-chosen {\n fill: var(--ae-accent);\n}\n.ae-plot-ring {\n fill: none;\n stroke: var(--ae-ink-muted);\n stroke-width: 1.5;\n}\n.ae-plot-label {\n fill: var(--ae-ink-muted);\n font-size: 11px;\n letter-spacing: 0.02em;\n}\n.ae-plot-label.is-dominated {\n fill: var(--ae-ink-faint);\n}\n.ae-plot-label.is-chosen {\n fill: var(--ae-accent);\n}\n\n/* a gate-failed datum is a colored mark where its dot would sit —\n status rides the glyph; compose with .ae-ok / .ae-warn / .ae-err */\n.ae-plot-glyph {\n fill: none;\n stroke: var(--ae-ink);\n stroke-width: 2;\n stroke-linecap: round;\n}\n.ae-plot-glyph.ae-ok {\n stroke: var(--ae-ok);\n}\n.ae-plot-glyph.ae-warn {\n stroke: var(--ae-warn);\n}\n.ae-plot-glyph.ae-err {\n stroke: var(--ae-err);\n}\n\n/* the flow: a pipeline as a diagram — hairline-framed nodes (hard\n corners, radius 0) joined by orthogonal ink wires. Like the plot, the\n kit dresses a consumer-authored so node and wire geometry share\n one coordinate space; non-scaling strokes keep hairlines 1px at any\n fit. State rides the frame and a glyph (✓ done, ✗ failed), the running\n stage takes the accent, locked stages go dashed-faint. The default is\n the spine: a row of stages, the gate/lineage backbone. */\n.ae-flow {\n display: block;\n width: 100%;\n height: auto;\n}\n.ae-node {\n fill: var(--ae-surface);\n stroke: var(--ae-line);\n stroke-width: 1;\n vector-effect: non-scaling-stroke;\n}\n.ae-node.is-done {\n stroke: var(--ae-ink-muted);\n}\n.ae-node.is-active {\n stroke: var(--ae-accent);\n stroke-width: 1.5;\n}\n.ae-node.is-locked {\n fill: var(--ae-wash);\n stroke: var(--ae-ink-faint);\n stroke-dasharray: 3 3;\n}\n.ae-node.is-failed {\n stroke: var(--ae-err);\n}\n\n/* the node label — the 13px instrument register, ink. State lives in the\n frame and the glyph; only the running stage spends the accent. */\n.ae-node-label {\n fill: var(--ae-ink);\n font-family: var(--ae-font);\n font-size: 13px;\n font-weight: var(--ae-w-medium);\n}\n.ae-node-label.is-active {\n fill: var(--ae-accent);\n font-weight: var(--ae-w-black);\n}\n.ae-node-label.is-locked {\n fill: var(--ae-ink-faint);\n font-weight: var(--ae-w-regular);\n}\n\n/* a mono ordinal above the label, and mono in/out port labels at the\n node's edges (a node's typed interface) */\n.ae-node-kicker {\n fill: var(--ae-ink-faint);\n font-family: var(--ae-font-mono);\n font-size: 11px;\n letter-spacing: 0.1em;\n}\n.ae-node-port {\n fill: var(--ae-ink-muted);\n font-family: var(--ae-font-mono);\n font-size: 11px;\n letter-spacing: 0.04em;\n}\n.ae-node-port.is-locked {\n fill: var(--ae-ink-faint);\n}\n.ae-node-stub {\n stroke: var(--ae-line);\n stroke-width: 1;\n vector-effect: non-scaling-stroke;\n}\n\n/* the wire: an orthogonal ink connector (H/V segments only), colored by\n the stage it feeds. A round junction marks a split or merge — the one\n round mark, like the spark's caps; the frames stay square. */\n.ae-wire {\n fill: none;\n stroke: var(--ae-line);\n stroke-width: 1;\n vector-effect: non-scaling-stroke;\n}\n.ae-wire.is-reached {\n stroke: var(--ae-ink-muted);\n}\n.ae-wire.is-active {\n stroke: var(--ae-accent);\n}\n.ae-wire.is-locked {\n stroke: var(--ae-ink-faint);\n stroke-dasharray: 3 3;\n}\n.ae-wire-junction {\n fill: var(--ae-ink-muted);\n}\n.ae-wire-junction.is-active {\n fill: var(--ae-accent);\n}\n\n/* ── choice controls: rows that open ──────────────────────────────── */\n\n/* a setting is a quiet hairline row — label left, current value right\n in muted ink; activating it opens a chooser beneath. Progressive\n disclosure: the form rests as a column of lines. */\n.ae-settings {\n border-bottom: 1px solid var(--ae-line);\n}\n\n.ae-setting {\n display: flex;\n width: 100%;\n justify-content: space-between;\n align-items: baseline;\n gap: var(--ae-space-5);\n background: transparent;\n border: 0;\n border-top: 1px solid var(--ae-line);\n border-radius: var(--ae-radius);\n padding: 0.8em 0;\n margin: 0;\n font: inherit;\n line-height: inherit;\n color: var(--ae-ink);\n font-weight: var(--ae-w-medium);\n text-align: left;\n cursor: pointer;\n}\n\n.ae-setting-val {\n color: var(--ae-ink-muted);\n font-weight: var(--ae-w-regular);\n transition: color var(--ae-quick) var(--ae-ease);\n}\n\n.ae-setting:hover .ae-setting-val {\n color: var(--ae-ink);\n}\n\n/* the chooser: folds open under its row — the smooth expand is\n feedback to the activation, once per action */\n.ae-setting-panel {\n display: grid;\n grid-template-rows: 0fr;\n transition: grid-template-rows var(--ae-soft) var(--ae-ease);\n}\n\n.ae-setting-panel.is-open {\n grid-template-rows: 1fr;\n}\n\n.ae-setting-panel > * {\n overflow: hidden;\n min-height: 0;\n\n /* the child's border and padding outlive a 0fr row; stop painting\n them once the fold completes */\n visibility: hidden;\n transition: visibility 0s var(--ae-soft);\n}\n\n.ae-setting-panel.is-open > * {\n visibility: visible;\n transition: visibility 0s;\n}\n\n/* the menu: a quiet option list (also the long-select answer) */\n.ae-menu {\n margin: 0 0 0.9em;\n padding: 0.3em 0;\n border: 1px solid var(--ae-line);\n background: var(--ae-surface);\n max-height: 14em;\n overflow-y: auto;\n}\n\n.ae-menu button,\n.ae-menu-item {\n display: block;\n width: 100%;\n text-align: left;\n background: transparent;\n border: 0;\n border-radius: var(--ae-radius);\n font: inherit;\n font-size: 13px;\n line-height: 1.7;\n color: var(--ae-ink-muted);\n padding: 0.4em 1.1em;\n cursor: pointer;\n transition:\n color var(--ae-quick) var(--ae-ease),\n background-color var(--ae-quick) var(--ae-ease);\n}\n\n.ae-menu button:hover,\n.ae-menu-item:hover,\n.ae-menu-item[data-highlighted] {\n background: var(--ae-wash);\n color: var(--ae-ink);\n}\n\n.ae-menu button.is-sel,\n.ae-menu-item.is-sel {\n color: var(--ae-ink);\n font-weight: var(--ae-w-medium);\n}\n\n/* the fold: disclosure as a hairline row — native details in the\n settings costume. The marker is a mono plus that turns into a\n close; the quarter-turn is the feedback, once per action. */\ndetails.ae-fold {\n border-top: 1px solid var(--ae-line);\n}\n\ndetails.ae-fold:last-of-type {\n border-bottom: 1px solid var(--ae-line);\n}\n\n.ae-fold summary {\n list-style: none;\n display: flex;\n justify-content: space-between;\n align-items: baseline;\n gap: var(--ae-space-5);\n padding: 0.8em 0;\n font-weight: var(--ae-w-medium);\n cursor: pointer;\n}\n\n.ae-fold summary::marker {\n content: '';\n}\n\n.ae-fold summary::after {\n content: '+';\n font-family: var(--ae-font-mono);\n font-weight: var(--ae-w-regular);\n color: var(--ae-ink-muted);\n transition: rotate var(--ae-soft) var(--ae-ease);\n}\n\n.ae-fold[open] summary::after {\n rotate: 45deg;\n}\n\n.ae-fold > :not(summary) {\n padding-bottom: var(--ae-space-4);\n}\n\n/* ── structure: paths, waiting, and absence ───────────────────────── */\n\n/* the breadcrumb: a chrome-register path — here is ink, the way back\n is muted, the separators are faint */\n.ae-crumbs {\n font-size: 13px;\n color: var(--ae-ink-muted);\n}\n\n.ae-crumbs ol {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-wrap: wrap;\n gap: 0.6em;\n}\n\n.ae-crumbs li {\n display: flex;\n gap: 0.6em;\n}\n\n.ae-crumbs li + li::before {\n content: '/';\n color: var(--ae-ink-faint);\n}\n\n.ae-crumbs a {\n color: var(--ae-ink-muted);\n text-decoration: none;\n}\n\n.ae-crumbs a:hover {\n color: var(--ae-ink);\n}\n\n.ae-crumbs [aria-current] {\n color: var(--ae-ink);\n font-weight: var(--ae-w-medium);\n}\n\n/* waiting: still wash, never a shimmer. The skeleton holds the layout\n in quiet blocks; motion is feedback and waiting is not an action —\n the view's entrance acknowledges arrival. Give the words to a\n chrome-register status line (role=\"status\"), not to an animation. */\n.ae-skeleton {\n display: block;\n background: var(--ae-wash);\n color: transparent;\n pointer-events: none;\n user-select: none;\n}\n\n.ae-skeleton * {\n visibility: hidden;\n}\n\n/* absence: an honest sentence where the content will be — muted words,\n one quiet action, no illustration party */\n.ae-empty {\n padding: 2.4em 0;\n border-block: 1px solid var(--ae-line);\n}\n\n.ae-empty > p {\n margin-bottom: 0.15em;\n}\n\n.ae-empty .ae-button {\n margin-top: 1.2em;\n}\n\n/* opt-in via data-ae-anticipate on the form; the recipe drives\n --ae-intent (0–0.6) on the nearest field's label and input.\n Color only — nothing moves; focus snaps to the committed state;\n off for touch, keyboards, and reduced motion. */\n[data-ae-anticipate] .ae-label {\n color: color-mix(\n in srgb,\n var(--ae-ink) calc(var(--ae-intent, 0) * 100%),\n var(--ae-ink-muted)\n );\n transition: color 120ms ease-out;\n}\n\n[data-ae-anticipate] .ae-input {\n border-bottom-color: color-mix(\n in srgb,\n var(--ae-ink) calc(var(--ae-intent, 0) * 100%),\n var(--ae-line)\n );\n transition: border-color 120ms ease-out;\n}\n\n[data-ae-anticipate] .ae-label:has(+ .ae-input:focus-visible) {\n color: var(--ae-ink);\n}\n\n[data-ae-anticipate] .ae-input:focus-visible {\n border-bottom-color: var(--ae-ink);\n}\n\n/* ── the mode change: quick and interruptible ─────────────────────── */\n\n/* the recipe wraps the flip in document.startViewTransition and sets\n .ae-vt-mode on the root for one quick beat. Rapid toggles call\n skipTransition() and cut the in-flight animation. */\n.ae-vt-mode::view-transition-old(root),\n.ae-vt-mode::view-transition-new(root) {\n animation-duration: var(--ae-quick);\n animation-timing-function: var(--ae-ease);\n}\n\n/* fallback where view transitions are unsupported: every surface\n eases its colors together for one quick beat */\n.ae-mode-easing,\n.ae-mode-easing *,\n.ae-mode-easing *::before,\n.ae-mode-easing *::after {\n transition:\n background-color var(--ae-quick) var(--ae-ease),\n color var(--ae-quick) var(--ae-ease),\n border-color var(--ae-quick) var(--ae-ease),\n fill var(--ae-quick) var(--ae-ease),\n stroke var(--ae-quick) var(--ae-ease) !important;\n}\n\n/* ── the app shell: a chrome rail beside the working desk ─────────── */\n\n/* the second archetype. One viewport, hairline-divided: a 13px rail\n for places, a desk for work. The screen law holds — the desk\n scrolls inside itself, the page never moves. */\n.ae-shell {\n height: 100dvh;\n display: grid;\n grid-template-columns: 14rem minmax(0, 1fr);\n overflow: hidden;\n}\n\n.ae-rail {\n border-right: 1px solid var(--ae-line);\n padding: 1.6em 1.4em;\n font-size: 13px;\n color: var(--ae-ink-muted);\n overflow-y: auto;\n display: flex;\n flex-direction: column;\n}\n\n.ae-rail .ae-h {\n margin-top: 1.8em;\n}\n\n.ae-rail .ae-h:first-child {\n margin-top: 0;\n}\n\n.ae-rail a,\n.ae-rail button:not(.ae-mode) {\n display: block;\n width: 100%;\n background: transparent;\n border: 0;\n font: inherit;\n line-height: inherit;\n text-align: left;\n text-decoration: none;\n color: var(--ae-ink-muted);\n padding: 0.3em 0;\n margin-block: 0;\n cursor: pointer;\n transition: color var(--ae-quick) var(--ae-ease);\n}\n\n.ae-rail a:hover,\n.ae-rail button:hover {\n color: var(--ae-ink);\n}\n\n.ae-rail [aria-current],\n.ae-rail .is-active {\n color: var(--ae-ink);\n font-weight: var(--ae-w-medium);\n}\n\n/* the rail's foot pins to the bottom: version, account, the mode */\n.ae-rail-foot {\n margin-top: auto;\n padding-top: 1.6em;\n}\n\n.ae-desk {\n min-width: 0;\n overflow-y: auto;\n padding: 2.2em 2.8em;\n}\n\n/* md breakpoint: tokens.layout.breakpoints.md = 48rem. The rail becomes\n bottom chrome rather than disappearing; places stay visible and the desk\n keeps the only vertical scroll. */\n@media (max-width: 48rem) {\n .ae-shell {\n grid-template-columns: 1fr;\n grid-template-rows: minmax(0, 1fr) auto;\n }\n\n .ae-rail {\n grid-column: 1;\n grid-row: 2;\n min-width: 0;\n border-right: 0;\n border-top: 1px solid var(--ae-line);\n padding: 0 1.2rem;\n overflow: auto hidden;\n flex-direction: row;\n align-items: center;\n gap: var(--ae-space-5);\n }\n\n .ae-rail .ae-h {\n display: none;\n }\n\n .ae-rail a,\n .ae-rail button:not(.ae-mode) {\n flex: none;\n width: auto;\n padding: 0.85em 0;\n white-space: nowrap;\n }\n\n .ae-rail-foot {\n flex: none;\n margin-top: 0;\n margin-left: auto;\n padding-top: 0;\n }\n\n .ae-desk {\n grid-column: 1;\n grid-row: 1;\n min-height: 0;\n padding: 1.8em 1.6rem;\n }\n\n .ae-list-row {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n }\n\n .ae-list-status {\n justify-self: stretch;\n }\n\n .ae-board {\n grid-template-columns: 1fr;\n }\n}\n\n/* ── the document: long-form prose, unclassed inside ──────────────── */\n\n/* the third archetype: rendered markdown drops in bare. Headings are\n weight, never size; rules are hairlines; quotes are inset. Put the\n article in a scrolling stage (.ae-stage-scroll) or a desk. */\n.ae-doc h1,\n.ae-doc h2 {\n font-weight: var(--ae-w-black);\n margin: 2.2em 0 0.6em;\n}\n\n.ae-doc h3,\n.ae-doc h4 {\n font-weight: var(--ae-w-medium);\n margin: 1.8em 0 0.4em;\n}\n\n.ae-doc h5,\n.ae-doc h6 {\n font-weight: var(--ae-w-medium);\n color: var(--ae-ink-muted);\n margin: 1.8em 0 0.4em;\n}\n\n.ae-doc h1:first-child,\n.ae-doc h2:first-child,\n.ae-doc h3:first-child {\n margin-top: 0;\n}\n\n.ae-doc p,\n.ae-doc ul,\n.ae-doc ol {\n margin-bottom: 0.9em;\n}\n\n.ae-doc ul,\n.ae-doc ol {\n padding-left: 1.4em;\n}\n\n.ae-doc li {\n margin-bottom: 0.2em;\n}\n\n.ae-doc hr {\n border: 0;\n border-top: 1px solid var(--ae-line);\n margin: 2.4em 0;\n}\n\n.ae-doc blockquote {\n border-left: 1px solid var(--ae-line);\n padding-left: 1.4em;\n color: var(--ae-ink-muted);\n margin: 1.2em 0;\n}\n\n.ae-doc pre {\n background: var(--ae-wash);\n font-size: 13px;\n line-height: 1.75;\n padding: 1.2em 1.4em;\n margin: 1.2em 0;\n overflow-x: auto;\n}\n\n.ae-doc :not(pre) > code {\n background: var(--ae-wash);\n padding: 0.1em 0.35em;\n}\n\n.ae-doc img {\n max-width: 100%;\n}\n\n/* ── report sub-registers: opt-in, for a generated document that is read\n and shared. Weight + space + hairlines + the 11px mono caption\n register only — never a new type size, never a serif. The plain\n .ae-doc above stays the default. ──────────────────────────────────── */\n\n/* numbered figures: a
in a doc auto-numbers and its
\n drops to the mono caption register, prefixed FIG n. Pairs with\n .ae-plate for the figure body. */\n.ae-doc {\n counter-reset: ae-fig;\n}\n\n.ae-doc figure {\n counter-increment: ae-fig;\n margin: 1.8em 0;\n}\n\n.ae-doc figcaption {\n margin-bottom: 0.7em;\n font-family: var(--ae-font-mono);\n font-size: 11px;\n letter-spacing: 0.14em;\n color: var(--ae-ink-muted);\n}\n\n.ae-doc figcaption::before {\n content: 'FIG ' counter(ae-fig) ' · ';\n color: var(--ae-ink);\n font-weight: var(--ae-w-medium);\n}\n\n/* the lede / standfirst: the opening paragraph lifts to medium weight\n with a hairline rule beneath — a way in, the conclusion up front. */\n.ae-doc .ae-lede {\n margin-bottom: 1.6em;\n padding-bottom: 1.4em;\n border-bottom: 1px solid var(--ae-line);\n font-weight: var(--ae-w-medium);\n}\n\n/* the findings box: a hairline frame (never a filled card) summarizing\n the conclusion. The called-out result is weight; the recommendation\n takes the one accent rule the report spends. */\n.ae-findings {\n margin: 1.8em 0;\n padding: 1.4em 1.6em 1.3em;\n border: 1px solid var(--ae-line);\n}\n\n.ae-findings-title {\n margin-bottom: var(--ae-space-4);\n font-family: var(--ae-font-mono);\n font-size: 11px;\n letter-spacing: 0.14em;\n color: var(--ae-ink-muted);\n}\n\n.ae-rec {\n margin-top: 1.1em;\n padding-left: 1.1em;\n border-left: 2px solid var(--ae-accent);\n}\n\n/* the pull-quote: a sentence lifted forward by weight + space + a\n hairline rule. Never a larger size, never an italic serif — the\n closest call to the one-size law, held on the right side of it. */\n.ae-pull {\n margin: 1.8em 0;\n padding-left: 1.4em;\n border-left: 1px solid var(--ae-line);\n font-weight: var(--ae-w-medium);\n line-height: 1.7;\n}\n\n.ae-pull-by {\n display: block;\n margin-top: 0.6em;\n font-family: var(--ae-font-mono);\n font-size: 11px;\n font-weight: var(--ae-w-regular);\n letter-spacing: 0.14em;\n color: var(--ae-ink-faint);\n}\n\n/* ── utilities ────────────────────────────────────────────────────── */\n\n/* present to assistive tech, invisible on paper — live regions and\n labels the layout has no room to speak */\n.ae-sr {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n border: 0;\n clip-path: inset(50%);\n overflow: hidden;\n white-space: nowrap;\n}\n\n/* ── small screens ────────────────────────────────────────────────── */\n\n/* sm breakpoint: tokens.layout.breakpoints.sm = 40rem. Media queries inline\n token values because CSS custom properties cannot drive @media. */\n@media (max-width: 40rem) {\n .ae-bar,\n .ae-stage {\n width: calc(100% - 2.4rem);\n }\n\n .ae-group {\n margin-bottom: var(--ae-space-6);\n }\n\n /* the shell folds to one column; compose a bar for the places the\n rail held, then tighten the bottom chrome for phone width */\n .ae-shell {\n grid-template-columns: 1fr;\n }\n\n .ae-rail {\n padding-inline: 1.2rem;\n gap: var(--ae-space-4);\n }\n\n .ae-desk {\n padding: 1.6em 1.2rem;\n }\n\n .ae-wall-card {\n grid-template-columns: 1fr;\n }\n\n .ae-wall-figure {\n text-align: left;\n }\n\n .ae-list-row {\n grid-template-columns: 1fr;\n gap: var(--ae-space-3);\n }\n\n .ae-list-value {\n overflow: visible;\n text-overflow: clip;\n white-space: normal;\n overflow-wrap: anywhere;\n }\n}\n\n/* ── reduced motion: everything is instant ────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n *,\n *::before,\n *::after {\n transition: none !important;\n animation: none !important;\n }\n}\n" }, { "path": "tokens.json", "type": "registry:file", "target": "~/aesthetic.tokens.json", "content": "{\n \"name\": \"@misty-step/aesthetic\",\n \"version\": \"0.25.0\",\n \"law\": {\n \"size\": \"Exactly one font size on every surface; the headline is heavier and blacker, never larger.\",\n \"registers\": \"Three inks by three weights (400 / 550 / 800): nine registers.\",\n \"accent\": \"The accent is yours: define your scheme and use it with judgment. Identity comes from ink, hairlines, type, and motion — never from a color-counting rule. Color earns its place; it is not ambient.\",\n \"status\": \"Status rides the glyph: danger, success, and warning color the icon, never the sentence, never a filled pill.\",\n \"layout\": \"Every view fits the viewport. 100dvh screens, no page scrolling; a quiet nav swaps views when content exceeds one screen.\",\n \"motion\": \"Motion is feedback, never decoration: small, brief, eased, and gone under prefers-reduced-motion. State resolutions are gentler and resolve once; success persists, nothing changes size.\",\n \"modes\": \"Light and dark, defaulting to the system, with a user toggle. The change itself is quick and interruptible: the toggle stays enabled, rapid clicks cut any in-flight animation, and the visual transition uses --ae-quick.\",\n \"steering\": \"Downstream projects steer the scheme: --ae-accent / --ae-accent-dark, and the status triplet when the domain needs different hues. Everything else is the shared identity.\",\n \"buttons\": \"Buttons are not links: contained ink shapes, solid primary and hairline secondary.\"\n },\n \"color\": {\n \"light\": {\n \"surface\": \"#fcfcfc\",\n \"wash\": \"#f3f3f3\",\n \"ink\": \"#151515\",\n \"ink-muted\": \"#737373\",\n \"ink-faint\": \"#a3a3a3\",\n \"line\": \"#e9e9e9\",\n \"accent\": \"#2643d0\",\n \"ok\": \"#15714b\",\n \"warn\": \"#8a5f32\",\n \"err\": \"#a84138\"\n },\n \"dark\": {\n \"surface\": \"#121212\",\n \"wash\": \"#1b1b1b\",\n \"ink\": \"#ededed\",\n \"ink-muted\": \"#8f8f8f\",\n \"ink-faint\": \"#5c5c5c\",\n \"line\": \"#262626\",\n \"accent\": \"#8c9eff\",\n \"ok\": \"#6fd2a8\",\n \"warn\": \"#c49d72\",\n \"err\": \"#e58379\"\n }\n },\n \"themes\": {\n \"ultramarine\": {\n \"label\": \"Ultramarine\",\n \"note\": \"The house default: saturated technical blue against the shared ink system.\",\n \"light\": { \"accent\": \"#2643d0\" },\n \"dark\": { \"accent\": \"#8c9eff\" }\n },\n \"moss\": {\n \"label\": \"Moss\",\n \"note\": \"Quiet green for operational tools that want a field-notebook register.\",\n \"light\": { \"accent\": \"#2f6b3f\" },\n \"dark\": { \"accent\": \"#80d98e\" }\n },\n \"ember\": {\n \"label\": \"Ember\",\n \"note\": \"Warm orange for public proof, launch notes, and decisive actions.\",\n \"light\": { \"accent\": \"#c2410c\" },\n \"dark\": { \"accent\": \"#ff8a5c\" }\n },\n \"violet\": {\n \"label\": \"Violet\",\n \"note\": \"A sharper speculative register for labs, demos, and exploration surfaces.\",\n \"light\": { \"accent\": \"#6d28d9\" },\n \"dark\": { \"accent\": \"#c4a3ff\" }\n }\n },\n \"contrast\": {\n \"note\": \"WCAG 2.1 ratios for every ink against every surface, recomputed whenever a color token changes. Contract: faint ink is decoration only — input placeholders supplement always-visible labels, and the resting link underline is decorative. Nothing interactive rests at faint. Status inks ride glyphs (3:1 non-text applies) but pass 4.5:1 anyway.\",\n \"light\": {\n \"surface\": {\n \"ink\": { \"ratio\": 17.8, \"aa\": true },\n \"ink-muted\": { \"ratio\": 4.62, \"aa\": true },\n \"ink-faint\": { \"ratio\": 2.46, \"aa\": false, \"decorationOnly\": true },\n \"accent\": { \"ratio\": 7.32, \"aa\": true },\n \"ok\": { \"ratio\": 5.86, \"aa\": true },\n \"warn\": { \"ratio\": 5.44, \"aa\": true },\n \"err\": { \"ratio\": 5.88, \"aa\": true }\n },\n \"wash\": {\n \"ink\": { \"ratio\": 16.46, \"aa\": true },\n \"ink-muted\": { \"ratio\": 4.27, \"aa\": false, \"notForText\": true },\n \"ink-faint\": { \"ratio\": 2.27, \"aa\": false, \"decorationOnly\": true },\n \"accent\": { \"ratio\": 6.77, \"aa\": true },\n \"ok\": { \"ratio\": 5.41, \"aa\": true },\n \"warn\": { \"ratio\": 5.03, \"aa\": true },\n \"err\": { \"ratio\": 5.44, \"aa\": true }\n }\n },\n \"dark\": {\n \"surface\": {\n \"ink\": { \"ratio\": 16.0, \"aa\": true },\n \"ink-muted\": { \"ratio\": 5.79, \"aa\": true },\n \"ink-faint\": { \"ratio\": 2.8, \"aa\": false, \"decorationOnly\": true },\n \"accent\": { \"ratio\": 7.54, \"aa\": true },\n \"ok\": { \"ratio\": 10.22, \"aa\": true },\n \"warn\": { \"ratio\": 7.5, \"aa\": true },\n \"err\": { \"ratio\": 7.01, \"aa\": true }\n },\n \"wash\": {\n \"ink\": { \"ratio\": 14.71, \"aa\": true },\n \"ink-muted\": { \"ratio\": 5.33, \"aa\": true },\n \"ink-faint\": { \"ratio\": 2.58, \"aa\": false, \"decorationOnly\": true },\n \"accent\": { \"ratio\": 6.93, \"aa\": true },\n \"ok\": { \"ratio\": 9.4, \"aa\": true },\n \"warn\": { \"ratio\": 6.9, \"aa\": true },\n \"err\": { \"ratio\": 6.44, \"aa\": true }\n }\n }\n },\n \"type\": {\n \"family\": \"Geist\",\n \"familyMono\": \"Geist Mono\",\n \"size\": \"16px\",\n \"lineHeight\": 1.8,\n \"weights\": {\n \"regular\": 400,\n \"medium\": 550,\n \"black\": 800\n }\n },\n \"motion\": {\n \"ease\": \"cubic-bezier(0.23, 1, 0.32, 1)\",\n \"quick\": \"160ms\",\n \"soft\": \"240ms\",\n \"gentle\": \"480ms\"\n },\n \"space\": {\n \"0\": \"0.1em\",\n \"1\": \"0.25em\",\n \"2\": \"0.5em\",\n \"3\": \"0.75em\",\n \"4\": \"1em\",\n \"5\": \"1.5em\",\n \"6\": \"2em\",\n \"7\": \"2.5em\",\n \"8\": \"3em\"\n },\n \"z\": {\n \"backdrop\": 40,\n \"dialog\": 41,\n \"pop\": 50,\n \"tip\": 60,\n \"toast\": 100\n },\n \"layout\": {\n \"measure\": \"38rem\",\n \"measureWide\": \"68rem\",\n \"radius\": \"0\",\n \"breakpoints\": {\n \"note\": \"Responsive vocabulary for the single-file stylesheet. CSS media queries inline the same rem values because custom properties cannot be used in @media conditions.\",\n \"sm\": {\n \"value\": \"40rem\",\n \"px\": 640,\n \"rationale\": \"Phone and narrow split-view width: one-column data, bottom shell rail, compact screen gutters.\"\n },\n \"md\": {\n \"value\": \"48rem\",\n \"px\": 768,\n \"rationale\": \"Tablet portrait and wide phone landscape: dense rows and shell chrome stop pretending there is room for a sidebar.\"\n },\n \"lg\": {\n \"value\": \"64rem\",\n \"px\": 1024,\n \"rationale\": \"Small laptop threshold: multi-column boards can breathe without crowding column contents.\"\n }\n }\n }\n}\n" } ] }