anentrypoint-design 0.0.167 → 0.0.169

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anentrypoint-design",
3
- "version": "0.0.167",
3
+ "version": "0.0.169",
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",
@@ -11,6 +11,12 @@
11
11
  "default": "./dist/247420.js"
12
12
  },
13
13
  "./css": "./dist/247420.css",
14
+ "./kits/spoint": {
15
+ "import": "./src/kits/spoint/index.js",
16
+ "default": "./src/kits/spoint/index.js"
17
+ },
18
+ "./kits/spoint/loading-screen.js": "./src/kits/spoint/loading-screen.js",
19
+ "./kits/spoint/loading-screen.css": "./src/kits/spoint/loading-screen.css",
14
20
  "./kits/os": {
15
21
  "import": "./src/kits/os/index.js",
16
22
  "default": "./src/kits/os/index.js"
@@ -56,8 +62,8 @@
56
62
  "README.md"
57
63
  ],
58
64
  "scripts": {
59
- "build": "node scripts/build.mjs",
60
- "test": "node test.js"
65
+ "lint:tokens": "node scripts/lint-tokens.mjs",
66
+ "build": "node scripts/build.mjs"
61
67
  },
62
68
  "repository": {
63
69
  "type": "git",
@@ -186,7 +186,7 @@ export function mountCommunityApp(root, adapter = {}) {
186
186
  h('nav', {},
187
187
  h('a', { href: '../', title: 'Home', onclick: (e) => { if (A.goHome) { e.preventDefault(); A.goHome(); } } }, 'home'),
188
188
  h('a', { href: '#', title: 'Servers', onclick: (e) => { e.preventDefault(); A.openServers && A.openServers(); } }, 'servers'),
189
- h('a', { href: 'https://github.com/AnEntrypoint/zellous', target: '_blank', rel: 'noopener' }, 'source '),
189
+ h('a', { href: 'https://github.com/AnEntrypoint/zellous', target: '_blank', rel: 'noopener' }, 'source ->'),
190
190
  ),
191
191
  ),
192
192
  MobileHeader({ channelType: ch.type || 'text', channelName: ch.name || '', onMenu: () => A.openMobileMenu && A.openMobileMenu(), onMembers: () => A.toggleMembers && A.toggleMembers() }),
@@ -82,7 +82,7 @@ const PART_RENDERERS = {
82
82
  h('span', { class: 'glyph', 'aria-hidden': 'true' }, '▤'),
83
83
  h('span', { class: 'name' }, p.name || 'document.pdf'),
84
84
  p.size != null ? h('span', { class: 'size' }, fmtBytes(p.size)) : null,
85
- h('a', { class: 'open', href: p.src, target: '_blank', rel: 'noopener', 'aria-label': `open PDF: ${p.name || 'document.pdf'}` }, 'open ')
85
+ h('a', { class: 'open', href: p.src, target: '_blank', rel: 'noopener', 'aria-label': `open PDF: ${p.name || 'document.pdf'}` }, 'open ->')
86
86
  ),
87
87
  h('embed', { src: p.src, type: 'application/pdf', 'aria-label': `PDF document: ${p.name || 'document.pdf'}` })),
88
88
  file: (p) => h('a', { class: 'chat-file', href: p.src, target: '_blank', rel: 'noopener', download: p.name || true, 'aria-label': `download file: ${p.name || 'attachment'} (${p.kindLabel || (p.name || '').split('.').pop().toUpperCase()})` },
@@ -109,7 +109,7 @@ export function WorksList({ works = [], openedIndex = -1, onToggle }) {
109
109
  Row({
110
110
  code: w.code,
111
111
  title: w.title, sub: w.sub,
112
- meta: w.meta + ' ' + (isOpen ? '' : '+'),
112
+ meta: w.meta + ' ' + (isOpen ? '-' : '+'),
113
113
  active: isOpen,
114
114
  onClick: () => onToggle && onToggle(isOpen ? -1 : i)
115
115
  }),
@@ -118,7 +118,7 @@ export function WorksList({ works = [], openedIndex = -1, onToggle }) {
118
118
  h('p', { class: 'ds-work-body' }, w.body)
119
119
  ),
120
120
  h('div', { class: 'ds-work-actions' },
121
- Btn({ primary: true, href: w.href || '#', children: 'open ' }),
121
+ Btn({ primary: true, href: w.href || '#', children: 'open ->' }),
122
122
  Btn({ href: w.source || '#', children: 'source' })
123
123
  )
124
124
  ) : null
@@ -147,7 +147,7 @@ export const voice = makePage((ctx) => {
147
147
  PageHeader({ eyebrow: 'freddie', title: 'voice', lede: 'voice surfaces', right: enabled ? Chip({ tone: 'ok', children: 'enabled' }) : Chip({ tone: 'neutral', children: 'not configured' }) }),
148
148
  enabled
149
149
  ? section('backends', Table({ headers: ['capability', 'status'], rows: [['transcription', v.transcription ? Chip({ tone: 'ok', children: 'on' }) : Chip({ tone: 'neutral', children: 'off' })], ['tts', v.tts ? Chip({ tone: 'ok', children: 'on' }) : Chip({ tone: 'neutral', children: 'off' })]] }))
150
- : section('status', emptyState('no voice backend wired in this build. configure a transcription/tts plugin to enable.', '🎙')),
150
+ : section('status', emptyState('no voice backend wired in this build. configure a transcription/tts plugin to enable.')),
151
151
  ];
152
152
  };
153
153
  });
@@ -405,7 +405,7 @@ export const config = makePage((ctx) => {
405
405
  }
406
406
  async function setSkin(name) {
407
407
  ctx.set({ busy: true, note: null });
408
- try { await api('/api/config', { method: 'POST', body: { skin: name } }); await load(); ctx.set({ note: { kind: 'success', msg: 'skin ' + name } }); }
408
+ try { await api('/api/config', { method: 'POST', body: { skin: name } }); await load(); ctx.set({ note: { kind: 'success', msg: 'skin -> ' + name } }); }
409
409
  catch (e) { ctx.set({ note: { kind: 'error', msg: String(e.message || e) } }); }
410
410
  ctx.set({ busy: false });
411
411
  }
@@ -574,7 +574,7 @@ export const chains = makePage((ctx) => {
574
574
  PageHeader({ eyebrow: 'freddie', title: 'chains', lede: 'acptoapi fallback chains', right: up ? Chip({ tone: 'ok', children: 'acptoapi up' }) : Chip({ tone: 'miss', children: 'acptoapi down' }) }),
575
575
  noteAlert(s.note),
576
576
  section('chains', Array.isArray(chainsList) && chainsList.length ? chainsList.map((c, i) => Row({
577
- key: i, title: c.name || c, sub: Array.isArray(c.links) ? c.links.join(' ') : '',
577
+ key: i, title: c.name || c, sub: Array.isArray(c.links) ? c.links.join(' -> ') : '',
578
578
  trailing: Btn({ danger: true, children: 'delete', onClick: () => del(c.name || c) }),
579
579
  })) : emptyState('no chains defined')),
580
580
  section('new chain',
@@ -117,7 +117,7 @@ export function Topbar({ brand = '247420', leaf = '', items = [], active = '', o
117
117
  h('input', { type: 'search', placeholder: search, 'aria-label': `search ${search}` })
118
118
  ) : null,
119
119
  h('nav', { 'aria-label': 'main navigation' }, ...items.map(([label, href]) => {
120
- const cleanLabel = String(label).replace(' ', '');
120
+ const cleanLabel = String(label).replace(' ->', '');
121
121
  return h('a', {
122
122
  key: label,
123
123
  href,
@@ -181,7 +181,7 @@ export function Status({ left = [], right = [] } = {}) {
181
181
  }
182
182
 
183
183
  // Toggle the mobile sidebar drawer. Pure-DOM because AppShell is stateless
184
- // chrome; the class lives on .app-body and is read by the 900px media query.
184
+ // chrome; the class lives on .app-body and is read by the <=900px media query.
185
185
  function toggleSide(open) {
186
186
  const body = document.querySelector('.app-body');
187
187
  if (!body) return;
package/src/index.js CHANGED
@@ -74,6 +74,9 @@ export { applyTheme, getTheme, resolvedTheme, onThemeChange, initTheme,
74
74
  export const h = webjsx.createElement;
75
75
  export const applyDiff = webjsx.applyDiff;
76
76
 
77
+ // spoint kit paint surfaces (loading screen, HUD, editor chrome).
78
+ export { renderLoadingScreen } from './kits/spoint/loading-screen.js';
79
+
77
80
  // Re-export freddie helpers so consumers can `import { FREDDIE_PAGES } from
78
81
  // 'anentrypoint-design'` directly.
79
82
  export {
@@ -51,9 +51,9 @@ export function makeCorePages(ctx) {
51
51
  ['open chat', "click 'chat' in sidebar — set a working directory and pick a skill"],
52
52
  ['pick skill', "software dev, research, planning — shown with descriptions"],
53
53
  ['pick model', "select a configured provider + model in the chat bar"],
54
- ['list tools', '/tools in chat tools tab'],
55
- ['set api key', 'keys tab click chip to set value'],
56
- ['add cron', 'cron tab form'],
54
+ ['list tools', '/tools in chat -> tools tab'],
55
+ ['set api key', 'keys tab -> click chip to set value'],
56
+ ['add cron', 'cron tab -> form'],
57
57
  ] }) }),
58
58
  Panel({ title: 'host', children: Receipt({ rows: Object.entries(health).map(([k, v]) => [k, String(v)]) }) }),
59
59
  ];
@@ -43,7 +43,7 @@ export function makeOsPages(ctx) {
43
43
  return [
44
44
  Kpi({ items: [[list.length, 'paths'], [instance.id, 'instance']] }),
45
45
  Panel({ title: 'paths', count: list.length, children: list.length === 0
46
- ? EmptyState({ text: 'empty fs', glyph: '📁' })
46
+ ? EmptyState({ text: 'empty fs', glyph: '' })
47
47
  : pre(list.join('\n')) }),
48
48
  ];
49
49
  },
@@ -115,7 +115,7 @@ export function makeToolsPages(ctx) {
115
115
  if (typeof h0.pi.env.set === 'function') { h0.pi.env.set(k.key, v); rerender(); }
116
116
  },
117
117
  },
118
- Chip({ tone: k.set ? 'ok' : 'miss', children: k.key + (k.set ? ' ' : ' ·') })
118
+ Chip({ tone: k.set ? 'ok' : 'miss', children: k.key + (k.set ? ' [x]' : ' [ ]') })
119
119
  ));
120
120
  return [
121
121
  Kpi({ items: [[setCount, 'set'], [list.length - setCount, 'missing'], [list.length, 'total known']] }),
@@ -20,5 +20,5 @@ export const OS_ROUTE_DEFS = [
20
20
  { path: 'os-instances', label: 'instances', glyph: '◫' },
21
21
  { path: 'os-windows', label: 'windows', glyph: '▭' },
22
22
  { path: 'os-x', label: 'x-server', glyph: '✕' },
23
- { path: 'os-fs', label: 'fs', glyph: '📁' },
23
+ { path: 'os-fs', label: 'fs', glyph: '' },
24
24
  ];
@@ -443,9 +443,9 @@ html, body {
443
443
  * --os-* aliases above consume the canonical semantic tokens (--bg/--fg/
444
444
  * --accent), and colors_and_type.css already drives [data-theme] (paper/ink/
445
445
  * auto, with color-scheme + --danger tuning) and [data-accent] (green/purple/
446
- * mascot). Re-declaring --os-bg-*/--os-accent per theme here only re-introduced
447
- * drift (ink #14131a vs canonical #131318; purple #7c5cff vs canonical
448
- * #420247). Density bar-height is genuinely OS-shell-specific, so it stays;
446
+ * mascot). Re-declaring the os surface or accent tokens per theme here only
447
+ * re-introduced drift (ink vs canonical ink; purple vs canonical purple).
448
+ * Density bar-height is genuinely OS-shell-specific, so it stays;
449
449
  * --os-accent-tint maps to the canonical accent tint. */
450
450
  :root { --os-accent-tint: var(--accent-tint); }
451
451
  :root[data-density="compact"] { --os-bar-h: 28px; --os-tile-pad: 6px; --os-gap: 6px; }
@@ -463,18 +463,11 @@ html, body {
463
463
  * silently mutated the base theme for EVERY portfolio consumer (canonical
464
464
  * --paper is #F6F5F1; thebird wanted #F5F0E4). The warm-paper variant is now a
465
465
  * named preset [data-theme="thebird"] a consumer opts into, leaving the base
466
- * theme untouched. Typography + bar geometry below are brand chrome, not
467
- * color, so they stay scoped to .ds-247420.
466
+ * theme untouched. The [data-theme="thebird"] COLOR preset now lives in the
467
+ * canonical colors_and_type.css (a first-class swappable theme); only the
468
+ * OS-shell chrome (bar geometry, typography alias) stays here scoped to
469
+ * .ds-247420.
468
470
  * ============================================================ */
469
- [data-theme="thebird"] {
470
- color-scheme: light;
471
- --paper: #F5F0E4;
472
- --paper-2: #EFE9DB;
473
- --paper-3: #E3DAC7;
474
- --bg: var(--paper); --bg-2: var(--paper-2); --bg-3: var(--paper-3);
475
- --fg: var(--ink); --fg-2: var(--ink-2); --fg-3: var(--ink-3);
476
- --accent: var(--green); --accent-fg: var(--paper);
477
- }
478
471
  .ds-247420 {
479
472
  --ff-ui: var(--ff-body);
480
473
  /* uniform bar height: 22px button + 6px+6px padding = 34px */
@@ -0,0 +1,8 @@
1
+ // spoint kit — paint surfaces for the spoint game client (loading screen,
2
+ // HUD, editor chrome). Each render fn returns {node, ...controls, dispose};
3
+ // the consumer owns state/events, this kit owns layout + classes. Surfaced
4
+ // through the main bundle entry so spoint can import directly from unpkg.
5
+
6
+ export { renderLoadingScreen } from './loading-screen.js';
7
+
8
+ export const themeUrl = new URL('./loading-screen.css', import.meta.url).href;
@@ -0,0 +1,67 @@
1
+ /* Loading-screen kit styles. Scoped under .ds-247420 at build time.
2
+ All colors reference design tokens (no raw literals) per the token-lint gate. */
3
+ .sp-loading {
4
+ position: fixed;
5
+ inset: 0;
6
+ z-index: 10000;
7
+ display: flex;
8
+ align-items: center;
9
+ justify-content: center;
10
+ background: var(--bg);
11
+ color: var(--fg);
12
+ font-family: var(--ff-mono);
13
+ transition: opacity var(--dur-slow) var(--ease);
14
+ }
15
+ .sp-loading-fade { opacity: 0; pointer-events: none; }
16
+ .sp-loading-container {
17
+ width: min(420px, 86vw);
18
+ display: flex;
19
+ flex-direction: column;
20
+ gap: 20px;
21
+ }
22
+ .sp-loading-header h1 {
23
+ margin: 0;
24
+ font-family: var(--ff-display);
25
+ font-size: var(--fs-h2);
26
+ letter-spacing: 0.04em;
27
+ }
28
+ .sp-loading-label {
29
+ margin: 4px 0 0;
30
+ color: var(--fg-2);
31
+ font-size: var(--fs-body);
32
+ }
33
+ .sp-loading-bars { display: flex; flex-direction: column; gap: 10px; }
34
+ .sp-loading-bar-row {
35
+ display: grid;
36
+ grid-template-columns: 88px 1fr 44px;
37
+ align-items: center;
38
+ gap: 10px;
39
+ }
40
+ .sp-loading-bar-name {
41
+ color: var(--fg-3);
42
+ font-size: var(--fs-small, 11px);
43
+ text-transform: uppercase;
44
+ letter-spacing: 0.1em;
45
+ }
46
+ .sp-loading-bar-track {
47
+ height: 6px;
48
+ border-radius: 3px;
49
+ background: var(--bg-3);
50
+ overflow: hidden;
51
+ }
52
+ .sp-loading-bar-fill {
53
+ height: 100%;
54
+ width: 0%;
55
+ background: var(--accent);
56
+ transition: width var(--dur-base) var(--ease);
57
+ }
58
+ .sp-loading-bar-pct {
59
+ color: var(--fg-2);
60
+ font-size: var(--fs-small, 11px);
61
+ text-align: right;
62
+ }
63
+ .sp-loading-detail {
64
+ min-height: 16px;
65
+ color: var(--fg-3);
66
+ font-size: var(--fs-small, 11px);
67
+ }
@@ -0,0 +1,75 @@
1
+ // Loading-screen paint surface for the spoint game client.
2
+ // renderLoadingScreen({brand, label}) -> { node, setLabel, setDownload,
3
+ // setProcessing, setDetail, hide, dispose }. The consumer (spoint
4
+ // LoadingManager) owns progress events; this module owns layout + classes.
5
+ // Two progress bars (download, processing), a label, and a detail line.
6
+
7
+ export function renderLoadingScreen(opts = {}) {
8
+ const { brand = 'spoint', label = 'Connecting...' } = opts;
9
+
10
+ const node = document.createElement('div');
11
+ node.className = 'sp-loading ds-247420';
12
+ node.dataset.component = 'loading-screen';
13
+
14
+ const container = document.createElement('div');
15
+ container.className = 'sp-loading-container';
16
+
17
+ const header = document.createElement('div');
18
+ header.className = 'sp-loading-header';
19
+ const title = document.createElement('h1');
20
+ title.textContent = brand;
21
+ const labelEl = document.createElement('p');
22
+ labelEl.className = 'sp-loading-label';
23
+ labelEl.textContent = label;
24
+ header.append(title, labelEl);
25
+
26
+ const bars = document.createElement('div');
27
+ bars.className = 'sp-loading-bars';
28
+
29
+ const mkBar = (name) => {
30
+ const row = document.createElement('div');
31
+ row.className = 'sp-loading-bar-row';
32
+ const nameEl = document.createElement('span');
33
+ nameEl.className = 'sp-loading-bar-name';
34
+ nameEl.textContent = name;
35
+ const track = document.createElement('div');
36
+ track.className = 'sp-loading-bar-track';
37
+ const fill = document.createElement('div');
38
+ fill.className = 'sp-loading-bar-fill';
39
+ track.appendChild(fill);
40
+ const pct = document.createElement('span');
41
+ pct.className = 'sp-loading-bar-pct';
42
+ pct.textContent = '0%';
43
+ row.append(nameEl, track, pct);
44
+ return { row, fill, pct };
45
+ };
46
+
47
+ const dl = mkBar('Download');
48
+ const proc = mkBar('Processing');
49
+ bars.append(dl.row, proc.row);
50
+
51
+ const detail = document.createElement('div');
52
+ detail.className = 'sp-loading-detail';
53
+
54
+ container.append(header, bars, detail);
55
+ node.appendChild(container);
56
+
57
+ const setBar = (bar, percent) => {
58
+ bar.fill.style.width = percent + '%';
59
+ bar.pct.textContent = Math.round(percent) + '%';
60
+ };
61
+
62
+ return {
63
+ node,
64
+ setLabel: (text) => { labelEl.textContent = text; },
65
+ setDownload: (percent) => setBar(dl, percent),
66
+ setProcessing: (percent) => setBar(proc, percent),
67
+ setDetail: (text) => { detail.textContent = text; },
68
+ hide: async () => {
69
+ node.classList.add('sp-loading-fade');
70
+ await new Promise((r) => setTimeout(r, 500));
71
+ node.remove();
72
+ },
73
+ dispose: () => node.remove(),
74
+ };
75
+ }
@@ -158,9 +158,9 @@ async function runAllTests() {
158
158
  const speedup = (10000 + initStats.initMs.markdown + initStats.initMs.prism) / (mdStats.avg * 100 + initStats.initMs.markdown + initStats.initMs.prism);
159
159
  console.log(`Performance improvement: ~${speedup.toFixed(1)}x faster`);
160
160
 
161
- console.log('\n✓ All tests completed successfully');
161
+ console.log('\nPASS All tests completed successfully');
162
162
  } catch (err) {
163
- console.error('\n✗ Test failed:', err.message);
163
+ console.error('\nFAIL Test failed:', err.message);
164
164
  console.error(err.stack);
165
165
  process.exit(1);
166
166
  }
package/src/page-html.js CHANGED
@@ -99,15 +99,32 @@ ${cssLink}
99
99
  .page-body h1 { margin-top: 0 } .page-body h2 { margin-top: var(--space-5, 32px) } .page-body h3 { margin-top: var(--space-4, 24px) }
100
100
  .page-body > * + * { margin-top: var(--space-3, 16px) }
101
101
  .page-body pre { margin: var(--space-3, 16px) 0; background: var(--panel-2); padding: var(--space-3, 16px); border-radius: var(--r-1, 10px); overflow-x: auto }
102
- /* .app-stage owns inter-block rhythm via grid gap; sections/hero must not double it */
103
- .app-stage > .ds-hero { margin: 0; padding: 0 }
104
- .app-stage > .ds-section { margin: 0 }
102
+ /* .app-stage owns inter-block rhythm via grid gap; sections/hero must not double it.
103
+ These selectors carry !important because this inline block loads before the
104
+ unpkg CSS bundle, which would otherwise win on load-order for equal specificity. */
105
+ .ds-247420 .app-stage > .ds-hero { margin: 0 !important; padding: var(--space-4, 24px) 0 0 !important; max-width: none !important; gap: var(--space-4, 24px) !important }
106
+ .ds-247420 .app-stage > .ds-section { margin: 0 !important }
105
107
  .app-stage .row + .row { margin-top: var(--space-1, 4px) }
106
108
  .app-stage .ds-section .row { margin-top: var(--space-2, 8px) }
107
109
  .app-stage .ds-section > p.ds-lede { margin: 0 0 var(--space-3, 16px); max-width: var(--measure, 68ch); color: var(--fg-2) }
108
110
  .row-benefit { font-style: italic; color: var(--fg-3); font-size: var(--fs-sm); margin-top: var(--space-1, 4px) }
109
111
  .ds-row-arrow { margin-left: auto; opacity: .5; transition: opacity var(--dur-snap, 80ms) var(--ease) }
110
112
  a.row:hover .ds-row-arrow { opacity: 1 }
113
+ /* hero stat strip — all badges as a wrapping inline rhythm, not one empty panel */
114
+ .ds-hero-stats { display: flex; flex-wrap: wrap; gap: var(--space-3, 16px) var(--space-5, 32px); margin-top: var(--space-2, 8px) }
115
+ .ds-hero-stat { display: flex; align-items: baseline; gap: var(--space-2, 8px) }
116
+ .ds-hero-stat-n { font-family: var(--ff-body); font-weight: 700; font-size: var(--fs-lg, 18px); color: var(--fg) }
117
+ .ds-hero-stat-l { font-size: var(--fs-sm, 15px); color: var(--fg-3) }
118
+ /* feature rows — single-column stack with a rail accent (the dashboard .row grid
119
+ forces a 3-col code/title/meta layout that mangles title+desc+benefit) */
120
+ .ds-feature { position: relative; padding: var(--space-3, 16px) var(--space-4, 24px); background: var(--bg, #fff); border-radius: var(--r-2, 14px); display: grid; gap: var(--space-1, 4px) }
121
+ .ds-feature::before { content: ''; position: absolute; left: 0; top: var(--space-2, 8px); bottom: var(--space-2, 8px); width: 3px; border-radius: 3px; background: var(--rail-color, var(--rule-strong)) }
122
+ .ds-feature.rail-green { --rail-color: var(--green) } .ds-feature.rail-purple { --rail-color: var(--purple) } .ds-feature.rail-mascot { --rail-color: var(--mascot) }
123
+ .ds-feature.rail-sun { --rail-color: var(--sun) } .ds-feature.rail-flame { --rail-color: var(--flame) } .ds-feature.rail-sky { --rail-color: var(--sky) }
124
+ .ds-feature + .ds-feature { margin-top: var(--space-2, 8px) }
125
+ .ds-feature-title { font-weight: 600; font-size: var(--fs-lg, 18px); color: var(--fg) }
126
+ .ds-feature-desc { font-size: var(--fs-sm, 15px); color: var(--fg-2); line-height: 1.5 }
127
+ .ds-feature-benefit { font-style: italic; font-size: var(--fs-sm, 15px); color: var(--fg-3); margin-top: var(--space-1, 4px) }
111
128
  </style>
112
129
  <script id="__site__" type="application/json">${JSON.stringify(pageData).replace(/</g, '\\u003c')}</script>
113
130
  ${headExtra}
@@ -121,24 +138,37 @@ const RAILS = ['rail-green', 'rail-purple', 'rail-mascot', 'rail-sun', 'rail-fla
121
138
 
122
139
  function heroNode(hero) {
123
140
  if (!hero) return null;
124
- return C.Hero({
125
- eyebrow: hero.eyebrow,
126
- title: hero.heading || hero.title || data.title,
127
- body: hero.body || hero.subheading || '',
128
- accent: hero.accent,
129
- badge: Array.isArray(hero.badges) && hero.badges[0] ? hero.badges[0].label : undefined,
130
- actions: Array.isArray(hero.ctas) ? hero.ctas.map((c, i) => h('a', { key: i, class: i === 0 ? 'btn btn-accent' : 'btn btn-ghost', href: c.href || '#' }, c.label || c.cta || 'go')) : null,
131
- });
141
+ const badges = Array.isArray(hero.badges) ? hero.badges.filter(Boolean) : [];
142
+ const badgeRow = badges.length
143
+ ? h('div', { class: 'ds-hero-stats' }, ...badges.map((b, i) =>
144
+ h('span', { key: i, class: 'ds-hero-stat' },
145
+ h('strong', { class: 'ds-hero-stat-n' }, String(b.label != null ? b.label : b)),
146
+ b.desc ? h('span', { class: 'ds-hero-stat-l' }, String(b.desc)) : null,
147
+ )))
148
+ : null;
149
+ return h('div', { class: 'ds-hero' },
150
+ hero.eyebrow ? h('span', { class: 'eyebrow' }, hero.eyebrow) : null,
151
+ h('h1', { class: 'ds-hero-title' }, hero.heading || hero.title || data.title),
152
+ (hero.body || hero.subheading) ? h('p', { class: 'ds-hero-body' },
153
+ hero.body || hero.subheading,
154
+ hero.accent ? h('span', { class: 'ds-hero-accent' }, ' ' + hero.accent) : null,
155
+ ) : null,
156
+ Array.isArray(hero.ctas) && hero.ctas.length
157
+ ? h('div', { class: 'ds-hero-actions' }, ...hero.ctas.map((c, i) =>
158
+ h('a', { key: i, class: i === 0 ? 'btn btn-accent' : 'btn btn-ghost', href: c.href || '#' }, c.label || c.cta || 'go')))
159
+ : null,
160
+ badgeRow,
161
+ );
132
162
  }
133
163
 
134
164
  function sectionNode(sec, idx) {
135
165
  const rail = RAILS[idx % RAILS.length];
136
166
  const features = sec.features || sec.items || [];
137
167
  const rows = features.map((f, i) => {
138
- const kids = [h('span', { key: 't', class: 'title' }, String(f.name || ''))];
139
- if (f.desc) kids.push(h('div', { key: 'd', class: 'sub', innerHTML: String(f.desc).replace(/\`([^\`]+)\`/g, '<code>$1</code>') }));
140
- if (f.benefit) kids.push(h('div', { key: 'b', class: 'row-benefit' }, String(f.benefit)));
141
- return h('div', { key: i, class: 'row ' + rail }, ...kids);
168
+ const kids = [h('div', { key: 't', class: 'ds-feature-title' }, String(f.name || ''))];
169
+ if (f.desc) kids.push(h('div', { key: 'd', class: 'ds-feature-desc', innerHTML: String(f.desc).replace(/\`([^\`]+)\`/g, '<code>$1</code>') }));
170
+ if (f.benefit) kids.push(h('div', { key: 'b', class: 'ds-feature-benefit' }, String(f.benefit)));
171
+ return h('div', { key: i, class: 'ds-feature ' + rail }, ...kids);
142
172
  });
143
173
  return C.Section({
144
174
  title: sec.name || sec.title || sec.id,
@@ -161,7 +191,7 @@ function examplesNode(examples) {
161
191
  h('span', { key: 't', class: 'title' }, String(e.label || e.name || e.href || '')),
162
192
  ];
163
193
  if (e.desc) kids.push(h('span', { key: 'm', class: 'meta dim' }, ' — ' + e.desc));
164
- kids.push(h('span', { key: 'a', class: 'ds-row-arrow' }, ''));
194
+ kids.push(h('span', { key: 'a', class: 'ds-row-arrow' }, '->'));
165
195
  return h('a', { key: i, class: 'row ' + rail, href: e.href || '#' }, ...kids);
166
196
  }),
167
197
  });
@@ -12,7 +12,7 @@ class DsChat extends HTMLElement {
12
12
  constructor() {
13
13
  super();
14
14
  this._messages = [];
15
- this._placeholder = 'type, then ';
15
+ this._placeholder = 'type, then enter';
16
16
  this._title = 'chat';
17
17
  this._sub = '';
18
18
  this._composerValue = '';