anentrypoint-design 0.0.414 → 0.0.416

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 (102) hide show
  1. package/dist/247420.js +62 -12
  2. package/dist/app-shell.css +40 -594
  3. package/dist/colors_and_type.css +607 -47
  4. package/dist/index.html +227 -277
  5. package/dist/preview/buttons.html +39 -28
  6. package/dist/preview/colors-core.html +18 -8
  7. package/dist/preview/colors-lore.html +26 -17
  8. package/dist/preview/colors-semantic.html +24 -18
  9. package/dist/preview/dateline.html +6 -5
  10. package/dist/preview/dropzone.html +4 -3
  11. package/dist/preview/file-grid.html +10 -9
  12. package/dist/preview/file-row.html +14 -13
  13. package/dist/preview/file-toolbar.html +4 -3
  14. package/dist/preview/file-viewer.html +4 -3
  15. package/dist/preview/header.html +26 -24
  16. package/dist/preview/icons-unicode.html +8 -5
  17. package/dist/preview/index-row.html +17 -9
  18. package/dist/preview/inputs.html +4 -3
  19. package/dist/preview/manifesto.html +34 -17
  20. package/dist/preview/motion-default.js +77 -57
  21. package/dist/preview/rules.html +4 -3
  22. package/dist/preview/spacing.html +13 -12
  23. package/dist/preview/stamps-lore.html +5 -4
  24. package/dist/preview/stamps.html +4 -3
  25. package/dist/preview/theme-ink.html +7 -6
  26. package/dist/preview/type-display.html +14 -5
  27. package/dist/preview/type-mono.html +6 -5
  28. package/dist/preview/type-prose.html +12 -10
  29. package/dist/preview/type-scale.html +4 -3
  30. package/dist/preview/wordmarks.html +10 -7
  31. package/dist/site/content/globals/site.yaml +5 -3
  32. package/dist/site/content/pages/freddie.yaml +2 -2
  33. package/dist/site/content/pages/home.yaml +55 -55
  34. package/dist/site/theme.mjs +96 -348
  35. package/dist/slides/index.html +13 -13
  36. package/dist/src/bootstrap.js +10 -1
  37. package/dist/src/components/chat.js +43 -195
  38. package/dist/src/components/community.js +18 -166
  39. package/dist/src/components/content.js +30 -210
  40. package/dist/src/components/files-modals.js +14 -106
  41. package/dist/src/components/files.js +15 -116
  42. package/dist/src/components/freddie/helpers.js +53 -8
  43. package/dist/src/components/freddie.js +35 -25
  44. package/dist/src/components/shell.js +16 -114
  45. package/dist/src/components/theme-toggle.js +69 -25
  46. package/dist/src/components.js +92 -13
  47. package/dist/src/deck-stage.js +6 -4
  48. package/dist/src/highlight.js +51 -7
  49. package/dist/src/index.js +58 -12
  50. package/dist/src/kits/os/about-app.js +2 -3
  51. package/dist/src/kits/os/app-panes.css +46 -70
  52. package/dist/src/kits/os/browser-app.js +28 -1
  53. package/dist/src/kits/os/files-app.js +7 -0
  54. package/dist/src/kits/os/freddie/pages-chat.js +90 -75
  55. package/dist/src/kits/os/freddie/pages-core.js +9 -9
  56. package/dist/src/kits/os/freddie/pages-os.js +8 -8
  57. package/dist/src/kits/os/freddie/pages-tools.js +14 -14
  58. package/dist/src/kits/os/freddie/routes.js +21 -19
  59. package/dist/src/kits/os/freddie-dashboard.css +42 -21
  60. package/dist/src/kits/os/freddie-dashboard.js +6 -6
  61. package/dist/src/kits/os/icons.js +38 -16
  62. package/dist/src/kits/os/index.js +11 -0
  63. package/dist/src/kits/os/launcher.css +26 -10
  64. package/dist/src/kits/os/launcher.js +3 -0
  65. package/dist/src/kits/os/shell.js +72 -108
  66. package/dist/src/kits/os/terminal-app.js +1 -1
  67. package/dist/src/kits/os/theme.css +1330 -150
  68. package/dist/src/kits/os/wm.css +68 -15
  69. package/dist/src/kits/os/wm.js +93 -17
  70. package/dist/src/markdown.js +121 -12
  71. package/dist/src/motion.js +18 -2
  72. package/dist/src/page-html.js +62 -134
  73. package/dist/src/theme.js +106 -13
  74. package/dist/src/web-components/ds-chat.js +80 -9
  75. package/dist/ui_kits/aicat/app.js +54 -6
  76. package/dist/ui_kits/aicat/index.html +2 -0
  77. package/dist/ui_kits/blog/index.html +33 -17
  78. package/dist/ui_kits/chat/app.js +90 -22
  79. package/dist/ui_kits/chat/index.html +2 -0
  80. package/dist/ui_kits/community/app.js +86 -24
  81. package/dist/ui_kits/community/index.html +1 -0
  82. package/dist/ui_kits/dashboard/app.js +123 -34
  83. package/dist/ui_kits/dashboard/index.html +1 -0
  84. package/dist/ui_kits/docs/index.html +43 -33
  85. package/dist/ui_kits/error_404/app.js +78 -23
  86. package/dist/ui_kits/file_browser/README.md +1 -1
  87. package/dist/ui_kits/file_browser/app.js +45 -10
  88. package/dist/ui_kits/file_browser/index.html +1 -0
  89. package/dist/ui_kits/gallery/app.js +130 -51
  90. package/dist/ui_kits/homepage/app.js +156 -138
  91. package/dist/ui_kits/project_page/app.js +142 -125
  92. package/dist/ui_kits/project_page/index.html +2 -1
  93. package/dist/ui_kits/search/app.js +52 -15
  94. package/dist/ui_kits/settings/app.js +166 -39
  95. package/dist/ui_kits/signin/app.js +175 -49
  96. package/dist/ui_kits/signin/index.html +1 -0
  97. package/dist/ui_kits/slide_deck/app.js +83 -49
  98. package/dist/ui_kits/system_primer/app.js +57 -40
  99. package/dist/ui_kits/terminal/app.js +160 -43
  100. package/package.json +1 -1
  101. package/src/page-html/client-script.js +40 -11
  102. package/src/page-html/page-styles.js +22 -1
@@ -13,7 +13,41 @@ const liveTranscript = [
13
13
  { kind: 'cmd', text: 'echo "ready"' },
14
14
  { kind: 'out', text: 'ready' }
15
15
  ];
16
- const live = { input: '', cwd: '~/dev/design' };
16
+ const live = { input: '', cwd: '~/dev/design', phase: 'ready' };
17
+ const PHASES = ['ready', 'loading', 'empty', 'error'];
18
+
19
+ // Line-shaped shimmer for scrollback still being read off disk. Reuses
20
+ // .ds-event-row-skeleton + .ds-skel* (app-shell/files.css); a cli row is the
21
+ // same prompt-mark / text rhythm the primitive was cut for.
22
+ function ScrollbackSkeleton() {
23
+ return h('div', {},
24
+ ...Array.from({ length: 5 }, (_, i) => h('div', { key: 'sk' + i, class: 'ds-event-row-skeleton' },
25
+ h('span', { class: 'ds-skel ds-skel-rank' }),
26
+ h('span', { class: 'ds-skel ds-skel-title' })
27
+ ))
28
+ );
29
+ }
30
+
31
+ function ScrollbackEmpty() {
32
+ return h('div', { class: 'ds-empty-state' },
33
+ h('div', { class: 'ds-empty-state-glyph' }, '$'),
34
+ h('p', { class: 'ds-empty-state-msg' }, 'nothing run in this shell yet'),
35
+ h('p', { class: 'ds-empty-state-hint' }, 'commands and their output land here in order. type one below and press enter — scrollback survives until you clear it.')
36
+ );
37
+ }
38
+
39
+ function ScrollbackError() {
40
+ return h('div', { class: 'ds-alert ds-alert-error' },
41
+ h('span', { class: 'ds-alert-icon' }, '!'),
42
+ h('div', { class: 'ds-alert-content' },
43
+ h('div', { class: 'ds-alert-title' }, 'shell exited (code 137)'),
44
+ h('div', { class: 'ds-alert-message' }, 'the pty was killed by the OOM reaper, so scrollback is frozen and nothing you type now would reach a shell. a new session starts in the same working directory.'),
45
+ h('div', { class: 'ds-alert-retry' },
46
+ h('button', { class: 'btn', onclick: () => { live.phase = 'ready'; kit.render(); } }, 'start new session')
47
+ )
48
+ )
49
+ );
50
+ }
17
51
 
18
52
  // Demo loop — decorative showcase of the .cli row primitives playing back
19
53
  // a build-pipeline transcript. Clearly separated from the live terminal.
@@ -26,32 +60,57 @@ const demoScript = [
26
60
  { d: 220, kind: 'cmd', text: 'npm run build' },
27
61
  { d: 180, kind: 'out', text: '> 247420-design build' },
28
62
  { d: 180, kind: 'out', text: '[247420] css gzip+base64: 18.4kb (raw 96.1kb)' },
29
- { d: 220, kind: 'ok', text: 'bundle written to dist/247420.js (84.0 kb)' },
63
+ { d: 220, kind: 'ok', text: 'bundle written to dist/247420.js (84.0 kb)' },
30
64
  { d: 280, kind: 'cmd', text: 'npm test' },
31
65
  { d: 500, kind: 'out', text: '116 assertions, 0 failures' },
32
- { d: 200, kind: 'ok', text: 'all tests passed' },
66
+ { d: 200, kind: 'ok', text: 'all tests passed' },
33
67
  { d: 320, kind: 'cmd', text: 'git push' },
34
68
  { d: 240, kind: 'out', text: 'remote: Deploying to gh-pages…' },
35
- { d: 900, kind: 'ok', text: 'deploy in 11s' }
69
+ { d: 900, kind: 'ok', text: 'deploy in 11s' }
36
70
  ];
37
71
  const reduced = typeof matchMedia !== 'undefined'
38
72
  && matchMedia('(prefers-reduced-motion: reduce)').matches;
39
73
  const demo = { visible: reduced ? demoScript.slice() : [], looping: !reduced };
40
74
 
75
+ // Commands the live shell has run, newest last — what `history (up)` walks.
76
+ const history = [];
77
+ let historyIdx = -1;
78
+
79
+ // Empties the scrollback and drops the shell into its own empty state, which is
80
+ // the honest reading of a cleared shell (the empty panel explains what lands
81
+ // there). Both the sidebar item and Cmd/Ctrl+K call this.
82
+ function clearScrollback() {
83
+ liveTranscript.length = 0;
84
+ live.phase = 'empty';
85
+ historyIdx = -1;
86
+ kit.render();
87
+ }
88
+
89
+ // Walks back through previously-run commands into the input, oldest-last like a
90
+ // real shell. With no history it says so in the scrollback instead of silently
91
+ // doing nothing — the whole point of this pass.
92
+ function recallHistory() {
93
+ if (!history.length) {
94
+ if (live.phase !== 'ready') live.phase = 'ready';
95
+ liveTranscript.push({ kind: 'cmt', text: '# nothing in history yet — run a command first' });
96
+ kit.render();
97
+ return;
98
+ }
99
+ historyIdx = historyIdx < 0 ? history.length - 1 : Math.max(0, historyIdx - 1);
100
+ live.input = history[historyIdx];
101
+ if (live.phase !== 'ready') live.phase = 'ready';
102
+ kit.render();
103
+ }
104
+
105
+ const LINE_PROMPTS = { cmt: '#', cmd: '$', out: '·', ok: '+', warn: '!', log: '·' };
41
106
  function Line(l, i, opts = {}) {
42
- const cursor = opts.cursor;
43
- const baseChildren = (prompt, text, promptStyle, textStyle) => [
44
- h('span', { class: 'prompt', style: promptStyle || '' }, prompt),
45
- h('span', { class: 'cmd', style: textStyle || '' }, text),
46
- cursor ? h('span', { class: 'cursor-blink' }, '') : null
47
- ];
48
- if (l.kind === 'cmt') return h('div', { key: 'l' + i, class: 'cli' }, ...baseChildren('#', l.text, '', 'color:var(--fg-3)'));
49
- if (l.kind === 'cmd') return h('div', { key: 'l' + i, class: 'cli' }, ...baseChildren('$', l.text));
50
- if (l.kind === 'out') return h('div', { key: 'l' + i, class: 'cli' }, ...baseChildren('·', l.text, '', 'color:var(--fg-2)'));
51
- if (l.kind === 'ok') return h('div', { key: 'l' + i, class: 'cli' }, ...baseChildren('✓', l.text, 'color:var(--accent)', 'color:var(--accent)'));
52
- if (l.kind === 'warn') return h('div', { key: 'l' + i, class: 'cli' }, ...baseChildren('!', l.text, 'color:var(--mascot)', 'color:var(--mascot)'));
53
- if (l.kind === 'log') return h('div', { key: 'l' + i, class: 'cli' }, ...baseChildren('·', l.text, 'color:var(--fg-3)', 'color:var(--fg-2);font-family:var(--ff-mono)'));
54
- return null;
107
+ const prompt = LINE_PROMPTS[l.kind];
108
+ if (!prompt) return null;
109
+ return h('div', { key: 'l' + i, class: 'cli ds-cli-' + l.kind },
110
+ h('span', { class: 'prompt' }, prompt),
111
+ h('span', { class: 'cmd' }, l.text),
112
+ opts.cursor ? h('span', { class: 'cursor-blink' }, '') : null
113
+ );
55
114
  }
56
115
 
57
116
  function App() {
@@ -65,18 +124,41 @@ function App() {
65
124
  side: Side({
66
125
  sections: [
67
126
  { group: 'sessions', items: [
68
- { glyph: '', label: 'live', count: 'on', key: 'l' },
69
- { glyph: '◇', label: 'demo loop', count: demo.looping ? 'play' : 'still', key: 'd' }
127
+ // 'live' is a readout of which shell this kit is showing —
128
+ // there is only one, so it anchors to that panel rather
129
+ // than posing as a session picker.
130
+ { glyph: '*', label: 'live', count: live.phase === 'ready' ? liveTranscript.length : 0, key: 'l', href: '#p-live' },
131
+ // The demo loop control actually pauses and resumes the
132
+ // playback it names.
133
+ { glyph: '-', label: 'demo loop', count: demo.looping ? 'play' : 'still', key: 'd',
134
+ href: '#p-demo',
135
+ onClick: (e) => {
136
+ e.preventDefault();
137
+ demo.looping = !demo.looping;
138
+ if (demo.looping) tick();
139
+ kit.render();
140
+ } }
70
141
  ] },
142
+ // Reachable state switcher for the live shell panel.
143
+ { group: 'shell state', items: PHASES.map((p) => ({
144
+ glyph: h('span', { class: live.phase === p ? 'ds-dot ds-dot-on' : 'ds-dot ds-dot-off' }),
145
+ label: p, key: 'ph-' + p, active: live.phase === p,
146
+ onClick: (e) => { e.preventDefault(); live.phase = p; kit.render(); }
147
+ })) },
148
+ // These name real shell affordances, so they perform them
149
+ // rather than documenting a keystroke and doing nothing when
150
+ // clicked. Both are also bound to the keys they advertise.
71
151
  { group: 'shortcuts', items: [
72
- { glyph: '·', label: 'clear (⌘k)', key: 'c' },
73
- { glyph: '·', label: 'history ()', key: 'h' }
152
+ { glyph: '·', label: 'clear (⌘k)', key: 'c',
153
+ onClick: (e) => { e.preventDefault(); clearScrollback(); } },
154
+ { glyph: '·', label: 'history (up)', key: 'h',
155
+ onClick: (e) => { e.preventDefault(); recallHistory(); } }
74
156
  ] }
75
157
  ]
76
158
  }),
77
159
  main: [
78
- h('div', { class: 'ds-section', style: 'padding:8px' },
79
- h('div', { style: 'display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap' },
160
+ h('div', { class: 'ds-app-surface ds-section-pad-sm' },
161
+ h('div', { class: 'ds-kit-head-row' },
80
162
  h('div', {}, Heading({ level: 1, children: 'terminal' })),
81
163
  ThemeToggle()
82
164
  ),
@@ -84,24 +166,35 @@ function App() {
84
166
 
85
167
  // Live terminal — usable, no reveal delays.
86
168
  Panel({
169
+ id: 'p-live',
87
170
  title: 'live · ' + live.cwd,
88
- count: liveTranscript.length,
89
- style: 'margin:8px 0',
90
- children: h('div', { style: 'padding:14px 18px;display:flex;flex-direction:column;gap:4px;background:var(--bg-2);border-radius:10px' },
171
+ count: live.phase === 'ready' ? liveTranscript.length : 0,
172
+ class: 'ds-panel-gap',
173
+ children: live.phase === 'loading' ? ScrollbackSkeleton()
174
+ : live.phase === 'error' ? ScrollbackError()
175
+ : live.phase === 'empty' ? ScrollbackEmpty()
176
+ : h('div', { class: 'ds-term-body' },
91
177
  ...liveTranscript.map((l, i) => Line(l, i)),
92
- h('div', { class: 'cli', style: 'margin-top:6px' },
178
+ h('div', { class: 'cli ds-term-input-row' },
93
179
  h('span', { class: 'prompt' }, '$'),
94
180
  h('input', {
95
181
  value: live.input,
96
182
  placeholder: 'type a command and press enter…',
97
- style: 'flex:1;background:transparent;border:0;outline:0;font-family:var(--ff-mono);font-size:13px;color:var(--fg)',
183
+ class: 'ds-term-input',
98
184
  oninput: (e) => { live.input = e.target.value; },
99
185
  onkeydown: (e) => {
100
186
  if (e.key === 'Enter' && live.input.trim()) {
101
187
  liveTranscript.push({ kind: 'cmd', text: live.input });
102
188
  liveTranscript.push({ kind: 'out', text: '(stub) ran: ' + live.input });
189
+ history.push(live.input);
190
+ historyIdx = -1;
103
191
  live.input = '';
104
192
  kit.render();
193
+ } else if (e.key === 'ArrowUp') {
194
+ // The sidebar advertises 'history (up)';
195
+ // the key it names has to do it too.
196
+ e.preventDefault();
197
+ recallHistory();
105
198
  }
106
199
  }
107
200
  })
@@ -111,15 +204,16 @@ function App() {
111
204
 
112
205
  // Demo loop — decorative showcase only.
113
206
  Panel({
207
+ id: 'p-demo',
114
208
  title: 'demo · build pipeline',
115
209
  count: demo.visible.length + '/' + demoScript.length,
116
- style: 'margin:8px 0',
117
- children: h('div', { style: 'padding:14px 18px;display:flex;flex-direction:column;gap:4px;background:var(--bg-2);border-radius:10px;min-height:280px' },
210
+ class: 'ds-panel-gap',
211
+ children: h('div', { class: 'ds-term-body ds-term-body--tall' },
118
212
  ...demo.visible.map((l, i) => Line(l, i, { cursor: i === demo.visible.length - 1 && demo.looping }))
119
213
  )
120
214
  }),
121
215
 
122
- Panel({ title: 'about this kit', style: 'margin:8px 0', children: h('div', { class: 'ds-pattern-notes' },
216
+ Panel({ title: 'about this kit', class: 'ds-panel-gap', children: h('div', { class: 'ds-pattern-notes' },
123
217
  h('p', {}, '· ', Chip({ tone: 'accent', children: '.cli' }), ' rows pair ', h('code', {}, '.prompt'), ' + ', h('code', {}, '.cmd'), '.'),
124
218
  h('p', {}, '· six line kinds: ', Chip({ tone: 'dim', children: 'cmt' }), ' ', Chip({ tone: 'dim', children: 'cmd' }), ' ', Chip({ tone: 'dim', children: 'out' }), ' ', Chip({ tone: 'accent', children: 'ok' }), ' ', Chip({ tone: '', children: 'warn' }), ' ', Chip({ tone: 'dim', children: 'log' }), '.'),
125
219
  h('p', {}, '· live panel is instant; demo panel reveals lines on a loop for the showcase only — never fake-animate output a user is waiting on.')
@@ -127,7 +221,7 @@ function App() {
127
221
  )
128
222
  ],
129
223
  status: Status({
130
- left: ['terminal', ' live ' + liveTranscript.length + ' lines', demo.looping ? ' demo playing' : ' demo still'],
224
+ left: ['terminal', '- live ' + (live.phase === 'ready' ? liveTranscript.length : 0) + ' lines', '- shell ' + live.phase, demo.looping ? '- demo playing' : '- demo still'],
131
225
  right: ['247420 / mmxxvi']
132
226
  })
133
227
  });
@@ -135,16 +229,39 @@ function App() {
135
229
 
136
230
  const kit = mountKit({ root, view: App, screen: '09 Terminal' });
137
231
 
138
- // Demo loop animation. Stays off when prefers-reduced-motion is on.
139
- if (demo.looping) {
140
- let i = 0;
141
- function tick() {
142
- if (i >= demoScript.length) {
143
- setTimeout(() => { demo.visible = []; i = 0; kit.render(); tick(); }, 2500);
144
- return;
145
- }
146
- const step = demoScript[i++];
147
- setTimeout(() => { demo.visible.push(step); kit.render(); tick(); }, step.d);
232
+ // The sidebar advertises 'clear (⌘k)'; bind the keystroke it names so the label
233
+ // is true on both surfaces.
234
+ document.addEventListener('keydown', (e) => {
235
+ if ((e.metaKey || e.ctrlKey) && (e.key === 'k' || e.key === 'K')) {
236
+ e.preventDefault();
237
+ clearScrollback();
238
+ }
239
+ });
240
+
241
+ // Demo loop animation. Stays off when prefers-reduced-motion is on, and the
242
+ // sidebar's 'demo loop' item pauses/resumes it — hence module scope with a
243
+ // `looping` guard on every step rather than a closure that cannot be stopped.
244
+ // `pending` is the guard against a paused-then-resumed loop running two
245
+ // interleaved timer chains over the same index.
246
+ let demoIdx = 0;
247
+ let demoPending = false;
248
+ function tick() {
249
+ if (!demo.looping || demoPending) return;
250
+ if (demoIdx >= demoScript.length) {
251
+ demoPending = true;
252
+ setTimeout(() => {
253
+ demoPending = false;
254
+ if (!demo.looping) return;
255
+ demo.visible = []; demoIdx = 0; kit.render(); tick();
256
+ }, 2500);
257
+ return;
148
258
  }
149
- tick();
259
+ const step = demoScript[demoIdx++];
260
+ demoPending = true;
261
+ setTimeout(() => {
262
+ demoPending = false;
263
+ if (!demo.looping) return;
264
+ demo.visible.push(step); kit.render(); tick();
265
+ }, step.d);
150
266
  }
267
+ tick();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anentrypoint-design",
3
- "version": "0.0.414",
3
+ "version": "0.0.416",
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",
@@ -60,10 +60,10 @@ function examplesNode(examples) {
60
60
  return C.Section({
61
61
  title: 'explore',
62
62
  children: examples.map((e, i) => {
63
- const kids = [
64
- h('span', { key: 'c', class: 'code' }, String(i + 1).padStart(2, '0')),
65
- h('span', { key: 't', class: 'title' }, String(e.label || e.name || e.href || '')),
66
- ];
63
+ const code = e.code == null ? '' : String(e.code).trim();
64
+ const kids = [];
65
+ if (code) kids.push(h('span', { key: 'c', class: 'code' }, code));
66
+ kids.push(h('span', { key: 't', class: 'title' }, String(e.label || e.name || e.href || '')));
67
67
  if (e.desc) kids.push(h('span', { key: 'm', class: 'meta dim' }, ' — ' + e.desc));
68
68
  kids.push(h('span', { key: 'a', class: 'ds-row-arrow' }, '->'));
69
69
  return h('a', { key: i, class: 'row', href: e.href || '#' }, ...kids);
@@ -75,10 +75,10 @@ function panelNode(panel, idx) {
75
75
  const items = Array.isArray(panel.items) ? panel.items : [];
76
76
  if (!items.length) return null;
77
77
  const rows = items.map((it, i) => {
78
- const kids = [
79
- h('span', { key: 'c', class: 'code' }, String(it.code || String(i + 1).padStart(2, '0'))),
80
- h('span', { key: 't', class: 'title' }, String(it.title || it.name || '')),
81
- ];
78
+ const code = it.code == null ? '' : String(it.code).trim();
79
+ const kids = [];
80
+ if (code) kids.push(h('span', { key: 'c', class: 'code' }, code));
81
+ kids.push(h('span', { key: 't', class: 'title' }, String(it.title || it.name || '')));
82
82
  if (it.sub || it.desc) kids.push(h('span', { key: 'm', class: 'meta dim' }, ' — ' + (it.sub || it.desc)));
83
83
  kids.push(h('span', { key: 'a', class: 'ds-row-arrow' }, it.meta || '->'));
84
84
  return h('a', { key: i, class: 'row', href: it.href || '#' }, ...kids);
@@ -128,13 +128,42 @@ function __md(md) {
128
128
 
129
129
  const bodyNode = data.bodyHtml ? C.Section({ children: h('div', { class: 'page-body', innerHTML: data.bodyHtml }) }) : null;
130
130
 
131
+ function tierNode(tier, children) {
132
+ const kids = children.filter(Boolean);
133
+ if (!kids.length) return null;
134
+ const labelId = 'tier-' + tier.key + '-label';
135
+ const head = h('div', { key: 'h', class: 'ds-tier-head' },
136
+ h('h2', { class: 'eyebrow', id: labelId }, tier.label),
137
+ tier.lede ? h('p', { class: 'ds-tier-lede' }, tier.lede) : null,
138
+ );
139
+ return h('section', { key: tier.key, class: 'ds-tier ds-tier-' + tier.key, id: tier.key, 'aria-labelledby': labelId }, head, ...kids);
140
+ }
141
+
142
+ const panelsById = new Map((data.panels || []).map((p) => [p.id || p.title || p.name || '', p]));
143
+ const takePanel = (id) => { const p = panelsById.get(id); if (p) panelsById.delete(id); return p ? panelNode(p) : null; };
144
+
145
+ const TIERS = [
146
+ { key: 'open', label: 'open', lede: 'browse and try the system running.', ids: ['kits', 'previews', 'decks'], extra: () => [examplesNode(data.examples)] },
147
+ { key: 'ships', label: 'ships', lede: 'what the package contains.', ids: ['file_browser', 'desktop_os', 'web_components', 'api_exports'], extra: () => [] },
148
+ { key: 'read', label: 'read', lede: 'understand the rules behind it.', ids: ['docs', 'features'], extra: () => [quickstartNode(data.quickstart)] },
149
+ ];
150
+
151
+ const tierNodes = TIERS.map((t) => {
152
+ const kids = [...t.ids.map(takePanel), ...t.extra()].filter(Boolean);
153
+ if (t.key === 'open' && kids.length) {
154
+ const lead = kids[0];
155
+ if (lead.props) lead.props.class = (lead.props.class || '') + ' ds-tier-lead';
156
+ }
157
+ return tierNode(t, kids);
158
+ });
159
+ const leftoverPanels = [...panelsById.values()].map(panelNode);
160
+
131
161
  const mainChildren = [
132
162
  heroNode(data.hero),
133
163
  marqueeNode(data.marquee),
134
164
  ...data.sections.map(sectionNode),
135
- ...(data.panels || []).map(panelNode),
136
- quickstartNode(data.quickstart),
137
- examplesNode(data.examples),
165
+ ...tierNodes,
166
+ ...leftoverPanels,
138
167
  bodyNode,
139
168
  ].filter(Boolean);
140
169
 
@@ -5,8 +5,29 @@
5
5
  // bundle on load-order at equal specificity.
6
6
 
7
7
  export const PAGE_INLINE_STYLES = `
8
- .app-stage { width: 100%; max-width: var(--stage-wide, min(96vw, 1440px)); margin-inline: auto; padding: var(--space-6, 48px) var(--space-4, 24px) var(--space-8, 96px); display: grid; gap: var(--space-6, 48px); box-sizing: border-box }
8
+ .app-stage { width: 100%; max-width: var(--stage-wide, min(96%, 1600px)); margin-inline: auto; padding: var(--space-6, 48px) var(--space-4, 24px) var(--space-8, 96px); display: grid; gap: var(--space-6, 48px); box-sizing: border-box }
9
9
  @media (max-width: 768px) { .app-stage { padding: var(--space-4, 24px) var(--space-3, 16px) var(--space-6, 48px); gap: var(--space-5, 32px) } }
10
+ /* Tier movements — the stage's uniform gap is deliberately overridden here.
11
+ Panels inside one tier sit tight (--space-3, 16px) because they are peers;
12
+ between tiers that reopens to --space-5 on top of the stage's own --space-6,
13
+ so the three movements read as distinct instead of one repeating slab. */
14
+ .app-stage > .ds-tier { display: grid; gap: var(--space-3, 16px) }
15
+ .app-stage > .ds-tier + .ds-tier { margin-top: var(--space-5, 32px) }
16
+ .ds-tier-head { display: grid; gap: var(--space-2, 8px); margin-bottom: var(--space-1, 4px); justify-items: start }
17
+ /* The tier label is an h2 so the three movements are real landmarks in the
18
+ accessibility tree; it keeps the .eyebrow voice, so the UA heading margin
19
+ and size are reset back onto the mono kicker scale. */
20
+ .ds-tier-head > h2.eyebrow { margin: 0; font-size: var(--fs-tiny, 11px); line-height: 1.2 }
21
+ .ds-tier-lede { margin: 0; max-width: var(--measure, 68ch); color: var(--fg-3); font-size: var(--fs-sm, 15px) }
22
+ /* The lead tier carries the accent spine so the primary path is visible at a
23
+ squint; supporting tiers stay unmarked rather than competing. The head div
24
+ is the tier's first child, so the marker is an explicit class rather than a
25
+ positional :first-of-type, which would not match past it. */
26
+ .ds-tier-lead { border-radius: 0 var(--r-2, 14px) var(--r-2, 14px) 0; padding-left: calc(var(--space-3, 16px) + var(--bw-chunk, 6px)); position: relative }
27
+ .ds-tier-lead::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: var(--bw-chunk, 6px); background: var(--accent); border-radius: var(--bw-chunk, 6px) 0 0 var(--bw-chunk, 6px) }
28
+ /* Tertiary tier reads at prose weight: quieter rows, no panel fill competition. */
29
+ .ds-tier-read .panel { background: var(--panel-1, var(--bg)) }
30
+ .ds-tier-read .row .meta { color: var(--fg-3) }
10
31
  .page-body > :first-child { margin-top: 0 }
11
32
  .page-body h1 { margin-top: 0 } .page-body h2 { margin-top: var(--space-5, 32px) } .page-body h3 { margin-top: var(--space-4, 24px) }
12
33
  .page-body > * + * { margin-top: var(--space-3, 16px) }