@invisibleloop/pulse 0.20.11 → 0.20.20

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 (266) hide show
  1. package/package.json +6 -5
  2. package/public/.pulse-ui-version +1 -1
  3. package/scripts/build.js +26 -0
  4. package/scripts/release-version.js +17 -8
  5. package/scripts/strip-server.test.js +60 -0
  6. package/src/agent/checklist.md +8 -12
  7. package/src/agent/commands/pulse-start.md +2 -0
  8. package/src/agent/commands/verify.md +30 -8
  9. package/src/agent/guide-components.md +7 -25
  10. package/src/agent/guide-routing.md +0 -12
  11. package/src/agent/guide-server.md +102 -1
  12. package/src/agent/guide-spec.md +8 -1
  13. package/src/agent/guide-styles.md +23 -0
  14. package/src/agent/identity.md +9 -0
  15. package/src/agent/workflow.md +4 -23
  16. package/src/cli/check-bundles.js +113 -0
  17. package/src/cli/check-bundles.test.js +137 -0
  18. package/src/cli/cli.test.js +105 -0
  19. package/src/cli/dev.js +48 -3
  20. package/src/cli/diagnose.js +105 -0
  21. package/src/cli/diagnose.test.js +124 -0
  22. package/src/cli/discover.js +9 -2
  23. package/src/cli/index.js +171 -9
  24. package/src/cli/process-utils.js +57 -0
  25. package/src/cli/process-utils.test.js +114 -0
  26. package/src/cli/review.js +391 -0
  27. package/src/cli/review.test.js +143 -0
  28. package/src/cli/scaffold.js +9 -4
  29. package/src/cli/stamp.js +102 -0
  30. package/src/cli/stamp.test.js +149 -0
  31. package/src/cli/start.js +15 -2
  32. package/src/cli/validate.js +184 -0
  33. package/src/cli/validate.test.js +153 -0
  34. package/src/mcp/mcp.test.js +261 -30
  35. package/src/mcp/server.js +207 -515
  36. package/src/mcp/validate-worker.js +18 -1
  37. package/src/runtime/index.js +144 -11
  38. package/src/runtime/runtime.test.js +199 -0
  39. package/src/runtime/ssr.js +13 -6
  40. package/src/runtime/ssr.test.js +31 -0
  41. package/src/server/index.js +162 -9
  42. package/src/server/server.test.js +225 -2
  43. package/src/spec/schema.js +1 -1
  44. package/src/ui/index.js +0 -5
  45. package/src/ui/ui.test.js +0 -75
  46. package/types/ui.d.ts +0 -21
  47. package/.claude/commands/build-page.md +0 -63
  48. package/.claude/commands/new-doc-page.md +0 -45
  49. package/.claude/commands/pulse-start.md +0 -11
  50. package/.claude/commands/pulse-stop.md +0 -5
  51. package/.claude/commands/verify.md +0 -118
  52. package/.claude/pulse-checklist.md +0 -189
  53. package/.github/workflows/ci.yml +0 -25
  54. package/.github/workflows/publish.yml +0 -45
  55. package/.pulse/load-reports/home/1773432711417.json +0 -22
  56. package/.pulse-verified +0 -1
  57. package/AGENTS.md +0 -697
  58. package/CLAUDE.md +0 -697
  59. package/README.md +0 -95
  60. package/benchmark/public/dist/counter.boot-PO6SNSJZ.js +0 -8
  61. package/benchmark/public/dist/home.boot-YK6VAEQI.js +0 -6
  62. package/benchmark/public/dist/manifest.json +0 -5
  63. package/benchmark/public/dist/runtime-5Y7ESAIB.js +0 -2
  64. package/benchmark/public/dist/runtime-KO4BHUQ3.js +0 -49
  65. package/benchmark/results/2026-03-27-09-20.json +0 -188
  66. package/benchmark/results/2026-03-27-09-21.json +0 -188
  67. package/benchmark/results/2026-03-27-09-29.json +0 -188
  68. package/benchmark/results/2026-03-27-10-14.json +0 -188
  69. package/benchmark/results/2026-03-27-10-18.json +0 -232
  70. package/benchmark/src/pages/counter.js +0 -37
  71. package/benchmark/src/pages/home.js +0 -21
  72. package/docs/.claude/commands/pulse-build.md +0 -7
  73. package/docs/.claude/commands/pulse-dev.md +0 -7
  74. package/docs/.claude/commands/pulse-start.md +0 -9
  75. package/docs/.claude/commands/pulse-stop.md +0 -5
  76. package/docs/.claude/commands/verify.md +0 -84
  77. package/docs/.claude/coverage-check.js +0 -196
  78. package/docs/.claude/pulse-checklist.md +0 -189
  79. package/docs/public/.pulse-ui-version +0 -1
  80. package/docs/public/andypixel.css +0 -237
  81. package/docs/public/bookworms.css +0 -422
  82. package/docs/public/coutumes.css +0 -561
  83. package/docs/public/docs.css +0 -2388
  84. package/docs/public/favicon.svg +0 -4
  85. package/docs/public/home-brut.css +0 -982
  86. package/docs/public/images/bookworms/hero-books.jpg +0 -0
  87. package/docs/public/images/bookworms/how-we-read-now.jpg +0 -0
  88. package/docs/public/images/bookworms/lantern-keepers.jpg +0 -0
  89. package/docs/public/images/bookworms/provincial-bookseller.jpg +0 -0
  90. package/docs/public/images/journey/178.jpg +0 -0
  91. package/docs/public/images/journey/29.jpg +0 -0
  92. package/docs/public/images/retrowheels/hero.jpg +0 -0
  93. package/docs/public/images/shop/devbook-pro.jpg +0 -0
  94. package/docs/public/images/suzukibento/1015.jpg +0 -0
  95. package/docs/public/images/suzukibento/1025.jpg +0 -0
  96. package/docs/public/images/suzukibento/1043.jpg +0 -0
  97. package/docs/public/images/suzukibento/1059.jpg +0 -0
  98. package/docs/public/images/suzukibento/1062.jpg +0 -0
  99. package/docs/public/images/suzukibento/1084.jpg +0 -0
  100. package/docs/public/images/swish/1469334031218-e382a71b716b.jpg +0 -0
  101. package/docs/public/images/swish/1483985988355-763728e1935b.jpg +0 -0
  102. package/docs/public/images/swish/1485518882345-15568b007407.jpg +0 -0
  103. package/docs/public/images/swish/1490481651871-ab68de25d43d.jpg +0 -0
  104. package/docs/public/images/swish/1496747611176-843222e1e57c.jpg +0 -0
  105. package/docs/public/images/swish/1502716119720-b23a93e5fe1b.jpg +0 -0
  106. package/docs/public/images/swish/1539109136881-3be0616acf4b.jpg +0 -0
  107. package/docs/public/images/swish/1551803091-e20673f15770.jpg +0 -0
  108. package/docs/public/images/swish/1558769132-cb1aea458c5e.jpg +0 -0
  109. package/docs/public/images/swish/1572804013309-59a88b7e92f1.jpg +0 -0
  110. package/docs/public/img/heic1501a.jpg +0 -0
  111. package/docs/public/img/placeholder-landscape.svg +0 -18
  112. package/docs/public/img/placeholder-square.svg +0 -14
  113. package/docs/public/img/placeholder-tall.svg +0 -22
  114. package/docs/public/journey.css +0 -528
  115. package/docs/public/menu.js +0 -327
  116. package/docs/public/pulse-ui.css +0 -3028
  117. package/docs/public/pulse-ui.js +0 -257
  118. package/docs/public/retrowheels.css +0 -425
  119. package/docs/public/shop.css +0 -269
  120. package/docs/public/sparkys.css +0 -382
  121. package/docs/public/suzukibento.css +0 -517
  122. package/docs/public/swish.css +0 -394
  123. package/docs/public/theme.css +0 -52
  124. package/docs/public/themes/lumio.css +0 -50
  125. package/docs/public/themes/observatory.css +0 -104
  126. package/docs/server.js +0 -161
  127. package/docs/src/lib/component-page.js +0 -49
  128. package/docs/src/lib/highlight.js +0 -2
  129. package/docs/src/lib/layout.js +0 -190
  130. package/docs/src/lib/metrics-store.js +0 -6
  131. package/docs/src/lib/nav.js +0 -171
  132. package/docs/src/lib/stats.js +0 -81
  133. package/docs/src/pages/accessibility.js +0 -157
  134. package/docs/src/pages/actions.js +0 -194
  135. package/docs/src/pages/auth.js +0 -176
  136. package/docs/src/pages/caching.js +0 -95
  137. package/docs/src/pages/components/accordion.js +0 -48
  138. package/docs/src/pages/components/alert.js +0 -35
  139. package/docs/src/pages/components/app-badge.js +0 -41
  140. package/docs/src/pages/components/avatar.js +0 -35
  141. package/docs/src/pages/components/badge.js +0 -36
  142. package/docs/src/pages/components/breadcrumbs.js +0 -94
  143. package/docs/src/pages/components/button.js +0 -84
  144. package/docs/src/pages/components/card.js +0 -225
  145. package/docs/src/pages/components/charts.js +0 -283
  146. package/docs/src/pages/components/checkbox.js +0 -129
  147. package/docs/src/pages/components/cluster.js +0 -47
  148. package/docs/src/pages/components/container.js +0 -40
  149. package/docs/src/pages/components/cta.js +0 -54
  150. package/docs/src/pages/components/divider.js +0 -37
  151. package/docs/src/pages/components/empty.js +0 -36
  152. package/docs/src/pages/components/feature.js +0 -60
  153. package/docs/src/pages/components/fieldset.js +0 -65
  154. package/docs/src/pages/components/fileupload.js +0 -128
  155. package/docs/src/pages/components/footer.js +0 -58
  156. package/docs/src/pages/components/grid.js +0 -169
  157. package/docs/src/pages/components/heading.js +0 -107
  158. package/docs/src/pages/components/hero.js +0 -123
  159. package/docs/src/pages/components/icons.js +0 -410
  160. package/docs/src/pages/components/image.js +0 -71
  161. package/docs/src/pages/components/input.js +0 -51
  162. package/docs/src/pages/components/media.js +0 -51
  163. package/docs/src/pages/components/modal.js +0 -111
  164. package/docs/src/pages/components/nav.js +0 -136
  165. package/docs/src/pages/components/phone-frame.js +0 -74
  166. package/docs/src/pages/components/progress.js +0 -102
  167. package/docs/src/pages/components/prose.js +0 -111
  168. package/docs/src/pages/components/pullquote.js +0 -71
  169. package/docs/src/pages/components/radio.js +0 -194
  170. package/docs/src/pages/components/rating.js +0 -106
  171. package/docs/src/pages/components/search.js +0 -61
  172. package/docs/src/pages/components/section.js +0 -98
  173. package/docs/src/pages/components/segmented.js +0 -121
  174. package/docs/src/pages/components/select.js +0 -45
  175. package/docs/src/pages/components/slider.js +0 -115
  176. package/docs/src/pages/components/spinner.js +0 -73
  177. package/docs/src/pages/components/stack.js +0 -48
  178. package/docs/src/pages/components/stat.js +0 -55
  179. package/docs/src/pages/components/stepper.js +0 -66
  180. package/docs/src/pages/components/table.js +0 -45
  181. package/docs/src/pages/components/textarea.js +0 -31
  182. package/docs/src/pages/components/toggle.js +0 -84
  183. package/docs/src/pages/components/tooltip.js +0 -48
  184. package/docs/src/pages/components.js +0 -200
  185. package/docs/src/pages/config.js +0 -193
  186. package/docs/src/pages/constraints.js +0 -100
  187. package/docs/src/pages/deploy.js +0 -233
  188. package/docs/src/pages/error-pages.js +0 -99
  189. package/docs/src/pages/extending.js +0 -198
  190. package/docs/src/pages/faq.js +0 -99
  191. package/docs/src/pages/forms.js +0 -129
  192. package/docs/src/pages/getting-started.js +0 -147
  193. package/docs/src/pages/guard.js +0 -120
  194. package/docs/src/pages/home.js +0 -350
  195. package/docs/src/pages/how-it-works.js +0 -244
  196. package/docs/src/pages/hydration.js +0 -108
  197. package/docs/src/pages/images.js +0 -120
  198. package/docs/src/pages/markdown.js +0 -156
  199. package/docs/src/pages/meta.js +0 -193
  200. package/docs/src/pages/mutations.js +0 -107
  201. package/docs/src/pages/navigation.js +0 -85
  202. package/docs/src/pages/performance.js +0 -157
  203. package/docs/src/pages/persist.js +0 -89
  204. package/docs/src/pages/project-structure.js +0 -91
  205. package/docs/src/pages/prompt-examples.js +0 -186
  206. package/docs/src/pages/raw-responses.js +0 -122
  207. package/docs/src/pages/routing.js +0 -120
  208. package/docs/src/pages/server-api.js +0 -291
  209. package/docs/src/pages/server-data.js +0 -203
  210. package/docs/src/pages/slash-commands.js +0 -55
  211. package/docs/src/pages/spec.js +0 -207
  212. package/docs/src/pages/state.js +0 -102
  213. package/docs/src/pages/store.js +0 -203
  214. package/docs/src/pages/streaming.js +0 -107
  215. package/docs/src/pages/stripe.js +0 -194
  216. package/docs/src/pages/supabase.js +0 -324
  217. package/docs/src/pages/templates/andypixel.js +0 -260
  218. package/docs/src/pages/templates/blog-post.js +0 -226
  219. package/docs/src/pages/templates/bookworms.js +0 -227
  220. package/docs/src/pages/templates/coutumes.js +0 -237
  221. package/docs/src/pages/templates/journey.js +0 -186
  222. package/docs/src/pages/templates/mobile-app.js +0 -371
  223. package/docs/src/pages/templates/retrowheels.js +0 -355
  224. package/docs/src/pages/templates/shop.js +0 -268
  225. package/docs/src/pages/templates/sparkys.js +0 -264
  226. package/docs/src/pages/templates/suzukibento.js +0 -200
  227. package/docs/src/pages/templates/swish.js +0 -313
  228. package/docs/src/pages/templates.js +0 -534
  229. package/docs/src/pages/testing.js +0 -199
  230. package/docs/src/pages/validation.js +0 -138
  231. package/examples/.claude/commands/pulse-build.md +0 -7
  232. package/examples/.claude/commands/pulse-dev.md +0 -7
  233. package/examples/.claude/commands/pulse-start.md +0 -9
  234. package/examples/.claude/commands/pulse-stop.md +0 -5
  235. package/examples/.claude/commands/verify.md +0 -84
  236. package/examples/.claude/coverage-check.js +0 -196
  237. package/examples/.claude/pulse-checklist.md +0 -189
  238. package/examples/andypixel.css +0 -237
  239. package/examples/andypixel.js +0 -258
  240. package/examples/bookworms.css +0 -422
  241. package/examples/bookworms.js +0 -225
  242. package/examples/contact.js +0 -166
  243. package/examples/counter.js +0 -94
  244. package/examples/dev.server.js +0 -110
  245. package/examples/examples.test.js +0 -394
  246. package/examples/journey.css +0 -528
  247. package/examples/journey.js +0 -186
  248. package/examples/pricing.js +0 -262
  249. package/examples/products.js +0 -190
  250. package/examples/public/.pulse-ui-version +0 -1
  251. package/examples/public/pulse-ui.css +0 -3028
  252. package/examples/public/pulse-ui.js +0 -257
  253. package/examples/quiz.js +0 -208
  254. package/examples/retrowheels.css +0 -425
  255. package/examples/retrowheels.js +0 -353
  256. package/examples/shared.js +0 -84
  257. package/examples/suzukibento.css +0 -513
  258. package/examples/suzukibento.js +0 -200
  259. package/examples/swish.css +0 -394
  260. package/examples/swish.js +0 -311
  261. package/examples/todos.js +0 -162
  262. package/src/ui/fieldset.js +0 -37
  263. package/src/ui/phone-frame.js +0 -40
  264. package/src/ui/search.js +0 -78
  265. package/src/ui/segmented.js +0 -50
  266. package/src/ui/tooltip.js +0 -28
@@ -1,111 +0,0 @@
1
- import { renderComponentPage, demo } from '../../lib/component-page.js'
2
- import { prevNext } from '../../lib/nav.js'
3
- import { table, callout } from '../../lib/layout.js'
4
- import { modal, modalTrigger, button, cluster } from '../../../../src/ui/index.js'
5
-
6
- const { prev, next } = prevNext('/components/modal')
7
-
8
- export default {
9
- route: '/components/modal',
10
- meta: {
11
- theme: 'light',
12
- title: 'Modal — Pulse Docs',
13
- description: 'Modal dialog component for Pulse UI.',
14
- styles: ['/pulse-ui.css', '/theme.css', '/docs.css'],
15
- },
16
- view: () => renderComponentPage({
17
- currentHref: '/components/modal',
18
- prev,
19
- next,
20
- name: 'modal / modalTrigger',
21
- description: 'A <code>&lt;dialog&gt;</code>-based modal with backdrop, animated open/close, and keyboard dismiss. The Pulse runtime handles open and close natively — no extra script needed.',
22
- content: `
23
- ${demo(
24
- cluster({ justify: 'center', content:
25
- modalTrigger({ target: 'demo-modal', label: 'Open modal' }) +
26
- modal({
27
- id: 'demo-modal',
28
- title: 'Confirm action',
29
- content: '<p style="color:var(--ui-muted);margin:0">Are you sure you want to delete this item? This action cannot be undone.</p>',
30
- footer:
31
- button({ label: 'Cancel', variant: 'secondary', type: 'submit' }) +
32
- button({ label: 'Delete', variant: 'danger' }),
33
- }),
34
- }),
35
- `// Render the dialog somewhere on the page
36
- modal({
37
- id: 'confirm-delete',
38
- title: 'Confirm action',
39
- content: '<p>Are you sure? This cannot be undone.</p>',
40
- footer:
41
- button({ label: 'Cancel', variant: 'secondary' }) +
42
- button({ label: 'Delete', variant: 'danger' }),
43
- })
44
-
45
- // Open it with a trigger button (or any element with data-dialog-open)
46
- modalTrigger({ target: 'confirm-delete', label: 'Delete item', variant: 'danger' })`
47
- )}
48
-
49
- <h2 class="doc-h2" id="sizes"><a href="#sizes" class="heading-anchor">Sizes</a></h2>
50
- ${demo(
51
- cluster({ gap: 'sm', justify: 'center', content:
52
- modalTrigger({ target: 'demo-sm', label: 'Small', variant: 'secondary', size: 'sm' }) +
53
- modalTrigger({ target: 'demo-lg', label: 'Large', variant: 'secondary', size: 'sm' }) +
54
- modal({ id: 'demo-sm', title: 'Small modal', size: 'sm', content: '<p style="color:var(--ui-muted);margin:0">A compact dialog for quick confirmations.</p>' }) +
55
- modal({ id: 'demo-lg', title: 'Large modal', size: 'lg', content: '<p style="color:var(--ui-muted);margin:0">Use large modals for forms, rich content, or detail views that need more space.</p>' }),
56
- }),
57
- `modal({ id: 'my-modal', title: 'Large modal', size: 'lg', content: '...' })`
58
- )}
59
-
60
- ${table(
61
- ['Prop', 'Type', 'Default', 'Description'],
62
- [
63
- ['<code>id</code>', 'string', '—', 'Unique ID — required for triggers to target this dialog'],
64
- ['<code>title</code>', 'string', '—', ''],
65
- ['<code>level</code>', 'number', '2', 'Heading tag for the title (1–6). Visual style is unchanged.'],
66
- ['<code>content</code>', 'string (HTML)', '—', 'Body HTML'],
67
- ['<code>footer</code>', 'string (HTML)', '—', 'Footer HTML — typically button() calls'],
68
- ['<code>size</code>', '<code>sm | md | lg | xl</code>', '<code>md</code>', ''],
69
- ['<code>class</code>', 'string', '—', ''],
70
- ]
71
- )}
72
-
73
- <h2 class="doc-h2" id="trigger-props"><a href="#trigger-props" class="heading-anchor">modalTrigger props</a></h2>
74
-
75
- ${table(
76
- ['Prop', 'Type', 'Default', 'Description'],
77
- [
78
- ['<code>target</code>', 'string', '—', "The modal's <code>id</code>"],
79
- ['<code>label</code>', 'string', '<code>Open</code>', ''],
80
- ['<code>variant</code>', '<code>primary | secondary | ghost | danger</code>', '<code>primary</code>', ''],
81
- ['<code>size</code>', '<code>sm | md | lg</code>', '<code>md</code>', ''],
82
- ['<code>class</code>', 'string', '—', ''],
83
- ]
84
- )}
85
-
86
- <h2 class="doc-h2" id="custom-trigger"><a href="#custom-trigger" class="heading-anchor">Custom triggers</a></h2>
87
- <p class="doc-body">Any element with <code>data-dialog-open="&lt;id&gt;"</code> opens the dialog when clicked. Use <code>data-dialog-close</code> on any element inside or outside the dialog to close it programmatically:</p>
88
- <pre class="code-block"><code>&lt;button data-dialog-open="my-modal"&gt;Open&lt;/button&gt;
89
- &lt;button data-dialog-close&gt;Cancel&lt;/button&gt;</code></pre>
90
- ${callout('tip', 'The dialog also closes on ESC, backdrop click, and <code>&lt;form method="dialog"&gt;</code> submit — all native browser behaviour, no JavaScript needed.')}
91
-
92
- <h2 class="doc-h2" id="forms-inside"><a href="#forms-inside" class="heading-anchor">Forms inside a modal</a></h2>
93
- <p class="doc-body"><code>modal()</code> wraps all content in <code>&lt;form method="dialog"&gt;</code> for native close behaviour. You <strong>cannot nest a <code>&lt;form data-action="..."&gt;</code> inside it</strong> — browsers silently discard nested forms, so the action will never fire.</p>
94
- <p class="doc-body">When a modal button needs to trigger a Pulse action, place the form <em>outside</em> the modal and use the HTML <code>form</code> attribute to associate the button with it:</p>
95
- <pre class="code-block"><code>// The action form lives outside the modal — hidden, no visible fields needed
96
- &lt;form id="delete-form" data-action="deleteAccount" style="display:none"&gt;&lt;/form&gt;
97
-
98
- \${modal({
99
- id: 'confirm-delete',
100
- title: 'Delete account?',
101
- content: '&lt;p&gt;This cannot be undone.&lt;/p&gt;',
102
- footer:
103
- // type="submit" with no form= closes the modal natively (submits &lt;form method="dialog"&gt;)
104
- button({ label: 'Cancel', variant: 'secondary', type: 'submit' }) +
105
- // form="delete-form" associates this button with the external form → fires the action
106
- button({ label: 'Confirm delete', variant: 'danger', attrs: { form: 'delete-form', type: 'submit' } }),
107
- })}</code></pre>
108
- ${callout('note', 'The hidden form needs no visible fields. <code>onStart</code> receives <code>state</code> and <code>formData</code> — read anything you need from state directly.')}
109
- `,
110
- }),
111
- }
@@ -1,136 +0,0 @@
1
- import { renderComponentPage, demo } from '../../lib/component-page.js'
2
- import { prevNext } from '../../lib/nav.js'
3
- import { table } from '../../lib/layout.js'
4
- import { button, nav as uiNav } from '../../../../src/ui/index.js'
5
-
6
- const { prev, next } = prevNext('/components/nav')
7
-
8
- export default {
9
- route: '/components/nav',
10
- meta: {
11
- theme: 'light',
12
- title: 'Nav — Pulse Docs',
13
- description: 'Nav component for Pulse UI.',
14
- styles: ['/pulse-ui.css', '/theme.css', '/docs.css'],
15
- },
16
- view: () => renderComponentPage({
17
- currentHref: '/components/nav',
18
- prev,
19
- next,
20
- name: 'nav',
21
- description: 'Sticky-capable site header with logo, navigation links, and an optional CTA button. On mobile (&lt; 640px) links collapse behind a burger button — clicking it reveals an overlay panel that sits on top of page content without pushing it down. Wired automatically by <code>pulse-ui.js</code>.',
22
- content: `
23
- ${demo(
24
- uiNav({
25
- logo: '<strong>MyApp</strong>',
26
- logoHref: '/',
27
- links: [{ label: 'Features', href: '#features' }, { label: 'Pricing', href: '#pricing' }, { label: 'FAQ', href: '#faq' }],
28
- action: button({ label: 'Download', size: 'sm' }),
29
- sticky: false,
30
- }),
31
- `nav({
32
- logo: '<strong>MyApp</strong>',
33
- logoHref: '/',
34
- links: [
35
- { label: 'Features', href: '#features' },
36
- { label: 'Pricing', href: '#pricing' },
37
- { label: 'FAQ', href: '#faq' },
38
- ],
39
- action: button({ label: 'Download', size: 'sm' }),
40
- sticky: true,
41
- })`
42
- )}
43
-
44
- <h2 class="doc-h2" id="mega">Mega nav</h2>
45
- <p>Replace a link's <code>href</code> with a <code>mega</code> array to render a dropdown panel. Each entry is a column with an optional heading and a list of items. Items support an optional <code>description</code> line.</p>
46
- ${demo(
47
- uiNav({
48
- logo: '<strong>MyApp</strong>',
49
- logoHref: '/',
50
- links: [
51
- { label: 'Products', mega: [
52
- { heading: 'Platform', items: [
53
- { label: 'Analytics', href: '#', description: 'Dashboards and usage metrics' },
54
- { label: 'Automation', href: '#', description: 'Scheduled jobs and webhooks' },
55
- { label: 'Storage', href: '#', description: 'Files, blobs, and databases' },
56
- ]},
57
- { heading: 'Integrations', items: [
58
- { label: 'Supabase', href: '#' },
59
- { label: 'Stripe', href: '#' },
60
- { label: 'Auth0', href: '#' },
61
- ]},
62
- ]},
63
- { label: 'Pricing', href: '#pricing' },
64
- { label: 'Docs', href: '#docs' },
65
- ],
66
- action: button({ label: 'Get started', size: 'sm' }),
67
- }),
68
- `nav({
69
- links: [
70
- { label: 'Products', mega: [
71
- { heading: 'Platform', items: [
72
- { label: 'Analytics', href: '/analytics', description: 'Dashboards and usage metrics' },
73
- { label: 'Automation', href: '/automation', description: 'Scheduled jobs and webhooks' },
74
- ]},
75
- { heading: 'Integrations', items: [
76
- { label: 'Supabase', href: '/supabase' },
77
- { label: 'Stripe', href: '/stripe' },
78
- ]},
79
- ]},
80
- { label: 'Pricing', href: '/pricing' },
81
- ],
82
- })`
83
- )}
84
-
85
- <h2 class="doc-h2" id="mobile">Mobile view</h2>
86
- <p>At &lt; 640px the links and action are hidden and replaced with a burger button. The panel opens as an overlay — no layout shift.</p>
87
-
88
- <div class="demo-phone demo-mobile-nav">
89
- <div class="demo-phone-statusbar"><div class="demo-phone-pill"></div></div>
90
- ${uiNav({
91
- logo: '<strong>MyApp</strong>',
92
- logoHref: '/',
93
- links: [{ label: 'Features', href: '#features' }, { label: 'Pricing', href: '#pricing' }, { label: 'FAQ', href: '#faq' }],
94
- action: button({ label: 'Download', size: 'sm' }),
95
- })}
96
- <div class="demo-phone-content"><p>Tap the burger to open the overlay</p></div>
97
- </div>
98
-
99
- <h2 class="doc-h2" id="burger-left">Burger on the left</h2>
100
- <p>Set <code>burgerAlign: 'left'</code> to place the burger before the logo — common in app-style layouts.</p>
101
-
102
- <div class="demo-phone demo-mobile-nav">
103
- <div class="demo-phone-statusbar"><div class="demo-phone-pill"></div></div>
104
- ${uiNav({
105
- logo: '<strong>MyApp</strong>',
106
- logoHref: '/',
107
- links: [{ label: 'Features', href: '#features' }, { label: 'Pricing', href: '#pricing' }, { label: 'FAQ', href: '#faq' }],
108
- action: button({ label: 'Download', size: 'sm' }),
109
- burgerAlign: 'left',
110
- })}
111
- <div class="demo-phone-content"><p>Burger on the left — logo stays right</p></div>
112
- </div>
113
-
114
- ${table(
115
- ['Prop', 'Type', 'Default', 'Description'],
116
- [
117
- ['<code>logo</code>', 'string (HTML)', '—', 'Raw HTML slot — SVG, img, or text'],
118
- ['<code>logoHref</code>', 'string', "'/'", ''],
119
- ['<code>links</code>', 'array', '[]', '<code>{ label, href }[]</code> or <code>{ label, mega: Column[] }</code>'],
120
- ['<code>action</code>', 'string (HTML)', '—', 'Raw HTML slot — shown in desktop bar and mobile menu'],
121
- ['<code>sticky</code>', 'boolean', 'false', 'position: sticky with backdrop blur'],
122
- ['<code>burgerAlign</code>', 'string', "'right'", "'right' or 'left' — mobile burger position"],
123
- ]
124
- )}
125
-
126
- <h3 class="doc-h3" style="margin-top:2rem">Mega nav column</h3>
127
- ${table(
128
- ['Prop', 'Type', 'Description'],
129
- [
130
- ['<code>heading</code>', 'string', 'Optional column heading (uppercase label)'],
131
- ['<code>items</code>', 'array', '<code>{ label, href, description? }[]</code>'],
132
- ]
133
- )}
134
- `,
135
- }),
136
- }
@@ -1,74 +0,0 @@
1
- import { renderComponentPage, demo } from '../../lib/component-page.js'
2
- import { prevNext } from '../../lib/nav.js'
3
- import { table } from '../../lib/layout.js'
4
- import { phoneFrame } from '../../../../src/ui/index.js'
5
-
6
- const { prev, next } = prevNext('/components/phone-frame')
7
-
8
- const sampleScreen = `
9
- <div style="background:#1a2c24;min-height:520px;padding:0.75rem;display:flex;flex-direction:column;gap:0.75rem;font-family:system-ui,sans-serif;">
10
- <div style="display:flex;justify-content:space-between;align-items:center;padding:0 0.25rem;font-size:0.6rem;color:rgba(255,255,255,0.5)">
11
- <span>9:41</span>
12
- <svg width="20" height="8" viewBox="0 0 20 8" fill="none" aria-hidden="true"><rect x="0.5" y="0.5" width="16" height="7" rx="2" stroke="rgba(255,255,255,0.35)" stroke-width="1"/><rect x="1.5" y="1.5" width="12" height="5" rx="1" fill="rgba(255,255,255,0.7)"/><path d="M18 3v2c.8-.3.8-1.7 0-2z" fill="rgba(255,255,255,0.6)"/></svg>
13
- </div>
14
- <div style="font-size:0.85rem;font-weight:700;color:#fff">Good morning, Sarah</div>
15
- <div style="background:rgba(255,255,255,0.06);border-radius:0.75rem;padding:0.75rem;flex:1;display:flex;flex-direction:column;gap:0.5rem">
16
- <div style="font-size:0.6rem;color:rgba(255,255,255,0.5);letter-spacing:0.04em">TODAY'S ENTRY</div>
17
- <div style="font-size:0.72rem;color:rgba(255,255,255,0.85);line-height:1.6">Feeling grateful today. The early morning light was something else — golden and soft through the curtains.</div>
18
- </div>
19
- <div style="background:rgba(255,255,255,0.04);border-radius:0.75rem;padding:0.75rem">
20
- <div style="font-size:0.6rem;color:rgba(255,255,255,0.5);letter-spacing:0.04em;margin-bottom:0.4rem">STREAK</div>
21
- <div style="display:flex;gap:0.3rem">
22
- ${[1,2,3,4,5,6,7].map(d => `<div style="flex:1;height:28px;background:${d < 6 ? 'rgba(74,222,128,0.7)' : 'rgba(255,255,255,0.08)'};border-radius:4px"></div>`).join('')}
23
- </div>
24
- <div style="font-size:0.6rem;color:rgba(255,255,255,0.4);margin-top:0.4rem;text-align:right">5 day streak 🔥</div>
25
- </div>
26
- </div>`
27
-
28
- export default {
29
- route: '/components/phone-frame',
30
- meta: {
31
- theme: 'light',
32
- title: 'Phone Frame — Pulse Docs',
33
- description: 'Phone Frame component for Pulse UI.',
34
- styles: ['/pulse-ui.css', '/theme.css', '/docs.css'],
35
- },
36
- state: {},
37
- view: () => renderComponentPage({
38
- currentHref: '/components/phone-frame',
39
- prev,
40
- next,
41
- name: 'phoneFrame',
42
- description: 'A realistic CSS mobile phone frame — device chrome, dynamic island, side buttons, and home indicator. Pass arbitrary HTML into the <code>content</code> slot to fill the screen area. No images, pure CSS/HTML.',
43
- content: `
44
- ${demo(
45
- phoneFrame({ content: sampleScreen }),
46
- `phoneFrame({
47
- content: \`<div style="background:#1a2c24;min-height:520px;padding:0.75rem;">
48
- <!-- your app screen UI -->
49
- </div>\`,
50
- })`,
51
- )}
52
-
53
- <h2>Hover animation</h2>
54
- <p>Pass <code>animate: true</code> to enable a gentle 3-D tilt when the user hovers over the frame. Ideal for hero sections — hover the phone below to see it.</p>
55
-
56
- ${demo(
57
- phoneFrame({ content: sampleScreen, animate: true }),
58
- `phoneFrame({
59
- content: \`...\`,
60
- animate: true,
61
- })`,
62
- )}
63
-
64
- ${table(
65
- ['Prop', 'Type', 'Default', 'Description'],
66
- [
67
- ['<code>content</code>', 'string (HTML)', "''", 'HTML rendered inside the screen area'],
68
- ['<code>animate</code>', 'boolean', 'false', 'Enables a gentle 3-D tilt on mouse hover — recommended for hero usage'],
69
- ['<code>class</code>', 'string', "''", 'Extra CSS classes on the root element'],
70
- ]
71
- )}
72
- `,
73
- }),
74
- }
@@ -1,102 +0,0 @@
1
- import { renderComponentPage, demo } from '../../lib/component-page.js'
2
- import { prevNext } from '../../lib/nav.js'
3
- import { table } from '../../lib/layout.js'
4
- import { progress, stack } from '../../../../src/ui/index.js'
5
-
6
- const { prev, next } = prevNext('/components/progress')
7
-
8
- export default {
9
- route: '/components/progress',
10
- meta: {
11
- theme: 'light',
12
- title: 'Progress — Pulse Docs',
13
- description: 'Progress bar component for Pulse UI.',
14
- styles: ['/pulse-ui.css', '/theme.css', '/docs.css'],
15
- },
16
- view: () => renderComponentPage({
17
- currentHref: '/components/progress',
18
- prev,
19
- next,
20
- name: 'progress',
21
- description: 'Horizontal progress bar with determinate and indeterminate states. Correct <code>role="progressbar"</code> and ARIA attributes are set automatically.',
22
- content: `
23
-
24
- <h2 class="doc-h2" id="default">Default</h2>
25
- ${demo(
26
- stack({ gap: 'md', content:
27
- progress({ value: 25 }) +
28
- progress({ value: 50 }) +
29
- progress({ value: 75 }) +
30
- progress({ value: 100 }),
31
- }),
32
- `progress({ value: 25 })
33
- progress({ value: 50 })
34
- progress({ value: 75 })
35
- progress({ value: 100 })`,
36
- { col: true }
37
- )}
38
-
39
- <h2 class="doc-h2" id="label">With label and value</h2>
40
- <p>Set <code>showLabel</code> and <code>showValue</code> to render text above the bar.</p>
41
- ${demo(
42
- stack({ gap: 'lg', content:
43
- progress({ value: 68, label: 'Storage used', showLabel: true, showValue: true }) +
44
- progress({ value: 32, label: 'Upload complete', showLabel: true, showValue: true }),
45
- }),
46
- `progress({ value: 68, label: 'Storage used', showLabel: true, showValue: true })
47
- progress({ value: 32, label: 'Upload complete', showLabel: true, showValue: true })`,
48
- { col: true }
49
- )}
50
-
51
- <h2 class="doc-h2" id="variants">Variants</h2>
52
- ${demo(
53
- stack({ gap: 'md', content:
54
- progress({ value: 80, variant: 'accent' }) +
55
- progress({ value: 80, variant: 'success' }) +
56
- progress({ value: 80, variant: 'warning' }) +
57
- progress({ value: 80, variant: 'error' }),
58
- }),
59
- `progress({ value: 80, variant: 'accent' })
60
- progress({ value: 80, variant: 'success' })
61
- progress({ value: 80, variant: 'warning' })
62
- progress({ value: 80, variant: 'error' })`,
63
- { col: true }
64
- )}
65
-
66
- <h2 class="doc-h2" id="sizes">Sizes</h2>
67
- ${demo(
68
- stack({ gap: 'md', content:
69
- progress({ value: 60, size: 'sm' }) +
70
- progress({ value: 60, size: 'md' }) +
71
- progress({ value: 60, size: 'lg' }),
72
- }),
73
- `progress({ value: 60, size: 'sm' })
74
- progress({ value: 60, size: 'md' })
75
- progress({ value: 60, size: 'lg' })`,
76
- { col: true }
77
- )}
78
-
79
- <h2 class="doc-h2" id="indeterminate">Indeterminate</h2>
80
- <p>Omit <code>value</code> when the total duration is unknown — the bar animates continuously.</p>
81
- ${demo(
82
- progress({ label: 'Loading…' }),
83
- `progress({ label: 'Loading…' }) // no value = indeterminate`,
84
- { col: true }
85
- )}
86
-
87
- ${table(
88
- ['Prop', 'Type', 'Default', 'Description'],
89
- [
90
- ['<code>value</code>', 'number', '—', 'Current value. Omit for indeterminate.'],
91
- ['<code>max</code>', 'number', '100', ''],
92
- ['<code>label</code>', 'string', '—', 'Sets <code>aria-label</code> and the visible label when <code>showLabel</code> is true'],
93
- ['<code>showLabel</code>', 'boolean', 'false', 'Render label text above the bar'],
94
- ['<code>showValue</code>', 'boolean', 'false', 'Render percentage above the bar (right-aligned)'],
95
- ['<code>variant</code>', '<code>accent | success | warning | error</code>', '<code>accent</code>', ''],
96
- ['<code>size</code>', '<code>sm | md | lg</code>', '<code>md</code>', '.25rem / .5rem / 1rem'],
97
- ['<code>class</code>', 'string', '—', ''],
98
- ]
99
- )}
100
- `,
101
- }),
102
- }
@@ -1,111 +0,0 @@
1
- import { renderComponentPage, demo } from '../../lib/component-page.js'
2
- import { prevNext } from '../../lib/nav.js'
3
- import { table, callout } from '../../lib/layout.js'
4
- import { prose } from '../../../../src/ui/index.js'
5
-
6
- const { prev, next } = prevNext('/components/prose')
7
-
8
- const SAMPLE_HTML = `<h2>Why great coffee starts with water</h2>
9
- <p>Most home baristas obsess over beans and grinders, but water quality is the single biggest variable in your cup. Tap water that is too hard leaves bitter, chalky espresso. Too soft and your shots taste flat and lifeless.</p>
10
- <h3>The ideal mineral balance</h3>
11
- <p>Speciality roasters recommend water with a total dissolved solids (TDS) between <strong>75–150 mg/L</strong> and a magnesium content of at least 10 mg/L. Magnesium is the mineral most responsible for extracting the fruity, floral notes from light roasts.</p>
12
- <h3>What to do if your tap water is off</h3>
13
- <ul>
14
- <li>Use a filter jug — Brita Maxtra+ reduces hardness and chlorine</li>
15
- <li>Try third-wave water sachets — add to distilled water for precise control</li>
16
- <li>Blend tap with still mineral water to hit the right TDS range</li>
17
- </ul>
18
- <blockquote>Water is the ingredient you can control most precisely, yet almost nobody does.</blockquote>
19
- <p>Once your water is dialled in, even a modest grinder will produce noticeably better results. <a href="#">Read our water guide</a> for a full breakdown by region.</p>`
20
-
21
- export default {
22
- route: '/components/prose',
23
- meta: {
24
- theme: 'light',
25
- title: 'Prose — Pulse Docs',
26
- description: 'Typography wrapper for CMS output and rich text content in Pulse UI.',
27
- styles: ['/pulse-ui.css', '/theme.css', '/docs.css'],
28
- },
29
- view: () => renderComponentPage({
30
- currentHref: '/components/prose',
31
- prev,
32
- next,
33
- name: 'prose',
34
- description: 'Typography wrapper for rich text you don\'t control: CMS output, markdown-rendered HTML, database content, blog posts. Styles all descendant elements — headings, paragraphs, lists, blockquotes, code, tables — using <code>--ui-*</code> tokens. No classes needed on individual elements.',
35
- content: `
36
-
37
- <h2 class="doc-h2" id="basic">Basic</h2>
38
- <p>Pass any HTML string to <code>content</code>. All elements inside are styled automatically.</p>
39
- ${demo(
40
- prose({ content: SAMPLE_HTML }),
41
- `import { prose } from '@invisibleloop/pulse/ui'
42
-
43
- // CMS rich text field — output directly, fully styled
44
- prose({ content: server.article.bodyHtml })
45
-
46
- // Markdown file via md() helper
47
- prose({ content: server.post.html }) // see /markdown`,
48
- { col: true }
49
- )}
50
-
51
- <h2 class="doc-h2" id="size">Size</h2>
52
- <p>Scale the base font size for different contexts.</p>
53
- ${demo(
54
- `<div class="u-flex u-flex-col u-gap-8">
55
- <div>
56
- <p class="u-text-muted u-text-sm u-mb-3">sm — footnotes, sidebars</p>
57
- ${prose({ content: '<p>The quick brown fox jumps over the lazy dog. Pack my box with five dozen liquor jugs.</p><ul><li>Smaller text</li><li>Tighter line height</li></ul>', size: 'sm' })}
58
- </div>
59
- <div>
60
- <p class="u-text-muted u-text-sm u-mb-3">base (default)</p>
61
- ${prose({ content: '<p>The quick brown fox jumps over the lazy dog. Pack my box with five dozen liquor jugs.</p><ul><li>Default size</li><li>Standard line height</li></ul>' })}
62
- </div>
63
- <div>
64
- <p class="u-text-muted u-text-sm u-mb-3">lg — hero intro, feature descriptions</p>
65
- ${prose({ content: '<p>The quick brown fox jumps over the lazy dog. Pack my box with five dozen liquor jugs.</p><ul><li>Larger text</li><li>More generous spacing</li></ul>', size: 'lg' })}
66
- </div>
67
- </div>`,
68
- `prose({ content: cms.body, size: 'sm' }) // footnotes, sidebars
69
- prose({ content: cms.body }) // default
70
- prose({ content: cms.intro, size: 'lg' }) // hero intro`,
71
- { col: true }
72
- )}
73
-
74
- <h2 class="doc-h2" id="elements">Styled elements</h2>
75
- <p>Every common HTML element rendered inside <code>prose()</code> is styled using <code>--ui-*</code> tokens:</p>
76
- ${demo(
77
- prose({ content: `
78
- <h2>h2 heading</h2>
79
- <h3>h3 heading</h3>
80
- <p>A paragraph with <strong>bold</strong>, <em>italic</em>, and <a href="#">a link</a>. Also <code>inline code</code>.</p>
81
- <ul><li>Unordered item one</li><li>Unordered item two</li></ul>
82
- <ol><li>Ordered item one</li><li>Ordered item two</li></ol>
83
- <blockquote>A blockquote with an accent left border.</blockquote>
84
- <pre><code>// A code block
85
- const x = 1 + 2</code></pre>
86
- <hr>
87
- <p class="u-text-sm u-text-muted">End of content.</p>
88
- ` }),
89
- `prose({ content: \`
90
- <h2>Section heading</h2>
91
- <p>Paragraph with <strong>bold</strong> and <a href="#">a link</a>.</p>
92
- <ul><li>List item</li></ul>
93
- <blockquote>A quote.</blockquote>
94
- <pre><code>const x = 1</code></pre>
95
- \` })`,
96
- { col: true }
97
- )}
98
-
99
- ${callout('warning', '<strong>Do not escape the content prop.</strong> <code>prose()</code> renders raw HTML — it is designed for trusted server-side content only. Never pass unescaped user input directly. Sanitise CMS output before rendering if your CMS allows arbitrary HTML.')}
100
-
101
- ${table(
102
- ['Prop', 'Type', 'Default', 'Description'],
103
- [
104
- ['<code>content</code>', 'string', '—', 'Raw HTML string — rendered as-is, not escaped. Use for server-side content only.'],
105
- ['<code>size</code>', '<code>sm | base | lg</code>', '<code>base</code>', 'Base font size scale. <code>sm</code>=0.875rem, <code>base</code>=1rem, <code>lg</code>=1.125rem'],
106
- ['<code>class</code>', 'string', '—', 'Extra classes on the wrapper <code>&lt;div&gt;</code>'],
107
- ]
108
- )}
109
- `,
110
- }),
111
- }
@@ -1,71 +0,0 @@
1
- import { renderComponentPage, demo } from '../../lib/component-page.js'
2
- import { prevNext } from '../../lib/nav.js'
3
- import { table } from '../../lib/layout.js'
4
- import { pullquote } from '../../../../src/ui/index.js'
5
-
6
- const { prev, next } = prevNext('/components/pullquote')
7
-
8
- export default {
9
- route: '/components/pullquote',
10
- meta: {
11
- theme: 'light',
12
- title: 'Pullquote — Pulse Docs',
13
- description: 'Styled blockquote component for Pulse UI.',
14
- styles: ['/pulse-ui.css', '/theme.css', '/docs.css'],
15
- },
16
- view: () => renderComponentPage({
17
- currentHref: '/components/pullquote',
18
- prev,
19
- next,
20
- name: 'pullquote',
21
- description: 'Styled blockquote with an accent left border and optional attribution. Use it to highlight key quotes or testimonials within body content.',
22
- content: `
23
-
24
- <h2 class="doc-h2" id="basic">Basic</h2>
25
- ${demo(
26
- pullquote({
27
- quote: 'Design is not just what it looks like and feels like. Design is how it works.',
28
- cite: 'Steve Jobs, co-founder of Apple',
29
- }),
30
- `pullquote({
31
- quote: 'Design is not just what it looks like and feels like. Design is how it works.',
32
- cite: 'Steve Jobs, co-founder of Apple',
33
- })`
34
- )}
35
-
36
- <h2 class="doc-h2" id="large">Large</h2>
37
- ${demo(
38
- pullquote({
39
- quote: 'Simplicity is the ultimate sophistication.',
40
- cite: 'Leonardo da Vinci',
41
- size: 'lg',
42
- }),
43
- `pullquote({
44
- quote: 'Simplicity is the ultimate sophistication.',
45
- cite: 'Leonardo da Vinci',
46
- size: 'lg',
47
- })`
48
- )}
49
-
50
- <h2 class="doc-h2" id="no-cite">Without attribution</h2>
51
- ${demo(
52
- pullquote({
53
- quote: 'Good design makes a product understandable.',
54
- }),
55
- `pullquote({
56
- quote: 'Good design makes a product understandable.',
57
- })`
58
- )}
59
-
60
- ${table(
61
- ['Prop', 'Type', 'Default', 'Description'],
62
- [
63
- ['<code>quote</code>', 'string', '—', 'The quote text — escaped automatically'],
64
- ['<code>cite</code>', 'string', '—', 'Attribution text — rendered in a <code>&lt;figcaption&gt;</code>'],
65
- ['<code>size</code>', '<code>md | lg</code>', '<code>md</code>', 'Controls the font size of the quote text'],
66
- ['<code>class</code>', 'string', '—', ''],
67
- ]
68
- )}
69
- `,
70
- }),
71
- }