@invisibleloop/pulse 0.1.21

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 (344) hide show
  1. package/.claude/commands/build-page.md +59 -0
  2. package/.claude/commands/new-doc-page.md +45 -0
  3. package/.claude/commands/verify.md +52 -0
  4. package/.claude/pulse-checklist.md +111 -0
  5. package/.claude/settings.local.json +102 -0
  6. package/.github/workflows/ci.yml +22 -0
  7. package/.github/workflows/publish.yml +41 -0
  8. package/.pulse/load-reports/home/1773432711417.json +22 -0
  9. package/CLAUDE.md +383 -0
  10. package/README.md +95 -0
  11. package/docs/.claude/pulse-checklist.md +111 -0
  12. package/docs/public/.pulse-ui-version +1 -0
  13. package/docs/public/dist/accessibility.boot-5DVTARJU.js +115 -0
  14. package/docs/public/dist/actions.boot-P66HKQEM.js +164 -0
  15. package/docs/public/dist/auth.boot-IMAJAUPH.js +140 -0
  16. package/docs/public/dist/caching.boot-DVR6KDE7.js +53 -0
  17. package/docs/public/dist/components--accordion.boot-3HVKMNWC.js +11 -0
  18. package/docs/public/dist/components--alert.boot-GCEXOZAC.js +6 -0
  19. package/docs/public/dist/components--app-badge.boot-DVT3GCHJ.js +6 -0
  20. package/docs/public/dist/components--avatar.boot-PSW24EVA.js +5 -0
  21. package/docs/public/dist/components--badge.boot-TYDY2RMK.js +7 -0
  22. package/docs/public/dist/components--banner.boot-EI5PZSZK.js +7 -0
  23. package/docs/public/dist/components--breadcrumbs.boot-SMA2E2GO.js +34 -0
  24. package/docs/public/dist/components--button.boot-J54BQM2E.js +23 -0
  25. package/docs/public/dist/components--card.boot-PZGNDIB6.js +138 -0
  26. package/docs/public/dist/components--carousel.boot-TP6LPFZZ.js +12 -0
  27. package/docs/public/dist/components--charts.boot-2EOYQWKL.js +108 -0
  28. package/docs/public/dist/components--checkbox.boot-DS5BSL6T.js +54 -0
  29. package/docs/public/dist/components--cluster.boot-HHVIBBJG.js +9 -0
  30. package/docs/public/dist/components--code-window.boot-2GR2DV33.js +20 -0
  31. package/docs/public/dist/components--container.boot-7LOOGK2K.js +5 -0
  32. package/docs/public/dist/components--cta.boot-FSNZ5YRT.js +11 -0
  33. package/docs/public/dist/components--divider.boot-3NI2C3QG.js +6 -0
  34. package/docs/public/dist/components--empty.boot-YX2UR3PV.js +7 -0
  35. package/docs/public/dist/components--feature.boot-MUD7NSUO.js +13 -0
  36. package/docs/public/dist/components--fieldset.boot-J7BYHMKF.js +19 -0
  37. package/docs/public/dist/components--fileupload.boot-NIKVTTPD.js +52 -0
  38. package/docs/public/dist/components--footer.boot-EYUK5FRG.js +14 -0
  39. package/docs/public/dist/components--grid.boot-URDQVDDR.js +59 -0
  40. package/docs/public/dist/components--heading.boot-BPQKU43E.js +44 -0
  41. package/docs/public/dist/components--hero.boot-4RAPRGAB.js +17 -0
  42. package/docs/public/dist/components--icons.boot-ZITNU5JP.js +68 -0
  43. package/docs/public/dist/components--image.boot-XEEGHQZF.js +19 -0
  44. package/docs/public/dist/components--input.boot-SGASZG5K.js +7 -0
  45. package/docs/public/dist/components--list.boot-W3XC5MHD.js +55 -0
  46. package/docs/public/dist/components--media.boot-5VFIETZO.js +13 -0
  47. package/docs/public/dist/components--modal.boot-RZUYXBN2.js +47 -0
  48. package/docs/public/dist/components--nav.boot-ODBOHU7O.js +33 -0
  49. package/docs/public/dist/components--pricing.boot-4AQ4ZVBY.js +21 -0
  50. package/docs/public/dist/components--progress.boot-GHAGYZOK.js +30 -0
  51. package/docs/public/dist/components--prose.boot-QANJL6JI.js +67 -0
  52. package/docs/public/dist/components--pullquote.boot-Q2WMNAZU.js +22 -0
  53. package/docs/public/dist/components--radio.boot-TJRDQ2OL.js +75 -0
  54. package/docs/public/dist/components--rating.boot-QBAN6DEL.js +38 -0
  55. package/docs/public/dist/components--search.boot-PXH5O5AG.js +17 -0
  56. package/docs/public/dist/components--section.boot-AQGIYHWW.js +12 -0
  57. package/docs/public/dist/components--segmented.boot-BEVTKEJO.js +33 -0
  58. package/docs/public/dist/components--select.boot-47X5RHOC.js +10 -0
  59. package/docs/public/dist/components--slider.boot-PSRRX7XL.js +47 -0
  60. package/docs/public/dist/components--spinner.boot-MZ5MO2OH.js +22 -0
  61. package/docs/public/dist/components--stack.boot-DI4NJXBF.js +9 -0
  62. package/docs/public/dist/components--stat.boot-QMFUWBQT.js +9 -0
  63. package/docs/public/dist/components--stepper.boot-34PP2NEV.js +22 -0
  64. package/docs/public/dist/components--table.boot-FCQGSFIQ.js +11 -0
  65. package/docs/public/dist/components--testimonial.boot-DWQPDKYG.js +11 -0
  66. package/docs/public/dist/components--textarea.boot-QVXLBOJ5.js +4 -0
  67. package/docs/public/dist/components--timeline.boot-26LN52P2.js +95 -0
  68. package/docs/public/dist/components--toggle.boot-IQQEI76S.js +29 -0
  69. package/docs/public/dist/components--tooltip.boot-LGHCO6NN.js +9 -0
  70. package/docs/public/dist/components.boot-SE6PQ4P7.js +103 -0
  71. package/docs/public/dist/config.boot-DTRRWUE6.js +126 -0
  72. package/docs/public/dist/constraints.boot-DUHDZBMC.js +71 -0
  73. package/docs/public/dist/deploy.boot-SLAD3NI2.js +163 -0
  74. package/docs/public/dist/docs-8e3d4b5c.css +1 -0
  75. package/docs/public/dist/extending.boot-UA3CN243.js +159 -0
  76. package/docs/public/dist/faq.boot-6EQAWLQR.js +43 -0
  77. package/docs/public/dist/getting-started.boot-TDKIFL5U.js +86 -0
  78. package/docs/public/dist/guard.boot-AUHAWTG4.js +80 -0
  79. package/docs/public/dist/home.boot-BVQXRH32.js +383 -0
  80. package/docs/public/dist/how-it-works.boot-LTWAKWKW.js +104 -0
  81. package/docs/public/dist/hydration.boot-JRM6IPJL.js +78 -0
  82. package/docs/public/dist/images.boot-M6ZVKTZS.js +80 -0
  83. package/docs/public/dist/manifest.json +94 -0
  84. package/docs/public/dist/meta.boot-7NXGPHR4.js +79 -0
  85. package/docs/public/dist/mutations.boot-F6F43UDX.js +79 -0
  86. package/docs/public/dist/navigation.boot-AOXWS3ZF.js +57 -0
  87. package/docs/public/dist/performance.boot-C3UPCOBK.js +98 -0
  88. package/docs/public/dist/persist.boot-WT32PQOQ.js +61 -0
  89. package/docs/public/dist/project-structure.boot-FB3LRVJ4.js +63 -0
  90. package/docs/public/dist/prompt-examples.boot-YKR4VDK4.js +31 -0
  91. package/docs/public/dist/pulse-ui-81a85c03.css +1 -0
  92. package/docs/public/dist/raw-responses.boot-M4KA5YXL.js +104 -0
  93. package/docs/public/dist/routing.boot-FNX5FDGH.js +70 -0
  94. package/docs/public/dist/runtime-B73WLANC.js +1 -0
  95. package/docs/public/dist/runtime-KO4BHUQ3.js +49 -0
  96. package/docs/public/dist/runtime-L2HNXIHW.js +59 -0
  97. package/docs/public/dist/runtime-QFURDKA2.js +5 -0
  98. package/docs/public/dist/runtime-UVPXO4IR.js +375 -0
  99. package/docs/public/dist/runtime-VMJA3Z4N.js +10 -0
  100. package/docs/public/dist/runtime-ZJ4FXT5O.js +11 -0
  101. package/docs/public/dist/server-api.boot-K7X3LCFB.js +219 -0
  102. package/docs/public/dist/server-data.boot-Y7HQYC4R.js +157 -0
  103. package/docs/public/dist/slash-commands.boot-V2UV7OW2.js +26 -0
  104. package/docs/public/dist/spec.boot-2WU7ZHCV.js +159 -0
  105. package/docs/public/dist/state.boot-B24GUE3R.js +73 -0
  106. package/docs/public/dist/store.boot-TLIB4XHH.js +150 -0
  107. package/docs/public/dist/streaming.boot-W2DZSMW4.js +80 -0
  108. package/docs/public/dist/stripe.boot-QN3C2GEL.js +164 -0
  109. package/docs/public/dist/supabase.boot-BG4XXLZE.js +303 -0
  110. package/docs/public/dist/testing.boot-6U4WKMTE.js +130 -0
  111. package/docs/public/dist/validation.boot-PQHYGW5B.js +100 -0
  112. package/docs/public/docs.css +2020 -0
  113. package/docs/public/menu.js +83 -0
  114. package/docs/public/pulse-ui.css +2739 -0
  115. package/docs/public/pulse-ui.js +236 -0
  116. package/docs/server.js +192 -0
  117. package/docs/src/lib/component-page.js +47 -0
  118. package/docs/src/lib/highlight.js +255 -0
  119. package/docs/src/lib/layout.js +131 -0
  120. package/docs/src/lib/metrics-store.js +6 -0
  121. package/docs/src/lib/nav.js +159 -0
  122. package/docs/src/lib/stats.js +81 -0
  123. package/docs/src/pages/accessibility.js +157 -0
  124. package/docs/src/pages/actions.js +191 -0
  125. package/docs/src/pages/auth.js +177 -0
  126. package/docs/src/pages/caching.js +95 -0
  127. package/docs/src/pages/components/accordion.js +48 -0
  128. package/docs/src/pages/components/alert.js +35 -0
  129. package/docs/src/pages/components/app-badge.js +41 -0
  130. package/docs/src/pages/components/avatar.js +35 -0
  131. package/docs/src/pages/components/badge.js +36 -0
  132. package/docs/src/pages/components/banner.js +45 -0
  133. package/docs/src/pages/components/breadcrumbs.js +94 -0
  134. package/docs/src/pages/components/button.js +84 -0
  135. package/docs/src/pages/components/card.js +225 -0
  136. package/docs/src/pages/components/carousel.js +72 -0
  137. package/docs/src/pages/components/charts.js +278 -0
  138. package/docs/src/pages/components/checkbox.js +129 -0
  139. package/docs/src/pages/components/cluster.js +47 -0
  140. package/docs/src/pages/components/code-window.js +57 -0
  141. package/docs/src/pages/components/container.js +40 -0
  142. package/docs/src/pages/components/cta.js +53 -0
  143. package/docs/src/pages/components/divider.js +37 -0
  144. package/docs/src/pages/components/empty.js +36 -0
  145. package/docs/src/pages/components/feature.js +60 -0
  146. package/docs/src/pages/components/fieldset.js +65 -0
  147. package/docs/src/pages/components/fileupload.js +127 -0
  148. package/docs/src/pages/components/footer.js +58 -0
  149. package/docs/src/pages/components/grid.js +165 -0
  150. package/docs/src/pages/components/heading.js +107 -0
  151. package/docs/src/pages/components/hero.js +65 -0
  152. package/docs/src/pages/components/icons.js +285 -0
  153. package/docs/src/pages/components/image.js +71 -0
  154. package/docs/src/pages/components/input.js +51 -0
  155. package/docs/src/pages/components/list.js +112 -0
  156. package/docs/src/pages/components/media.js +51 -0
  157. package/docs/src/pages/components/modal.js +111 -0
  158. package/docs/src/pages/components/nav.js +86 -0
  159. package/docs/src/pages/components/pricing.js +68 -0
  160. package/docs/src/pages/components/progress.js +102 -0
  161. package/docs/src/pages/components/prose.js +111 -0
  162. package/docs/src/pages/components/pullquote.js +71 -0
  163. package/docs/src/pages/components/radio.js +194 -0
  164. package/docs/src/pages/components/rating.js +106 -0
  165. package/docs/src/pages/components/search.js +61 -0
  166. package/docs/src/pages/components/section.js +59 -0
  167. package/docs/src/pages/components/segmented.js +121 -0
  168. package/docs/src/pages/components/select.js +45 -0
  169. package/docs/src/pages/components/slider.js +114 -0
  170. package/docs/src/pages/components/spinner.js +73 -0
  171. package/docs/src/pages/components/stack.js +48 -0
  172. package/docs/src/pages/components/stat.js +55 -0
  173. package/docs/src/pages/components/stepper.js +66 -0
  174. package/docs/src/pages/components/table.js +45 -0
  175. package/docs/src/pages/components/testimonial.js +49 -0
  176. package/docs/src/pages/components/textarea.js +31 -0
  177. package/docs/src/pages/components/timeline.js +227 -0
  178. package/docs/src/pages/components/toggle.js +84 -0
  179. package/docs/src/pages/components/tooltip.js +48 -0
  180. package/docs/src/pages/components.js +204 -0
  181. package/docs/src/pages/config.js +193 -0
  182. package/docs/src/pages/constraints.js +99 -0
  183. package/docs/src/pages/deploy.js +233 -0
  184. package/docs/src/pages/extending.js +198 -0
  185. package/docs/src/pages/faq.js +96 -0
  186. package/docs/src/pages/getting-started.js +106 -0
  187. package/docs/src/pages/guard.js +121 -0
  188. package/docs/src/pages/home.js +401 -0
  189. package/docs/src/pages/how-it-works.js +183 -0
  190. package/docs/src/pages/hydration.js +98 -0
  191. package/docs/src/pages/images.js +121 -0
  192. package/docs/src/pages/meta.js +120 -0
  193. package/docs/src/pages/mutations.js +106 -0
  194. package/docs/src/pages/navigation.js +85 -0
  195. package/docs/src/pages/performance.js +157 -0
  196. package/docs/src/pages/persist.js +88 -0
  197. package/docs/src/pages/project-structure.js +90 -0
  198. package/docs/src/pages/prompt-examples.js +186 -0
  199. package/docs/src/pages/raw-responses.js +124 -0
  200. package/docs/src/pages/routing.js +99 -0
  201. package/docs/src/pages/server-api.js +281 -0
  202. package/docs/src/pages/server-data.js +185 -0
  203. package/docs/src/pages/slash-commands.js +55 -0
  204. package/docs/src/pages/spec.js +207 -0
  205. package/docs/src/pages/state.js +101 -0
  206. package/docs/src/pages/store.js +181 -0
  207. package/docs/src/pages/streaming.js +108 -0
  208. package/docs/src/pages/stripe.js +193 -0
  209. package/docs/src/pages/supabase.js +323 -0
  210. package/docs/src/pages/testing.js +198 -0
  211. package/docs/src/pages/validation.js +138 -0
  212. package/examples/contact.js +166 -0
  213. package/examples/counter.js +94 -0
  214. package/examples/dev.server.js +91 -0
  215. package/examples/examples.test.js +394 -0
  216. package/examples/pricing.js +244 -0
  217. package/examples/products.js +191 -0
  218. package/examples/quiz.js +208 -0
  219. package/examples/shared.js +78 -0
  220. package/examples/todos.js +162 -0
  221. package/package.json +75 -0
  222. package/public/.pulse-ui-version +1 -0
  223. package/public/chippy-bird.css +246 -0
  224. package/public/examples/contact.css +119 -0
  225. package/public/examples/counter.css +79 -0
  226. package/public/examples/pricing.css +132 -0
  227. package/public/examples/products.css +100 -0
  228. package/public/examples/quiz.css +200 -0
  229. package/public/examples/todos.css +137 -0
  230. package/public/favicon.ico +0 -0
  231. package/public/log-dashboard.css +383 -0
  232. package/public/pulse-ui.css +2740 -0
  233. package/public/pulse-ui.js +236 -0
  234. package/public/pulse.css +149 -0
  235. package/scripts/build.js +411 -0
  236. package/src/agent/checklist.md +111 -0
  237. package/src/agent/coverage-check.js +66 -0
  238. package/src/agent/guide-components.md +274 -0
  239. package/src/agent/guide-examples.md +54 -0
  240. package/src/agent/guide-routing.md +36 -0
  241. package/src/agent/guide-server.md +258 -0
  242. package/src/agent/guide-spec.md +103 -0
  243. package/src/agent/guide-styles.md +191 -0
  244. package/src/agent/guide.md +979 -0
  245. package/src/agent/identity.md +106 -0
  246. package/src/agent/workflow.md +108 -0
  247. package/src/cli/cli.test.js +82 -0
  248. package/src/cli/dev.js +195 -0
  249. package/src/cli/discover.js +113 -0
  250. package/src/cli/index.js +361 -0
  251. package/src/cli/load-report.js +91 -0
  252. package/src/cli/load-runner.js +121 -0
  253. package/src/cli/report-server.js +723 -0
  254. package/src/cli/report.js +116 -0
  255. package/src/cli/scaffold.archive.js +1371 -0
  256. package/src/cli/scaffold.js +349 -0
  257. package/src/cli/start.js +74 -0
  258. package/src/html.js +19 -0
  259. package/src/mcp/server.js +884 -0
  260. package/src/mcp/validate-worker.js +110 -0
  261. package/src/runtime/image.js +74 -0
  262. package/src/runtime/image.test.js +111 -0
  263. package/src/runtime/index.js +621 -0
  264. package/src/runtime/navigate.js +146 -0
  265. package/src/runtime/runtime.test.js +773 -0
  266. package/src/runtime/ssr.js +464 -0
  267. package/src/runtime/ssr.test.js +421 -0
  268. package/src/runtime/store.js +92 -0
  269. package/src/runtime/toast.js +163 -0
  270. package/src/server/index.js +1386 -0
  271. package/src/server/server.test.js +1248 -0
  272. package/src/spec/schema.js +428 -0
  273. package/src/spec/schema.test.js +291 -0
  274. package/src/store/index.js +102 -0
  275. package/src/store/store.test.js +210 -0
  276. package/src/testing/html.js +283 -0
  277. package/src/testing/index.js +249 -0
  278. package/src/testing/testing.test.js +450 -0
  279. package/src/ui/accordion.js +28 -0
  280. package/src/ui/alert.js +43 -0
  281. package/src/ui/app-badge.js +48 -0
  282. package/src/ui/avatar.js +47 -0
  283. package/src/ui/badge.js +24 -0
  284. package/src/ui/banner.js +26 -0
  285. package/src/ui/breadcrumbs.js +38 -0
  286. package/src/ui/button.js +66 -0
  287. package/src/ui/card.js +34 -0
  288. package/src/ui/carousel.js +59 -0
  289. package/src/ui/charts.js +321 -0
  290. package/src/ui/checkbox.js +65 -0
  291. package/src/ui/cluster.js +44 -0
  292. package/src/ui/code-window.js +39 -0
  293. package/src/ui/container.js +24 -0
  294. package/src/ui/cta.js +37 -0
  295. package/src/ui/divider.js +29 -0
  296. package/src/ui/empty.js +33 -0
  297. package/src/ui/feature.js +33 -0
  298. package/src/ui/fieldset.js +37 -0
  299. package/src/ui/fileupload.js +89 -0
  300. package/src/ui/footer.js +38 -0
  301. package/src/ui/grid.js +36 -0
  302. package/src/ui/heading.js +45 -0
  303. package/src/ui/hero.js +37 -0
  304. package/src/ui/icons.js +161 -0
  305. package/src/ui/index.js +89 -0
  306. package/src/ui/input.js +74 -0
  307. package/src/ui/list.js +36 -0
  308. package/src/ui/media.js +44 -0
  309. package/src/ui/modal.js +80 -0
  310. package/src/ui/nav.js +61 -0
  311. package/src/ui/pricing.js +56 -0
  312. package/src/ui/progress.js +62 -0
  313. package/src/ui/prose.js +29 -0
  314. package/src/ui/pullquote.js +34 -0
  315. package/src/ui/radio.js +102 -0
  316. package/src/ui/rating.js +93 -0
  317. package/src/ui/search.js +77 -0
  318. package/src/ui/section.js +69 -0
  319. package/src/ui/segmented.js +50 -0
  320. package/src/ui/select.js +77 -0
  321. package/src/ui/slider.js +84 -0
  322. package/src/ui/spinner.js +34 -0
  323. package/src/ui/stack.js +36 -0
  324. package/src/ui/stat.js +52 -0
  325. package/src/ui/stepper.js +46 -0
  326. package/src/ui/switch.js +57 -0
  327. package/src/ui/table.js +45 -0
  328. package/src/ui/testimonial.js +48 -0
  329. package/src/ui/textarea.js +72 -0
  330. package/src/ui/timeline.js +72 -0
  331. package/src/ui/tooltip.js +28 -0
  332. package/src/ui/ui.test.js +1241 -0
  333. package/src/ui/uiimage.js +65 -0
  334. package/tsconfig.json +13 -0
  335. package/types/html.d.ts +17 -0
  336. package/types/image.d.ts +70 -0
  337. package/types/index.d.ts +7 -0
  338. package/types/navigate.d.ts +38 -0
  339. package/types/runtime.d.ts +63 -0
  340. package/types/schema.d.ts +243 -0
  341. package/types/server.d.ts +145 -0
  342. package/types/ssr.d.ts +110 -0
  343. package/types/testing.d.ts +154 -0
  344. package/types/ui.d.ts +704 -0
@@ -0,0 +1,107 @@
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 { heading, card } from '../../../../src/ui/index.js'
5
+
6
+ const { prev, next } = prevNext('/components/heading')
7
+
8
+ export default {
9
+ route: '/components/heading',
10
+ meta: {
11
+ title: 'Heading — Pulse Docs',
12
+ description: 'Styled semantic heading component for Pulse UI.',
13
+ styles: ['/pulse-ui.css', '/docs.css'],
14
+ },
15
+ state: {},
16
+ view: () => renderComponentPage({
17
+ currentHref: '/components/heading',
18
+ prev,
19
+ next,
20
+ name: 'heading',
21
+ description: 'Styled semantic heading. Renders the correct HTML tag (<code>h1</code>–<code>h6</code>) with consistent typography tokens. Use this instead of raw heading tags so you get the right visual style without remembering utility classes.',
22
+ content: `
23
+
24
+ <h2 class="doc-h2" id="levels">All levels</h2>
25
+ <p>Each level has a default visual size. The semantic tag and visual size are independent — see <a href="#size-override">size override</a> below.</p>
26
+ ${demo(
27
+ [1, 2, 3, 4, 5, 6].map(l => heading({ level: l, text: `Heading level ${l}` })).join(''),
28
+ `heading({ level: 1, text: 'Page title' })
29
+ heading({ level: 2, text: 'Section title' })
30
+ heading({ level: 3, text: 'Subsection' })
31
+ heading({ level: 4, text: 'Card heading' })
32
+ heading({ level: 5, text: 'Label' })
33
+ heading({ level: 6, text: 'Caption' })`,
34
+ { col: true }
35
+ )}
36
+
37
+ <h2 class="doc-h2" id="size-override">Size override</h2>
38
+ <p>The <code>size</code> prop lets you use a different visual scale than the default for that level. Useful when you need the correct semantic structure for SEO or accessibility but want a different visual weight.</p>
39
+ ${demo(
40
+ [
41
+ heading({ level: 2, text: 'Semantic h2, looks like h4', size: 'xl' }),
42
+ heading({ level: 3, text: 'Semantic h3, extra large', size: '4xl' }),
43
+ ].join(''),
44
+ `// h2 for structure, but visually smaller
45
+ heading({ level: 2, text: 'Related articles', size: 'xl' })
46
+
47
+ // h3 that needs to be visually prominent
48
+ heading({ level: 3, text: 'Featured', size: '4xl' })`,
49
+ { col: true }
50
+ )}
51
+
52
+ <h2 class="doc-h2" id="color">Color</h2>
53
+ ${demo(
54
+ [
55
+ heading({ level: 3, text: 'Default colour' }),
56
+ heading({ level: 3, text: 'Muted — for secondary labels', color: 'muted' }),
57
+ heading({ level: 3, text: 'Accent — for highlights', color: 'accent' }),
58
+ ].join(''),
59
+ `heading({ level: 3, text: 'Default colour' })
60
+ heading({ level: 3, text: 'Muted — for secondary labels', color: 'muted' })
61
+ heading({ level: 3, text: 'Accent — for highlights', color: 'accent' })`,
62
+ { col: true }
63
+ )}
64
+
65
+ <h2 class="doc-h2" id="spacing">Spacing</h2>
66
+ <p><code>heading()</code> adds no margin. Use <code>u-mt-*</code> and <code>u-mb-*</code> utility classes to control spacing in context.</p>
67
+ ${demo(
68
+ card({ content: `
69
+ ${heading({ level: 2, text: 'Account settings', class: 'u-mb-2' })}
70
+ <p class="u-text-muted u-text-sm">Manage your profile and preferences.</p>
71
+ `}),
72
+ `card({ content: \`
73
+ \${heading({ level: 2, text: 'Account settings', class: 'u-mb-2' })}
74
+ <p class="u-text-muted u-text-sm">Manage your profile and preferences.</p>
75
+ \`})`,
76
+ { col: true }
77
+ )}
78
+
79
+ <h2 class="doc-h2" id="balance">Preventing orphans</h2>
80
+ <p>When a heading wraps across lines, the last line can be left with a single short word — an orphan. The <code>balance</code> prop adds <code>text-wrap: balance</code>, which distributes text evenly across all lines so no word is stranded.</p>
81
+ ${demo(
82
+ [
83
+ heading({ level: 2, text: 'The quick brown fox jumps over the lazy dog tonight' }),
84
+ heading({ level: 2, text: 'The quick brown fox jumps over the lazy dog tonight', balance: true }),
85
+ ].join('<p class="u-text-muted u-text-sm u-mt-2 u-mb-4">↑ without balance — ↓ with balance: true</p>'),
86
+ `// Without — last line may have a single word
87
+ heading({ level: 2, text: 'The quick brown fox jumps over the lazy dog tonight' })
88
+
89
+ // With — text distributed evenly across lines
90
+ heading({ level: 2, text: 'The quick brown fox jumps over the lazy dog tonight', balance: true })`,
91
+ { col: true }
92
+ )}
93
+
94
+ ${table(
95
+ ['Prop', 'Type', 'Default', ''],
96
+ [
97
+ ['<code>level</code>', 'number (1–6)', '2', 'Controls both the HTML tag and the default visual size'],
98
+ ['<code>text</code>', 'string', '—', 'Heading text — escaped automatically'],
99
+ ['<code>size</code>', '<code>xs | sm | base | lg | xl | 2xl | 3xl | 4xl</code>', '—', 'Override the visual font size independently of the semantic level. Defaults: h1=4xl, h2=3xl, h3=2xl, h4=xl, h5=base, h6=sm'],
100
+ ['<code>color</code>', '<code>default | muted | accent</code>', '<code>default</code>', 'Text colour token'],
101
+ ['<code>balance</code>', 'boolean', '<code>false</code>', 'Adds <code>text-wrap: balance</code> — prevents orphaned words on the last line when the heading wraps'],
102
+ ['<code>class</code>', 'string', '—', 'Extra classes — use for spacing utilities such as <code>u-mb-4</code>'],
103
+ ]
104
+ )}
105
+ `,
106
+ }),
107
+ }
@@ -0,0 +1,65 @@
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 { hero, appBadge } from '../../../../src/ui/index.js'
5
+
6
+ const { prev, next } = prevNext('/components/hero')
7
+
8
+ export default {
9
+ route: '/components/hero',
10
+ meta: {
11
+ title: 'Hero — Pulse Docs',
12
+ description: 'Hero component for Pulse UI.',
13
+ styles: ['/pulse-ui.css', '/docs.css'],
14
+ },
15
+ state: {},
16
+ view: () => renderComponentPage({
17
+ currentHref: '/components/hero',
18
+ prev,
19
+ next,
20
+ name: 'hero',
21
+ description: 'Full-width hero section. The <code>actions</code> slot accepts any combination of <code>button()</code> and <code>appBadge()</code> calls. Set <code>align: \'left\'</code> for a split-layout hero. Use <code>size: \'sm\'</code> for inner-page headers that don\'t need the full vertical padding.',
22
+ content: `
23
+ ${demo(
24
+ hero({
25
+ eyebrow: 'Now available',
26
+ title: 'The app your phone deserves',
27
+ subtitle: 'Beautifully simple. Ridiculously fast. Available on iOS and Android.',
28
+ actions: appBadge({ store: 'apple', href: '#' }) + ' ' + appBadge({ store: 'google', href: '#' }),
29
+ }),
30
+ `hero({
31
+ eyebrow: 'Now available',
32
+ title: 'The app your phone deserves',
33
+ subtitle: 'Beautifully simple. Ridiculously fast.',
34
+ actions: appBadge({ store: 'apple', href: appStoreUrl }) +
35
+ appBadge({ store: 'google', href: playStoreUrl }),
36
+ })`
37
+ )}
38
+
39
+ ${demo(
40
+ hero({
41
+ title: 'Blog',
42
+ subtitle: 'Thoughts on building for the web.',
43
+ size: 'sm',
44
+ }),
45
+ `hero({
46
+ title: 'Blog',
47
+ subtitle: 'Thoughts on building for the web.',
48
+ size: 'sm',
49
+ })`
50
+ )}
51
+
52
+ ${table(
53
+ ['Prop', 'Type', 'Default', ''],
54
+ [
55
+ ['<code>eyebrow</code>', 'string', '—', 'Small label above the title'],
56
+ ['<code>title</code>', 'string', '—', ''],
57
+ ['<code>subtitle</code>', 'string', '—', ''],
58
+ ['<code>actions</code>', 'string (HTML)', '—', 'Raw HTML slot'],
59
+ ['<code>align</code>', 'string', "'center'", "'center' or 'left'"],
60
+ ['<code>size</code>', 'string', "'md'", "'md' (5rem padding) or 'sm' (2.5rem top, no bottom) — use sm for inner-page headers"],
61
+ ]
62
+ )}
63
+ `,
64
+ }),
65
+ }
@@ -0,0 +1,285 @@
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 {
5
+ button, feature,
6
+ iconArrowLeft, iconArrowRight, iconArrowUp, iconArrowDown,
7
+ iconChevronLeft, iconChevronRight, iconChevronUp, iconChevronDown,
8
+ iconExternalLink, iconMenu, iconX, iconMoreHorizontal, iconMoreVertical,
9
+ iconCheck, iconCheckCircle, iconXCircle, iconAlertCircle, iconAlertTriangle, iconInfo,
10
+ iconPlus, iconMinus, iconEdit, iconTrash, iconCopy, iconSearch, iconFilter,
11
+ iconDownload, iconUpload, iconRefresh, iconSend,
12
+ iconEye, iconEyeOff, iconLock, iconUnlock, iconSettings, iconBell,
13
+ iconUser, iconUsers, iconMail, iconMessageSquare,
14
+ iconHome, iconLogOut, iconLogIn,
15
+ iconFile, iconImage, iconLink, iconCode, iconCalendar, iconClock, iconBookmark, iconTag,
16
+ iconPlay, iconPause, iconVolume, iconStar, iconHeart,
17
+ iconPhone, iconGamepad,
18
+ iconHandPointUp, iconHandPointDown, iconHandPointLeft, iconHandPointRight,
19
+ iconGlobe, iconShield, iconZap, iconTrendingUp, iconTrendingDown, iconLoader, iconGrid, iconBug,
20
+ iconMapPin,
21
+ iconSun, iconMoon,
22
+ } from '../../../../src/ui/index.js'
23
+
24
+ const { prev, next } = prevNext('/components/icons')
25
+
26
+ // All icons grouped for the visual grid
27
+ const ICONS = [
28
+ { group: 'Navigation & Direction', icons: [
29
+ { name: 'iconArrowLeft', fn: iconArrowLeft },
30
+ { name: 'iconArrowRight', fn: iconArrowRight },
31
+ { name: 'iconArrowUp', fn: iconArrowUp },
32
+ { name: 'iconArrowDown', fn: iconArrowDown },
33
+ { name: 'iconChevronLeft', fn: iconChevronLeft },
34
+ { name: 'iconChevronRight', fn: iconChevronRight },
35
+ { name: 'iconChevronUp', fn: iconChevronUp },
36
+ { name: 'iconChevronDown', fn: iconChevronDown },
37
+ { name: 'iconExternalLink', fn: iconExternalLink },
38
+ { name: 'iconMenu', fn: iconMenu },
39
+ { name: 'iconX', fn: iconX },
40
+ { name: 'iconMoreHorizontal', fn: iconMoreHorizontal },
41
+ { name: 'iconMoreVertical', fn: iconMoreVertical },
42
+ ]},
43
+ { group: 'Status', icons: [
44
+ { name: 'iconCheck', fn: iconCheck },
45
+ { name: 'iconCheckCircle', fn: iconCheckCircle },
46
+ { name: 'iconXCircle', fn: iconXCircle },
47
+ { name: 'iconAlertCircle', fn: iconAlertCircle },
48
+ { name: 'iconAlertTriangle', fn: iconAlertTriangle },
49
+ { name: 'iconInfo', fn: iconInfo },
50
+ ]},
51
+ { group: 'Actions', icons: [
52
+ { name: 'iconPlus', fn: iconPlus },
53
+ { name: 'iconMinus', fn: iconMinus },
54
+ { name: 'iconEdit', fn: iconEdit },
55
+ { name: 'iconTrash', fn: iconTrash },
56
+ { name: 'iconCopy', fn: iconCopy },
57
+ { name: 'iconSearch', fn: iconSearch },
58
+ { name: 'iconFilter', fn: iconFilter },
59
+ { name: 'iconDownload', fn: iconDownload },
60
+ { name: 'iconUpload', fn: iconUpload },
61
+ { name: 'iconRefresh', fn: iconRefresh },
62
+ { name: 'iconSend', fn: iconSend },
63
+ ]},
64
+ { group: 'UI Controls', icons: [
65
+ { name: 'iconEye', fn: iconEye },
66
+ { name: 'iconEyeOff', fn: iconEyeOff },
67
+ { name: 'iconLock', fn: iconLock },
68
+ { name: 'iconUnlock', fn: iconUnlock },
69
+ { name: 'iconSettings', fn: iconSettings },
70
+ { name: 'iconBell', fn: iconBell },
71
+ ]},
72
+ { group: 'People & Communication', icons: [
73
+ { name: 'iconUser', fn: iconUser },
74
+ { name: 'iconUsers', fn: iconUsers },
75
+ { name: 'iconMail', fn: iconMail },
76
+ { name: 'iconMessageSquare', fn: iconMessageSquare },
77
+ ]},
78
+ { group: 'Navigation & Pages', icons: [
79
+ { name: 'iconHome', fn: iconHome },
80
+ { name: 'iconMapPin', fn: iconMapPin },
81
+ { name: 'iconLogOut', fn: iconLogOut },
82
+ { name: 'iconLogIn', fn: iconLogIn },
83
+ ]},
84
+ { group: 'Content & Files', icons: [
85
+ { name: 'iconFile', fn: iconFile },
86
+ { name: 'iconImage', fn: iconImage },
87
+ { name: 'iconLink', fn: iconLink },
88
+ { name: 'iconCode', fn: iconCode },
89
+ { name: 'iconCalendar', fn: iconCalendar },
90
+ { name: 'iconClock', fn: iconClock },
91
+ { name: 'iconBookmark', fn: iconBookmark },
92
+ { name: 'iconTag', fn: iconTag },
93
+ ]},
94
+ { group: 'Media & Rating', icons: [
95
+ { name: 'iconPlay', fn: iconPlay },
96
+ { name: 'iconPause', fn: iconPause },
97
+ { name: 'iconVolume', fn: iconVolume },
98
+ { name: 'iconStar', fn: iconStar },
99
+ { name: 'iconHeart', fn: iconHeart },
100
+ ]},
101
+ { group: 'Devices', icons: [
102
+ { name: 'iconPhone', fn: iconPhone },
103
+ { name: 'iconGamepad', fn: iconGamepad },
104
+ ]},
105
+ { group: 'Hand Pointers', icons: [
106
+ { name: 'iconHandPointUp', fn: iconHandPointUp },
107
+ { name: 'iconHandPointDown', fn: iconHandPointDown },
108
+ { name: 'iconHandPointLeft', fn: iconHandPointLeft },
109
+ { name: 'iconHandPointRight', fn: iconHandPointRight },
110
+ ]},
111
+ { group: 'Misc', icons: [
112
+ { name: 'iconGlobe', fn: iconGlobe },
113
+ { name: 'iconShield', fn: iconShield },
114
+ { name: 'iconZap', fn: iconZap },
115
+ { name: 'iconTrendingUp', fn: iconTrendingUp },
116
+ { name: 'iconTrendingDown', fn: iconTrendingDown },
117
+ { name: 'iconLoader', fn: iconLoader },
118
+ { name: 'iconGrid', fn: iconGrid },
119
+ { name: 'iconBug', fn: iconBug },
120
+ ]},
121
+ { group: 'Theme', icons: [
122
+ { name: 'iconSun', fn: iconSun },
123
+ { name: 'iconMoon', fn: iconMoon },
124
+ ]},
125
+ ]
126
+
127
+ function iconGrid_() {
128
+ return ICONS.map(({ group, icons }) => `
129
+ <h3 class="doc-h3" style="margin-top:2rem">${group}</h3>
130
+ <div class="icon-grid">
131
+ ${icons.map(({ name, fn }) => `
132
+ <div class="icon-grid-item">
133
+ <div class="icon-grid-preview">${fn({ size: 20 })}</div>
134
+ <span class="icon-grid-name">${name}</span>
135
+ </div>
136
+ `).join('')}
137
+ </div>
138
+ `).join('')
139
+ }
140
+
141
+ export default {
142
+ route: '/components/icons',
143
+ meta: {
144
+ title: 'Icons — Pulse Docs',
145
+ description: 'Built-in icon set for Pulse UI.',
146
+ styles: ['/pulse-ui.css', '/docs.css'],
147
+ },
148
+ state: {},
149
+ view: () => renderComponentPage({
150
+ currentHref: '/components/icons',
151
+ prev,
152
+ next,
153
+ name: 'icons',
154
+ description: '55 curated icons. All are pure functions returning an SVG string — no external library, no DOM dependency, tree-shakeable. Style: 24×24 viewBox, <code>stroke="currentColor"</code>, compatible with any colour token.',
155
+ content: `
156
+
157
+ <h2 class="doc-h2" id="usage">Usage</h2>
158
+ <p>Import the icon functions you need alongside other components. Call each as a function — optionally pass <code>size</code> and <code>class</code>.</p>
159
+ ${demo(
160
+ `<div style="display:flex;gap:1rem;align-items:center;flex-wrap:wrap">` +
161
+ iconCheck({ size: 20 }) +
162
+ iconSearch({ size: 20 }) +
163
+ iconUser({ size: 20 }) +
164
+ iconStar({ size: 20 }) +
165
+ iconZap({ size: 20 }) +
166
+ iconSettings({ size: 20 }) +
167
+ `</div>`,
168
+ `import { iconCheck, iconSearch, iconUser } from '@invisibleloop/pulse/ui'
169
+
170
+ // Default size (16px)
171
+ iconCheck()
172
+
173
+ // Custom size
174
+ iconSearch({ size: 20 })
175
+
176
+ // With extra class
177
+ iconUser({ size: 20, class: 'u-text-accent' })`
178
+ )}
179
+
180
+ <h2 class="doc-h2" id="with-button">With button</h2>
181
+ <p>Pass an icon into the <code>icon</code> prop of <code>button()</code>.</p>
182
+ ${demo(
183
+ `<div style="display:flex;gap:.75rem;flex-wrap:wrap;align-items:center">` +
184
+ button({ label: 'Download', variant: 'primary', icon: iconDownload({ size: 14 }) }) +
185
+ button({ label: 'Edit', variant: 'secondary', icon: iconEdit({ size: 14 }) }) +
186
+ button({ label: 'Delete', variant: 'danger', icon: iconTrash({ size: 14 }) }) +
187
+ button({ label: 'Search', variant: 'ghost', icon: iconSearch({ size: 14 }) }) +
188
+ `</div>`,
189
+ `button({ label: 'Download', variant: 'primary', icon: iconDownload({ size: 14 }) })
190
+ button({ label: 'Edit', variant: 'secondary', icon: iconEdit({ size: 14 }) })
191
+ button({ label: 'Delete', variant: 'danger', icon: iconTrash({ size: 14 }) })
192
+ button({ label: 'Search', variant: 'ghost', icon: iconSearch({ size: 14 }) })`
193
+ )}
194
+
195
+ <h2 class="doc-h2" id="with-feature">With feature</h2>
196
+ <p>Icons compose naturally into the <code>feature()</code> icon slot.</p>
197
+ ${demo(
198
+ `<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:1rem">` +
199
+ feature({ icon: iconZap({ size: 20 }), title: 'Fast', description: 'Streaming SSR. No build step.' }) +
200
+ feature({ icon: iconShield({ size: 20 }), title: 'Secure', description: 'Security headers on every response.' }) +
201
+ feature({ icon: iconCode({ size: 20 }), title: 'Simple', description: 'Plain JS objects, no JSX.' }) +
202
+ `</div>`,
203
+ `feature({ icon: iconZap({ size: 20 }), title: 'Fast', description: '...' })
204
+ feature({ icon: iconShield({ size: 20 }), title: 'Secure', description: '...' })
205
+ feature({ icon: iconCode({ size: 20 }), title: 'Simple', description: '...' })`
206
+ )}
207
+
208
+ <h2 class="doc-h2" id="background">Background</h2>
209
+ <p>Add <code>bg: 'circle'</code> or <code>bg: 'square'</code> to wrap the icon in a tinted background. Use <code>bgColor</code> to pick the colour — defaults to <code>'accent'</code>.</p>
210
+
211
+ <h3 class="doc-h3">Circle</h3>
212
+ ${demo(
213
+ `<div style="display:flex;gap:1rem;align-items:center;flex-wrap:wrap">` +
214
+ iconZap({ size: 20, bg: 'circle', bgColor: 'accent' }) +
215
+ iconCheck({ size: 20, bg: 'circle', bgColor: 'success' }) +
216
+ iconAlertTriangle({ size: 20, bg: 'circle', bgColor: 'warning' }) +
217
+ iconXCircle({ size: 20, bg: 'circle', bgColor: 'error' }) +
218
+ iconSettings({size: 20, bg: 'circle', bgColor: 'muted' }) +
219
+ `</div>`,
220
+ `iconZap({ size: 20, bg: 'circle', bgColor: 'accent' })
221
+ iconCheck({ size: 20, bg: 'circle', bgColor: 'success' })
222
+ iconAlertTriangle({ size: 20, bg: 'circle', bgColor: 'warning' })
223
+ iconXCircle({ size: 20, bg: 'circle', bgColor: 'error' })
224
+ iconSettings({ size: 20, bg: 'circle', bgColor: 'muted' })`
225
+ )}
226
+
227
+ <h3 class="doc-h3">Square (rounded)</h3>
228
+ ${demo(
229
+ `<div style="display:flex;gap:1rem;align-items:center;flex-wrap:wrap">` +
230
+ iconZap({ size: 20, bg: 'square', bgColor: 'accent' }) +
231
+ iconCheck({ size: 20, bg: 'square', bgColor: 'success' }) +
232
+ iconAlertTriangle({ size: 20, bg: 'square', bgColor: 'warning' }) +
233
+ iconXCircle({ size: 20, bg: 'square', bgColor: 'error' }) +
234
+ iconSettings({size: 20, bg: 'square', bgColor: 'muted' }) +
235
+ `</div>`,
236
+ `iconZap({ size: 20, bg: 'square', bgColor: 'accent' })`
237
+ )}
238
+
239
+ <h3 class="doc-h3">With feature()</h3>
240
+ <p>Pairs naturally with the <code>feature()</code> icon slot at larger sizes.</p>
241
+ ${demo(
242
+ `<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:1rem">` +
243
+ feature({ icon: iconZap({ size: 22, bg: 'square', bgColor: 'accent' }), title: 'Fast', description: 'Streaming SSR, zero config.' }) +
244
+ feature({ icon: iconShield({ size: 22, bg: 'square', bgColor: 'success' }), title: 'Secure', description: 'Security headers by default.' }) +
245
+ feature({ icon: iconCode({ size: 22, bg: 'square', bgColor: 'muted' }), title: 'Simple', description: 'Plain JS objects, no JSX.' }) +
246
+ `</div>`,
247
+ `feature({ icon: iconZap({ size: 22, bg: 'square', bgColor: 'accent' }), title: 'Fast', description: '...' })
248
+ feature({ icon: iconShield({ size: 22, bg: 'square', bgColor: 'success' }), title: 'Secure', description: '...' })
249
+ feature({ icon: iconCode({ size: 22, bg: 'square', bgColor: 'muted' }), title: 'Simple', description: '...' })`
250
+ )}
251
+
252
+ <h2 class="doc-h2" id="colour">Colour</h2>
253
+ <p>Icons inherit <code>color</code> from their parent — use utility classes or CSS tokens to tint them.</p>
254
+ ${demo(
255
+ `<div style="display:flex;gap:1.25rem;align-items:center">` +
256
+ `<span class="u-text-accent">${iconStar({ size: 20 })}</span>` +
257
+ `<span class="u-text-green">${iconCheckCircle({ size: 20 })}</span>` +
258
+ `<span class="u-text-red">${iconXCircle({ size: 20 })}</span>` +
259
+ `<span class="u-text-yellow">${iconAlertTriangle({ size: 20 })}</span>` +
260
+ `<span class="u-text-blue">${iconInfo({ size: 20 })}</span>` +
261
+ `<span class="u-text-muted">${iconClock({ size: 20 })}</span>` +
262
+ `</div>`,
263
+ `<span class="u-text-accent">\${iconStar({ size: 20 })}</span>
264
+ <span class="u-text-green">\${iconCheckCircle({ size: 20 })}</span>
265
+ <span class="u-text-red">\${iconXCircle({ size: 20 })}</span>
266
+ <span class="u-text-yellow">\${iconAlertTriangle({ size: 20 })}</span>`
267
+ )}
268
+
269
+ <h2 class="doc-h2" id="all-icons">All icons</h2>
270
+ <p>Click any icon name to copy the import.</p>
271
+
272
+ ${iconGrid_()}
273
+
274
+ ${table(
275
+ ['Prop', 'Type', 'Default', ''],
276
+ [
277
+ ['<code>size</code>', 'number', '16', 'Width and height in px'],
278
+ ['<code>class</code>', 'string', '—', 'Extra CSS classes (on wrapper when <code>bg</code> is set, otherwise on the SVG)'],
279
+ ['<code>bg</code>', 'string', '—', "'circle' · 'square' — wraps the icon in a tinted background"],
280
+ ['<code>bgColor</code>', 'string', "'accent'", "'accent' · 'success' · 'warning' · 'error' · 'muted'"],
281
+ ]
282
+ )}
283
+ `,
284
+ }),
285
+ }
@@ -0,0 +1,71 @@
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 { uiImage } from '../../../../src/ui/index.js'
5
+
6
+ const { prev, next } = prevNext('/components/image')
7
+
8
+ export default {
9
+ route: '/components/image',
10
+ meta: {
11
+ title: 'Image — Pulse Docs',
12
+ description: 'Responsive image component with optional aspect-ratio crop and caption for Pulse UI.',
13
+ styles: ['/pulse-ui.css', '/docs.css'],
14
+ },
15
+ state: {},
16
+ view: () => renderComponentPage({
17
+ currentHref: '/components/image',
18
+ prev,
19
+ next,
20
+ name: 'uiImage',
21
+ description: 'Responsive image component. Supports aspect-ratio cropping with <code>object-fit: cover</code>, optional caption, and rounded corners. Always uses <code>loading="lazy"</code> and <code>decoding="async"</code>.',
22
+ content: `
23
+
24
+ <h2 class="doc-h2" id="ratio">With aspect ratio</h2>
25
+ <p>Set <code>ratio</code> to constrain the image to a fixed aspect ratio. The image fills the crop area with <code>object-fit: cover</code>.</p>
26
+ ${demo(
27
+ uiImage({ src: 'https://picsum.photos/seed/pulse1/800/450', alt: 'Mountain landscape at dusk', ratio: '16/9' }),
28
+ `uiImage({ src: '/img/photo.jpg', alt: 'Mountain landscape at dusk', ratio: '16/9' })`,
29
+ { col: true }
30
+ )}
31
+
32
+ <h2 class="doc-h2" id="rounded">Square and rounded</h2>
33
+ ${demo(
34
+ `<div style="max-width:200px;margin:0 auto">${uiImage({ src: 'https://picsum.photos/seed/pulse2/400/400', alt: 'Profile photo', ratio: '1/1', rounded: true })}</div>`,
35
+ `uiImage({ src: '/img/avatar.jpg', alt: 'Profile photo', ratio: '1/1', rounded: true })`,
36
+ { col: true }
37
+ )}
38
+
39
+ <h2 class="doc-h2" id="caption">With caption</h2>
40
+ ${demo(
41
+ uiImage({
42
+ src: 'https://picsum.photos/seed/pulse3/800/600',
43
+ alt: 'Aerial view of a coastal town',
44
+ ratio: '4/3',
45
+ caption: 'Aerial view of Porto, Portugal. Photo by João Silva.',
46
+ }),
47
+ `uiImage({
48
+ src: '/img/photo.jpg',
49
+ alt: 'Aerial view of a coastal town',
50
+ ratio: '4/3',
51
+ caption: 'Aerial view of Porto, Portugal. Photo by João Silva.',
52
+ })`,
53
+ { col: true }
54
+ )}
55
+
56
+ ${table(
57
+ ['Prop', 'Type', 'Default', ''],
58
+ [
59
+ ['<code>src</code>', 'string', '—', 'Image source URL'],
60
+ ['<code>alt</code>', 'string', '—', 'Alt text — required for accessibility'],
61
+ ['<code>caption</code>', 'string', '—', 'Renders a <code>&lt;figcaption&gt;</code> below the image'],
62
+ ['<code>ratio</code>', 'string', '—', 'CSS aspect-ratio string e.g. <code>\'16/9\'</code>, <code>\'4/3\'</code>, <code>\'1/1\'</code>. When set, the image fills the crop area with <code>object-fit: cover</code>.'],
63
+ ['<code>rounded</code>', 'boolean', 'false', 'Applies full border-radius to the image wrap'],
64
+ ['<code>width</code>', 'number', '—', 'Sets the <code>width</code> attribute on the <code>&lt;img&gt;</code>'],
65
+ ['<code>height</code>', 'number', '—', 'Sets the <code>height</code> attribute on the <code>&lt;img&gt;</code>'],
66
+ ['<code>class</code>', 'string', '—', ''],
67
+ ]
68
+ )}
69
+ `,
70
+ }),
71
+ }
@@ -0,0 +1,51 @@
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 { input } from '../../../../src/ui/index.js'
5
+
6
+ const { prev, next } = prevNext('/components/input')
7
+
8
+ export default {
9
+ route: '/components/input',
10
+ meta: {
11
+ title: 'Input — Pulse Docs',
12
+ description: 'Input component for Pulse UI.',
13
+ styles: ['/pulse-ui.css', '/docs.css'],
14
+ },
15
+ state: {},
16
+ view: () => renderComponentPage({
17
+ currentHref: '/components/input',
18
+ prev,
19
+ next,
20
+ name: 'input',
21
+ description: 'The label and error are wired up automatically — <code>for</code>/<code>id</code> and <code>aria-describedby</code> are set from <code>name</code>. You don\'t need to manage ids yourself.',
22
+ content: `
23
+ ${demo(
24
+ input({ name: 'email1', label: 'Email address', type: 'email', placeholder: 'you@example.com' }) +
25
+ input({ name: 'email2', label: 'Email address', type: 'email', value: 'bad@', error: 'Enter a valid email address' }) +
26
+ input({ name: 'search', label: 'Search', placeholder: 'Filter by name…', hint: 'Results update as you type' }),
27
+ `input({ name: 'email', label: 'Email address', type: 'email', placeholder: 'you@example.com' })
28
+ input({ name: 'email', label: 'Email address', error: 'Enter a valid email address', value: state.email })
29
+ input({ name: 'search', label: 'Search', placeholder: 'Filter by name…', hint: 'Results update as you type' })`,
30
+ { col: true }
31
+ )}
32
+
33
+ ${table(
34
+ ['Prop', 'Type', 'Default', ''],
35
+ [
36
+ ['<code>name</code>', 'string', '—', 'Also used as id base: <code>field-{name}</code>'],
37
+ ['<code>label</code>', 'string', '—', ''],
38
+ ['<code>type</code>', 'string', 'text', 'Any valid HTML input type'],
39
+ ['<code>placeholder</code>', 'string', '—', ''],
40
+ ['<code>value</code>', 'string', '—', 'Pre-filled value — escaped automatically'],
41
+ ['<code>error</code>', 'string', '—', 'Triggers <code>aria-invalid</code> and <code>role="alert"</code>'],
42
+ ['<code>hint</code>', 'string', '—', 'Helper text below the input'],
43
+ ['<code>required</code>', 'boolean', 'false','Adds <code>required</code>, <code>aria-required</code>, and a visual asterisk'],
44
+ ['<code>disabled</code>', 'boolean', 'false',''],
45
+ ['<code>id</code>', 'string', '—', 'Override the generated id'],
46
+ ['<code>attrs</code>', 'object', '{}', 'Extra attributes on the <code>&lt;input&gt;</code> element'],
47
+ ]
48
+ )}
49
+ `,
50
+ }),
51
+ }