anentrypoint-design 0.0.477 → 0.0.480

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.
Files changed (53) hide show
  1. package/README.md +101 -0
  2. package/chat.css +4 -4
  3. package/colors_and_type.css +33 -3
  4. package/community.css +22 -8
  5. package/dist/247420.css +324 -49
  6. package/dist/247420.js +123 -55
  7. package/dist/index.html +109 -32
  8. package/package.json +1 -1
  9. package/src/community-app.js +4 -2
  10. package/src/components/community/navigation.js +2 -2
  11. package/src/components/community/presence.js +10 -4
  12. package/src/components/community/views.js +8 -2
  13. package/src/components/content/avatar.js +30 -0
  14. package/src/components/content/hero.js +9 -2
  15. package/src/components/content.js +2 -2
  16. package/src/components/freddie/pages-missing.js +1 -1
  17. package/src/components/overlay-primitives/emoji-picker.js +15 -3
  18. package/src/components/shell/app-shell.js +15 -4
  19. package/src/components/shell/workspace-shell.js +1 -1
  20. package/src/components/theme-toggle.js +8 -3
  21. package/src/copy-code.js +103 -0
  22. package/src/css/app-shell/base.css +18 -0
  23. package/src/css/app-shell/catalog-theme.css +20 -2
  24. package/src/css/app-shell/chat-polish.css +12 -0
  25. package/src/css/app-shell/collab.css +5 -0
  26. package/src/css/app-shell/data-density.css +4 -0
  27. package/src/css/app-shell/files.css +7 -1
  28. package/src/css/app-shell/hero-content.css +14 -1
  29. package/src/css/app-shell/kits-appended.css +13 -0
  30. package/src/css/app-shell/loading-alerts.css +14 -12
  31. package/src/css/app-shell/otp-input.css +2 -2
  32. package/src/css/app-shell/panel-row.css +15 -0
  33. package/src/css/app-shell/plugins-config.css +3 -0
  34. package/src/css/app-shell/primitives.css +13 -4
  35. package/src/css/app-shell/responsive.css +13 -0
  36. package/src/css/app-shell/responsive2-workspace.css +4 -1
  37. package/src/css/app-shell/row-print.css +34 -0
  38. package/src/css/app-shell/states-interactions.css +62 -4
  39. package/src/css/app-shell/topbar.css +1 -0
  40. package/src/kits/os/app-panes.css +3 -2
  41. package/src/kits/os/icons.js +16 -0
  42. package/src/kits/os/shell-geometry.js +2 -2
  43. package/src/kits/os/shell.js +24 -3
  44. package/src/kits/os/theme.css +272 -78
  45. package/src/kits/os/wm.css +19 -6
  46. package/src/kits/os/wm.js +38 -1
  47. package/src/kits/spoint/host-join-lobby.css +3 -3
  48. package/src/page-html/client-script.js +96 -28
  49. package/src/page-html.js +3 -2
  50. package/src/theme.js +6 -2
  51. package/src/web-components/ds-chat.js +9 -0
  52. package/src/web-components/freddie-chat.js +2 -2
  53. package/types/components.d.ts +6 -0
package/dist/index.html CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  <meta name="description" content="the visual paradigm. layered surfaces, monospace labels, loud content inside quiet chrome. consumed by every 247420 surface.">
9
9
  <meta name="keywords" content="design-system, tokens, 247420, anentrypoint">
10
- <meta name="author" content="247420 / AnEntrypoint">
10
+ <meta name="author" content="247420 · a design system by AnEntrypoint">
11
11
  <meta name="robots" content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1">
12
12
  <meta name="generator" content="anentrypoint-design">
13
13
  <link rel="canonical" href="https://anentrypoint.github.io/design">
@@ -116,14 +116,21 @@ a.row:hover .ds-row-arrow { opacity: 1 }
116
116
  .ds-page-footer-links a { color: var(--fg-3) }
117
117
  .ds-page-footer-links a:hover { color: var(--fg) }
118
118
  </style>
119
- <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":"14 kit surfaces, zero raw color literals, one importmap script tag to install. every panel, row, and form control in this system reads its color, radius, and spacing from a single token file — swap the tokens and every surface built on them changes with it.","badges":[{"label":"design-system"},{"label":"tokens"},{"label":"web"}],"ctas":[{"label":"GitHub","href":"https://github.com/AnEntrypoint/design","primary":true},{"label":"247420","href":"https://247420.xyz"}]},"sections":[],"examples":[{"label":"index shell","desc":"full navigation shell with tabs, filters, and row interactions.","href":"./index.html"},{"label":"homepage kit","desc":"hero + works layout and updated readable type scale.","href":"./ui_kits/homepage/"},{"label":"project page kit","desc":"overview, install, receipt, and changelog template.","href":"./ui_kits/project_page/"},{"label":"docs kit","desc":"technical documentation layout with sidebar and prose.","href":"./ui_kits/docs/"},{"label":"blog kit","desc":"long-form writing layout tuned for readability.","href":"./ui_kits/blog/"},{"label":"chat kit","desc":"message thread + composer with pill bubbles and room sidebar.","href":"./ui_kits/chat/"},{"label":"aicat kit","desc":"ai assistant with cat persona — ascii portrait + mood face.","href":"./ui_kits/aicat/"},{"label":"slide deck","desc":"16:9 deck stage with improved presenter controls text.","href":"./slides/"},{"label":"dashboard kit","desc":"kpis, tables, receipts, changelog — operations surface.","href":"./ui_kits/dashboard/"},{"label":"terminal kit","desc":"cli prompt, command lines, log viewer.","href":"./ui_kits/terminal/"},{"label":"settings kit","desc":"sectioned forms, toggles, segmented buttons, save bar.","href":"./ui_kits/settings/"},{"label":"signin kit","desc":"auth panel, providers, magic link.","href":"./ui_kits/signin/"},{"label":"search kit","desc":"query bar, faceted filters, ranked results.","href":"./ui_kits/search/"},{"label":"404 kit","desc":"empty-state hero with suggested routes.","href":"./ui_kits/error_404/"},{"label":"gallery kit","desc":"visual grid + lightbox over tonal cards.","href":"./ui_kits/gallery/"},{"label":"preview samples","desc":"design previews and component samples.","href":"./preview/buttons.html"}],"bodyHtml":"","sidebar":{"sections":[{"group":"open","items":[{"glyph":"+","label":"open","href":"./ui_kits/homepage/"}]},{"group":"bins","items":[{"label":"everything","href":"#all","active":true,"count":43},{"label":"pinned","href":"#kits","count":14},{"label":"shipped","href":"#previews","count":25},{"label":"drafts","href":"#decks","count":1}]},{"group":"labels","items":[{"label":"ui kits","href":"#kits","count":14},{"label":"decks","href":"#decks","count":1},{"label":"previews","href":"#previews","count":25},{"label":"docs","href":"#docs","count":3}]},{"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"}]}]},"marquee":{"items":["open source","design tokens","buildless components","zero raw color literals"],"sep":"/"},"panels":[{"id":"kits","title":"ui kits","count":"14 surfaces","items":[{"code":"","title":"homepage","sub":"marquee + 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 + reading view","meta":"open ->","href":"./ui_kits/blog/"},{"code":"","title":"chat","sub":"message thread + composer","meta":"open ->","href":"./ui_kits/chat/"},{"code":"","title":"aicat","sub":"ai assistant with cat persona","meta":"open ->","href":"./ui_kits/aicat/"},{"code":"","title":"file browser","sub":"rails by file type drop 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":"signin","sub":"auth providers 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/"}]},{"id":"file_browser","title":"file browser","count":"primitives","items":[{"code":"fb","title":"file_browser kit","sub":"drop, list, preview, dialogs","meta":"open kit","href":"./ui_kits/file_browser/"},{"code":"fb","title":"FileRow","sub":"rail icon name meta","meta":"preview","href":"./preview/file-row.html"},{"code":"fb","title":"FileGrid","sub":"stack of rows in a panel","meta":"preview","href":"./preview/file-grid.html"},{"code":"fb","title":"DropZone","sub":"tonal upload target","meta":"preview","href":"./preview/dropzone.html"},{"code":"fb","title":"FileViewer","sub":"modal preview keyed head","meta":"preview","href":"./preview/file-viewer.html"},{"code":"fb","title":"FileToolbar","sub":"actions breadcrumb upload","meta":"preview","href":"./preview/file-toolbar.html"},{"code":"fb","title":"fsbrowse","sub":"Express + busboy backend","meta":"source","href":"https://github.com/AnEntrypoint/fsbrowse"}]},{"id":"desktop_os","title":"desktop os shell","count":"in-page wm","items":[{"code":"os","title":"createDesktopShell","sub":"menubar apps launcher taskbar window manager","meta":"shell.js","href":"https://github.com/AnEntrypoint/design/blob/main/src/desktop/shell.js"},{"code":"os","title":"icons set","sub":"svg glyphs for menubar + taskbar","meta":"icons.js","href":"https://github.com/AnEntrypoint/design/blob/main/src/desktop/icons.js"},{"code":"os","title":"theme.css","sub":"os chrome tokens menubar launcher wm","meta":"theme.css","href":"https://github.com/AnEntrypoint/design/blob/main/src/desktop/theme.css"},{"code":"os","title":"import path","sub":"anentrypoint-design/desktop","meta":"exports","href":"https://github.com/AnEntrypoint/design/blob/main/package.json"}]},{"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"}]},{"id":"api_exports","title":"public api","count":"sdk surface","items":[{"code":"api","title":"h / applyDiff","sub":"webjsx createElement + 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 + 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":"ds shell + 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 stack","meta":"markdown.js","href":"https://github.com/AnEntrypoint/design/blob/main/src/markdown.js"},{"code":"api","title":"ensurePrism / highlightAllUnder","sub":"prism core + 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"}]},{"id":"decks","title":"decks","count":1,"items":[{"code":"","title":"deck template","sub":"16:9 stage with auto-fit","meta":"open ->","href":"./slides/"}]},{"id":"docs","title":"docs","count":3,"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"}]},{"id":"features","title":"why design","count":4,"items":[{"code":"","title":"layered surfaces","sub":"panel-on-panel rhythm; loud content sits inside quiet chrome.","meta":"panel-row.css","href":"https://github.com/AnEntrypoint/design/blob/main/src/css/app-shell/panel-row.css"},{"code":"","title":"monospace labels","sub":"mono only for codes/labels/CLI; sans for everything legible.","meta":"colors_and_type.css","href":"https://github.com/AnEntrypoint/design/blob/main/colors_and_type.css"},{"code":"","title":"indicator rails","sub":"color-coded inset rails for category separation, never borders.","meta":"panel-row.css","href":"https://github.com/AnEntrypoint/design/blob/main/src/css/app-shell/panel-row.css#L126"},{"code":"","title":"tokens-only","sub":"every surface, type ramp, accent driven by --panel-* / --fs-* tokens.","meta":"colors_and_type.css","href":"https://github.com/AnEntrypoint/design/blob/main/colors_and_type.css"}]},{"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","14 kits"]}</script>
119
+ <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":"14 kit surfaces, 25 previews, one deck, and a full desktop-OS shell — plus a web component and an SDK, all tokens-only, zero raw color literals, one importmap script tag to install. every panel, row, and form control in this system reads its color, radius, and spacing from a single token file — swap the tokens and every surface built on them changes with it.","badges":[{"label":"design-system"},{"label":"tokens"},{"label":"web"}],"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":45},{"label":"pinned","href":"#kits","count":14},{"label":"shipped","href":"#previews","count":25},{"label":"drafts","href":"#decks","count":1}]},{"group":"labels","items":[{"label":"ui kits","href":"#kits","count":14},{"label":"decks","href":"#decks","count":1},{"label":"previews","href":"#previews","count":25},{"label":"docs","href":"#docs","count":5}]},{"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","buildless components","zero raw color literals"],"sep":"/"},"panels":[{"id":"kits","title":"ui kits","count":"14 surfaces","items":[{"code":"","title":"homepage","sub":"marquee + 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 + reading view","meta":"open ->","href":"./ui_kits/blog/"},{"code":"","title":"chat","sub":"message thread + composer","meta":"open ->","href":"./ui_kits/chat/"},{"code":"","title":"aicat","sub":"ai assistant with cat persona","meta":"open ->","href":"./ui_kits/aicat/"},{"code":"","title":"file browser","sub":"rails by file type drop 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":"signin","sub":"auth providers 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/"}]},{"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":"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"}]},{"id":"desktop_os","title":"desktop os shell","count":"in-page wm","items":[{"code":"os","title":"createDesktopShell","sub":"menubar apps launcher taskbar window manager","meta":"shell.js","href":"https://github.com/AnEntrypoint/design/blob/main/src/desktop/shell.js"},{"code":"os","title":"icons set","sub":"svg glyphs for menubar + taskbar","meta":"icons.js","href":"https://github.com/AnEntrypoint/design/blob/main/src/desktop/icons.js"},{"code":"os","title":"theme.css","sub":"os chrome tokens menubar launcher wm","meta":"theme.css","href":"https://github.com/AnEntrypoint/design/blob/main/src/desktop/theme.css"},{"code":"os","title":"import path","sub":"anentrypoint-design/desktop","meta":"exports","href":"https://github.com/AnEntrypoint/design/blob/main/package.json"}]},{"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"}]},{"id":"api_exports","title":"public api","count":"sdk surface","items":[{"code":"api","title":"h / applyDiff","sub":"webjsx createElement + 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 + 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":"ds shell + 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 stack","meta":"markdown.js","href":"https://github.com/AnEntrypoint/design/blob/main/src/markdown.js"},{"code":"api","title":"ensurePrism / highlightAllUnder","sub":"prism core + 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"}]},{"id":"decks","title":"decks","count":1,"items":[{"code":"","title":"deck template","sub":"16:9 stage with auto-fit","meta":"open ->","href":"./slides/"}]},{"id":"docs","title":"docs","count":5,"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"}]},{"id":"features","title":"why design","count":4,"items":[{"code":"","title":"layered surfaces","sub":"panel-on-panel rhythm; loud content sits inside quiet chrome.","meta":"panel-row.css","href":"https://github.com/AnEntrypoint/design/blob/main/src/css/app-shell/panel-row.css"},{"code":"","title":"monospace labels","sub":"mono only for codes/labels/CLI; sans for everything legible.","meta":"colors_and_type.css","href":"https://github.com/AnEntrypoint/design/blob/main/colors_and_type.css"},{"code":"","title":"indicator rails","sub":"color-coded inset rails for category separation, never borders.","meta":"panel-row.css","href":"https://github.com/AnEntrypoint/design/blob/main/src/css/app-shell/panel-row.css#L126"},{"code":"","title":"tokens-only","sub":"every surface, type ramp, accent driven by --panel-* / --fs-* tokens.","meta":"colors_and_type.css","href":"https://github.com/AnEntrypoint/design/blob/main/colors_and_type.css"}]},{"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","14 kits"],"seoAuthor":"247420 · a design system by AnEntrypoint"}</script>
120
120
  <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>
121
121
  </head>
122
122
  <body>
123
123
  <div id="app"></div>
124
124
  <script type="module">
125
125
  import { mount, components as C, h } from 'anentrypoint-design';
126
- const data = JSON.parse(document.getElementById('__site__').textContent);
126
+ let data;
127
+ try {
128
+ data = JSON.parse(document.getElementById('__site__').textContent);
129
+ } catch (e) {
130
+ console.error('[page-html] failed to parse #__site__ bootstrap data:', e);
131
+ document.getElementById('app').textContent = 'This page failed to load correctly. Please refresh, or contact support if the problem persists.';
132
+ throw e;
133
+ }
127
134
 
128
135
  function heroNode(hero) {
129
136
  if (!hero) return null;
@@ -150,11 +157,13 @@ function heroNode(hero) {
150
157
  );
151
158
  }
152
159
 
160
+ function __esc(s) { return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'); }
161
+
153
162
  function sectionNode(sec, idx) {
154
163
  const features = sec.features || sec.items || [];
155
164
  const rows = features.map((f, i) => {
156
165
  const kids = [h('div', { key: 't', class: 'ds-feature-title' }, String(f.name || ''))];
157
- if (f.desc) kids.push(h('div', { key: 'd', class: 'ds-feature-desc', innerHTML: String(f.desc).replace(/`([^`]+)`/g, '<code>$1</code>') }));
166
+ if (f.desc) kids.push(h('div', { key: 'd', class: 'ds-feature-desc', innerHTML: __esc(String(f.desc)).replace(/`([^`]+)`/g, '<code>$1</code>') }));
158
167
  if (f.benefit) kids.push(h('div', { key: 'b', class: 'ds-feature-benefit' }, String(f.benefit)));
159
168
  return h('div', { key: i, class: 'ds-feature' }, ...kids);
160
169
  });
@@ -185,9 +194,64 @@ function examplesNode(examples) {
185
194
  });
186
195
  }
187
196
 
188
- function panelNode(panel, idx) {
189
- const items = Array.isArray(panel.items) ? panel.items : [];
190
- if (!items.length) return null;
197
+ // Category glyph per homepage panel id — the same line-icon vocabulary
198
+ // icons.js already provides, so kits/previews/decks/docs read as distinct
199
+ // categories at a glance instead of by text label alone. Falls back to no
200
+ // icon for panel ids outside this table (e.g. one-off feature panels).
201
+ const PANEL_ICON = {
202
+ kits: 'grid',
203
+ file_browser: 'folder',
204
+ desktop_os: 'square',
205
+ web_components: 'page',
206
+ api_exports: 'link',
207
+ decks: 'screen',
208
+ docs: 'file-text',
209
+ previews: 'eye',
210
+ features: 'info',
211
+ };
212
+
213
+ // Filter state for the kits search box above the "ui kits" panel. Reuses
214
+ // ui_kits/search's own query-bar pattern (plain module-level state object,
215
+ // mutated on input, driving a re-render) rather than inventing a second
216
+ // filtering mechanism -- this homepage already ships that kit, so the
217
+ // homepage's own kit listing gets the same affordance instead of being the
218
+ // one surface on the site without a way to search kits by name.
219
+ const kitsFilterState = { q: '' };
220
+
221
+ function panelNode(panel, idx, rerender) {
222
+ let items = Array.isArray(panel.items) ? panel.items : [];
223
+ const isKits = panel.id === 'kits';
224
+ const q = isKits ? kitsFilterState.q.trim().toLowerCase() : '';
225
+ if (isKits && q) {
226
+ items = items.filter((it) => {
227
+ const hay = (String(it.title || it.name || '') + ' ' + String(it.sub || it.desc || '')).toLowerCase();
228
+ return hay.includes(q);
229
+ });
230
+ }
231
+ const iconName = panel.id && PANEL_ICON[panel.id];
232
+ const titleText = panel.title || panel.name || '';
233
+ const titleNode = iconName && titleText
234
+ ? h('span', { class: 'ds-panel-title-glyph' }, C.Icon(iconName, { size: 15 }), h('span', {}, titleText))
235
+ : titleText;
236
+ const filterInput = isKits && rerender ? h('div', { class: 'ds-kits-filter' },
237
+ h('input', {
238
+ type: 'search', class: 'input ds-kits-filter-input',
239
+ placeholder: 'filter kits by name or description…',
240
+ value: kitsFilterState.q,
241
+ 'aria-label': 'filter ui kits',
242
+ oninput: (e) => { kitsFilterState.q = e.target.value; rerender(); },
243
+ })) : null;
244
+ if (!items.length) {
245
+ if (isKits && q) {
246
+ return h('div', { class: 'ds-kits-panel-wrap' }, filterInput,
247
+ C.Panel({ id: panel.id || null, title: titleNode, count: 0, children:
248
+ h('div', { class: 'ds-empty-state' },
249
+ h('div', { class: 'ds-empty-state-glyph' }, '( )'),
250
+ h('p', { class: 'ds-empty-state-msg' }, 'no kits match ', h('code', {}, '"' + kitsFilterState.q.trim() + '"')),
251
+ ) }));
252
+ }
253
+ return filterInput ? h('div', { class: 'ds-kits-panel-wrap' }, filterInput) : null;
254
+ }
191
255
  const rows = items.map((it, i) => {
192
256
  const code = it.code == null ? '' : String(it.code).trim();
193
257
  const kids = [];
@@ -197,7 +261,8 @@ function panelNode(panel, idx) {
197
261
  kids.push(h('span', { key: 'a', class: 'ds-row-arrow' }, it.meta || '->'));
198
262
  return h('a', { key: i, class: 'row', href: it.href || '#' }, ...kids);
199
263
  });
200
- return C.Panel({ id: panel.id || null, title: panel.title || panel.name || '', count: panel.count || items.length, children: rows });
264
+ const panelEl = C.Panel({ id: panel.id || null, title: titleNode, count: items.length, children: rows });
265
+ return filterInput ? h('div', { class: 'ds-kits-panel-wrap' }, filterInput, panelEl) : panelEl;
201
266
  }
202
267
 
203
268
  function marqueeNode(marquee) {
@@ -301,8 +366,14 @@ const bodyNode = data.bodyHtml ? C.Section({ children: h('div', { class: 'page-b
301
366
  function footerNode() {
302
367
  const year = new Date().getFullYear();
303
368
  const links = (data.navItems || []).filter(([, href]) => /^https?:/.test(String(href)));
369
+ // Names the actual maintaining entity, not just the project's numeric
370
+ // brand. This is a personal/community project (MIT, copyright holder
371
+ // "AnEntrypoint" per LICENSE) with no separate legal org behind it, so the
372
+ // footer says that plainly instead of leaving "247420" to stand in for an
373
+ // entity it isn't.
374
+ const entity = (data.seoAuthor || '247420 · a design system by AnEntrypoint').toLowerCase();
304
375
  return h('footer', { class: 'ds-page-footer' },
305
- h('span', { class: 'ds-page-footer-copy' }, String(year) + ' · ' + data.siteName),
376
+ h('span', { class: 'ds-page-footer-copy' }, String(year) + ' · ' + entity),
306
377
  links.length ? h('nav', { class: 'ds-page-footer-links', 'aria-label': 'footer' },
307
378
  ...links.map(([label, href], i) => h('a', { key: i, href }, String(label).replace(' ->', '')))
308
379
  ) : null,
@@ -320,40 +391,46 @@ function tierNode(tier, children) {
320
391
  return h('section', { key: tier.key, class: 'ds-tier ds-tier-' + tier.key, id: tier.key, 'aria-labelledby': labelId }, head, ...kids);
321
392
  }
322
393
 
323
- const panelsById = new Map((data.panels || []).map((p) => [p.id || p.title || p.name || '', p]));
324
- const takePanel = (id) => { const p = panelsById.get(id); if (p) panelsById.delete(id); return p ? panelNode(p) : null; };
325
-
326
394
  const TIERS = [
327
395
  { key: 'open', label: 'open', lede: 'browse and try the system running.', ids: ['kits', 'previews', 'decks'], extra: () => [examplesNode(data.examples)] },
328
396
  { key: 'ships', label: 'ships', lede: 'what the package contains.', ids: ['file_browser', 'desktop_os', 'web_components', 'api_exports'], extra: () => [] },
329
397
  { key: 'read', label: 'read', lede: 'understand the rules behind it.', ids: ['docs', 'features'], extra: () => [quickstartNode(data.quickstart)] },
330
398
  ];
331
399
 
332
- const tierNodes = TIERS.map((t) => {
333
- const kids = [...t.ids.map(takePanel), ...t.extra()].filter(Boolean);
334
- if (t.key === 'open' && kids.length) {
335
- const lead = kids[0];
336
- if (lead.props) lead.props.class = (lead.props.class || '') + ' ds-tier-lead';
337
- }
338
- return tierNode(t, kids);
339
- });
340
- const leftoverPanels = [...panelsById.values()].map(panelNode);
400
+ // Builds the whole main-content tree fresh each render so the kits filter
401
+ // (module-level kitsFilterState) can drive a real re-render via mount()'s
402
+ // own returned render callback -- same reactive shape mountKit() gives every
403
+ // other kit on the site, applied here to the static-page renderer.
404
+ function buildMainChildren(rerender) {
405
+ const panelsById = new Map((data.panels || []).map((p) => [p.id || p.title || p.name || '', p]));
406
+ const takePanel = (id) => { const p = panelsById.get(id); if (p) panelsById.delete(id); return p ? panelNode(p, 0, rerender) : null; };
341
407
 
342
- const mainChildren = [
343
- heroNode(data.hero),
344
- marqueeNode(data.marquee),
345
- ...data.sections.map(sectionNode),
346
- ...tierNodes,
347
- ...leftoverPanels,
348
- bodyNode,
349
- footerNode(),
350
- ].filter(Boolean);
408
+ const tierNodes = TIERS.map((t) => {
409
+ const kids = [...t.ids.map(takePanel), ...t.extra()].filter(Boolean);
410
+ if (t.key === 'open' && kids.length) {
411
+ const lead = kids[0];
412
+ if (lead.props) lead.props.class = (lead.props.class || '') + ' ds-tier-lead';
413
+ }
414
+ return tierNode(t, kids);
415
+ });
416
+ const leftoverPanels = [...panelsById.values()].map((p) => panelNode(p, 0, rerender));
417
+
418
+ return [
419
+ heroNode(data.hero),
420
+ marqueeNode(data.marquee),
421
+ ...data.sections.map(sectionNode),
422
+ ...tierNodes,
423
+ ...leftoverPanels,
424
+ bodyNode,
425
+ footerNode(),
426
+ ].filter(Boolean);
427
+ }
351
428
 
352
- mount(document.getElementById('app'), () => C.AppShell({
429
+ mount(document.getElementById('app'), (rerender) => C.AppShell({
353
430
  topbar: C.Topbar({ brand: data.siteName, items: data.navItems, active: data.title }),
354
431
  crumb: C.Crumb({ leaf: data.title }),
355
432
  side: sideNode(data.sidebar),
356
- main: h('div', { class: 'app-stage' }, ...mainChildren),
433
+ main: h('div', { class: 'app-stage' }, ...buildMainChildren(rerender)),
357
434
  status: C.Status({
358
435
  left: data.statusLeft || [data.siteName.toLowerCase(), data.slug],
359
436
  right: data.statusRight || ['live'],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anentrypoint-design",
3
- "version": "0.0.477",
3
+ "version": "0.0.480",
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",
@@ -15,7 +15,9 @@
15
15
  // audioQueueItems, audioQueueCurrentId, audioQueuePaused,
16
16
  // showAuthModal, settingsOpen, voiceSettingsOpen, replyTarget,
17
17
  // mobileMenuOpen, // drives the .ca-rail off-canvas drawer on narrow shells
18
- // canManage // gates the rail's "+ create channel" affordance
18
+ // canManage, // gates the rail's "+ create channel" affordance
19
+ // forumPosts, // ch.type==='forum': [{id,title,snippet,author,time,replyCount,tags?}]
20
+ // pageHtml, pageAuthor, pageUpdatedAt // ch.type==='page': PageView content + attribution
19
21
  // }
20
22
  // adapter.subscribe(cb) -> unsubscribe // cb fires when any snapshot field changes
21
23
  // adapter.actions = {
@@ -223,7 +225,7 @@ export function mountCommunityApp(root, adapter = {}) {
223
225
  const inVoiceChannel = ch.type === 'voice';
224
226
  const bodyMain = inVoiceChannel ? voiceView(s)
225
227
  : ch.type === 'forum' ? ForumView({ posts: s.forumPosts || [], onSelect: (id) => A.openThread && A.openThread(id), onNewPost: () => A.newForumPost && A.newForumPost() })
226
- : ch.type === 'page' ? PageView({ title: ch.name, html: s.pageHtml || '', isAdmin: !!s.canManage, onEdit: () => A.editPage && A.editPage() })
228
+ : ch.type === 'page' ? PageView({ title: ch.name, html: s.pageHtml || '', author: s.pageAuthor || '', updatedAt: s.pageUpdatedAt || 0, isAdmin: !!s.canManage, onEdit: () => A.editPage && A.editPage() })
227
229
  : chatView(s);
228
230
  const showVoiceBanner = s.voiceConnected && s.voiceChannelName && !(inVoiceChannel && s.voiceChannelName === ch.name);
229
231
  return h('div', { class: 'ca-app' },
@@ -4,7 +4,7 @@
4
4
 
5
5
  import * as webjsx from '../../../vendor/webjsx/index.js';
6
6
  import { Icon } from '../shell.js';
7
- import { Avatar, avatarInitial } from '../content.js';
7
+ import { Avatar, avatarInitial, avatarContrastFg } from '../content.js';
8
8
  import { UserPanel } from './presence.js';
9
9
  const h = webjsx.createElement;
10
10
 
@@ -97,7 +97,7 @@ export function ChannelItem({ id, name, type = 'text', active, voiceActive, voic
97
97
  ),
98
98
  voiceActive && participants.length ? h('div', { class: 'cm-ch-voice-users' },
99
99
  ...participants.map(p => h('div', { class: 'cm-ch-voice-user' + (p.speaking ? ' speaking' : '') },
100
- h('div', { class: 'cm-ch-voice-user-avatar', style: p.color ? `--avatar-bg:${p.color}` : null }, avatarInitial(p.identity)),
100
+ h('div', { class: 'cm-ch-voice-user-avatar', style: p.color ? (avatarContrastFg(p.color) ? `--avatar-bg:${p.color};--avatar-fg:${avatarContrastFg(p.color)}` : `--avatar-bg:${p.color}`) : null }, avatarInitial(p.identity)),
101
101
  h('span', { class: 'cm-ch-voice-user-name' }, p.identity)
102
102
  ))
103
103
  ) : null
@@ -4,13 +4,19 @@
4
4
 
5
5
  import * as webjsx from '../../../vendor/webjsx/index.js';
6
6
  import { Icon } from '../shell.js';
7
- import { avatarInitial } from '../content.js';
7
+ import { avatarInitial, avatarContrastFg } from '../content.js';
8
8
  const h = webjsx.createElement;
9
9
 
10
+ function avatarStyle(color) {
11
+ if (!color) return null;
12
+ const fg = avatarContrastFg(color);
13
+ return fg ? `--avatar-bg:${color};--avatar-fg:${fg}` : `--avatar-bg:${color}`;
14
+ }
15
+
10
16
  export function VoiceUser({ identity, speaking, color } = {}) {
11
17
  const initial = avatarInitial(identity);
12
18
  return h('div', { class: 'cm-voice-user' + (speaking ? ' speaking' : '') },
13
- h('div', { class: 'cm-voice-user-avatar', style: color ? `--avatar-bg:${color}` : null }, initial),
19
+ h('div', { class: 'cm-voice-user-avatar', style: avatarStyle(color) }, initial),
14
20
  h('span', { class: 'cm-voice-user-name' }, identity)
15
21
  );
16
22
  }
@@ -29,7 +35,7 @@ export function UserPanel({ name, tag, color, muted, deafened, onMute, onDeafen,
29
35
  }
30
36
  };
31
37
  return h('div', { class: 'cm-user-panel' },
32
- h('div', { class: 'cm-user-avatar', style: color ? `--avatar-bg:${color}` : null },
38
+ h('div', { class: 'cm-user-avatar', style: avatarStyle(color) },
33
39
  h('span', { class: 'cm-user-status-dot' }),
34
40
  initial
35
41
  ),
@@ -48,7 +54,7 @@ export function UserPanel({ name, tag, color, muted, deafened, onMute, onDeafen,
48
54
  export function MemberItem({ identity, name, color, status = 'online' } = {}) {
49
55
  const initial = avatarInitial(name || identity);
50
56
  return h('div', { class: 'cm-member-item' },
51
- h('div', { class: 'cm-member-avatar', style: color ? `--avatar-bg:${color}` : null },
57
+ h('div', { class: 'cm-member-avatar', style: avatarStyle(color) },
52
58
  h('span', { class: 'cm-member-status' + (status === 'online' ? ' online' : '') }),
53
59
  initial
54
60
  ),
@@ -111,10 +111,16 @@ export function ForumView({ posts = [], onSearch, onSort, onSelect, onNewPost, l
111
111
  );
112
112
  }
113
113
 
114
- export function PageView({ title = '', html = '', isAdmin = false, onEdit } = {}) {
114
+ export function PageView({ title = '', html = '', author = '', updatedAt = 0, isAdmin = false, onEdit } = {}) {
115
115
  return h('div', { class: 'cm-page', role: 'document' },
116
116
  h('div', { class: 'cm-page-head' },
117
- h('h1', { class: 'cm-page-title' }, title || ''),
117
+ h('div', { class: 'cm-page-head-title' },
118
+ h('h1', { class: 'cm-page-title' }, title || ''),
119
+ (author || updatedAt) ? h('div', { class: 'cm-page-meta' },
120
+ author ? h('span', { class: 'cm-page-author' }, author) : null,
121
+ updatedAt ? h('span', { class: 'cm-page-time' }, fmtRelTime(updatedAt)) : null
122
+ ) : null
123
+ ),
118
124
  isAdmin && onEdit ? h('button', { type: 'button', class: 'cm-page-edit', onclick: onEdit }, 'Edit') : null
119
125
  ),
120
126
  h('div', {
@@ -16,6 +16,36 @@ export function avatarInitial(name, count = 1) {
16
16
  return name ? String(name).trim().slice(0, count).toUpperCase() || '?' : '?';
17
17
  }
18
18
 
19
+ // avatarContrastFg — picks black or white text against an arbitrary
20
+ // per-user hashed --avatar-bg color, via WCAG relative luminance, so
21
+ // initials clear 4.5:1 regardless of which hue the hash lands on (a
22
+ // single fixed --fg-2 token can't: some hashed hues are light enough
23
+ // that dark-on-dark or light-on-light both existed in the wild).
24
+ // Returns null for an unparsable color so callers can omit the
25
+ // inline style entirely and fall back to the CSS default.
26
+ export function avatarContrastFg(color) {
27
+ if (!color) return null;
28
+ let r, g, b;
29
+ const hex = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(String(color).trim());
30
+ if (hex) {
31
+ let h = hex[1];
32
+ if (h.length === 3) h = h.split('').map(c => c + c).join('');
33
+ r = parseInt(h.slice(0, 2), 16); g = parseInt(h.slice(2, 4), 16); b = parseInt(h.slice(4, 6), 16);
34
+ } else {
35
+ const rgb = /^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/i.exec(String(color).trim());
36
+ if (!rgb) return null;
37
+ r = +rgb[1]; g = +rgb[2]; b = +rgb[3];
38
+ }
39
+ const lin = (c) => { const v = c / 255; return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4); };
40
+ const L = 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);
41
+ // Contrast against pure black/white; pick whichever side clears more
42
+ // headroom (both landing >=4.5:1 is common, but some hues only clear
43
+ // one side, so max-headroom is the right tiebreak, not a 0.5 cutoff).
44
+ const contrastWhite = 1.05 / (L + 0.05);
45
+ const contrastBlack = (L + 0.05) / 0.05;
46
+ return contrastWhite >= contrastBlack ? '#fff' : '#000';
47
+ }
48
+
19
49
  // Avatar — the single letter-fallback/image avatar primitive. `initialsCount`
20
50
  // (default 1) controls how many leading characters of `name` become the
21
51
  // fallback letters when no `src`/`fallback` is given (community.js's
@@ -99,13 +99,20 @@ export function Marquee({ items = [], sep = '/' }) {
99
99
  if (!items.length) return null;
100
100
  // Two identical runs make the -50% translate loop seamless. Each text and
101
101
  // separator is a keyed span so webjsx applyDiff never sees a primitive
102
- // sibling beside a keyed VElement.
102
+ // sibling beside a keyed VElement. Run 'a' is the real, assistive-tech-
103
+ // visible content; run 'b' is a purely visual duplicate for the seamless
104
+ // loop and must not be exposed to screen readers as doubled text, so it
105
+ // is wrapped in its own aria-hidden container (standard seamless-marquee
106
+ // technique).
103
107
  const run = (runKey) => items.flatMap((it, i) => [
104
108
  h('span', { class: 'ds-marquee-item', key: `${runKey}-i${i}` }, it),
105
109
  h('span', { class: 'ds-marquee-sep', key: `${runKey}-s${i}`, 'aria-hidden': 'true' }, sep),
106
110
  ]);
107
111
  return h('div', { class: 'ds-marquee', role: 'marquee' },
108
- h('div', { class: 'ds-marquee-track' }, ...run('a'), ...run('b'))
112
+ h('div', { class: 'ds-marquee-track' },
113
+ h('span', { class: 'ds-marquee-run ds-marquee-run-a' }, ...run('a')),
114
+ h('span', { class: 'ds-marquee-run ds-marquee-run-b', 'aria-hidden': 'true' }, ...run('b')),
115
+ )
109
116
  );
110
117
  }
111
118
 
@@ -6,7 +6,7 @@
6
6
  // submodule under ./content/, and the public export surface here is unchanged
7
7
  // — no consumer import needs to move.
8
8
 
9
- import { avatarInitial, Avatar } from './content/avatar.js';
9
+ import { avatarInitial, avatarContrastFg, Avatar } from './content/avatar.js';
10
10
  import { Row, RowLink } from './content/row.js';
11
11
  import { Panel, Card, PanelFromItems, Section, Receipt, Changelog } from './content/panel.js';
12
12
  import { Hero, HeroFromPageData, Marquee, Manifesto, PageHeader } from './content/hero.js';
@@ -20,7 +20,7 @@ import { Spinner, Skeleton, Alert, FilterPills } from './content/feedback.js';
20
20
  import { HomeView, ProjectView } from './content/views.js';
21
21
 
22
22
  export {
23
- avatarInitial, Avatar,
23
+ avatarInitial, avatarContrastFg, Avatar,
24
24
  Row, RowLink,
25
25
  Panel, Card, PanelFromItems, Section, Receipt, Changelog,
26
26
  Hero, HeroFromPageData, Marquee, Manifesto, PageHeader,
@@ -90,7 +90,7 @@ export const themePage = makePage((ctx) => {
90
90
  return [
91
91
  PageHeader({ title: 'theme', lede: 'theme preference' }),
92
92
  section('current', Table({ headers: ['setting', 'value'], rows: [
93
- ['theme', (typeof window !== 'undefined' && document.body.getAttribute('data-theme')) || 'github-dark'],
93
+ ['theme', (typeof window !== 'undefined' && document.documentElement.getAttribute('data-theme')) || 'github-dark'],
94
94
  ['density', (typeof window !== 'undefined' && document.documentElement.getAttribute('data-density')) || 'compact'],
95
95
  ['accent', (typeof window !== 'undefined' && document.documentElement.getAttribute('data-accent')) || 'default'],
96
96
  ] })),
@@ -44,12 +44,18 @@ const ALL_EMOJI = EMOJI_CATEGORIES.flatMap((c) => c.emoji);
44
44
  export function EmojiPicker({ open, anchorX = 0, anchorY = 0, onSelect, onClose, query = '' } = {}) {
45
45
  if (!open) return null;
46
46
  let cat = EMOJI_CATEGORIES[0].id;
47
- let rootEl = null, gridEl = null;
47
+ let rootEl = null, gridEl = null, searchEl = null;
48
+ // Internal search state, seeded from the `query` prop so a consumer that
49
+ // already knows the typed ':smile' trigger text (e.g. a composer keydown
50
+ // handler) can still pre-fill it — but typing in the picker's own input
51
+ // (added here since no consumer previously had anywhere to route text
52
+ // into `query`) is the primary path now.
53
+ let search = query || '';
48
54
  const close = () => onClose && onClose();
49
55
 
50
56
  const renderGrid = () => {
51
57
  if (!gridEl) return;
52
- const q = (query || '').trim().toLowerCase();
58
+ const q = (search || '').trim().toLowerCase();
53
59
  const cells = q
54
60
  ? ALL_EMOJI.filter(([, name]) => name.toLowerCase().includes(q))
55
61
  : (EMOJI_CATEGORIES.find(x => x.id === cat) || EMOJI_CATEGORIES[0]).emoji;
@@ -82,7 +88,13 @@ export function EmojiPicker({ open, anchorX = 0, anchorY = 0, onSelect, onClose,
82
88
  el._ovEmojiCleanup = _anchoredOverlayLifecycle(el, { anchorX, anchorY, fallbackW: 260, fallbackH: 240, close });
83
89
  },
84
90
  },
85
- (query || '').trim() ? null : h('div', { class: 'ov-emoji-tabs', role: 'tablist' },
91
+ h('input', {
92
+ type: 'search', class: 'ov-emoji-search', placeholder: 'Search emoji…',
93
+ 'aria-label': 'search emoji', value: search,
94
+ ref: (el) => { searchEl = el; },
95
+ oninput: (e) => { search = e.target.value; renderGrid(); },
96
+ }),
97
+ (search || '').trim() ? null : h('div', { class: 'ov-emoji-tabs', role: 'tablist' },
86
98
  ...EMOJI_CATEGORIES.map((c) => h('button', {
87
99
  type: 'button', class: 'ov-emoji-tab', role: 'tab',
88
100
  'aria-selected': c.id === cat ? 'true' : 'false',
@@ -7,9 +7,14 @@ import * as webjsx from '../../../vendor/webjsx/index.js';
7
7
  import { trapTab } from '../overlay-primitives.js';
8
8
  import { Brand, Glyph } from './atoms.js';
9
9
  import { Icon } from './icons.js';
10
+ import { ThemeToggle } from '../theme-toggle.js';
10
11
  const h = webjsx.createElement;
11
12
 
12
- export function Topbar({ brand = '247420', leaf = '', items = [], active = '', onNav, search } = {}) {
13
+ // `themeToggle` defaults on: every Topbar consumer (homepage included) gets a
14
+ // real, working light/dark/auto control in the shell chrome instead of each
15
+ // call site having to remember to wire ThemeToggle() in by hand. Pass
16
+ // `themeToggle: false` to opt a shell out (e.g. a kit that renders its own).
17
+ export function Topbar({ brand = '247420', leaf = '', items = [], active = '', onNav, search, themeToggle = true } = {}) {
13
18
  return h('header', { class: 'app-topbar', role: 'banner' },
14
19
  Brand({ name: brand, leaf }),
15
20
  search ? h('label', { class: 'app-search' },
@@ -50,7 +55,8 @@ export function Topbar({ brand = '247420', leaf = '', items = [], active = '', o
50
55
  }
51
56
  }
52
57
  }, label);
53
- }))
58
+ })),
59
+ themeToggle ? h('div', { class: 'app-topbar-theme' }, ThemeToggle({ compact: true })) : null
54
60
  );
55
61
  }
56
62
 
@@ -208,9 +214,14 @@ export function AppShell({ topbar, crumb, side, main, status, narrow } = {}) {
208
214
  // brand + nav (topbar) and breadcrumb + right slot (crumb) share a single
209
215
  // band so the chrome reads as one bar, not two. Either prop alone still
210
216
  // renders on its own (consumers that pass only a topbar are unaffected).
217
+ // Landmark wrapper: topbar/crumb are opaque children with no semantics of
218
+ // their own, so screen-reader users landmark-navigating a page saw no
219
+ // "banner"/"navigation" region at all above <main>. <header role="banner">
220
+ // names the brand+nav band; the app-side-shell aside below already exists
221
+ // as a real <aside>, so this closes the missing landmark for the top band.
211
222
  const chrome = (topbar && crumb)
212
- ? h('div', { class: 'app-chrome' }, topbar, crumb)
213
- : (topbar || crumb || null);
223
+ ? h('header', { class: 'app-chrome', role: 'banner' }, topbar, crumb)
224
+ : (topbar || crumb) ? h('header', { class: 'app-chrome', role: 'banner' }, topbar || crumb) : null;
214
225
  return h('div', { class: 'app', ref: syncAppSide },
215
226
  h('a', { href: '#app-main', class: 'skip-link' }, 'skip to main content'),
216
227
  hasSide ? h('button', {
@@ -173,7 +173,7 @@ export function WorkspaceRail({ brand = '247420', action, items = [], footer } =
173
173
  it.icon ? Icon(it.icon) : h('span', { class: 'ws-rail-item-glyph', 'aria-hidden': 'true' }),
174
174
  h('span', { class: 'ws-rail-item-label' }, it.label),
175
175
  (it.count != null && it.count !== 0 && it.count !== '0')
176
- ? h('span', { class: 'ws-rail-item-count', 'aria-hidden': 'true' }, String(it.count))
176
+ ? h('span', { class: 'ws-rail-item-count ds-badge ds-badge--sm', 'aria-hidden': 'true' }, String(it.count))
177
177
  : null,
178
178
  it.rail ? h('span', { class: 'ws-rail-item-flag tone-' + it.rail, 'aria-hidden': 'true' }) : null)))),
179
179
  footer ? h('div', { class: 'ws-rail-foot' }, footer) : null,