anentrypoint-design 1.0.9 → 1.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.html CHANGED
@@ -131,6 +131,16 @@ a.row:hover .ds-row-arrow { opacity: 1 }
131
131
  letter-spacing: 0.06em; color: var(--fg-3, #6b6b6b);
132
132
  }
133
133
  .ds-showcase-row { display: flex; flex-wrap: wrap; gap: var(--space-2, 8px); align-items: center; }
134
+ /* Sets Danger apart from Primary/Default/Ghost in the button showcase row: an
135
+ extra left margin beyond the row's own gap, plus a hairline divider, so a
136
+ danger action never reads as just one more same-weight choice beside the
137
+ primary action. */
138
+ .ds-showcase-btn-danger-group {
139
+ display: inline-flex; align-items: center;
140
+ margin-left: var(--space-3, 16px);
141
+ padding-left: var(--space-3, 16px);
142
+ border-left: 1px solid var(--rule, rgba(0,0,0,.12));
143
+ }
134
144
  /* Card-grid layout (panel.layout === 'cards'): a browsable gallery of tiles
135
145
  instead of a dense text-row list, opt-in per panel via home.yaml's
136
146
  kits.layout: cards. */
@@ -158,7 +168,7 @@ a.row:hover .ds-row-arrow { opacity: 1 }
158
168
  color: var(--fg-3, #6b6b6b); font-size: var(--fs-sm, 15px);
159
169
  }
160
170
  </style>
161
- <script id="__site__" type="application/json">{"title":"design","slug":"index","siteName":"design","navItems":[["247420","https://247420.xyz"],["AnEntrypoint ->","https://github.com/AnEntrypoint"]],"theme":"auto","hero":{"heading":"Design","subheading":"The 247420 design system","body":"A complete, tokens-driven component system for building consistent, accessible interfaces fast: 15 ready-to-ship kit surfaces, 25 live previews, a slide deck, and a full desktop-shell demo — plus a web component and an SDK. Every panel, row, and form control reads its color, radius, and spacing from one shared token file, so a single theme change updates every surface built on it at once. WCAG AA verified across all 21 kits, zero JavaScript bundler required.","badges":[{"label":"Design system"},{"label":"Design tokens"},{"label":"WCAG AA"}],"ctas":[{"label":"GitHub","href":"https://github.com/AnEntrypoint/design","primary":true},{"label":"247420","href":"https://247420.xyz"}]},"sections":[],"examples":[],"bodyHtml":"","sidebar":{"sections":[{"group":"open","items":[{"glyph":"+","label":"open","href":"./ui_kits/homepage/"}]},{"group":"bins","items":[{"label":"Everything","href":"#all","active":true,"count":48},{"label":"Pinned","href":"#kits","count":15},{"label":"Shipped","href":"#previews","count":25},{"label":"Drafts","href":"#decks","count":1}]},{"group":"Labels","items":[{"label":"UI kits","href":"#kits","count":15},{"label":"Decks","href":"#decks","count":1},{"label":"Previews","href":"#previews","count":25},{"label":"Docs","href":"#docs","count":7}]},{"group":"More","items":[{"label":"Readme","href":"https://github.com/AnEntrypoint/design/blob/main/README.md"},{"label":"Skill","href":"https://github.com/AnEntrypoint/design/blob/main/SKILL.md"},{"label":"Tokens","href":"https://github.com/AnEntrypoint/design/blob/main/colors_and_type.css"},{"label":"Source","href":"https://github.com/AnEntrypoint/design"},{"label":"MIT license","href":"https://github.com/AnEntrypoint/design/blob/main/LICENSE"}]}]},"marquee":{"items":["Open source","Design tokens","WCAG AA verified","No bundler required"],"sep":"/"},"showcase":{"heading":"Live components","lede":"Not a screenshot — the same Btn, Chip, Badge, and Table components every kit page imports, rendered right here from the SDK you'd actually install."},"panels":[{"id":"kits","title":"UI kits","count":"15 surfaces","items":[{"code":"","title":"Homepage","sub":"Marquee and works grid","meta":"open ->","href":"./ui_kits/homepage/"},{"code":"","title":"Project page","sub":"Hero, receipt, install, changelog","meta":"open ->","href":"./ui_kits/project_page/"},{"code":"","title":"Docs","sub":"Long-form prose chrome","meta":"open ->","href":"./ui_kits/docs/"},{"code":"","title":"Blog","sub":"Dated index and reading view","meta":"open ->","href":"./ui_kits/blog/"},{"code":"","title":"Chat","sub":"Message thread and composer","meta":"open ->","href":"./ui_kits/chat/"},{"code":"","title":"AICat","sub":"AI assistant with a cat persona","meta":"open ->","href":"./ui_kits/aicat/"},{"code":"","title":"File browser","sub":"Type-aware rails, drop zone, preview","meta":"open ->","href":"./ui_kits/file_browser/"},{"code":"","title":"Dashboard","sub":"KPIs, tables, receipts, changelog","meta":"open ->","href":"./ui_kits/dashboard/"},{"code":"","title":"Terminal","sub":"CLI prompt, command lines, log view","meta":"open ->","href":"./ui_kits/terminal/"},{"code":"","title":"Settings","sub":"Sectioned forms, toggles, save bar","meta":"open ->","href":"./ui_kits/settings/"},{"code":"","title":"Sign in","sub":"Auth providers and magic link","meta":"open ->","href":"./ui_kits/signin/"},{"code":"","title":"Search","sub":"Topbar query, faceted filters, rows","meta":"open ->","href":"./ui_kits/search/"},{"code":"","title":"404","sub":"Empty-state hero, suggested routes","meta":"open ->","href":"./ui_kits/error_404/"},{"code":"","title":"Gallery","sub":"Visual grid, tonal cards, lightbox","meta":"open ->","href":"./ui_kits/gallery/"},{"code":"","title":"OS shell","sub":"Menubar, window manager, taskbar","meta":"open ->","href":"./ui_kits/os/"}],"layout":"cards"},{"id":"file_browser","title":"File browser","count":"primitives","items":[{"code":"fb","title":"file_browser kit","sub":"Drop, list, preview, dialogs","meta":"open ->","href":"./ui_kits/file_browser/"},{"code":"fb","title":"FileRow","sub":"Rail, icon, name, meta","meta":"open ->","href":"./preview/file-row.html"},{"code":"fb","title":"FileGrid","sub":"A stack of rows in a panel","meta":"open ->","href":"./preview/file-grid.html"},{"code":"fb","title":"DropZone","sub":"Tonal upload target","meta":"open ->","href":"./preview/dropzone.html"},{"code":"fb","title":"FileViewer","sub":"Modal preview, keyed head","meta":"open ->","href":"./preview/file-viewer.html"},{"code":"fb","title":"FileToolbar","sub":"Actions, breadcrumb, upload","meta":"open ->","href":"./preview/file-toolbar.html"},{"code":"fb","title":"fsbrowse","sub":"Express + busboy backend","meta":"source","href":"https://github.com/AnEntrypoint/fsbrowse"}],"layout":null},{"id":"desktop_os","title":"Desktop OS shell","count":"in-page window manager","items":[{"code":"os","title":"Live demo","sub":"menubar, apps drawer, side rail, taskbar, floating windows","meta":"open ->","href":"./ui_kits/os/"},{"code":"os","title":"createDesktopShell","sub":"menubar, apps launcher, taskbar, window manager","meta":"shell.js","href":"https://github.com/AnEntrypoint/design/blob/main/src/kits/os/shell.js"},{"code":"os","title":"renderWindow / renderDock","sub":"pure paint-surface primitives","meta":"wm.js","href":"https://github.com/AnEntrypoint/design/blob/main/src/kits/os/wm.js"},{"code":"os","title":"Icon set","sub":"SVG glyphs for menubar and taskbar","meta":"icons.js","href":"https://github.com/AnEntrypoint/design/blob/main/src/kits/os/icons.js"},{"code":"os","title":"theme.css","sub":"OS chrome tokens: menubar, launcher, window manager","meta":"theme.css","href":"https://github.com/AnEntrypoint/design/blob/main/src/kits/os/theme.css"},{"code":"os","title":"Import path","sub":"anentrypoint-design/kits/os","meta":"exports","href":"https://github.com/AnEntrypoint/design/blob/main/package.json"}],"layout":null},{"id":"web_components","title":"Web components","count":"1 element","items":[{"code":"wc","title":"\u003cds-chat>","sub":"Auto-registers when the SDK loads in a browser","meta":"ds-chat.js","href":"https://github.com/AnEntrypoint/design/blob/main/src/web-components/ds-chat.js"},{"code":"wc","title":"registerChatElement","sub":"Manual register helper","meta":"source","href":"https://github.com/AnEntrypoint/design/blob/main/src/index.js"}],"layout":null},{"id":"api_exports","title":"Public API","count":"SDK surface","items":[{"code":"api","title":"h / applyDiff","sub":"webjsx createElement and diff","meta":"index.js","href":"https://github.com/AnEntrypoint/design/blob/main/src/index.js"},{"code":"api","title":"installStyles / scope","sub":"ds-247420 CSS scope and bootstrap","meta":"styles.js","href":"https://github.com/AnEntrypoint/design/blob/main/src/styles.js"},{"code":"api","title":"loadCss","sub":"Fetch the bundled CSS string","meta":"styles.js","href":"https://github.com/AnEntrypoint/design/blob/main/src/styles.js"},{"code":"api","title":"mount / mountKit","sub":"Shell and ui_kit bootstrap","meta":"bootstrap.js","href":"https://github.com/AnEntrypoint/design/blob/main/src/bootstrap.js"},{"code":"api","title":"registerDeckStage / getDeckStage","sub":"16:9 deck stage API","meta":"deck-stage.js","href":"https://github.com/AnEntrypoint/design/blob/main/src/deck-stage.js"},{"code":"api","title":"renderMarkdown","sub":"marked v15 + DOMPurify, lazy-loaded","meta":"markdown.js","href":"https://github.com/AnEntrypoint/design/blob/main/src/markdown.js"},{"code":"api","title":"ensurePrism / highlightAllUnder","sub":"Prism core plus per-language","meta":"highlight.js","href":"https://github.com/AnEntrypoint/design/blob/main/src/highlight.js"},{"code":"api","title":"renderPageHtml","sub":"Static page HTML template","meta":"page-html.js","href":"https://github.com/AnEntrypoint/design/blob/main/src/page-html.js"},{"code":"api","title":"components / motion / debug","sub":"Namespaces from the default export","meta":"components.js","href":"https://github.com/AnEntrypoint/design/blob/main/src/components.js"}],"layout":null},{"id":"decks","title":"Decks","count":1,"items":[{"code":"","title":"Deck template","sub":"16:9 stage with auto-fit","meta":"open ->","href":"./slides/"}],"layout":null},{"id":"docs","title":"Docs","count":7,"items":[{"code":"md","title":"Readme","sub":"Overview, manifesto, conventions","meta":"open ->","href":"https://github.com/AnEntrypoint/design/blob/main/README.md"},{"code":"md","title":"Skill","sub":"Authoring rules for the agent","meta":"open ->","href":"https://github.com/AnEntrypoint/design/blob/main/SKILL.md"},{"code":"css","title":"Tokens","sub":"Colors, type, panel surface tokens","meta":"open ->","href":"https://github.com/AnEntrypoint/design/blob/main/colors_and_type.css"},{"code":"md","title":"Component props","sub":"Generated prop reference","meta":"open ->","href":"https://github.com/AnEntrypoint/design/blob/main/docs/component-props.md"},{"code":"md","title":"Changelog","sub":"Release history","meta":"open ->","href":"https://github.com/AnEntrypoint/design/blob/main/CHANGELOG.md"},{"code":"md","title":"Accessibility","sub":"WCAG AA compliance, keyboard navigation","meta":"open ->","href":"https://github.com/AnEntrypoint/design/blob/main/docs/accessibility.md"},{"code":"md","title":"Responsive breakpoints","sub":"The documented breakpoint scale in use","meta":"open ->","href":"https://github.com/AnEntrypoint/design/blob/main/docs/responsive.md"}],"layout":null},{"id":"features","title":"Why teams choose 247420","count":5,"items":[{"code":"","title":"Clear visual hierarchy","sub":"Panel-on-panel layering keeps dense dashboards scannable — important content stays legible even in complex, data-heavy screens.","meta":"panel-row.css","href":"https://github.com/AnEntrypoint/design/blob/main/src/css/app-shell/panel-row.css"},{"code":"","title":"Readable at a glance","sub":"A disciplined type system reserves monospace for codes and labels and sans-serif for everything meant to be read, so interfaces stay legible under real workloads.","meta":"colors_and_type.css","href":"https://github.com/AnEntrypoint/design/blob/main/colors_and_type.css"},{"code":"","title":"Instant status recognition","sub":"Color-coded indicator rails let users scan for status and category at a glance, without relying on borders that add visual noise.","meta":"panel-row.css","href":"https://github.com/AnEntrypoint/design/blob/main/src/css/app-shell/panel-row.css#L126"},{"code":"","title":"Rebrand in minutes, not sprints","sub":"Every surface, type scale, and accent color is driven entirely by design tokens — retheme an entire product by editing one file, with zero component code changes.","meta":"colors_and_type.css","href":"https://github.com/AnEntrypoint/design/blob/main/colors_and_type.css"},{"code":"","title":"Accessible by default","sub":"Every kit is WCAG AA verified (0 blocking violations across all 21 surfaces), with shared focus-visible tokens, skip links, and correct ARIA roles built into every component.","meta":"a11y-report.md","href":"https://github.com/AnEntrypoint/design/blob/main/docs/a11y-report.md"}],"layout":null},{"id":"previews","title":"Previews","count":25,"items":[{"code":"","title":"buttons","sub":"preview · buttons.html","meta":"open ->","href":"./preview/buttons.html"},{"code":"","title":"colors core","sub":"preview · colors-core.html","meta":"open ->","href":"./preview/colors-core.html"},{"code":"","title":"colors lore","sub":"preview · colors-lore.html","meta":"open ->","href":"./preview/colors-lore.html"},{"code":"","title":"colors semantic","sub":"preview · colors-semantic.html","meta":"open ->","href":"./preview/colors-semantic.html"},{"code":"","title":"dateline","sub":"preview · dateline.html","meta":"open ->","href":"./preview/dateline.html"},{"code":"","title":"dropzone","sub":"preview · dropzone.html","meta":"open ->","href":"./preview/dropzone.html"},{"code":"","title":"file grid","sub":"preview · file-grid.html","meta":"open ->","href":"./preview/file-grid.html"},{"code":"","title":"file row","sub":"preview · file-row.html","meta":"open ->","href":"./preview/file-row.html"},{"code":"","title":"file toolbar","sub":"preview · file-toolbar.html","meta":"open ->","href":"./preview/file-toolbar.html"},{"code":"","title":"file viewer","sub":"preview · file-viewer.html","meta":"open ->","href":"./preview/file-viewer.html"},{"code":"","title":"header","sub":"preview · header.html","meta":"open ->","href":"./preview/header.html"},{"code":"","title":"icons unicode","sub":"preview · icons-unicode.html","meta":"open ->","href":"./preview/icons-unicode.html"},{"code":"","title":"index row","sub":"preview · index-row.html","meta":"open ->","href":"./preview/index-row.html"},{"code":"","title":"inputs","sub":"preview · inputs.html","meta":"open ->","href":"./preview/inputs.html"},{"code":"","title":"manifesto","sub":"preview · manifesto.html","meta":"open ->","href":"./preview/manifesto.html"},{"code":"","title":"rules","sub":"preview · rules.html","meta":"open ->","href":"./preview/rules.html"},{"code":"","title":"spacing","sub":"preview · spacing.html","meta":"open ->","href":"./preview/spacing.html"},{"code":"","title":"stamps","sub":"preview · stamps.html","meta":"open ->","href":"./preview/stamps.html"},{"code":"","title":"stamps lore","sub":"preview · stamps-lore.html","meta":"open ->","href":"./preview/stamps-lore.html"},{"code":"","title":"theme ink","sub":"preview · theme-ink.html","meta":"open ->","href":"./preview/theme-ink.html"},{"code":"","title":"type display","sub":"preview · type-display.html","meta":"open ->","href":"./preview/type-display.html"},{"code":"","title":"type mono","sub":"preview · type-mono.html","meta":"open ->","href":"./preview/type-mono.html"},{"code":"","title":"type prose","sub":"preview · type-prose.html","meta":"open ->","href":"./preview/type-prose.html"},{"code":"","title":"type scale","sub":"preview · type-scale.html","meta":"open ->","href":"./preview/type-scale.html"},{"code":"","title":"wordmarks","sub":"preview · wordmarks.html","meta":"open ->","href":"./preview/wordmarks.html"}]}],"quickstart":{"heading":"Quick start","lines":[{"kind":"cmt","text":"# Install via importmap (no bundler)"},{"kind":"cmd","text":"\u003cscript type='importmap'>{\"imports\":{\"anentrypoint-design\":\"https://unpkg.com/anentrypoint-design@latest/dist/247420.js\"}}\u003c/script>"},{"kind":"cmt","text":"# Or clone the source"},{"kind":"cmd","text":"git clone https://github.com/AnEntrypoint/design.git"}]},"statusLeft":["main","utf-8","lf"],"statusRight":["anentrypoint-design@latest","15 kits"],"seoAuthor":"247420 · a design system by AnEntrypoint"}</script>
171
+ <script id="__site__" type="application/json">{"title":"design","slug":"index","siteName":"design","navItems":[["247420","https://247420.xyz"],["AnEntrypoint ->","https://github.com/AnEntrypoint"]],"theme":"auto","hero":{"heading":"Design","subheading":"The 247420 design system","body":"A complete, tokens-driven component system for building consistent, accessible interfaces fast: 15 ready-to-ship kit surfaces, 25 live previews, a slide deck, and a full desktop-shell demo — plus a web component and an SDK. Every panel, row, and form control reads its color, radius, and spacing from one shared token file, so a single theme change updates every surface built on it at once. WCAG AA verified across all 15 kits, zero JavaScript bundler required.","badges":[{"label":"Design system"},{"label":"Design tokens"},{"label":"WCAG AA"}],"ctas":[{"label":"GitHub","href":"https://github.com/AnEntrypoint/design","primary":true},{"label":"247420","href":"https://247420.xyz"}]},"sections":[],"examples":[],"bodyHtml":"","sidebar":{"sections":[{"group":"open","items":[{"glyph":"+","label":"open","href":"./ui_kits/homepage/"}]},{"group":"bins","items":[{"label":"Everything","href":"#all","active":true,"count":49},{"label":"Pinned","href":"#kits","count":15},{"label":"Shipped","href":"#previews","count":25},{"label":"Drafts","href":"#decks","count":1}]},{"group":"Labels","items":[{"label":"UI kits","href":"#kits","count":15},{"label":"Decks","href":"#decks","count":1},{"label":"Previews","href":"#previews","count":25},{"label":"Docs","href":"#docs","count":8}]},{"group":"More","items":[{"label":"Readme","href":"https://github.com/AnEntrypoint/design/blob/main/README.md"},{"label":"Skill","href":"https://github.com/AnEntrypoint/design/blob/main/SKILL.md"},{"label":"Tokens","href":"https://github.com/AnEntrypoint/design/blob/main/colors_and_type.css"},{"label":"Source","href":"https://github.com/AnEntrypoint/design"},{"label":"MIT license","href":"https://github.com/AnEntrypoint/design/blob/main/LICENSE"}]}]},"marquee":{"items":["Open source","Design tokens","WCAG AA verified","No bundler required"],"sep":"/"},"showcase":{"heading":"Live components","lede":"Not a screenshot — the same Btn, Chip, Badge, and Table components every kit page imports, rendered right here from the SDK you'd actually install."},"panels":[{"id":"kits","title":"UI kits","count":"15 surfaces","items":[{"code":"","title":"Homepage","sub":"Marquee and works grid","meta":"open ->","href":"./ui_kits/homepage/"},{"code":"","title":"Project page","sub":"Hero, receipt, install, changelog","meta":"open ->","href":"./ui_kits/project_page/"},{"code":"","title":"Docs","sub":"Long-form prose chrome","meta":"open ->","href":"./ui_kits/docs/"},{"code":"","title":"Blog","sub":"Dated index and reading view","meta":"open ->","href":"./ui_kits/blog/"},{"code":"","title":"Chat","sub":"Message thread and composer","meta":"open ->","href":"./ui_kits/chat/"},{"code":"","title":"AICat","sub":"AI assistant with a cat persona","meta":"open ->","href":"./ui_kits/aicat/"},{"code":"","title":"File browser","sub":"Type-aware rails, drop zone, preview","meta":"open ->","href":"./ui_kits/file_browser/"},{"code":"","title":"Dashboard","sub":"KPIs, tables, receipts, changelog","meta":"open ->","href":"./ui_kits/dashboard/"},{"code":"","title":"Terminal","sub":"CLI prompt, command lines, log view","meta":"open ->","href":"./ui_kits/terminal/"},{"code":"","title":"Settings","sub":"Sectioned forms, toggles, save bar","meta":"open ->","href":"./ui_kits/settings/"},{"code":"","title":"Sign in","sub":"Auth providers and magic link","meta":"open ->","href":"./ui_kits/signin/"},{"code":"","title":"Search","sub":"Topbar query, faceted filters, rows","meta":"open ->","href":"./ui_kits/search/"},{"code":"","title":"404","sub":"Empty-state hero, suggested routes","meta":"open ->","href":"./ui_kits/error_404/"},{"code":"","title":"Gallery","sub":"Visual grid, tonal cards, lightbox","meta":"open ->","href":"./ui_kits/gallery/"},{"code":"","title":"OS shell","sub":"Menubar, window manager, taskbar","meta":"open ->","href":"./ui_kits/os/"}],"layout":"cards"},{"id":"file_browser","title":"File browser","count":"primitives","items":[{"code":"fb","title":"file_browser kit","sub":"Drop, list, preview, dialogs","meta":"open ->","href":"./ui_kits/file_browser/"},{"code":"fb","title":"FileRow","sub":"Rail, icon, name, meta","meta":"open ->","href":"./preview/file-row.html"},{"code":"fb","title":"FileGrid","sub":"A stack of rows in a panel","meta":"open ->","href":"./preview/file-grid.html"},{"code":"fb","title":"DropZone","sub":"Tonal upload target","meta":"open ->","href":"./preview/dropzone.html"},{"code":"fb","title":"FileViewer","sub":"Modal preview, keyed head","meta":"open ->","href":"./preview/file-viewer.html"},{"code":"fb","title":"FileToolbar","sub":"Actions, breadcrumb, upload","meta":"open ->","href":"./preview/file-toolbar.html"},{"code":"fb","title":"fsbrowse","sub":"Express + busboy backend","meta":"source","href":"https://github.com/AnEntrypoint/fsbrowse"}],"layout":null},{"id":"desktop_os","title":"Desktop OS shell","count":"in-page window manager","items":[{"code":"os","title":"Live demo","sub":"menubar, apps drawer, side rail, taskbar, floating windows","meta":"open ->","href":"./ui_kits/os/"},{"code":"os","title":"createDesktopShell","sub":"menubar, apps launcher, taskbar, window manager","meta":"shell.js","href":"https://github.com/AnEntrypoint/design/blob/main/src/kits/os/shell.js"},{"code":"os","title":"renderWindow / renderDock","sub":"pure paint-surface primitives","meta":"wm.js","href":"https://github.com/AnEntrypoint/design/blob/main/src/kits/os/wm.js"},{"code":"os","title":"Icon set","sub":"SVG glyphs for menubar and taskbar","meta":"icons.js","href":"https://github.com/AnEntrypoint/design/blob/main/src/kits/os/icons.js"},{"code":"os","title":"theme.css","sub":"OS chrome tokens: menubar, launcher, window manager","meta":"theme.css","href":"https://github.com/AnEntrypoint/design/blob/main/src/kits/os/theme.css"},{"code":"os","title":"Import path","sub":"anentrypoint-design/kits/os","meta":"exports","href":"https://github.com/AnEntrypoint/design/blob/main/package.json"}],"layout":null},{"id":"web_components","title":"Web components","count":"1 element","items":[{"code":"wc","title":"\u003cds-chat>","sub":"Auto-registers when the SDK loads in a browser","meta":"ds-chat.js","href":"https://github.com/AnEntrypoint/design/blob/main/src/web-components/ds-chat.js"},{"code":"wc","title":"registerChatElement","sub":"Manual register helper","meta":"source","href":"https://github.com/AnEntrypoint/design/blob/main/src/index.js"}],"layout":null},{"id":"api_exports","title":"Public API","count":"SDK surface","items":[{"code":"api","title":"h / applyDiff","sub":"webjsx createElement and diff","meta":"index.js","href":"https://github.com/AnEntrypoint/design/blob/main/src/index.js"},{"code":"api","title":"installStyles / scope","sub":"ds-247420 CSS scope and bootstrap","meta":"styles.js","href":"https://github.com/AnEntrypoint/design/blob/main/src/styles.js"},{"code":"api","title":"loadCss","sub":"Fetch the bundled CSS string","meta":"styles.js","href":"https://github.com/AnEntrypoint/design/blob/main/src/styles.js"},{"code":"api","title":"mount / mountKit","sub":"Shell and ui_kit bootstrap","meta":"bootstrap.js","href":"https://github.com/AnEntrypoint/design/blob/main/src/bootstrap.js"},{"code":"api","title":"registerDeckStage / getDeckStage","sub":"16:9 deck stage API","meta":"deck-stage.js","href":"https://github.com/AnEntrypoint/design/blob/main/src/deck-stage.js"},{"code":"api","title":"renderMarkdown","sub":"marked v15 + DOMPurify, lazy-loaded","meta":"markdown.js","href":"https://github.com/AnEntrypoint/design/blob/main/src/markdown.js"},{"code":"api","title":"ensurePrism / highlightAllUnder","sub":"Prism core plus per-language","meta":"highlight.js","href":"https://github.com/AnEntrypoint/design/blob/main/src/highlight.js"},{"code":"api","title":"renderPageHtml","sub":"Static page HTML template","meta":"page-html.js","href":"https://github.com/AnEntrypoint/design/blob/main/src/page-html.js"},{"code":"api","title":"components / motion / debug","sub":"Namespaces from the default export","meta":"components.js","href":"https://github.com/AnEntrypoint/design/blob/main/src/components.js"}],"layout":null},{"id":"decks","title":"Decks","count":1,"items":[{"code":"","title":"Deck template","sub":"16:9 stage with auto-fit","meta":"open ->","href":"./slides/"}],"layout":null},{"id":"docs","title":"Docs","count":8,"items":[{"code":"md","title":"Readme","sub":"Overview, manifesto, conventions","meta":"open ->","href":"https://github.com/AnEntrypoint/design/blob/main/README.md"},{"code":"md","title":"Skill","sub":"Authoring rules for the agent","meta":"open ->","href":"https://github.com/AnEntrypoint/design/blob/main/SKILL.md"},{"code":"css","title":"Tokens","sub":"Colors, type, panel surface tokens","meta":"open ->","href":"https://github.com/AnEntrypoint/design/blob/main/colors_and_type.css"},{"code":"md","title":"Component props","sub":"Generated prop reference","meta":"open ->","href":"https://github.com/AnEntrypoint/design/blob/main/docs/component-props.md"},{"code":"live","title":"Component explorer","sub":"Interactive, searchable, live-rendered","meta":"open ->","href":"./ui_kits/component_explorer/"},{"code":"md","title":"Changelog","sub":"Release history","meta":"open ->","href":"https://github.com/AnEntrypoint/design/blob/main/CHANGELOG.md"},{"code":"md","title":"Accessibility","sub":"WCAG AA compliance, keyboard navigation","meta":"open ->","href":"https://github.com/AnEntrypoint/design/blob/main/docs/accessibility.md"},{"code":"md","title":"Responsive breakpoints","sub":"The documented breakpoint scale in use","meta":"open ->","href":"https://github.com/AnEntrypoint/design/blob/main/docs/responsive.md"}],"layout":null},{"id":"features","title":"Why teams choose 247420","count":5,"items":[{"code":"","title":"Clear visual hierarchy","sub":"Panel-on-panel layering keeps dense dashboards scannable — important content stays legible even in complex, data-heavy screens.","meta":"panel-row.css","href":"https://github.com/AnEntrypoint/design/blob/main/src/css/app-shell/panel-row.css"},{"code":"","title":"Readable at a glance","sub":"A disciplined type system reserves monospace for codes and labels and sans-serif for everything meant to be read, so interfaces stay legible under real workloads.","meta":"colors_and_type.css","href":"https://github.com/AnEntrypoint/design/blob/main/colors_and_type.css"},{"code":"","title":"Instant status recognition","sub":"Color-coded indicator rails let users scan for status and category at a glance, without relying on borders that add visual noise.","meta":"panel-row.css","href":"https://github.com/AnEntrypoint/design/blob/main/src/css/app-shell/panel-row.css#L126"},{"code":"","title":"Rebrand in minutes, not sprints","sub":"Every surface, type scale, and accent color is driven entirely by design tokens — retheme an entire product by editing one file, with zero component code changes.","meta":"colors_and_type.css","href":"https://github.com/AnEntrypoint/design/blob/main/colors_and_type.css"},{"code":"","title":"Accessible by default","sub":"Every kit is WCAG AA verified (0 blocking violations across all 15 surfaces), with shared focus-visible tokens, skip links, and correct ARIA roles built into every component.","meta":"a11y-report.md","href":"https://github.com/AnEntrypoint/design/blob/main/docs/a11y-report.md"}],"layout":null},{"id":"previews","title":"Previews","count":25,"items":[{"code":"","title":"buttons","sub":"preview · buttons.html","meta":"open ->","href":"./preview/buttons.html"},{"code":"","title":"colors core","sub":"preview · colors-core.html","meta":"open ->","href":"./preview/colors-core.html"},{"code":"","title":"colors lore","sub":"preview · colors-lore.html","meta":"open ->","href":"./preview/colors-lore.html"},{"code":"","title":"colors semantic","sub":"preview · colors-semantic.html","meta":"open ->","href":"./preview/colors-semantic.html"},{"code":"","title":"dateline","sub":"preview · dateline.html","meta":"open ->","href":"./preview/dateline.html"},{"code":"","title":"dropzone","sub":"preview · dropzone.html","meta":"open ->","href":"./preview/dropzone.html"},{"code":"","title":"file grid","sub":"preview · file-grid.html","meta":"open ->","href":"./preview/file-grid.html"},{"code":"","title":"file row","sub":"preview · file-row.html","meta":"open ->","href":"./preview/file-row.html"},{"code":"","title":"file toolbar","sub":"preview · file-toolbar.html","meta":"open ->","href":"./preview/file-toolbar.html"},{"code":"","title":"file viewer","sub":"preview · file-viewer.html","meta":"open ->","href":"./preview/file-viewer.html"},{"code":"","title":"header","sub":"preview · header.html","meta":"open ->","href":"./preview/header.html"},{"code":"","title":"icons unicode","sub":"preview · icons-unicode.html","meta":"open ->","href":"./preview/icons-unicode.html"},{"code":"","title":"index row","sub":"preview · index-row.html","meta":"open ->","href":"./preview/index-row.html"},{"code":"","title":"inputs","sub":"preview · inputs.html","meta":"open ->","href":"./preview/inputs.html"},{"code":"","title":"manifesto","sub":"preview · manifesto.html","meta":"open ->","href":"./preview/manifesto.html"},{"code":"","title":"rules","sub":"preview · rules.html","meta":"open ->","href":"./preview/rules.html"},{"code":"","title":"spacing","sub":"preview · spacing.html","meta":"open ->","href":"./preview/spacing.html"},{"code":"","title":"stamps","sub":"preview · stamps.html","meta":"open ->","href":"./preview/stamps.html"},{"code":"","title":"stamps lore","sub":"preview · stamps-lore.html","meta":"open ->","href":"./preview/stamps-lore.html"},{"code":"","title":"theme ink","sub":"preview · theme-ink.html","meta":"open ->","href":"./preview/theme-ink.html"},{"code":"","title":"type display","sub":"preview · type-display.html","meta":"open ->","href":"./preview/type-display.html"},{"code":"","title":"type mono","sub":"preview · type-mono.html","meta":"open ->","href":"./preview/type-mono.html"},{"code":"","title":"type prose","sub":"preview · type-prose.html","meta":"open ->","href":"./preview/type-prose.html"},{"code":"","title":"type scale","sub":"preview · type-scale.html","meta":"open ->","href":"./preview/type-scale.html"},{"code":"","title":"wordmarks","sub":"preview · wordmarks.html","meta":"open ->","href":"./preview/wordmarks.html"}]}],"quickstart":{"heading":"Quick start","lines":[{"kind":"cmt","text":"# Install via importmap (no bundler)"},{"kind":"cmd","text":"\u003cscript type='importmap'>{\"imports\":{\"anentrypoint-design\":\"https://unpkg.com/anentrypoint-design@latest/dist/247420.js\"}}\u003c/script>"},{"kind":"cmt","text":"# Or clone the source"},{"kind":"cmd","text":"git clone https://github.com/AnEntrypoint/design.git"}]},"statusLeft":["main","utf-8","lf"],"statusRight":["anentrypoint-design@latest","15 kits"],"seoAuthor":"247420 · a design system by AnEntrypoint"}</script>
162
172
  <style>html,body{margin:0;padding:0}body{background:var(--bg,#F6F5F1);color:var(--fg,#131318);font-family:var(--ff-body,system-ui,sans-serif)}</style>
163
173
  </head>
164
174
  <body>
@@ -191,9 +201,15 @@ function heroNode(hero) {
191
201
  hero.body || hero.subheading,
192
202
  hero.accent ? h('span', { class: 'ds-hero-accent' }, ' ' + hero.accent) : null,
193
203
  ) : null,
204
+ // Every hero CTA is a plain <a href> that navigates away from this page
205
+ // (GitHub, the portfolio site, ...), never an in-page action -- so the
206
+ // trailing arrow is appended here once, at the render layer, rather than
207
+ // hand-typed per label in content yaml where it could drift out of sync
208
+ // with the "navigates away = arrow" rule used elsewhere (row ctas,
209
+ // signin's in-page submit buttons).
194
210
  Array.isArray(hero.ctas) && hero.ctas.length
195
211
  ? h('div', { class: 'ds-hero-actions' }, ...hero.ctas.map((c, i) =>
196
- h('a', { key: i, class: i === 0 ? 'btn btn-accent' : 'btn btn-ghost', href: c.href || '#' }, c.label || c.cta || 'go')))
212
+ h('a', { key: i, class: i === 0 ? 'btn btn-accent' : 'btn btn-ghost', href: c.href || '#' }, (c.label || c.cta || 'go') + ' ->')))
197
213
  : null,
198
214
  badgeRow,
199
215
  );
@@ -212,8 +228,18 @@ function showcaseNode(showcase) {
212
228
  C.Btn({ key: 'b1', variant: 'primary', children: 'Primary' }),
213
229
  C.Btn({ key: 'b2', variant: 'default', children: 'Default' }),
214
230
  C.Btn({ key: 'b3', variant: 'ghost', children: 'Ghost' }),
215
- C.Btn({ key: 'b4', variant: 'danger', children: 'Danger' }),
231
+ // .ds-showcase-btn-danger-group gives Danger its own visually separated
232
+ // cluster (extra leading gap + a hairline divider) instead of sitting in
233
+ // the same uniform-gap row as Primary -- two similarly-weighted filled
234
+ // buttons side by side otherwise compete for "the one action to take"
235
+ // with nothing marking Danger as the deliberately-set-apart one.
236
+ h('span', { key: 'b4-group', class: 'ds-showcase-btn-danger-group' },
237
+ C.Btn({ key: 'b4', variant: 'danger', children: 'Danger' })),
216
238
  );
239
+ // Hue documents STATUS, not decoration: green = shipping/live, blue = pre-
240
+ // release/in-flux, purple = newly added. Badge (not a bespoke pill) carries
241
+ // the '0 violations' count so every status indicator in this row is one of
242
+ // the two real chip-family components, never a one-off styled span.
217
243
  const chipRow = h('div', { class: 'ds-showcase-row' },
218
244
  C.Chip({ key: 'c1', tone: 'green', children: 'Live' }),
219
245
  C.Chip({ key: 'c2', tone: 'blue', children: 'Beta' }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anentrypoint-design",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "description": "247420 design system SDK — webjsx + modified ripple-ui, single-file ESM bundle for reproducible use of the AnEntrypoint design.",
5
5
  "type": "module",
6
6
  "main": "./dist/247420.js",
@@ -38,7 +38,7 @@ export function ChatMessage({ role, who = 'them', avatar, text, parts, time, typ
38
38
  : (resolvedWho === 'you' ? 'u' : avatarInitial(name));
39
39
  const av = h('span', { class: 'chat-avatar' }, fallbackAvatar);
40
40
  let bodyNodes;
41
- if (typing) bodyNodes = [h('div', { class: 'chat-bubble', key: 'typb' }, h('span', { class: 'chat-typing' }, h('span'), h('span'), h('span')))];
41
+ if (typing) bodyNodes = [h('div', { class: 'chat-bubble chat-bubble-typing', key: 'typb' }, h('span', { class: 'chat-typing' }, h('span'), h('span'), h('span')))];
42
42
  else if (parts && parts.length) bodyNodes = parts.map((p, i) => renderPart(p, i));
43
43
  else bodyNodes = [h('div', { class: 'chat-bubble', key: 't' }, ...renderInline(text || ''))];
44
44
  // A blinking caret at the stream head: while an assistant turn is streaming
@@ -23,16 +23,30 @@ export function Kpi({ items = [], emptyText = 'no metrics yet' }) {
23
23
  return h('div', { class: 'kpi' }, ...items.map(([n, l, meta], i) => {
24
24
  const isUp = meta && meta.tone !== 'down';
25
25
  const good = meta && meta.invert ? !isUp : isUp;
26
+ // Colour + arrow direction alone is not enough for colorblind/
27
+ // low-vision readers to tell "this movement is good" from "this
28
+ // movement is bad" -- the same up-arrow means opposite things for a
29
+ // normal metric vs an `invert` one (see the invert doc above). An
30
+ // sr-only word carries the actual verdict as text, independent of
31
+ // hue or glyph shape, reusing the same `good` boolean the color
32
+ // class already keys off so the two can never disagree.
33
+ const worseBetter = good ? 'better' : 'worse';
26
34
  return h('div', { key: i, class: 'kpi-card' },
27
35
  meta && meta.glyph ? h('div', { class: 'kpi-glyph' }, Icon(meta.glyph, { size: 16 })) : null,
28
- h('div', { class: 'num' }, String(n)),
29
- h('div', { class: 'lbl' }, l),
36
+ // Fixed-alignment number+caption stack: a shared flex column with
37
+ // an explicit start alignment means a short one-line caption and
38
+ // a long wrapping caption both anchor their number at the same
39
+ // top position instead of drifting per tile height.
40
+ h('div', { class: 'kpi-stack' },
41
+ h('div', { class: 'num' }, String(n)),
42
+ h('div', { class: 'lbl' }, l)),
30
43
  meta && (meta.delta != null || meta.spark)
31
44
  ? h('div', { class: 'kpi-foot' },
32
45
  meta.delta != null
33
46
  ? h('span', { class: 'kpi-delta kpi-delta-' + (good ? 'up' : 'down') },
34
47
  Icon(isUp ? 'arrow-up' : 'arrow-down', { size: 12 }),
35
- String(meta.delta))
48
+ String(meta.delta),
49
+ h('span', { class: 'sr-only' }, ' (' + worseBetter + ')'))
36
50
  : null,
37
51
  meta.spark ? Sparkline({ values: meta.spark, tone: good ? 'up' : 'down' }) : null)
38
52
  : null);
@@ -40,6 +54,10 @@ export function Kpi({ items = [], emptyText = 'no metrics yet' }) {
40
54
  }
41
55
 
42
56
  // Minimal inline SVG trend line — token-stroke only, no raw color literals.
57
+ // `title` (native <title> element, not the `title` attribute — SVG scopes
58
+ // tooltip text to the element it's nested in) gives every point a lightweight
59
+ // hover tooltip showing its value; no custom tooltip machinery, just the
60
+ // browser's own affordance, so this stays a "minimal inline SVG" primitive.
43
61
  export function Sparkline({ values = [], width = 72, height = 24, tone }) {
44
62
  if (!values.length) return null;
45
63
  const max = Math.max(...values), min = Math.min(...values);
@@ -47,8 +65,11 @@ export function Sparkline({ values = [], width = 72, height = 24, tone }) {
47
65
  const step = width / (values.length - 1 || 1);
48
66
  const points = values.map((v, i) => [i * step, height - ((v - min) / span) * height]);
49
67
  const d = points.map(([x, y], i) => (i === 0 ? 'M' : 'L') + x.toFixed(1) + ',' + y.toFixed(1)).join(' ');
50
- return h('svg', { class: 'ds-sparkline ds-sparkline-' + (tone === 'down' ? 'down' : 'up'), viewBox: '0 0 ' + width + ' ' + height, width, height, 'aria-hidden': 'true' },
51
- h('path', { d, fill: 'none', 'stroke-width': '2', 'stroke-linecap': 'round', 'stroke-linejoin': 'round' }));
68
+ return h('svg', { class: 'ds-sparkline ds-sparkline-' + (tone === 'down' ? 'down' : 'up'), viewBox: '0 0 ' + width + ' ' + height, width, height, role: 'img', 'aria-label': 'trend: ' + values.join(', ') },
69
+ h('title', {}, values.join(', ')),
70
+ h('path', { d, fill: 'none', 'stroke-width': '2', 'stroke-linecap': 'round', 'stroke-linejoin': 'round' }),
71
+ ...points.map(([x, y], i) => h('circle', { key: i, cx: x.toFixed(1), cy: y.toFixed(1), r: 6, fill: 'transparent', stroke: 'none' },
72
+ h('title', {}, String(values[i])))));
52
73
  }
53
74
 
54
75
  // Horizontal token-only bar breakdown — e.g. revenue by channel, traffic by source.
@@ -158,8 +158,9 @@ export function Dot({ tone = 'on' }) {
158
158
  // top of ds-dot-on so a live-broadcast indicator is never visually
159
159
  // identical to a plain "this thing is on" status dot — same split
160
160
  // rationale as .chip.tone-live / .ds-badge.tone-live.
161
- const cls = 'ds-dot ' + (isOn ? 'ds-dot-on' : 'ds-dot-off') + (tone === 'live' ? ' ds-dot-live' : '');
162
- const statusLabel = tone === 'live' ? 'live status indicator' : (isOn ? 'on status indicator' : 'off status indicator');
161
+ const modifierCls = tone === 'live' ? ' ds-dot-live' : (tone === 'warn' ? ' ds-dot-warn' : '');
162
+ const cls = 'ds-dot ' + (tone === 'warn' ? 'ds-dot-off' : (isOn ? 'ds-dot-on' : 'ds-dot-off')) + modifierCls;
163
+ const statusLabel = tone === 'live' ? 'live status indicator' : (tone === 'warn' ? 'warning status indicator' : (isOn ? 'on status indicator' : 'off status indicator'));
163
164
  // Drawn as a CSS circle (.ds-dot) — no decorative text glyph.
164
165
  return h('span', { class: cls, role: 'img', 'aria-label': statusLabel });
165
166
  }
@@ -92,7 +92,19 @@ export const ICON_PATHS = {
92
92
  cursor: '<path d="M5 3l14 8-6.5 1.5L11 20z"/>',
93
93
  // Password-visibility toggle — eye / eye-off pair.
94
94
  eye: '<path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7z"/><circle cx="12" cy="12" r="3"/>',
95
- 'eye-off': '<path d="M2 12s3.5-7 10-7c1.7 0 3.2.4 4.5 1.1M22 12s-3.5 7-10 7c-1.7 0-3.2-.4-4.5-1.1"/><path d="M9.9 9.9a3 3 0 0 0 4.2 4.2"/><path d="m4 4 16 16"/>'
95
+ 'eye-off': '<path d="M2 12s3.5-7 10-7c1.7 0 3.2.4 4.5 1.1M22 12s-3.5 7-10 7c-1.7 0-3.2-.4-4.5-1.1"/><path d="M9.9 9.9a3 3 0 0 0 4.2 4.2"/><path d="m4 4 16 16"/>',
96
+ // Auth-provider glyphs for the signin kit — generic provider-neutral
97
+ // shapes (not trademarked logo reproductions), so a real icon renders
98
+ // instead of a text-letter abbreviation per AGENTS.md icon policy.
99
+ // github: a rounded body + one circular "eye", a widely-recognized
100
+ // silhouette-family shape without tracing the actual brand mark.
101
+ github: '<path d="M12 3a8 8 0 0 0-2.5 15.6c.4.1.5-.2.5-.4v-1.7c-2.2.4-2.7-1-2.9-1.6-.1-.3-.5-1-1-1.2-.3-.1-.6-.4 0-.5.9-.1 1.5.8 1.7 1.1.7 1.1 1.7.8 2.1.6.1-.5.3-.8.6-1-2.2-.3-3.4-1.4-3.4-3.4 0-.8.3-1.5.7-2-.1-.3-.3-1.1.1-2.2 0 0 .8-.2 2.5.9a8 8 0 0 1 4.5 0c1.7-1.1 2.5-.9 2.5-.9.4 1.1.2 1.9.1 2.2.5.5.7 1.2.7 2 0 2-1.2 3.1-3.4 3.4.3.3.6.8.6 1.5v2.1c0 .2.1.5.5.4A8 8 0 0 0 12 3z"/>',
102
+ // google: provider-neutral "G-circle" — a plain ring with a break and a
103
+ // short spoke, evoking the four-color pinwheel mark's silhouette only.
104
+ google: '<circle cx="12" cy="12" r="8"/><path d="M12 12h6"/><path d="M12 8v4"/>',
105
+ // sso: shield-check, a common auth/identity glyph for a generic
106
+ // single-sign-on entry point.
107
+ sso: '<path d="M12 3l7 3v6c0 4.5-3 7.5-7 9-4-1.5-7-4.5-7-9V6z"/><path d="M9 12l2 2 4-4"/>'
96
108
  };
97
109
 
98
110
  // The single SVG attribute contract (viewBox/stroke/linecap…) shared by both
@@ -10,6 +10,7 @@
10
10
 
11
11
  import * as webjsx from '../../vendor/webjsx/index.js';
12
12
  import { applyTheme, getTheme, resolvedTheme, onThemeChange } from '../theme.js';
13
+ import { Icon, iconMarkup } from './shell/icons.js';
13
14
 
14
15
  const h = webjsx.createElement;
15
16
 
@@ -19,6 +20,12 @@ const MODES = [
19
20
  ['ink', 'dark'],
20
21
  ];
21
22
 
23
+ // Sun/moon/contrast line-icon per mode (ICON_PATHS already carries these —
24
+ // see icons.js's "theme-toggle icons" entry) so the control reads at a
25
+ // glance even before the text label is parsed, not just via the CSS-drawn
26
+ // disc (which is icon-rail-only; the compact button keeps its text label).
27
+ const ICON_FOR_MODE = { auto: 'contrast', paper: 'sun', ink: 'moon' };
28
+
22
29
  // Both variants render from `getTheme()` at the moment their vnode is built,
23
30
  // but nothing re-renders this control when the theme changes: a host kit's view
24
31
  // is not subscribed to the theme, and applyTheme() only touches <html> and the
@@ -69,6 +76,12 @@ export function ThemeToggle({ compact = false, onChange } = {}) {
69
76
  if (lab) lab.textContent = labelFor(now);
70
77
  const disc = el.querySelector('.ds-theme-disc');
71
78
  if (disc) disc.setAttribute('data-mode', now);
79
+ // Repaint via raw markup (not a webjsx re-render — this ref
80
+ // runs outside any applyDiff pass), same pattern as icons.js's
81
+ // iconMarkup() is meant for: raw-DOM consumers with no
82
+ // webjsx render scope at hand.
83
+ const icon = el.querySelector('.ds-theme-toggle-icon');
84
+ if (icon) icon.innerHTML = iconMarkup(ICON_FOR_MODE[now] || 'contrast', { size: 14 });
72
85
  })
73
86
  },
74
87
  // CSS-drawn disc so the control still reads as the theme switch when
@@ -77,6 +90,11 @@ export function ThemeToggle({ compact = false, onChange } = {}) {
77
90
  // so the icon itself — not just the title tooltip — shows current
78
91
  // state at a glance.
79
92
  h('span', { class: 'ds-theme-disc', 'data-mode': current, 'aria-hidden': 'true' }),
93
+ // Sun/moon/contrast glyph alongside the text label — was text-only
94
+ // ("theme: auto"), so the control carried no visual cue of the
95
+ // current mode beyond the tiny CSS disc. Kept minimal: one small
96
+ // line-icon, no extra chrome.
97
+ h('span', { class: 'ds-theme-toggle-icon' }, Icon(ICON_FOR_MODE[current] || 'contrast', { size: 14 })),
80
98
  h('span', { class: 'ds-theme-toggle-label' }, labelFor(current)));
81
99
  }
82
100
 
@@ -393,6 +393,24 @@
393
393
  .chat-reactions .rxn .n { font-family: var(--ff-mono); font-weight: 600; font-size: var(--fs-nano); color: var(--fg-2); }
394
394
  .chat-reactions .rxn.you .n { color: var(--accent-ink); }
395
395
 
396
+ /* Typing indicator renders through the same .chat-bubble container as a real
397
+ message, but must never read as a bubble holding actual content — no
398
+ border, no hover-lift shadow, no tail-corner cut; just a pill-shaped
399
+ borderless dot cluster, visually lighter than any card/bubble on the page. */
400
+ /* Double-class selector (.chat-bubble.chat-bubble-typing) deliberately, not
401
+ bare .chat-bubble-typing: chat.css (loaded after app-shell.css on every
402
+ kit that links both) still carries a plain `.chat-bubble { background:
403
+ var(--bg-2) }` rule at the same (0,1,0) specificity, and cascade order
404
+ would let that later rule win over an equal-specificity selector here. */
405
+ .chat-bubble.chat-bubble-typing {
406
+ background: transparent;
407
+ padding: var(--space-2) var(--space-1);
408
+ border-radius: var(--r-pill);
409
+ }
410
+ .chat-msg:hover .chat-bubble.chat-bubble-typing { transform: none; box-shadow: none; }
411
+ .chat-msg:not(.you) .chat-bubble.chat-bubble-typing,
412
+ .chat-msg.you .chat-bubble.chat-bubble-typing { border-bottom-left-radius: var(--r-pill); border-bottom-right-radius: var(--r-pill); }
413
+
396
414
  .chat-typing { display: inline-flex; gap: var(--space-1); align-items: center; }
397
415
  .chat-typing span {
398
416
  width: 8px; height: 8px; border-radius: 50%; background: var(--fg-3);
@@ -92,7 +92,7 @@
92
92
  .ds-hero-accent { color: var(--accent-ink); font-weight: 700; }
93
93
  .ds-hero-actions {
94
94
  display: flex; gap: var(--space-2, 10px); flex-wrap: wrap;
95
- justify-content: flex-start; margin-top: var(--space-hair, 4px);
95
+ justify-content: flex-start; margin-top: var(--space-4);
96
96
  }
97
97
  /* Hero CTA labels are content-driven (e.g. "GitHub", proper nouns from
98
98
  home.yaml) and the site's voice is deliberately all-lowercase everywhere
@@ -435,7 +435,7 @@ table td.is-num, table th.is-num { text-align: right; font-variant-numeric: tabu
435
435
  /* live is a distinct hue from a plain "on" dot — see .chip.tone-live's
436
436
  comment for the success-vs-live rationale. */
437
437
  .ds-dot-live { color: var(--sky); }
438
- .ds-dot-live { color: var(--green-2); }
438
+ .ds-dot-warn { color: var(--amber); }
439
439
  .ds-dot-idle { color: var(--fg-3); }
440
440
 
441
441
  /* ============================================================
@@ -465,6 +465,17 @@ table td.is-num, table th.is-num { text-align: right; font-variant-numeric: tabu
465
465
  transform: translateY(-1px);
466
466
  box-shadow: var(--shadow-1, 0 2px 8px rgba(0,0,0,.12));
467
467
  }
468
+ /* Number+caption stack, isolated from .kpi-foot below it. Previously the
469
+ number and label were plain block children of .kpi-card with the label's
470
+ own margin-top providing the gap — visually fine for a single card, but
471
+ across a row of four the caption's WRAPPED height (one line vs two, e.g.
472
+ "requests · 24h" vs "error rate · 5xx+4xx") pushed .kpi-foot down by a
473
+ different amount per card, so the deltas/sparklines never lined up on one
474
+ shared baseline. A dedicated flex column groups exactly the two elements
475
+ that must stay glued together, independent of whatever sits below. */
476
+ .kpi-stack {
477
+ display: flex; flex-direction: column; align-items: flex-start;
478
+ }
468
479
  .kpi-glyph {
469
480
  position: absolute;
470
481
  top: var(--space-2);
@@ -551,14 +562,36 @@ table td.is-num, table th.is-num { text-align: right; font-variant-numeric: tabu
551
562
  padding: var(--space-5);
552
563
  background: var(--bg-2);
553
564
  border-radius: var(--r-3);
554
- }
565
+ /* Grid's default justify-items:stretch was silently stretching the submit
566
+ * button (a direct grid child, same as every input) to the full column
567
+ * width -- other primary buttons elsewhere (e.g. session-ui's "new
568
+ * session") are inline/compact by contrast, since they're not grid
569
+ * children. justify-self:start keeps inputs/textareas at their natural
570
+ * grid-stretched width (a form field SHOULD fill its column) while the
571
+ * submit button sizes to its own content, matching the compact convention
572
+ * used everywhere else primary buttons appear. */
573
+ }
574
+ .row-form > button[type="submit"] { justify-self: start; }
555
575
  .row-form input,
556
576
  .row-form textarea {
557
577
  font-family: inherit; font-size: var(--fs-sm);
558
578
  padding: 12px 16px;
559
- background: var(--bg); color: var(--fg);
579
+ /* --bg here previously resolved to the SAME color as .row-form's own
580
+ * --bg-2 background in the dark theme, leaving only a 14%-opacity
581
+ * currentColor ring (var(--rule)) as the sole edge between an input and
582
+ * the panel behind it -- effectively invisible. --bg-3 (one step darker
583
+ * than the panel) plus a stronger border restores a real, panel-distinct
584
+ * field surface.
585
+ * LOAD-BEARING !important: `.row-form input` and states-interactions.css's
586
+ * bare `input[type="text"]:where(...)` rule are both (0,1,1) specificity
587
+ * (the :where() wrapper there is intentionally zero-specificity) --
588
+ * states-interactions.css @imports AFTER this file in app-shell.css, so on
589
+ * this exact tie it wins by source order and silently discarded these two
590
+ * declarations. Only !important beats that without restructuring the
591
+ * shared bare-input rule's own selector, which many other consumers rely on. */
592
+ background: var(--bg-3) !important; color: var(--fg);
560
593
  border: 0; border-radius: var(--r-2);
561
- box-shadow: inset 0 0 0 1px var(--rule);
594
+ box-shadow: inset 0 0 0 1px color-mix(in oklab, currentColor 28%, transparent) !important;
562
595
  }
563
596
  .row-form input:focus-visible,
564
597
  .row-form textarea:focus-visible { box-shadow: var(--focus-ring-inset); }
@@ -507,6 +507,14 @@
507
507
  justify-content: flex-end; gap: var(--space-2); flex-wrap: wrap;
508
508
  padding: calc(var(--space-2-75) * var(--density)) calc(var(--space-3) * var(--density));
509
509
  background: var(--panel-2); border-radius: var(--r-1);
510
+ /* --panel-1 (this bar's parent .panel surface) and --panel-2 are adjacent
511
+ lightness steps that measure only ~1.1-1.5:1 apart (both themes) -- too
512
+ close to read as a distinct nesting depth by background tone alone.
513
+ A hairline border is the same edge-definition device .panel itself
514
+ already uses (see the top of this file) rather than a token-lightness
515
+ change, which would risk the fg-3-on-panel-2 text contrast that is
516
+ already right at its AA floor in the dark theme. */
517
+ border: var(--bw-hair) solid var(--rule);
510
518
  margin: calc(var(--space-5) * var(--density)) 0 var(--space-2);
511
519
  box-shadow: var(--shadow-2, var(--shadow-1));
512
520
  }
@@ -552,13 +560,37 @@
552
560
  /* .ds-auth-field-label joins the kit small-label voice above, no overrides. */
553
561
  .ds-auth-field-input-row { position: relative; display: flex; align-items: center; }
554
562
  /* Reserves room for the absolutely-positioned toggle: its --space-1 inset plus
555
- the --ds-icon-btn-sm 26px box, so the input text never sits under it. */
563
+ the --ds-icon-btn-sm 26px box, so the input text never sits under it. This
564
+ only holds at the pointer:fine 26px box size, though -- the coarse-pointer
565
+ touch-floor rule (states-interactions.css) grows .ds-icon-btn-sm to a
566
+ min-width/height of 44px without this padding following it, so on a
567
+ touchscreen the enlarged hit target actually overlapped 14px into the
568
+ typed password text (44 + 4 inset > 34 reserved). Reserve for the coarse
569
+ floor too, gated behind the same media query, so clearance holds at both
570
+ sizes instead of only the desktop one. */
556
571
  .ds-auth-field-input-row .input { flex: 1 1 auto; padding-right: calc(26px + var(--space-2)); }
572
+ @media (pointer: coarse) {
573
+ .ds-auth-field-input-row .input { padding-right: calc(44px + var(--space-2)); }
574
+ }
557
575
  .ds-auth-pw-toggle { position: absolute; right: var(--space-1); }
558
576
  .ds-auth-row-between { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); flex-wrap: wrap; }
559
577
  .ds-auth-remember { display: flex; align-items: center; gap: var(--space-1-75); cursor: pointer; }
578
+ /* The native checkbox is already re-tinted site-wide via `accent-color:
579
+ var(--accent-ink)` in states-interactions.css (that rule's own comment
580
+ names this exact control) -- reused here rather than hand-drawing a new
581
+ checkbox component. What that global rule doesn't cover is size: the UA
582
+ default box renders at ~13px, which is under the 24px comfortable-tap
583
+ floor other controls in this row (the link text, the input fields) sit
584
+ above. Sized up on the existing --space-2-5 token, no new primitive. */
585
+ .ds-auth-remember input[type="checkbox"] { width: var(--space-2-5); height: var(--space-2-5); margin: 0; }
560
586
  .ds-auth-remember-text { color: var(--panel-text-2); font-size: var(--fs-tiny); }
561
- .ds-auth-forgot { font-size: var(--fs-tiny); }
587
+ /* --accent-ink, not --accent, per the design system's text-vs-fill split
588
+ (AGENTS.md): --accent is a background/border-color lead only, --accent-ink
589
+ is the tone actually verified for text contrast. The link was previously
590
+ unstyled prose color at --fs-tiny, reading as inert grey next to the
591
+ checkbox label it shares a row with. */
592
+ .ds-auth-forgot { font-size: var(--fs-tiny); color: var(--accent-ink); font-weight: 500; }
593
+ .ds-auth-forgot:hover { text-decoration: underline; }
562
594
  /* Error and status share layout but not color: a color-only distinction
563
595
  between role="alert" (something is wrong) and role="status" (informational)
564
596
  fails WCAG 1.4.1, so each also carries a distinct ASCII marker via ::before —
@@ -568,6 +600,28 @@
568
600
  .ds-auth-error::before { content: '[!]'; flex: none; }
569
601
  .ds-auth-status { color: var(--panel-text-2); }
570
602
  .ds-auth-status::before { content: '[i]'; flex: none; }
603
+ /* The shared Divider() label defaults to --fs-tiny/--panel-text-3 (editor-
604
+ primitives.css), tuned for a dense editor toolbar rule -- inside the auth
605
+ form it sat at the same weight as fine print two rows below it and read
606
+ as barely-there rather than a section break between the password form and
607
+ the OAuth row. Scoped to this form only (never edit the shared component
608
+ sheet for one consumer): borrows the kit small-label voice above (mono,
609
+ --fs-nano, --tr-label, uppercase) at --panel-text-2 instead of the
610
+ dimmer -3 tier, plus the ::before/::after rule lines pushed to full
611
+ --panel-text opacity so the break itself, not just the label, is visible. */
612
+ .ds-auth-form .ds-ep-divider-labeled { margin: var(--space-1) 0; }
613
+ .ds-auth-form .ds-ep-divider-labeled::before,
614
+ .ds-auth-form .ds-ep-divider-labeled::after {
615
+ border-top-color: var(--panel-text-3);
616
+ }
617
+ .ds-auth-form .ds-ep-divider-label {
618
+ font-family: var(--ff-mono);
619
+ font-size: var(--fs-nano);
620
+ font-weight: 600;
621
+ letter-spacing: var(--tr-label);
622
+ text-transform: uppercase;
623
+ color: var(--panel-text-2);
624
+ }
571
625
  .ds-auth-providers { display: flex; gap: var(--space-2); flex-wrap: wrap; }
572
626
  .ds-auth-provider-btn { flex: 1 1 100px; min-width: 0; display: flex; align-items: center; justify-content: center; gap: var(--space-2); padding: var(--space-2-5); }
573
627
  .ds-auth-provider-btn--loading { opacity: 0.7; pointer-events: none; }
@@ -171,6 +171,10 @@
171
171
  .chip.tone-flame, .chip.tone-error, .chip.tone-miss {
172
172
  background: var(--flame); color: var(--on-color);
173
173
  }
174
+ .chip.tone-warn {
175
+ background: color-mix(in oklab, var(--amber) 22%, var(--bg));
176
+ color: var(--amber);
177
+ }
174
178
  .chip.tone-wip, .chip.tone-neutral {
175
179
  background: var(--bg-3); color: var(--fg-2);
176
180
  }
@@ -31,6 +31,19 @@
31
31
  .ds-filter-pill.active { background: var(--accent-tint); color: var(--fg); border-color: var(--accent); }
32
32
  .ds-filter-pill:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); }
33
33
 
34
+ .ds-filter-pill[class*="tone-"]::before {
35
+ content: '';
36
+ display: inline-block; width: 7px; height: 7px; border-radius: 50%;
37
+ margin-right: var(--space-1);
38
+ background: currentColor;
39
+ }
40
+ .ds-filter-pill.tone-accent { color: var(--accent-ink); }
41
+ .ds-filter-pill.tone-warn { color: var(--amber); }
42
+ .ds-filter-pill.tone-live { color: var(--sky); }
43
+ .ds-filter-pill.tone-sun { color: var(--sun); }
44
+ .ds-filter-pill.tone-success { color: var(--green-deep); }
45
+ .ds-filter-pill.tone-dim { color: var(--fg-3); }
46
+
34
47
  /* Touch floor for the new small controls. */
35
48
  @media (pointer: coarse) {
36
49
  .row-act, .ds-filter-pill { min-height: 44px; }
@@ -2,6 +2,21 @@
2
2
  Sidebar polish: hide zero-count badges; tonal active state;
3
3
  monospace count chips.
4
4
  ============================================================ */
5
+ /* Rail row rhythm: the sidebar rows (bins/labels) previously packed at
6
+ --space-hair (2px) margin-bottom while the main content column reads at
7
+ the --space-5/--space-3 outer/inner rhythm (AGENTS.md "panel rhythm").
8
+ Bumping the inter-row gap to --space-1-5 (5px) — still a tight, dense-app
9
+ value, but enough that adjacent rows no longer read as glued together —
10
+ and making it density-aware via [data-density] mirrors the existing
11
+ compact-density override pattern in files.css (.ds-file-grid[data-density]).
12
+ [data-density="compact"] keeps the old tighter feel for long rail lists;
13
+ [data-density="spacious"] opens it further. */
14
+ .app-side a { margin-bottom: var(--space-1-5); }
15
+ .app-side[data-density="compact"] a,
16
+ .app-side-shell[data-density="compact"] .app-side a { margin-bottom: var(--space-hair); }
17
+ .app-side[data-density="spacious"] a,
18
+ .app-side-shell[data-density="spacious"] .app-side a { margin-bottom: var(--space-2); }
19
+
5
20
  .app-side .count {
6
21
  padding: 1px 8px; font-size: var(--fs-micro); font-weight: 700;
7
22
  font-family: var(--ff-mono); letter-spacing: 0.04em;