@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,24 @@
1
+ /**
2
+ * Pulse UI — Container
3
+ *
4
+ * Max-width wrapper with horizontal padding. Every page section needs one.
5
+ *
6
+ * @param {object} opts
7
+ * @param {string} opts.content - Raw HTML slot
8
+ * @param {'sm'|'md'|'lg'|'xl'} opts.size - Max-width preset (default: 'lg')
9
+ * @param {string} opts.class
10
+ */
11
+
12
+ import { escHtml as e } from '../html.js'
13
+
14
+ const SIZES = new Set(['sm', 'md', 'lg', 'xl'])
15
+
16
+ export function container({
17
+ content = '',
18
+ size = 'lg',
19
+ class: cls = '',
20
+ } = {}) {
21
+ if (!SIZES.has(size)) size = 'lg'
22
+ const classes = ['ui-container', `ui-container--${size}`, cls].filter(Boolean).join(' ')
23
+ return `<div class="${e(classes)}">${content}</div>`
24
+ }
package/src/ui/cta.js ADDED
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Pulse UI — CTA (Call to Action)
3
+ *
4
+ * Centred block with eyebrow, large heading, body text, and an actions slot.
5
+ * Sits inside a section() + container() — does not add its own padding.
6
+ *
7
+ * @param {object} opts
8
+ * @param {string} opts.eyebrow - Small label above the heading
9
+ * @param {string} opts.title - Main heading
10
+ * @param {number} opts.level - Heading level 1–6 (default 2). Visual style is always ui-cta-title.
11
+ * @param {string} opts.subtitle - Supporting paragraph
12
+ * @param {string} opts.actions - Raw HTML slot — typically button() calls
13
+ * @param {'center'|'left'} opts.align - Text alignment (default: 'center')
14
+ * @param {string} opts.class
15
+ */
16
+
17
+ import { escHtml as e } from '../html.js'
18
+
19
+ export function cta({
20
+ eyebrow = '',
21
+ title = '',
22
+ level = 2,
23
+ subtitle = '',
24
+ actions = '',
25
+ align = 'center',
26
+ class: cls = '',
27
+ } = {}) {
28
+ const classes = ['ui-cta', align === 'left' && 'ui-cta--left', cls].filter(Boolean).join(' ')
29
+ const tag = `h${Math.min(Math.max(Math.floor(level), 1), 6)}`
30
+
31
+ return `<div class="${e(classes)}">
32
+ ${eyebrow ? `<p class="ui-cta-eyebrow">${e(eyebrow)}</p>` : ''}
33
+ ${title ? `<${tag} class="ui-cta-title">${e(title)}</${tag}>` : ''}
34
+ ${subtitle ? `<p class="ui-cta-subtitle">${e(subtitle)}</p>` : ''}
35
+ ${actions ? `<div class="ui-cta-actions">${actions}</div>` : ''}
36
+ </div>`
37
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Pulse UI — Divider
3
+ *
4
+ * Horizontal rule with an optional centred label.
5
+ * With a label, the line splits either side of the text.
6
+ *
7
+ * @param {object} opts
8
+ * @param {string} opts.label - Optional centred text (e.g. "or", "continue with")
9
+ * @param {string} opts.class
10
+ */
11
+
12
+ import { escHtml as e } from '../html.js'
13
+
14
+ export function divider({
15
+ label = '',
16
+ class: cls = '',
17
+ } = {}) {
18
+ const classes = ['ui-divider', label && 'ui-divider--label', cls].filter(Boolean).join(' ')
19
+
20
+ if (label) {
21
+ return `<div class="${e(classes)}" role="separator" aria-label="${e(label)}">
22
+ <span class="ui-divider-line" aria-hidden="true"></span>
23
+ <span class="ui-divider-text">${e(label)}</span>
24
+ <span class="ui-divider-line" aria-hidden="true"></span>
25
+ </div>`
26
+ }
27
+
28
+ return `<hr class="${e(classes)}">`
29
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Pulse UI — Empty state
3
+ *
4
+ * Shown when a list or section has no content yet.
5
+ *
6
+ * @param {object} opts
7
+ * @param {string} opts.title - Primary message
8
+ * @param {string} opts.description - Supporting text (optional)
9
+ * @param {object} opts.action - { label, href, variant } — renders a button (optional)
10
+ * @param {string} opts.class
11
+ */
12
+
13
+ import { escHtml as e } from '../html.js'
14
+ import { button } from './button.js'
15
+
16
+ export function empty({
17
+ title = 'Nothing here yet',
18
+ description = '',
19
+ action = null,
20
+ class: cls = '',
21
+ } = {}) {
22
+ const classes = ['ui-empty', cls].filter(Boolean).join(' ')
23
+
24
+ const actionHtml = action
25
+ ? button({ label: action.label, href: action.href, variant: action.variant || 'secondary' })
26
+ : ''
27
+
28
+ return `<div class="${e(classes)}">
29
+ <p class="ui-empty-title">${e(title)}</p>
30
+ ${description ? `<p class="ui-empty-desc">${e(description)}</p>` : ''}
31
+ ${actionHtml}
32
+ </div>`
33
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Pulse UI — Feature
3
+ *
4
+ * Icon + title + description block. The standard "why us" grid card.
5
+ *
6
+ * @param {object} opts
7
+ * @param {string} opts.icon - Raw HTML slot — SVG or emoji; displayed in an accent-tinted box
8
+ * @param {string} opts.title
9
+ * @param {number} opts.level - Heading level 1–6 (default 3). Visual style is always ui-feature-title.
10
+ * @param {string} opts.description
11
+ * @param {boolean} opts.center - Centre-align icon, title, and description
12
+ * @param {string} opts.class
13
+ */
14
+
15
+ import { escHtml as e } from '../html.js'
16
+
17
+ export function feature({
18
+ icon = '',
19
+ title = '',
20
+ level = 3,
21
+ description = '',
22
+ center = false,
23
+ class: cls = '',
24
+ } = {}) {
25
+ const classes = ['ui-feature', center && 'ui-feature--center', cls].filter(Boolean).join(' ')
26
+ const tag = `h${Math.min(Math.max(Math.floor(level), 1), 6)}`
27
+
28
+ return `<div class="${e(classes)}">
29
+ ${icon ? `<div class="ui-feature-icon" aria-hidden="true">${icon}</div>` : ''}
30
+ ${title ? `<${tag} class="ui-feature-title">${e(title)}</${tag}>` : ''}
31
+ ${description ? `<p class="ui-feature-desc">${e(description)}</p>` : ''}
32
+ </div>`
33
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Pulse UI — Fieldset
3
+ *
4
+ * Semantic grouping of related form fields. Renders a <fieldset> with an
5
+ * accessible <legend>, styled to match the design system. Use inside a
6
+ * card() or directly inside a <form>.
7
+ *
8
+ * @param {object} opts
9
+ * @param {string} opts.legend - Group label (renders as <legend>)
10
+ * @param {string} opts.content - Raw HTML slot — input(), select(), grid(), etc.
11
+ * @param {'xs'|'sm'|'md'|'lg'} opts.gap - Gap between fields (default: 'md')
12
+ * @param {string} opts.class
13
+ */
14
+
15
+ import { escHtml as e } from '../html.js'
16
+
17
+ const GAPS = new Set(['xs', 'sm', 'md', 'lg'])
18
+
19
+ export function fieldset({
20
+ legend = '',
21
+ content = '',
22
+ gap = 'md',
23
+ class: cls = '',
24
+ } = {}) {
25
+ if (!GAPS.has(gap)) gap = 'md'
26
+
27
+ const classes = [
28
+ 'ui-fieldset',
29
+ gap !== 'md' && `ui-fieldset--gap-${gap}`,
30
+ cls,
31
+ ].filter(Boolean).join(' ')
32
+
33
+ return `<fieldset class="${e(classes)}">
34
+ ${legend ? `<legend class="ui-fieldset-legend">${e(legend)}</legend>` : ''}
35
+ <div class="ui-fieldset-body">${content}</div>
36
+ </fieldset>`
37
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Pulse UI — File Upload
3
+ *
4
+ * Drag-and-drop file upload zone with a hidden <input type="file">.
5
+ * Clicking the zone opens the file picker. Dragging files over the zone
6
+ * highlights it; dropping triggers the native change event so Pulse can
7
+ * bind a mutation or action via data-event / data-action.
8
+ *
9
+ * The component is fully CSS-driven — no pulse-ui.js required.
10
+ * The drag highlight is handled by a small inline script on the zone element.
11
+ *
12
+ * Submitted in FormData under `name` — works inside <form data-action="...">.
13
+ *
14
+ * @param {object} opts
15
+ * @param {string} opts.name - Field name (submitted in FormData)
16
+ * @param {string} opts.label - Visible label text
17
+ * @param {string} opts.hint - Helper text (e.g. 'PNG, JPG up to 5 MB')
18
+ * @param {string} opts.error - Validation error message
19
+ * @param {string} opts.accept - Accepted MIME types or extensions, e.g. 'image/*'
20
+ * @param {boolean} opts.multiple - Allow multiple files
21
+ * @param {boolean} opts.required
22
+ * @param {boolean} opts.disabled
23
+ * @param {string} opts.id - Override generated id
24
+ * @param {string} opts.event - data-event binding on the <input>, e.g. 'change:fileSelected'
25
+ * @param {string} opts.class
26
+ */
27
+
28
+ import { escHtml as e } from '../html.js'
29
+ import { iconUpload } from './icons.js'
30
+
31
+ export function fileUpload({
32
+ name = '',
33
+ label = '',
34
+ hint = '',
35
+ error = '',
36
+ accept = '',
37
+ multiple = false,
38
+ required = false,
39
+ disabled = false,
40
+ id = '',
41
+ event = '',
42
+ class: cls = '',
43
+ } = {}) {
44
+ const fieldId = e(id || `field-${name}`)
45
+ const errorId = `${fieldId}-error`
46
+ const hintId = `${fieldId}-hint`
47
+ const described = [error ? errorId : '', hint ? hintId : ''].filter(Boolean).join(' ')
48
+
49
+ const wrapClasses = ['ui-field', error ? 'ui-field--error' : '', cls].filter(Boolean).join(' ')
50
+ const zoneClasses = ['ui-upload', disabled ? 'ui-upload--disabled' : '', error ? 'ui-upload--error' : ''].filter(Boolean).join(' ')
51
+
52
+ const labelHtml = label
53
+ ? `<label for="${fieldId}" class="ui-label">${e(label)}${required ? ' <span class="ui-required" aria-hidden="true">*</span>' : ''}</label>`
54
+ : ''
55
+
56
+ const hintHtml = hint ? `<p id="${hintId}" class="ui-hint">${e(hint)}</p>` : ''
57
+ const errorHtml = error ? `<p id="${errorId}" class="ui-error" role="alert">${e(error)}</p>` : ''
58
+
59
+ const icon = iconUpload({ size: 24, class: 'ui-upload-icon' })
60
+
61
+ // All drag/click/keyboard behaviour is handled by pulse-ui.js via event delegation.
62
+ // No inline handlers needed (and they'd be blocked by CSP anyway).
63
+
64
+ return `<div class="${e(wrapClasses)}">
65
+ ${labelHtml}
66
+ <div class="${e(zoneClasses)}" role="button" tabindex="${disabled ? '-1' : '0'}" aria-label="Upload file"
67
+ >
68
+ <div class="ui-upload-body">
69
+ ${icon}
70
+ <span class="ui-upload-text">Drag &amp; drop or <span class="ui-upload-browse">browse</span></span>
71
+ </div>
72
+ <input
73
+ type="file"
74
+ id="${fieldId}"
75
+ name="${e(name)}"
76
+ class="ui-upload-input"
77
+ ${accept ? `accept="${e(accept)}"` : ''}
78
+ ${multiple ? 'multiple' : ''}
79
+ ${required ? 'required aria-required="true"' : ''}
80
+ ${disabled ? 'disabled' : ''}
81
+ ${event ? `data-event="${e(event)}"` : ''}
82
+ ${described ? `aria-describedby="${described}"` : ''}
83
+ ${error ? 'aria-invalid="true"' : ''}
84
+ >
85
+ </div>
86
+ ${hintHtml}
87
+ ${errorHtml}
88
+ </div>`
89
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Pulse UI — Footer
3
+ *
4
+ * Site footer with logo slot, navigation links, and optional legal text.
5
+ * Handles accessibility (landmark role, aria-label on nav) and responsive
6
+ * stacking automatically.
7
+ *
8
+ * @param {object} opts
9
+ * @param {string} opts.logo - Raw HTML slot — SVG, img, or text
10
+ * @param {string} opts.logoHref - Logo link destination (default: '/')
11
+ * @param {Array<{label: string, href: string}>} opts.links - Footer nav links
12
+ * @param {string} opts.legal - Copyright / legal text (e.g. '© 2026 Acme Ltd.')
13
+ * @param {string} opts.class
14
+ */
15
+
16
+ import { escHtml as e } from '../html.js'
17
+
18
+ export function footer({
19
+ logo = '',
20
+ logoHref = '/',
21
+ links = [],
22
+ legal = '',
23
+ class: cls = '',
24
+ } = {}) {
25
+ const classes = ['ui-footer', cls].filter(Boolean).join(' ')
26
+
27
+ const linksHtml = links.map(({ label = '', href = '' }) =>
28
+ `<a href="${e(href)}" class="ui-footer-link">${e(label)}</a>`
29
+ ).join('')
30
+
31
+ return `<footer class="${e(classes)}">
32
+ <div class="ui-footer-inner">
33
+ ${logo ? `<a href="${e(logoHref)}" class="ui-footer-logo">${logo}</a>` : ''}
34
+ ${links.length ? `<nav class="ui-footer-links" aria-label="Footer navigation">${linksHtml}</nav>` : ''}
35
+ ${legal ? `<p class="ui-footer-legal">${e(legal)}</p>` : ''}
36
+ </div>
37
+ </footer>`
38
+ }
package/src/ui/grid.js ADDED
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Pulse UI — Grid
3
+ *
4
+ * Responsive CSS grid. Direct children become grid items.
5
+ * Collapses to a single column on mobile by default.
6
+ *
7
+ * @param {object} opts
8
+ * @param {string} opts.content - Raw HTML slot — direct children are grid items
9
+ * @param {1|2|3|4} opts.cols - Number of columns (default: 3)
10
+ * @param {'sm'|'md'|'lg'} opts.gap - Gap between items (default: 'md')
11
+ * @param {string} opts.class
12
+ */
13
+
14
+ import { escHtml as e } from '../html.js'
15
+
16
+ const VALID_COLS = new Set([1, 2, 3, 4])
17
+ const GAPS = new Set(['sm', 'md', 'lg'])
18
+
19
+ export function grid({
20
+ content = '',
21
+ cols = 3,
22
+ gap = 'md',
23
+ class: cls = '',
24
+ } = {}) {
25
+ if (!VALID_COLS.has(cols)) cols = 3
26
+ if (!GAPS.has(gap)) gap = 'md'
27
+
28
+ const classes = [
29
+ 'ui-grid',
30
+ `ui-grid--cols-${cols}`,
31
+ gap !== 'md' && `ui-grid--gap-${gap}`,
32
+ cls,
33
+ ].filter(Boolean).join(' ')
34
+
35
+ return `<div class="${e(classes)}">${content}</div>`
36
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Pulse UI — Heading
3
+ *
4
+ * Styled heading with the correct semantic tag. Use this instead of raw <h1>–<h6>
5
+ * to get consistent typography tokens without needing to remember utility classes.
6
+ *
7
+ * Does NOT add margin — use u-mt-* / u-mb-* utility classes for spacing.
8
+ *
9
+ * @param {object} opts
10
+ * @param {number} opts.level - 1–6, controls the HTML tag
11
+ * @param {string} opts.text - Heading text (escaped)
12
+ * @param {string} [opts.size] - Override visual size: 'xs'|'sm'|'base'|'lg'|'xl'|'2xl'|'3xl'|'4xl'
13
+ * Defaults to the level's natural size.
14
+ * @param {string} [opts.color] - 'default'|'muted'|'accent' (default: 'default')
15
+ * @param {boolean} [opts.balance] - Add text-wrap: balance to prevent orphaned words on the last line
16
+ * @param {string} [opts.class] - Extra classes on the element
17
+ */
18
+
19
+ import { escHtml as e } from '../html.js'
20
+
21
+ const LEVEL_SIZE = { 1: '4xl', 2: '3xl', 3: '2xl', 4: 'xl', 5: 'base', 6: 'sm' }
22
+
23
+ export function heading({
24
+ level = 2,
25
+ text = '',
26
+ size,
27
+ color = 'default',
28
+ balance = false,
29
+ class: cls = '',
30
+ } = {}) {
31
+ const tag = `h${Math.min(Math.max(Math.floor(level), 1), 6)}`
32
+ const sizeKey = size || LEVEL_SIZE[level] || 'base'
33
+ const weight = level <= 3 ? 'bold' : 'semibold'
34
+ const colorCls = color !== 'default' ? ` u-text-${color}` : ''
35
+ const classes = [
36
+ `u-text-${sizeKey}`,
37
+ `u-font-${weight}`,
38
+ 'u-leading-tight',
39
+ colorCls,
40
+ balance ? 'u-text-balance' : '',
41
+ cls,
42
+ ].filter(Boolean).join(' ')
43
+
44
+ return `<${tag} class="${classes}">${e(text)}</${tag}>`
45
+ }
package/src/ui/hero.js ADDED
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Pulse UI — Hero
3
+ *
4
+ * Full-width hero section with eyebrow, headline, subheadline, and action slot.
5
+ *
6
+ * @param {object} opts
7
+ * @param {string} opts.eyebrow - Small label above the title (e.g. "Now available")
8
+ * @param {string} opts.title - Main headline
9
+ * @param {string} opts.subtitle - Supporting text beneath the headline
10
+ * @param {string} opts.actions - Raw HTML slot — typically button() or appBadge() calls
11
+ * @param {'center'|'left'} opts.align - Text alignment (default: 'center')
12
+ * @param {'md'|'sm'} opts.size - Vertical padding size: 'md' (default, 5rem) or 'sm' (2.5rem)
13
+ * @param {string} opts.class
14
+ */
15
+
16
+ import { escHtml as e } from '../html.js'
17
+
18
+ export function hero({
19
+ eyebrow = '',
20
+ title = '',
21
+ subtitle = '',
22
+ actions = '',
23
+ align = 'center',
24
+ size = 'md',
25
+ class: cls = '',
26
+ } = {}) {
27
+ const classes = ['ui-hero', align === 'left' && 'ui-hero--left', size === 'sm' && 'ui-hero--sm', cls].filter(Boolean).join(' ')
28
+
29
+ return `<section class="${e(classes)}">
30
+ <div class="ui-hero-inner">
31
+ ${eyebrow ? `<p class="ui-hero-eyebrow">${e(eyebrow)}</p>` : ''}
32
+ ${title ? `<h1 class="ui-hero-title">${e(title)}</h1>` : ''}
33
+ ${subtitle ? `<p class="ui-hero-subtitle">${e(subtitle)}</p>` : ''}
34
+ ${actions ? `<div class="ui-hero-actions">${actions}</div>` : ''}
35
+ </div>
36
+ </section>`
37
+ }
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Pulse UI — Icon set
3
+ *
4
+ * ~55 curated icons. All are pure functions returning an SVG string.
5
+ * Style: 24×24 viewBox, stroke="currentColor", 2px stroke-width (Lucide-compatible).
6
+ *
7
+ * Usage:
8
+ * import { iconCheck, iconArrowRight } from '@invisibleloop/pulse/ui'
9
+ * feature({ icon: iconZap() })
10
+ * button({ icon: iconArrowRight({ size: 14 }) })
11
+ * iconCheck({ size: 20, bg: 'circle', bgColor: 'success' })
12
+ * iconZap({ size: 20, bg: 'square', bgColor: 'accent' })
13
+ *
14
+ * @param {object} opts
15
+ * @param {number} opts.size - Width and height in px (default: 16)
16
+ * @param {string} opts.class - Extra CSS classes (on wrapper when bg is set, else on SVG)
17
+ * @param {'circle'|'square'} opts.bg - Wrap in a tinted background shape
18
+ * @param {'accent'|'success'|'warning'|'error'|'muted'} opts.bgColor - Background colour (default: 'accent')
19
+ */
20
+
21
+ const BG_SHAPES = new Set(['circle', 'square'])
22
+ const BG_COLORS = new Set(['accent', 'success', 'warning', 'error', 'muted'])
23
+
24
+ // ─── Internal helpers ─────────────────────────────────────────────────────────
25
+
26
+ function wrap(svg, { bg, bgColor, cls }) {
27
+ if (!bg || !BG_SHAPES.has(bg)) return cls ? svg.replace('<svg ', `<svg class="${cls}" `) : svg
28
+ const bgCol = BG_COLORS.has(bgColor) ? bgColor : 'accent'
29
+ const wrapCls = ['ui-icon-wrap', `ui-icon-wrap--${bg}`, `ui-icon-wrap--${bgCol}`, cls].filter(Boolean).join(' ')
30
+ return `<span class="${wrapCls}">${svg}</span>`
31
+ }
32
+
33
+ function s(paths, o) {
34
+ const svg = `<svg width="${o.size}" height="${o.size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${paths}</svg>`
35
+ return wrap(svg, o)
36
+ }
37
+
38
+ function f(paths, o) {
39
+ const svg = `<svg width="${o.size}" height="${o.size}" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">${paths}</svg>`
40
+ return wrap(svg, o)
41
+ }
42
+
43
+ function p(paths, o) {
44
+ const svg = `<svg width="${o.size}" height="${o.size}" viewBox="0 0 256 256" fill="currentColor" aria-hidden="true">${paths}</svg>`
45
+ return wrap(svg, o)
46
+ }
47
+
48
+ function opts(o = {}) {
49
+ return { size: o.size ?? 16, cls: o.class ?? '', bg: o.bg ?? '', bgColor: o.bgColor ?? 'accent' }
50
+ }
51
+
52
+ // ─── Navigation & Direction ───────────────────────────────────────────────────
53
+
54
+ export const iconArrowLeft = (o) => s('<path d="M19 12H5M12 19l-7-7 7-7"/>', opts(o))
55
+ export const iconArrowRight = (o) => s('<path d="M5 12h14M12 5l7 7-7 7"/>', opts(o))
56
+ export const iconArrowUp = (o) => s('<path d="M12 19V5M5 12l7-7 7 7"/>', opts(o))
57
+ export const iconArrowDown = (o) => s('<path d="M12 5v14M19 12l-7 7-7-7"/>', opts(o))
58
+ export const iconChevronLeft = (o) => s('<polyline points="15 18 9 12 15 6"/>', opts(o))
59
+ export const iconChevronRight = (o) => s('<polyline points="9 18 15 12 9 6"/>', opts(o))
60
+ export const iconChevronUp = (o) => s('<polyline points="18 15 12 9 6 15"/>', opts(o))
61
+ export const iconChevronDown = (o) => s('<polyline points="6 9 12 15 18 9"/>', opts(o))
62
+ export const iconExternalLink = (o) => s('<path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/>', opts(o))
63
+ export const iconMenu = (o) => s('<line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="18" x2="21" y2="18"/>', opts(o))
64
+ export const iconX = (o) => s('<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>', opts(o))
65
+ export const iconMoreHorizontal = (o) => s('<circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/>', opts(o))
66
+ export const iconMoreVertical = (o) => s('<circle cx="12" cy="12" r="1"/><circle cx="12" cy="5" r="1"/><circle cx="12" cy="19" r="1"/>', opts(o))
67
+
68
+ // ─── Status ───────────────────────────────────────────────────────────────────
69
+
70
+ export const iconCheck = (o) => s('<polyline points="20 6 9 17 4 12"/>', opts(o))
71
+ export const iconCheckCircle = (o) => s('<path d="M22 11.08V12a10 10 0 11-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/>', opts(o))
72
+ export const iconXCircle = (o) => s('<circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/>', opts(o))
73
+ export const iconAlertCircle = (o) => s('<circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/>', opts(o))
74
+ export const iconAlertTriangle = (o) => s('<path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/>', opts(o))
75
+ export const iconInfo = (o) => s('<circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/>', opts(o))
76
+
77
+ // ─── Actions ─────────────────────────────────────────────────────────────────
78
+
79
+ export const iconPlus = (o) => s('<line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/>', opts(o))
80
+ export const iconMinus = (o) => s('<line x1="5" y1="12" x2="19" y2="12"/>', opts(o))
81
+ export const iconEdit = (o) => s('<path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/>', opts(o))
82
+ export const iconTrash = (o) => s('<polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 01-2 2H8a2 2 0 01-2-2L5 6"/><path d="M10 11v6M14 11v6"/><path d="M9 6V4a1 1 0 011-1h4a1 1 0 011 1v2"/>', opts(o))
83
+ export const iconCopy = (o) => s('<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/>', opts(o))
84
+ export const iconSearch = (o) => s('<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>', opts(o))
85
+ export const iconFilter = (o) => s('<polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"/>', opts(o))
86
+ export const iconDownload = (o) => s('<path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/>', opts(o))
87
+ export const iconUpload = (o) => s('<path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/>', opts(o))
88
+ export const iconRefresh = (o) => s('<polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15"/>', opts(o))
89
+ export const iconSend = (o) => s('<line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/>', opts(o))
90
+
91
+ // ─── UI Controls ─────────────────────────────────────────────────────────────
92
+
93
+ export const iconEye = (o) => s('<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/>', opts(o))
94
+ export const iconEyeOff = (o) => s('<path d="M17.94 17.94A10.07 10.07 0 0112 20c-7 0-11-8-11-8a18.45 18.45 0 015.06-5.94M9.9 4.24A9.12 9.12 0 0112 4c7 0 11 8 11 8a18.5 18.5 0 01-2.16 3.19m-6.72-1.07a3 3 0 11-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/>', opts(o))
95
+ export const iconLock = (o) => s('<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0110 0v4"/>', opts(o))
96
+ export const iconUnlock = (o) => s('<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 019.9-1"/>', opts(o))
97
+ export const iconSettings = (o) => s('<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-4 0v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83-2.83l.06-.06A1.65 1.65 0 004.68 15a1.65 1.65 0 00-1.51-1H3a2 2 0 010-4h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 012.83-2.83l.06.06A1.65 1.65 0 009 4.68a1.65 1.65 0 001-1.51V3a2 2 0 014 0v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 2.83l-.06.06A1.65 1.65 0 0019.4 9a1.65 1.65 0 001.51 1H21a2 2 0 010 4h-.09a1.65 1.65 0 00-1.51 1z"/>', opts(o))
98
+ export const iconBell = (o) => s('<path d="M18 8A6 6 0 006 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 01-3.46 0"/>', opts(o))
99
+
100
+ // ─── People & Communication ───────────────────────────────────────────────────
101
+
102
+ export const iconUser = (o) => s('<path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"/><circle cx="12" cy="7" r="4"/>', opts(o))
103
+ export const iconUsers = (o) => s('<path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 00-3-3.87"/><path d="M16 3.13a4 4 0 010 7.75"/>', opts(o))
104
+ export const iconMail = (o) => s('<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22 6 12 13 2 6"/>', opts(o))
105
+ export const iconMessageSquare = (o) => s('<path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/>', opts(o))
106
+
107
+ // ─── Navigation & Pages ───────────────────────────────────────────────────────
108
+
109
+ export const iconHome = (o) => s('<path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/>', opts(o))
110
+ export const iconLogOut = (o) => s('<path d="M9 21H5a2 2 0 01-2-2V5a2 2 0 012-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/>', opts(o))
111
+ export const iconLogIn = (o) => s('<path d="M15 3h4a2 2 0 012 2v14a2 2 0 01-2 2h-4"/><polyline points="10 17 15 12 10 7"/><line x1="15" y1="12" x2="3" y2="12"/>', opts(o))
112
+
113
+ // ─── Content & Files ─────────────────────────────────────────────────────────
114
+
115
+ export const iconFile = (o) => s('<path d="M13 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V9z"/><polyline points="13 2 13 9 20 9"/>', opts(o))
116
+ export const iconImage = (o) => s('<rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/>', opts(o))
117
+ export const iconLink = (o) => s('<path d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71"/>', opts(o))
118
+ export const iconCode = (o) => s('<polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/>', opts(o))
119
+ export const iconCalendar = (o) => s('<rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/>', opts(o))
120
+ export const iconClock = (o) => s('<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>', opts(o))
121
+ export const iconBookmark = (o) => s('<path d="M19 21l-7-5-7 5V5a2 2 0 012-2h10a2 2 0 012 2z"/>', opts(o))
122
+ export const iconTag = (o) => s('<path d="M20.59 13.41l-7.17 7.17a2 2 0 01-2.83 0L2 12V2h10l8.59 8.59a2 2 0 010 2.82z"/><line x1="7" y1="7" x2="7.01" y2="7"/>', opts(o))
123
+
124
+ // ─── Media & Rating ──────────────────────────────────────────────────────────
125
+
126
+ export const iconPlay = (o) => f('<polygon points="5 3 19 12 5 21 5 3"/>', opts(o))
127
+ export const iconPause = (o) => s('<rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/>', opts(o))
128
+ export const iconVolume = (o) => s('<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><path d="M19.07 4.93a10 10 0 010 14.14M15.54 8.46a5 5 0 010 7.07"/>', opts(o))
129
+ export const iconStar = (o) => f('<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>', opts(o))
130
+ export const iconHeart = (o) => s('<path d="M20.84 4.61a5.5 5.5 0 00-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 00-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 000-7.78z"/>', opts(o))
131
+
132
+ // ─── Devices ─────────────────────────────────────────────────────────────────
133
+
134
+ export const iconPhone = (o) => s('<rect x="5" y="2" width="14" height="20" rx="2"/><line x1="12" y1="18" x2="12.01" y2="18"/>', opts(o))
135
+ export const iconGamepad = (o) => s('<line x1="6" x2="10" y1="11" y2="11"/><line x1="8" x2="8" y1="9" y2="13"/><line x1="15" x2="15.01" y1="12" y2="12"/><line x1="18" x2="18.01" y1="10" y2="10"/><path d="M17.32 5H6.68a4 4 0 00-3.978 3.59c-.006.052-.01.101-.017.152C2.604 9.416 2 14.456 2 16a3 3 0 003 3c1 0 1.5-.5 2-1l1.414-1.414A2 2 0 019.828 16h4.344a2 2 0 011.414.586L17 18c.5.5 1 1 2 1a3 3 0 003-3c0-1.545-.604-6.584-.685-7.258-.007-.05-.011-.1-.017-.151A4 4 0 0017.32 5z"/>', opts(o))
136
+
137
+ // ─── Hand Pointers ────────────────────────────────────────────────────────────
138
+
139
+ // Phosphor hand-pointing (MIT) — rotated for each direction
140
+ const _HP = 'M196,88a27.86,27.86,0,0,0-13.35,3.39A28,28,0,0,0,144,74.7V44a28,28,0,0,0-56,0v80l-3.82-6.13A28,28,0,0,0,35.73,146l4.67,8.23C74.81,214.89,89.05,240,136,240a88.1,88.1,0,0,0,88-88V116A28,28,0,0,0,196,88Zm12,64a72.08,72.08,0,0,1-72,72c-37.63,0-47.84-18-81.68-77.68l-4.69-8.27,0-.05A12,12,0,0,1,54,121.61a11.88,11.88,0,0,1,6-1.6,12,12,0,0,1,10.41,6,1.76,1.76,0,0,0,.14.23l18.67,30A8,8,0,0,0,104,152V44a12,12,0,0,1,24,0v68a8,8,0,0,0,16,0V100a12,12,0,0,1,24,0v20a8,8,0,0,0,16,0v-4a12,12,0,0,1,24,0Z'
141
+ export const iconHandPointUp = (o) => p(`<g transform="matrix(-1 0 0 1 256 0)"><path d="${_HP}"/></g>`, opts(o))
142
+ export const iconHandPointDown = (o) => p(`<g transform="rotate(180,128,128)"><path d="${_HP}"/></g>`, opts(o))
143
+ export const iconHandPointLeft = (o) => p(`<g transform="matrix(1 0 0 -1 0 256) rotate(270,128,128)"><path d="${_HP}"/></g>`, opts(o))
144
+ export const iconHandPointRight = (o) => p(`<g transform="rotate(90,128,128)"><path d="${_HP}"/></g>`, opts(o))
145
+
146
+ // ─── Theme ────────────────────────────────────────────────────────────────────
147
+
148
+ export const iconSun = (o) => s('<circle cx="12" cy="12" r="4"/><line x1="12" y1="2" x2="12" y2="6"/><line x1="12" y1="18" x2="12" y2="22"/><line x1="4.93" y1="4.93" x2="7.76" y2="7.76"/><line x1="16.24" y1="16.24" x2="19.07" y2="19.07"/><line x1="2" y1="12" x2="6" y2="12"/><line x1="18" y1="12" x2="22" y2="12"/><line x1="4.93" y1="19.07" x2="7.76" y2="16.24"/><line x1="16.24" y1="7.76" x2="19.07" y2="4.93"/>', opts(o))
149
+ export const iconMoon = (o) => s('<path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"/>', opts(o))
150
+
151
+ // ─── Misc ─────────────────────────────────────────────────────────────────────
152
+
153
+ export const iconGlobe = (o) => s('<circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 014 10 15.3 15.3 0 01-4 10 15.3 15.3 0 01-4-10 15.3 15.3 0 014-10z"/>', opts(o))
154
+ export const iconShield = (o) => s('<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>', opts(o))
155
+ export const iconZap = (o) => s('<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/>', opts(o))
156
+ export const iconTrendingUp = (o) => s('<polyline points="23 6 13.5 15.5 8.5 10.5 1 18"/><polyline points="17 6 23 6 23 12"/>', opts(o))
157
+ export const iconTrendingDown = (o) => s('<polyline points="23 18 13.5 8.5 8.5 13.5 1 6"/><polyline points="17 18 23 18 23 12"/>', opts(o))
158
+ export const iconLoader = (o) => s('<line x1="12" y1="2" x2="12" y2="6"/><line x1="12" y1="18" x2="12" y2="22"/><line x1="4.93" y1="4.93" x2="7.76" y2="7.76"/><line x1="16.24" y1="16.24" x2="19.07" y2="19.07"/><line x1="2" y1="12" x2="6" y2="12"/><line x1="18" y1="12" x2="22" y2="12"/><line x1="4.93" y1="19.07" x2="7.76" y2="16.24"/><line x1="16.24" y1="7.76" x2="19.07" y2="4.93"/>', opts(o))
159
+ export const iconGrid = (o) => s('<rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/>', opts(o))
160
+ export const iconBug = (o) => s('<rect x="8" y="6" width="8" height="14" rx="4"/><path d="m19 7-3 2"/><path d="m5 7 3 2"/><path d="m19 19-3-2"/><path d="m5 19 3-2"/><path d="M20 13h-4"/><path d="M4 13h4"/><path d="m10 4 1 2"/><path d="m14 4-1 2"/>', opts(o))
161
+ export const iconMapPin = (o) => s('<path d="M20 10c0 6-8 13-8 13s-8-7-8-13a8 8 0 0 1 16 0Z"/><circle cx="12" cy="10" r="3"/>', opts(o))