decantr 0.9.0

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 (382) hide show
  1. package/AGENTS.md +868 -0
  2. package/CHANGELOG.md +255 -0
  3. package/CLAUDE.md +178 -0
  4. package/LICENSE +21 -0
  5. package/README.md +229 -0
  6. package/cli/art.js +127 -0
  7. package/cli/commands/a11y.js +61 -0
  8. package/cli/commands/audit.js +225 -0
  9. package/cli/commands/build.js +38 -0
  10. package/cli/commands/dev.js +18 -0
  11. package/cli/commands/doctor.js +197 -0
  12. package/cli/commands/figma-sync.js +48 -0
  13. package/cli/commands/figma-tokens.js +55 -0
  14. package/cli/commands/generate.js +26 -0
  15. package/cli/commands/init.js +116 -0
  16. package/cli/commands/lint.js +209 -0
  17. package/cli/commands/mcp.js +530 -0
  18. package/cli/commands/migrate.js +175 -0
  19. package/cli/commands/test.js +38 -0
  20. package/cli/commands/validate.js +354 -0
  21. package/cli/index.js +113 -0
  22. package/package.json +95 -0
  23. package/reference/atoms.md +517 -0
  24. package/reference/behaviors.md +384 -0
  25. package/reference/build-tooling.md +275 -0
  26. package/reference/color-guidelines.md +965 -0
  27. package/reference/component-lifecycle.md +137 -0
  28. package/reference/compound-spacing.md +95 -0
  29. package/reference/decantation-process.md +499 -0
  30. package/reference/dev-server-routes.md +93 -0
  31. package/reference/form-system.md +253 -0
  32. package/reference/i18n.md +336 -0
  33. package/reference/icons.md +576 -0
  34. package/reference/llm-primer.md +953 -0
  35. package/reference/plugins.md +252 -0
  36. package/reference/registry-consumption.md +76 -0
  37. package/reference/router.md +217 -0
  38. package/reference/shells.md +116 -0
  39. package/reference/spatial-guidelines.md +541 -0
  40. package/reference/ssr.md +234 -0
  41. package/reference/state-data.md +215 -0
  42. package/reference/state-patterns.md +166 -0
  43. package/reference/state.md +194 -0
  44. package/reference/style-system.md +110 -0
  45. package/reference/tokens.md +460 -0
  46. package/src/app.js +19 -0
  47. package/src/chart/_animate.js +266 -0
  48. package/src/chart/_base.js +109 -0
  49. package/src/chart/_data.js +209 -0
  50. package/src/chart/_format.js +106 -0
  51. package/src/chart/_interact.js +364 -0
  52. package/src/chart/_palette.js +105 -0
  53. package/src/chart/_renderer.js +52 -0
  54. package/src/chart/_scene.js +262 -0
  55. package/src/chart/_shared.js +371 -0
  56. package/src/chart/index.js +637 -0
  57. package/src/chart/layouts/_layout-base.js +328 -0
  58. package/src/chart/layouts/cartesian.js +148 -0
  59. package/src/chart/layouts/hierarchy.js +562 -0
  60. package/src/chart/layouts/polar.js +101 -0
  61. package/src/chart/renderers/canvas.js +179 -0
  62. package/src/chart/renderers/svg.js +256 -0
  63. package/src/chart/renderers/webgpu.js +715 -0
  64. package/src/chart/types/_type-base.js +26 -0
  65. package/src/chart/types/area.js +134 -0
  66. package/src/chart/types/bar.js +173 -0
  67. package/src/chart/types/box-plot.js +125 -0
  68. package/src/chart/types/bubble.js +63 -0
  69. package/src/chart/types/candlestick.js +115 -0
  70. package/src/chart/types/chord.js +85 -0
  71. package/src/chart/types/combination.js +108 -0
  72. package/src/chart/types/funnel.js +68 -0
  73. package/src/chart/types/gauge.js +163 -0
  74. package/src/chart/types/heatmap.js +98 -0
  75. package/src/chart/types/histogram.js +71 -0
  76. package/src/chart/types/line.js +111 -0
  77. package/src/chart/types/org-chart.js +93 -0
  78. package/src/chart/types/pie.js +81 -0
  79. package/src/chart/types/radar.js +96 -0
  80. package/src/chart/types/radial.js +68 -0
  81. package/src/chart/types/range-area.js +55 -0
  82. package/src/chart/types/range-bar.js +61 -0
  83. package/src/chart/types/sankey.js +73 -0
  84. package/src/chart/types/scatter.js +66 -0
  85. package/src/chart/types/sparkline.js +81 -0
  86. package/src/chart/types/sunburst.js +69 -0
  87. package/src/chart/types/swimlane.js +88 -0
  88. package/src/chart/types/treemap.js +62 -0
  89. package/src/chart/types/waterfall.js +100 -0
  90. package/src/components/_base.js +1658 -0
  91. package/src/components/_behaviors.js +1140 -0
  92. package/src/components/_primitives.js +534 -0
  93. package/src/components/_qr-encoder.js +539 -0
  94. package/src/components/accordion.js +207 -0
  95. package/src/components/affix.js +62 -0
  96. package/src/components/alert-dialog.js +75 -0
  97. package/src/components/alert.js +47 -0
  98. package/src/components/aspect-ratio.js +24 -0
  99. package/src/components/avatar-group.js +55 -0
  100. package/src/components/avatar.js +38 -0
  101. package/src/components/back-top.js +75 -0
  102. package/src/components/badge.js +74 -0
  103. package/src/components/banner.js +68 -0
  104. package/src/components/breadcrumb.js +162 -0
  105. package/src/components/button.js +115 -0
  106. package/src/components/calendar.js +131 -0
  107. package/src/components/card.js +192 -0
  108. package/src/components/carousel.js +98 -0
  109. package/src/components/cascader.js +261 -0
  110. package/src/components/checkbox.js +80 -0
  111. package/src/components/chip.js +81 -0
  112. package/src/components/code-block.js +82 -0
  113. package/src/components/collapsible.js +50 -0
  114. package/src/components/color-palette.js +438 -0
  115. package/src/components/color-picker.js +314 -0
  116. package/src/components/combobox.js +181 -0
  117. package/src/components/command.js +174 -0
  118. package/src/components/comment.js +206 -0
  119. package/src/components/context-menu.js +76 -0
  120. package/src/components/data-table.js +724 -0
  121. package/src/components/date-picker.js +217 -0
  122. package/src/components/date-range-picker.js +244 -0
  123. package/src/components/datetime-picker.js +271 -0
  124. package/src/components/descriptions.js +68 -0
  125. package/src/components/drawer.js +179 -0
  126. package/src/components/dropdown.js +88 -0
  127. package/src/components/empty.js +41 -0
  128. package/src/components/float-button.js +90 -0
  129. package/src/components/form.js +106 -0
  130. package/src/components/hover-card.js +49 -0
  131. package/src/components/icon.js +87 -0
  132. package/src/components/image.js +97 -0
  133. package/src/components/index.js +117 -0
  134. package/src/components/input-group.js +75 -0
  135. package/src/components/input-number.js +155 -0
  136. package/src/components/input-otp.js +178 -0
  137. package/src/components/input.js +91 -0
  138. package/src/components/kbd.js +36 -0
  139. package/src/components/label.js +25 -0
  140. package/src/components/list.js +118 -0
  141. package/src/components/masked-input.js +236 -0
  142. package/src/components/mentions.js +165 -0
  143. package/src/components/menu.js +259 -0
  144. package/src/components/message.js +80 -0
  145. package/src/components/modal.js +147 -0
  146. package/src/components/navigation-menu.js +166 -0
  147. package/src/components/notification.js +84 -0
  148. package/src/components/pagination.js +104 -0
  149. package/src/components/placeholder.js +132 -0
  150. package/src/components/popconfirm.js +70 -0
  151. package/src/components/popover.js +58 -0
  152. package/src/components/progress.js +61 -0
  153. package/src/components/qrcode.js +251 -0
  154. package/src/components/radiogroup.js +120 -0
  155. package/src/components/range-slider.js +176 -0
  156. package/src/components/rate.js +186 -0
  157. package/src/components/resizable.js +83 -0
  158. package/src/components/result.js +57 -0
  159. package/src/components/scroll-area.js +43 -0
  160. package/src/components/segmented.js +97 -0
  161. package/src/components/select.js +165 -0
  162. package/src/components/separator.js +31 -0
  163. package/src/components/shell.js +407 -0
  164. package/src/components/skeleton.js +39 -0
  165. package/src/components/slider.js +141 -0
  166. package/src/components/sortable-list.js +176 -0
  167. package/src/components/space.js +42 -0
  168. package/src/components/spinner.js +112 -0
  169. package/src/components/splitter.js +147 -0
  170. package/src/components/statistic.js +136 -0
  171. package/src/components/steps.js +99 -0
  172. package/src/components/switch.js +95 -0
  173. package/src/components/table.js +44 -0
  174. package/src/components/tabs.js +216 -0
  175. package/src/components/tag.js +115 -0
  176. package/src/components/textarea.js +82 -0
  177. package/src/components/time-picker.js +153 -0
  178. package/src/components/time-range-picker.js +170 -0
  179. package/src/components/timeline.js +226 -0
  180. package/src/components/toast.js +71 -0
  181. package/src/components/toggle.js +213 -0
  182. package/src/components/tooltip.js +57 -0
  183. package/src/components/tour.js +159 -0
  184. package/src/components/transfer.js +163 -0
  185. package/src/components/tree-select.js +274 -0
  186. package/src/components/tree.js +141 -0
  187. package/src/components/typography.js +136 -0
  188. package/src/components/upload.js +118 -0
  189. package/src/components/visually-hidden.js +20 -0
  190. package/src/components/watermark.js +124 -0
  191. package/src/core/index.js +539 -0
  192. package/src/core/lifecycle.js +69 -0
  193. package/src/css/atoms.js +651 -0
  194. package/src/css/components.js +940 -0
  195. package/src/css/derive.js +1296 -0
  196. package/src/css/index.js +265 -0
  197. package/src/css/runtime.js +268 -0
  198. package/src/css/styles/addons/bioluminescent.js +93 -0
  199. package/src/css/styles/addons/clay.js +70 -0
  200. package/src/css/styles/addons/clean.js +57 -0
  201. package/src/css/styles/addons/command-center.js +143 -0
  202. package/src/css/styles/addons/dopamine.js +83 -0
  203. package/src/css/styles/addons/editorial.js +80 -0
  204. package/src/css/styles/addons/glassmorphism.js +99 -0
  205. package/src/css/styles/addons/liquid-glass.js +105 -0
  206. package/src/css/styles/addons/prismatic.js +100 -0
  207. package/src/css/styles/addons/retro.js +63 -0
  208. package/src/css/styles/auradecantism.js +96 -0
  209. package/src/css/theme-registry.js +444 -0
  210. package/src/data/entity.js +281 -0
  211. package/src/data/index.js +13 -0
  212. package/src/data/persist.js +225 -0
  213. package/src/data/query.js +839 -0
  214. package/src/data/realtime.js +299 -0
  215. package/src/data/url.js +177 -0
  216. package/src/data/worker.js +134 -0
  217. package/src/explorer/archetypes.js +243 -0
  218. package/src/explorer/atoms.js +228 -0
  219. package/src/explorer/charts.js +497 -0
  220. package/src/explorer/components.js +129 -0
  221. package/src/explorer/foundations.js +949 -0
  222. package/src/explorer/icons.js +178 -0
  223. package/src/explorer/patterns.js +247 -0
  224. package/src/explorer/recipes.js +194 -0
  225. package/src/explorer/shared/pattern-examples.js +1337 -0
  226. package/src/explorer/shared/showcase-renderer.js +958 -0
  227. package/src/explorer/shared/spec-table.js +41 -0
  228. package/src/explorer/shared/usage-links.js +87 -0
  229. package/src/explorer/shell-config.js +10 -0
  230. package/src/explorer/shells.js +551 -0
  231. package/src/explorer/styles.js +161 -0
  232. package/src/explorer/tokens.js +262 -0
  233. package/src/explorer/tools.js +525 -0
  234. package/src/form/index.js +804 -0
  235. package/src/i18n/index.js +251 -0
  236. package/src/icons/essential.js +479 -0
  237. package/src/icons/index.js +53 -0
  238. package/src/plugins/index.js +282 -0
  239. package/src/registry/archetypes/content-site.json +71 -0
  240. package/src/registry/archetypes/docs-explorer.json +23 -0
  241. package/src/registry/archetypes/ecommerce.json +104 -0
  242. package/src/registry/archetypes/financial-dashboard.json +77 -0
  243. package/src/registry/archetypes/index.json +41 -0
  244. package/src/registry/archetypes/portfolio.json +82 -0
  245. package/src/registry/archetypes/recipe-community.json +159 -0
  246. package/src/registry/archetypes/saas-dashboard.json +86 -0
  247. package/src/registry/architect/cross-cutting.json +45 -0
  248. package/src/registry/architect/domains/ecommerce.json +294 -0
  249. package/src/registry/architect/domains/financial-services.json +302 -0
  250. package/src/registry/architect/index.json +26 -0
  251. package/src/registry/architect/traits.json +379 -0
  252. package/src/registry/atoms.json +16 -0
  253. package/src/registry/chart-showcase.json +160 -0
  254. package/src/registry/chart.json +136 -0
  255. package/src/registry/components.json +8616 -0
  256. package/src/registry/core.json +216 -0
  257. package/src/registry/css.json +319 -0
  258. package/src/registry/data.json +135 -0
  259. package/src/registry/foundations.json +11 -0
  260. package/src/registry/icons.json +463 -0
  261. package/src/registry/index.json +101 -0
  262. package/src/registry/patterns/activity-feed.json +37 -0
  263. package/src/registry/patterns/article-content.json +27 -0
  264. package/src/registry/patterns/auth-form.json +37 -0
  265. package/src/registry/patterns/author-card.json +20 -0
  266. package/src/registry/patterns/card-grid.json +127 -0
  267. package/src/registry/patterns/category-nav.json +26 -0
  268. package/src/registry/patterns/chart-grid.json +36 -0
  269. package/src/registry/patterns/chat-interface.json +37 -0
  270. package/src/registry/patterns/checklist-card.json +55 -0
  271. package/src/registry/patterns/comparison-panel.json +27 -0
  272. package/src/registry/patterns/component-showcase.json +24 -0
  273. package/src/registry/patterns/contact-form.json +31 -0
  274. package/src/registry/patterns/cta-section.json +20 -0
  275. package/src/registry/patterns/data-table.json +37 -0
  276. package/src/registry/patterns/detail-header.json +83 -0
  277. package/src/registry/patterns/detail-panel.json +27 -0
  278. package/src/registry/patterns/explorer-shell.json +22 -0
  279. package/src/registry/patterns/filter-bar.json +33 -0
  280. package/src/registry/patterns/filter-sidebar.json +27 -0
  281. package/src/registry/patterns/form-sections.json +110 -0
  282. package/src/registry/patterns/goal-tracker.json +27 -0
  283. package/src/registry/patterns/hero.json +107 -0
  284. package/src/registry/patterns/index.json +47 -0
  285. package/src/registry/patterns/kpi-grid.json +36 -0
  286. package/src/registry/patterns/media-gallery.json +20 -0
  287. package/src/registry/patterns/order-history.json +20 -0
  288. package/src/registry/patterns/pagination.json +19 -0
  289. package/src/registry/patterns/photo-to-recipe.json +36 -0
  290. package/src/registry/patterns/pipeline-tracker.json +28 -0
  291. package/src/registry/patterns/post-list.json +27 -0
  292. package/src/registry/patterns/pricing-table.json +32 -0
  293. package/src/registry/patterns/scorecard.json +28 -0
  294. package/src/registry/patterns/search-bar.json +20 -0
  295. package/src/registry/patterns/specimen-grid.json +19 -0
  296. package/src/registry/patterns/stat-card.json +55 -0
  297. package/src/registry/patterns/stats-bar.json +55 -0
  298. package/src/registry/patterns/steps-card.json +55 -0
  299. package/src/registry/patterns/table-of-contents.json +19 -0
  300. package/src/registry/patterns/testimonials.json +21 -0
  301. package/src/registry/patterns/timeline.json +27 -0
  302. package/src/registry/patterns/token-inspector.json +21 -0
  303. package/src/registry/patterns/wizard.json +27 -0
  304. package/src/registry/recipe-auradecantism.json +69 -0
  305. package/src/registry/recipe-clean.json +65 -0
  306. package/src/registry/recipe-command-center.json +78 -0
  307. package/src/registry/router.json +73 -0
  308. package/src/registry/schema/README.md +197 -0
  309. package/src/registry/skeletons.json +259 -0
  310. package/src/registry/state.json +137 -0
  311. package/src/registry/tokens.json +40 -0
  312. package/src/router/hash.js +17 -0
  313. package/src/router/history.js +18 -0
  314. package/src/router/index.js +598 -0
  315. package/src/ssr/index.js +922 -0
  316. package/src/state/arrays.js +181 -0
  317. package/src/state/devtools.js +647 -0
  318. package/src/state/index.js +498 -0
  319. package/src/state/middleware.js +288 -0
  320. package/src/state/scheduler.js +206 -0
  321. package/src/state/store.js +300 -0
  322. package/src/tags/index.js +19 -0
  323. package/src/tannins/auth.js +396 -0
  324. package/src/test/dom.js +352 -0
  325. package/src/test/index.js +62 -0
  326. package/src/test/state.js +306 -0
  327. package/tools/a11y-audit.js +487 -0
  328. package/tools/analyzer.js +315 -0
  329. package/tools/audit.js +706 -0
  330. package/tools/builder.js +1422 -0
  331. package/tools/css-extract.js +188 -0
  332. package/tools/dev-server.js +316 -0
  333. package/tools/dts-gen.js +1260 -0
  334. package/tools/figma-components.js +329 -0
  335. package/tools/figma-patterns.js +516 -0
  336. package/tools/figma-plugin/code.js +453 -0
  337. package/tools/figma-plugin/manifest.json +14 -0
  338. package/tools/figma-plugin/ui.html +268 -0
  339. package/tools/figma-render.js +293 -0
  340. package/tools/figma-tokens.js +712 -0
  341. package/tools/figma-upload.js +318 -0
  342. package/tools/generate.js +738 -0
  343. package/tools/icons.js +133 -0
  344. package/tools/init-templates.js +265 -0
  345. package/tools/install-hooks.sh +5 -0
  346. package/tools/migrations/0.5.0.js +53 -0
  347. package/tools/migrations/0.6.0.js +95 -0
  348. package/tools/minify.js +170 -0
  349. package/tools/pre-commit +4 -0
  350. package/tools/registry.js +662 -0
  351. package/tools/reset-playground.js +61 -0
  352. package/tools/starter-templates/content-site/app.js +49 -0
  353. package/tools/starter-templates/content-site/essence.js +19 -0
  354. package/tools/starter-templates/content-site/pages.js +31 -0
  355. package/tools/starter-templates/ecommerce/app.js +50 -0
  356. package/tools/starter-templates/ecommerce/essence.js +19 -0
  357. package/tools/starter-templates/ecommerce/pages.js +31 -0
  358. package/tools/starter-templates/landing-page/app.js +38 -0
  359. package/tools/starter-templates/landing-page/essence.js +18 -0
  360. package/tools/starter-templates/landing-page/pages.js +21 -0
  361. package/tools/starter-templates/portfolio/app.js +45 -0
  362. package/tools/starter-templates/portfolio/essence.js +19 -0
  363. package/tools/starter-templates/portfolio/pages.js +33 -0
  364. package/tools/starter-templates/saas-dashboard/app.js +70 -0
  365. package/tools/starter-templates/saas-dashboard/essence.js +19 -0
  366. package/tools/starter-templates/saas-dashboard/pages.js +31 -0
  367. package/tools/verify-pack.js +203 -0
  368. package/types/chart.d.ts +77 -0
  369. package/types/components.d.ts +587 -0
  370. package/types/core.d.ts +89 -0
  371. package/types/css.d.ts +149 -0
  372. package/types/data.d.ts +238 -0
  373. package/types/form.d.ts +164 -0
  374. package/types/i18n.d.ts +51 -0
  375. package/types/icons.d.ts +27 -0
  376. package/types/index.d.ts +13 -0
  377. package/types/router.d.ts +116 -0
  378. package/types/ssr.d.ts +102 -0
  379. package/types/state.d.ts +83 -0
  380. package/types/tags.d.ts +62 -0
  381. package/types/tannins.d.ts +63 -0
  382. package/types/test.d.ts +48 -0
@@ -0,0 +1,1337 @@
1
+ import { tags } from 'decantr/tags';
2
+ import { createSignal } from 'decantr/state';
3
+ import { css } from 'decantr/css';
4
+ import {
5
+ Button, Card, Input, Select, Badge, Chip, Avatar, Statistic,
6
+ DataTable, Tabs, Accordion, Separator, Progress, Steps, Image,
7
+ Checkbox, RadioGroup, Switch, Textarea, Slider, Descriptions,
8
+ Pagination, Breadcrumb, Timeline, Placeholder, icon, CodeBlock,
9
+ List, DatePicker, Modal, Upload, Segmented, InputNumber,
10
+ Collapsible, ScrollArea, Spinner
11
+ } from 'decantr/components';
12
+
13
+ const { div, h1, h2, h3, h4, p, span, a, ul, li, strong, em, blockquote,
14
+ code: codeEl, nav: navEl, article: articleEl, time, section } = tags;
15
+
16
+ // ─── Chart conditional import ───────────────────────────────────
17
+ let Chart = null;
18
+ let Sparkline = null;
19
+ try {
20
+ const chartMod = await import('decantr/chart');
21
+ Chart = chartMod.Chart;
22
+ Sparkline = chartMod.Sparkline;
23
+ } catch { /* chart module unavailable */ }
24
+
25
+ function chartFallback(label) {
26
+ return Placeholder({ height: '280px', label: label || 'Chart unavailable' });
27
+ }
28
+
29
+ function sparklineFallback() {
30
+ return Placeholder({ height: '40px', label: 'Sparkline' });
31
+ }
32
+
33
+ // ─── Pattern Examples ───────────────────────────────────────────
34
+
35
+ const PATTERN_EXAMPLES = {
36
+
37
+ // ── Layout ──────────────────────────────────────────────────
38
+
39
+ 'hero': () => {
40
+ return div({ class: css('_flex _col _aic _tc _gap6 _py16 _px6') },
41
+ h1({ class: css('_heading1') }, 'Build Faster, Ship Smarter'),
42
+ p({ class: css('_body _fgmuted _mw[640px]') }, 'The AI-first framework that turns your ideas into production-ready applications in minutes, not months.'),
43
+ div({ class: css('_flex _gap3') },
44
+ Button({ variant: 'primary', size: 'lg' }, 'Get Started'),
45
+ Button({ variant: 'outline', size: 'lg' }, icon('github'), 'View Source')
46
+ )
47
+ );
48
+ },
49
+
50
+ 'cta-section': () => {
51
+ return div({ class: css('_flex _col _aic _tc _gap4 _py12 _px6 _bgmuted _r4') },
52
+ h2({ class: css('_heading2') }, 'Ready to get started?'),
53
+ p({ class: css('_body _fgmuted _mw[480px]') }, 'Join thousands of teams building better products with our platform.'),
54
+ div({ class: css('_flex _gap3') },
55
+ Button({ variant: 'primary', size: 'lg' }, 'Start Free Trial'),
56
+ Button({ variant: 'outline', size: 'lg' }, 'Talk to Sales')
57
+ )
58
+ );
59
+ },
60
+
61
+ 'detail-header': () => {
62
+ const title = 'Project Alpha';
63
+ const breadcrumbs = [{ label: 'Home' }, { label: 'Projects' }, { label: 'Project Alpha' }];
64
+
65
+ return div({ class: css('_flex _col _gap3 _pb4 _borderB') },
66
+ Breadcrumb({ items: breadcrumbs }),
67
+ div({ class: css('_flex _aic _jcsb') },
68
+ div({ class: css('_flex _aic _gap3') },
69
+ h1({ class: css('_heading3') }, title),
70
+ Badge({ variant: 'success' }, 'active')
71
+ ),
72
+ div({ class: css('_flex _gap2') },
73
+ Button({ variant: 'outline', size: 'sm' }, icon('edit'), 'Edit'),
74
+ Button({ variant: 'outline', size: 'sm' }, icon('share'), 'Share')
75
+ )
76
+ )
77
+ );
78
+ },
79
+
80
+ 'detail-panel': () => {
81
+ const entity = {
82
+ name: 'Acme Corporation',
83
+ status: 'Active',
84
+ fields: [
85
+ { label: 'Industry', value: 'Technology' },
86
+ { label: 'Size', value: '500-1000 employees' },
87
+ { label: 'Location', value: 'San Francisco, CA' },
88
+ { label: 'Founded', value: '2018' }
89
+ ]
90
+ };
91
+
92
+ return div({ class: css('_flex _col _gap4 _p4') },
93
+ div({ class: css('_flex _aic _jcsb') },
94
+ div({ class: css('_flex _aic _gap3') },
95
+ h2({ class: css('_heading3') }, entity.name),
96
+ Badge({ variant: 'success' }, entity.status)
97
+ ),
98
+ div({ class: css('_flex _gap2') },
99
+ Button({ variant: 'outline', size: 'sm' }, 'Edit'),
100
+ Button({ variant: 'destructive', size: 'sm' }, 'Delete')
101
+ )
102
+ ),
103
+ Separator(),
104
+ Tabs({ items: [
105
+ { label: 'Overview', content: () => Descriptions({ items: entity.fields }) },
106
+ { label: 'Activity', content: () => div({ class: css('_p4 _fgmuted') }, 'Activity timeline...') },
107
+ { label: 'Settings', content: () => div({ class: css('_p4 _fgmuted') }, 'Settings panel...') }
108
+ ] })
109
+ );
110
+ },
111
+
112
+ // ── Data Display ────────────────────────────────────────────
113
+
114
+ 'kpi-grid': () => {
115
+ return div({ class: css('_flex _col _gap4') },
116
+ h2({ class: css('_heading4') }, 'Key Metrics'),
117
+ div({ class: css('_grid _gc4 _gap4') },
118
+ Statistic({ label: 'Revenue', value: 1248500, prefix: '$', trend: 'up', trendValue: '+12.5%' }),
119
+ Statistic({ label: 'Users', value: 84230, trend: 'up', trendValue: '+8.1%' }),
120
+ Statistic({ label: 'Orders', value: 6420, trend: 'down', trendValue: '-2.3%' }),
121
+ Statistic({ label: 'Conversion', value: 3.24, suffix: '%', trend: 'up', trendValue: '+0.5%' })
122
+ )
123
+ );
124
+ },
125
+
126
+ 'data-table': () => {
127
+ const [search, setSearch] = createSignal('');
128
+ const [status, setStatus] = createSignal('all');
129
+ const columns = [
130
+ { key: 'name', label: 'Name', sortable: true },
131
+ { key: 'email', label: 'Email', sortable: true },
132
+ { key: 'role', label: 'Role' },
133
+ { key: 'status', label: 'Status' }
134
+ ];
135
+ const data = [
136
+ { name: 'Alice Chen', email: 'alice@example.com', role: 'Admin', status: 'active' },
137
+ { name: 'Bob Patel', email: 'bob@example.com', role: 'Editor', status: 'active' },
138
+ { name: 'Carol Liu', email: 'carol@example.com', role: 'Viewer', status: 'inactive' },
139
+ { name: 'Dan Kim', email: 'dan@example.com', role: 'Editor', status: 'active' }
140
+ ];
141
+
142
+ return div({ class: css('_flex _col _gap4') },
143
+ div({ class: css('_flex _gap3 _aic _jcsb') },
144
+ Input({ placeholder: 'Search...', value: search, onchange: e => setSearch(e.target.value) }),
145
+ div({ class: css('_flex _gap2') },
146
+ Select({ value: status, onchange: v => setStatus(v), options: [
147
+ { label: 'All', value: 'all' },
148
+ { label: 'Active', value: 'active' },
149
+ { label: 'Inactive', value: 'inactive' }
150
+ ] }),
151
+ Button({ variant: 'outline' }, 'Export')
152
+ )
153
+ ),
154
+ DataTable({ columns, data, sortable: true, paginate: true, pageSize: 10 })
155
+ );
156
+ },
157
+
158
+ 'chart-grid': () => {
159
+ const data = {
160
+ revenue: [{ date: 'Jan', value: 4200 }, { date: 'Feb', value: 5100 }, { date: 'Mar', value: 4800 }, { date: 'Apr', value: 6200 }],
161
+ orders: [{ month: 'Jan', count: 120 }, { month: 'Feb', count: 145 }, { month: 'Mar', count: 132 }, { month: 'Apr', count: 168 }],
162
+ categories: [{ name: 'Electronics', value: 42 }, { name: 'Clothing', value: 28 }, { name: 'Books', value: 18 }, { name: 'Home', value: 12 }],
163
+ traffic: [{ date: 'Jan', visits: 8400 }, { date: 'Feb', visits: 9200 }, { date: 'Mar', visits: 8800 }, { date: 'Apr', visits: 11500 }]
164
+ };
165
+
166
+ return div({ class: css('_flex _col _gap4') },
167
+ div({ class: css('_flex _aic _jcsb') },
168
+ h2({ class: css('_heading4') }, 'Analytics'),
169
+ Select({ value: '7d', options: [
170
+ { label: 'Last 7 days', value: '7d' },
171
+ { label: 'Last 30 days', value: '30d' },
172
+ { label: 'Last 90 days', value: '90d' }
173
+ ] })
174
+ ),
175
+ div({ class: css('_grid _gc2 _gap4') },
176
+ Chart ? Chart({ type: 'line', data: data.revenue, x: 'date', y: 'value', title: 'Revenue', height: 280 }) : chartFallback('Revenue'),
177
+ Chart ? Chart({ type: 'bar', data: data.orders, x: 'month', y: 'count', title: 'Orders', height: 280 }) : chartFallback('Orders'),
178
+ Chart ? Chart({ type: 'pie', data: data.categories, x: 'name', y: 'value', title: 'Categories', height: 280 }) : chartFallback('Categories'),
179
+ Chart ? Chart({ type: 'area', data: data.traffic, x: 'date', y: 'visits', title: 'Traffic', height: 280 }) : chartFallback('Traffic')
180
+ )
181
+ );
182
+ },
183
+
184
+ 'scorecard': () => {
185
+ const groups = [
186
+ { name: 'Revenue', metrics: [
187
+ { label: 'Monthly Revenue', value: 142, target: 150, unit: 'K', status: 'on-track' },
188
+ { label: 'ARR Growth', value: 24, target: 30, unit: '%', status: 'behind' }
189
+ ]},
190
+ { name: 'Product', metrics: [
191
+ { label: 'Active Users', value: 84, target: 100, unit: 'K', status: 'at-risk' },
192
+ { label: 'NPS Score', value: 72, target: 75, unit: '', status: 'on-track' }
193
+ ]}
194
+ ];
195
+
196
+ return div({ class: css('_flex _col _gap6 _p4') },
197
+ ...groups.map(group =>
198
+ div({ class: css('_flex _col _gap3') },
199
+ h3({ class: css('_heading5') }, group.name),
200
+ div({ class: css('_grid _gc3 _gap4') },
201
+ ...group.metrics.map(m =>
202
+ div({ class: css('_flex _col _gap2 _p4 _b1 _r4') },
203
+ div({ class: css('_flex _aic _jcsb') },
204
+ span({ class: css('_textsm _bold') }, m.label),
205
+ Badge({ variant: m.status === 'on-track' ? 'success' : m.status === 'behind' ? 'error' : 'warning' }, m.status)
206
+ ),
207
+ Statistic({ value: m.value, suffix: m.unit }),
208
+ Progress({ value: (m.value / m.target) * 100 }),
209
+ span({ class: css('_textxs _fgmuted') }, `Target: ${m.target}${m.unit}`)
210
+ )
211
+ )
212
+ )
213
+ )
214
+ )
215
+ );
216
+ },
217
+
218
+ 'comparison-panel': () => {
219
+ const periods = [
220
+ { label: 'This Quarter', revenue: 142000, delta: 12.5, trend: [40, 52, 48, 61, 58, 72] },
221
+ { label: 'Last Quarter', revenue: 126200, delta: -3.1, trend: [38, 42, 40, 44, 41, 43] },
222
+ { label: 'Year Ago', revenue: 98400, delta: 0, trend: [30, 32, 28, 35, 33, 34] }
223
+ ];
224
+
225
+ return div({ class: css('_flex _col _gap4 _p4') },
226
+ h3({ class: css('_heading4') }, 'Period Comparison'),
227
+ div({ class: css('_grid _gc3 _gap4') },
228
+ ...periods.map(period =>
229
+ div({ class: css('_flex _col _gap3 _p4 _b1 _r4') },
230
+ span({ class: css('_textsm _fgmuted _bold') }, period.label),
231
+ Statistic({ label: 'Revenue', value: period.revenue, prefix: '$' }),
232
+ div({ class: css('_flex _aic _gap2') },
233
+ Badge({ variant: period.delta > 0 ? 'success' : period.delta < 0 ? 'error' : 'default' }, `${period.delta > 0 ? '+' : ''}${period.delta}%`),
234
+ span({ class: css('_textxs _fgmuted') }, 'vs baseline')
235
+ ),
236
+ Sparkline ? Sparkline({ data: period.trend, height: 40 }) : sparklineFallback()
237
+ )
238
+ )
239
+ )
240
+ );
241
+ },
242
+
243
+ // ── Content ─────────────────────────────────────────────────
244
+
245
+ 'article-content': () => {
246
+ return articleEl({ class: css('_flex _col _gap6 _mw[720px] _mxa _py8') },
247
+ div({ class: css('_flex _col _gap3') },
248
+ h1({ class: css('_heading1') }, 'Building Scalable Design Systems'),
249
+ div({ class: css('_flex _aic _gap3') },
250
+ Avatar({ name: 'Jane Doe', size: 'sm' }),
251
+ span({ class: css('_textsm') }, 'Jane Doe'),
252
+ time({ class: css('_textsm _fgmuted') }, 'Mar 12, 2026'),
253
+ span({ class: css('_textsm _fgmuted') }, '8 min read')
254
+ )
255
+ ),
256
+ Separator(),
257
+ div({ class: css('_body _lhrelaxed') },
258
+ p({}, 'Design systems provide a shared language between design and engineering. By establishing reusable components and tokens, teams ship faster while maintaining consistency.'),
259
+ p({ class: css('_mt4') }, 'The key to a successful design system is not just the components themselves, but the principles and processes that guide their creation and evolution over time.')
260
+ )
261
+ );
262
+ },
263
+
264
+ 'post-list': () => {
265
+ const posts = [
266
+ { slug: 'design-systems', category: 'Design', readTime: '5 min', title: 'Building Design Systems at Scale', excerpt: 'How to create and maintain design systems that grow with your organization.', author: { name: 'Alice Chen' }, date: 'Mar 10, 2026' },
267
+ { slug: 'reactive-state', category: 'Engineering', readTime: '8 min', title: 'Reactive State Management Patterns', excerpt: 'Exploring signals, stores, and effects for predictable UI state.', author: { name: 'Bob Patel' }, date: 'Mar 8, 2026' },
268
+ { slug: 'accessibility', category: 'Design', readTime: '6 min', title: 'Accessibility Beyond Compliance', excerpt: 'Moving past checklists to create truly inclusive experiences.', author: { name: 'Carol Liu' }, date: 'Mar 5, 2026' }
269
+ ];
270
+
271
+ return div({ class: css('_flex _col _gap4 _p4') },
272
+ ...posts.map(post =>
273
+ a({ href: '#', onclick: e => e.preventDefault(), class: css('_nounder') },
274
+ Card({ class: css('_flex _col _gap3') },
275
+ Card.Body({},
276
+ div({ class: css('_flex _aic _gap2 _mb2') },
277
+ Badge({}, post.category),
278
+ span({ class: css('_textxs _fgmuted') }, post.readTime)
279
+ ),
280
+ h3({ class: css('_heading5') }, post.title),
281
+ p({ class: css('_textsm _fgmuted _clamp2') }, post.excerpt),
282
+ div({ class: css('_flex _aic _gap2 _mt3') },
283
+ Avatar({ name: post.author.name, size: 'sm' }),
284
+ span({ class: css('_textsm') }, post.author.name),
285
+ time({ class: css('_textsm _fgmuted _mla') }, post.date)
286
+ )
287
+ )
288
+ )
289
+ )
290
+ )
291
+ );
292
+ },
293
+
294
+ 'testimonials': () => {
295
+ const items = [
296
+ { quote: 'This product transformed our workflow completely.', name: 'Sarah Johnson', role: 'CTO at TechCorp' },
297
+ { quote: 'The best investment we made this year.', name: 'Mike Chen', role: 'Product Lead at Acme' },
298
+ { quote: 'Incredible support team and amazing features.', name: 'Lisa Park', role: 'Engineering Manager at Globex' }
299
+ ];
300
+
301
+ return div({ class: css('_grid _gc3 _gap4 _p4') },
302
+ ...items.map(t =>
303
+ Card({ class: css('_flex _col _gap4') },
304
+ Card.Body({},
305
+ p({ class: css('_italic _fgmuted') }, `"${t.quote}"`),
306
+ div({ class: css('_flex _aic _gap3 _mt3') },
307
+ Avatar({ name: t.name }),
308
+ div({},
309
+ span({ class: css('_textsm _bold') }, t.name),
310
+ span({ class: css('_textxs _fgmuted _block') }, t.role)
311
+ )
312
+ )
313
+ )
314
+ )
315
+ )
316
+ );
317
+ },
318
+
319
+ 'author-card': () => {
320
+ const author = {
321
+ name: 'Jane Doe',
322
+ bio: 'Senior Design Engineer focusing on design systems and component architecture.',
323
+ socials: [{ ic: 'github' }, { ic: 'twitter' }, { ic: 'linkedin' }]
324
+ };
325
+
326
+ return div({ class: css('_flex _gap4 _p4 _b1 _r4') },
327
+ Avatar({ name: author.name, size: 'lg' }),
328
+ div({ class: css('_flex _col _gap2 _flex1') },
329
+ h3({ class: css('_heading5') }, author.name),
330
+ p({ class: css('_textsm _fgmuted') }, author.bio),
331
+ div({ class: css('_flex _gap2 _mt1') },
332
+ ...author.socials.map(s =>
333
+ Button({ variant: 'ghost', size: 'sm' }, icon(s.ic))
334
+ )
335
+ )
336
+ )
337
+ );
338
+ },
339
+
340
+ 'media-gallery': () => {
341
+ const images = [
342
+ { alt: 'Mountain landscape' },
343
+ { alt: 'Ocean sunset' },
344
+ { alt: 'Forest trail' },
345
+ { alt: 'City skyline' },
346
+ { alt: 'Desert dunes' },
347
+ { alt: 'Snowy peaks' }
348
+ ];
349
+
350
+ return div({ class: css('_grid _gcaf250 _gap3 _p4') },
351
+ ...images.map(img =>
352
+ div({ class: css('_overflow[hidden] _r4') },
353
+ Placeholder({ height: '200px', label: img.alt })
354
+ )
355
+ )
356
+ );
357
+ },
358
+
359
+ // ── Navigation ──────────────────────────────────────────────
360
+
361
+ 'category-nav': () => {
362
+ const categories = [
363
+ { id: 'design', name: 'Design' },
364
+ { id: 'engineering', name: 'Engineering' },
365
+ { id: 'product', name: 'Product' },
366
+ { id: 'marketing', name: 'Marketing' }
367
+ ];
368
+ const [active, setActive] = createSignal('all');
369
+
370
+ return div({ class: css('_flex _gap2 _wrap _py3 _overflow[auto]') },
371
+ Chip({
372
+ label: 'All',
373
+ variant: active() === 'all' ? 'primary' : 'outline',
374
+ onclick: () => setActive('all')
375
+ }),
376
+ ...categories.map(cat =>
377
+ Chip({
378
+ label: cat.name,
379
+ variant: active() === cat.id ? 'primary' : 'outline',
380
+ onclick: () => setActive(cat.id)
381
+ })
382
+ )
383
+ );
384
+ },
385
+
386
+ 'table-of-contents': () => {
387
+ const headings = [
388
+ { id: 'introduction', text: 'Introduction' },
389
+ { id: 'getting-started', text: 'Getting Started' },
390
+ { id: 'configuration', text: 'Configuration' },
391
+ { id: 'api-reference', text: 'API Reference' },
392
+ { id: 'examples', text: 'Examples' }
393
+ ];
394
+ const [activeId] = createSignal('introduction');
395
+
396
+ return navEl({ class: css('_flex _col _gap1 _p4') },
397
+ ul({ class: css('_flex _col _gap1 _borderL') },
398
+ ...headings.map(h =>
399
+ li({},
400
+ a({
401
+ href: `#${h.id}`,
402
+ onclick: e => e.preventDefault(),
403
+ class: css(`_flex _py1 _pl4 _textsm _nounder _trans ${activeId() === h.id ? '_fgprimary _bold _borderL _bcprimary' : '_fgmuted'}`)
404
+ }, h.text)
405
+ )
406
+ )
407
+ )
408
+ );
409
+ },
410
+
411
+ 'pagination': () => {
412
+ const total = 243;
413
+ const pageSize = 10;
414
+ const [page, setPage] = createSignal(1);
415
+ const totalPages = Math.ceil(total / pageSize);
416
+
417
+ return div({ class: css('_flex _aic _jcsb _py3') },
418
+ span({ class: css('_textsm _fgmuted') },
419
+ `Showing ${(page() - 1) * pageSize + 1}-${Math.min(page() * pageSize, total)} of ${total}`
420
+ ),
421
+ Pagination({ current: page, total: totalPages, onChange: setPage })
422
+ );
423
+ },
424
+
425
+ 'search-bar': () => {
426
+ const [query, setQuery] = createSignal('');
427
+
428
+ return div({ class: css('_flex _gap2 _aic') },
429
+ Input({
430
+ placeholder: 'Search...',
431
+ value: query,
432
+ onchange: e => setQuery(e.target.value),
433
+ class: css('_flex1')
434
+ }),
435
+ Button({ variant: 'primary' }, icon('search'), 'Search')
436
+ );
437
+ },
438
+
439
+ 'filter-bar': () => {
440
+ const [search, setSearch] = createSignal('');
441
+ const [category, setCategory] = createSignal('all');
442
+ const [filters, setFilters] = createSignal([]);
443
+
444
+ return div({ class: css('_flex _gap3 _aic _wrap _py3') },
445
+ Input({ placeholder: 'Search...', value: search, onchange: e => setSearch(e.target.value), class: css('_w[240px]') }),
446
+ Select({ value: category, onchange: v => { setCategory(v); if (v !== 'all') setFilters([...filters(), v]); },
447
+ options: [
448
+ { label: 'All Categories', value: 'all' },
449
+ { label: 'Sales', value: 'sales' },
450
+ { label: 'Marketing', value: 'marketing' }
451
+ ] }),
452
+ DatePicker({ placeholder: 'Date range' }),
453
+ ...filters().map(f => Chip({ label: f, onClose: () => setFilters(filters().filter(x => x !== f)) })),
454
+ filters().length > 0 ? Button({ variant: 'ghost', size: 'sm', onclick: () => setFilters([]) }, 'Clear All') : null
455
+ );
456
+ },
457
+
458
+ 'filter-sidebar': () => {
459
+ const categories = [
460
+ { id: 'electronics', name: 'Electronics' },
461
+ { id: 'clothing', name: 'Clothing' },
462
+ { id: 'books', name: 'Books' },
463
+ { id: 'home', name: 'Home & Garden' }
464
+ ];
465
+ const priceRanges = [
466
+ { id: 'under25', label: 'Under $25' },
467
+ { id: '25to50', label: '$25 - $50' },
468
+ { id: '50to100', label: '$50 - $100' },
469
+ { id: 'over100', label: 'Over $100' }
470
+ ];
471
+ const [selected, setSelected] = createSignal([]);
472
+
473
+ return div({ class: css('_flex _col _gap4 _p4 _w[260px] _borderR _overflow[auto]') },
474
+ h3({ class: css('_heading5') }, 'Filters'),
475
+ Accordion({ items: [
476
+ { title: 'Category', content: () =>
477
+ div({ class: css('_flex _col _gap2') },
478
+ ...categories.map(cat =>
479
+ Checkbox({ label: cat.name, checked: selected().includes(cat.id),
480
+ onchange: v => setSelected(v ? [...selected(), cat.id] : selected().filter(x => x !== cat.id))
481
+ })
482
+ )
483
+ )
484
+ },
485
+ { title: 'Price Range', content: () =>
486
+ RadioGroup({ options: priceRanges.map(r => ({ label: r.label, value: r.id })) })
487
+ }
488
+ ] }),
489
+ Button({ variant: 'primary', class: css('_wfull') }, 'Apply Filters')
490
+ );
491
+ },
492
+
493
+ // ── Forms ───────────────────────────────────────────────────
494
+
495
+ 'auth-form': () => {
496
+ const [email, setEmail] = createSignal('');
497
+ const [password, setPassword] = createSignal('');
498
+
499
+ return div({ class: css('_flex _col _aic _jcc _p6') },
500
+ Card({ class: css('_w[400px] _mw[100%]') },
501
+ Card.Header({},
502
+ h2({ class: css('_heading4 _tc') }, 'Sign In'),
503
+ p({ class: css('_fgmuted _tc _mt1') }, 'Enter your credentials to continue')
504
+ ),
505
+ Card.Body({ class: css('_flex _col _gap3') },
506
+ Input({ label: 'Email', type: 'email', placeholder: 'you@example.com', value: email, onchange: e => setEmail(e.target.value) }),
507
+ Input({ label: 'Password', type: 'password', placeholder: '\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022', value: password, onchange: e => setPassword(e.target.value) }),
508
+ Button({ variant: 'primary', class: css('_wfull _mt2') }, 'Sign In')
509
+ ),
510
+ Card.Footer({ class: css('_flex _col _gap3 _aic') },
511
+ Separator(),
512
+ span({ class: css('_fgmuted _textsm') }, 'Don\'t have an account? ',
513
+ a({ href: '#', onclick: e => e.preventDefault() }, 'Sign Up')
514
+ )
515
+ )
516
+ )
517
+ );
518
+ },
519
+
520
+ 'contact-form': () => {
521
+ return Card({ class: css('_mw[600px] _mxa') },
522
+ Card.Header({},
523
+ h2({ class: css('_heading4') }, 'Get in Touch'),
524
+ p({ class: css('_fgmuted _mt1') }, 'We\'ll get back to you within 24 hours.')
525
+ ),
526
+ Card.Body({ class: css('_flex _col _gap4') },
527
+ div({ class: css('_grid _gc2 _gap4') },
528
+ Input({ label: 'First Name', placeholder: 'John' }),
529
+ Input({ label: 'Last Name', placeholder: 'Doe' })
530
+ ),
531
+ Input({ label: 'Email', type: 'email', placeholder: 'john@example.com' }),
532
+ Select({ label: 'Subject', options: [
533
+ { label: 'General Inquiry', value: 'general' },
534
+ { label: 'Sales', value: 'sales' },
535
+ { label: 'Support', value: 'support' }
536
+ ] }),
537
+ Textarea({ label: 'Message', placeholder: 'Tell us about your project...', rows: 4 }),
538
+ Button({ variant: 'primary', class: css('_wfull') }, 'Send Message')
539
+ )
540
+ );
541
+ },
542
+
543
+ 'form-sections': () => {
544
+ const sectionBlock = (title, desc, ...fields) =>
545
+ div({ class: css('_flex _col _gap4') },
546
+ div({}, h3({ class: css('_heading5') }, title), p({ class: css('_fgmuted _textsm') }, desc)),
547
+ ...fields
548
+ );
549
+
550
+ return div({ class: css('_flex _col _gap8 _p6 _mw[720px]') },
551
+ sectionBlock('Personal Info', 'Basic contact details',
552
+ div({ class: css('_grid _gc2 _gap4') },
553
+ Input({ label: 'First Name' }),
554
+ Input({ label: 'Last Name' })
555
+ ),
556
+ Input({ label: 'Email', type: 'email' })
557
+ ),
558
+ Separator(),
559
+ sectionBlock('Company', 'Organization details',
560
+ Input({ label: 'Company Name' }),
561
+ Select({ label: 'Industry', options: [{ label: 'Technology', value: 'tech' }, { label: 'Finance', value: 'finance' }] })
562
+ ),
563
+ Separator(),
564
+ sectionBlock('Notes', 'Additional information',
565
+ Textarea({ label: 'Notes', rows: 4 })
566
+ ),
567
+ div({ class: css('_flex _jcfe _gap3') },
568
+ Button({ variant: 'outline' }, 'Cancel'),
569
+ Button({ variant: 'primary' }, 'Save')
570
+ )
571
+ );
572
+ },
573
+
574
+ 'wizard': () => {
575
+ const steps = [
576
+ { title: 'Account', content: () => div({ class: css('_flex _col _gap3 _p4') }, p({}, 'Enter your account details.')) },
577
+ { title: 'Profile', content: () => div({ class: css('_flex _col _gap3 _p4') }, p({}, 'Complete your profile information.')) },
578
+ { title: 'Confirm', content: () => div({ class: css('_flex _col _gap3 _p4') }, p({}, 'Review and confirm your details.')) }
579
+ ];
580
+ const [current, setCurrent] = createSignal(0);
581
+
582
+ return Card({ class: css('_flex _col _gap6 _p6') },
583
+ Steps({ current: current, items: steps.map(s => ({ title: s.title })) }),
584
+ div({ class: css('_flex _col _gap4 _py4') },
585
+ steps[current()]?.content()
586
+ ),
587
+ div({ class: css('_flex _jcsb') },
588
+ current() > 0
589
+ ? Button({ variant: 'outline', onclick: () => setCurrent(current() - 1) }, 'Back')
590
+ : div(),
591
+ current() < steps.length - 1
592
+ ? Button({ variant: 'primary', onclick: () => setCurrent(current() + 1) }, 'Next')
593
+ : Button({ variant: 'primary' }, 'Submit')
594
+ )
595
+ );
596
+ },
597
+
598
+ // ── Commerce ────────────────────────────────────────────────
599
+
600
+ 'product-grid': () => {
601
+ const products = [
602
+ { name: 'Wireless Headphones', badge: 'New', description: 'Premium noise-cancelling headphones.', price: 199.99 },
603
+ { name: 'Mechanical Keyboard', badge: null, description: 'RGB backlit mechanical keyboard.', price: 149.99 },
604
+ { name: 'USB-C Hub', badge: 'Sale', description: '7-in-1 USB-C hub with HDMI output.', price: 49.99 },
605
+ { name: 'Monitor Stand', badge: null, description: 'Adjustable aluminum monitor stand.', price: 79.99 }
606
+ ];
607
+
608
+ return div({ class: css('_grid _gcaf280 _gap4 _p4') },
609
+ ...products.map(prod =>
610
+ Card({},
611
+ Placeholder({ height: '200px', label: prod.name }),
612
+ Card.Body({ class: css('_flex _col _gap2') },
613
+ div({ class: css('_flex _aic _jcsb') },
614
+ h3({ class: css('_heading5') }, prod.name),
615
+ prod.badge ? Badge({ variant: 'accent' }, prod.badge) : null
616
+ ),
617
+ span({ class: css('_fgmuted _textsm') }, prod.description),
618
+ div({ class: css('_flex _aic _jcsb _mt2') },
619
+ span({ class: css('_heading4') }, `$${prod.price}`),
620
+ Button({ variant: 'primary', size: 'sm' }, 'Add to Cart')
621
+ )
622
+ )
623
+ )
624
+ )
625
+ );
626
+ },
627
+
628
+ 'pricing-table': () => {
629
+ const plans = [
630
+ { name: 'Starter', price: 9, featured: false, features: ['5 projects', '1 GB storage', 'Email support'] },
631
+ { name: 'Pro', price: 29, featured: true, features: ['Unlimited projects', '10 GB storage', 'Priority support', 'API access', 'Custom domain'] },
632
+ { name: 'Enterprise', price: 99, featured: false, features: ['Everything in Pro', '100 GB storage', 'Dedicated support', 'SSO', 'Audit logs', 'SLA'] }
633
+ ];
634
+
635
+ return div({ class: css('_grid _gc3 _gap6 _p4 _aic') },
636
+ ...plans.map(plan =>
637
+ Card({ class: css(plan.featured ? '_b2 _bcprimary' : '') },
638
+ Card.Header({ class: css('_tc') },
639
+ plan.featured ? Badge({ variant: 'primary', class: css('_mb2') }, 'Popular') : null,
640
+ h3({ class: css('_heading4') }, plan.name),
641
+ div({ class: css('_mt2') },
642
+ span({ class: css('_heading1') }, `$${plan.price}`),
643
+ span({ class: css('_fgmuted') }, '/month')
644
+ )
645
+ ),
646
+ Separator(),
647
+ Card.Body({},
648
+ ul({ class: css('_flex _col _gap2') },
649
+ ...plan.features.map(f =>
650
+ li({ class: css('_flex _aic _gap2 _textsm') }, icon('check', { class: css('_fgsuccess') }), f)
651
+ )
652
+ )
653
+ ),
654
+ Card.Footer({},
655
+ Button({ variant: plan.featured ? 'primary' : 'outline', class: css('_wfull') }, 'Get Started')
656
+ )
657
+ )
658
+ )
659
+ );
660
+ },
661
+
662
+ 'order-history': () => {
663
+ const orders = [
664
+ { id: 'ORD-1042', date: '2026-03-10', items: 3, total: 129.99, status: 'delivered' },
665
+ { id: 'ORD-1041', date: '2026-03-08', items: 1, total: 49.00, status: 'shipped' },
666
+ { id: 'ORD-1040', date: '2026-03-05', items: 2, total: 84.50, status: 'processing' },
667
+ { id: 'ORD-1039', date: '2026-03-01', items: 5, total: 212.75, status: 'delivered' }
668
+ ];
669
+ const columns = [
670
+ { key: 'id', label: 'Order #', sortable: true },
671
+ { key: 'date', label: 'Date', sortable: true },
672
+ { key: 'items', label: 'Items' },
673
+ { key: 'total', label: 'Total', sortable: true, render: val => val != null ? `$${val.toFixed(2)}` : '-' },
674
+ { key: 'status', label: 'Status', render: (val, row) =>
675
+ Badge({ variant: val === 'delivered' ? 'success' : val === 'shipped' ? 'primary' : 'default' }, val)
676
+ },
677
+ { key: 'actions', label: '', render: () =>
678
+ Button({ variant: 'ghost', size: 'sm' }, 'View')
679
+ }
680
+ ];
681
+
682
+ return div({ class: css('_flex _col _gap4 _p4') },
683
+ h3({ class: css('_heading4') }, 'Order History'),
684
+ DataTable({ columns, data: orders, sortable: true, paginate: true, pageSize: 10 })
685
+ );
686
+ },
687
+
688
+ // ── Activity ────────────────────────────────────────────────
689
+
690
+ 'activity-feed': () => {
691
+ const items = [
692
+ { user: 'Alice Chen', action: 'deployed v2.4.1 to production', time: '2 min ago', type: 'success' },
693
+ { user: 'Bob Patel', action: 'opened pull request #142', time: '15 min ago', type: 'default' },
694
+ { user: 'Carol Liu', action: 'commented on issue #89', time: '1 hour ago', type: 'default' },
695
+ { user: 'Dan Kim', action: 'merged branch feature/auth', time: '3 hours ago', type: 'success' }
696
+ ];
697
+
698
+ return div({ class: css('_flex _col _gap2 _p4') },
699
+ h3({ class: css('_heading5') }, 'Recent Activity'),
700
+ ...items.map(item =>
701
+ div({ class: css('_flex _gap3 _aic _py2 _borderB') },
702
+ Avatar({ name: item.user, size: 'sm' }),
703
+ div({ class: css('_flex _col _flex1') },
704
+ span({ class: css('_textsm') }, span({ class: css('_bold') }, item.user), ` ${item.action}`),
705
+ span({ class: css('_textxs _fgmuted') }, item.time)
706
+ ),
707
+ Badge({ variant: item.type === 'success' ? 'success' : 'default' }, item.type)
708
+ )
709
+ ),
710
+ Button({ variant: 'ghost', class: css('_wfull _mt2') }, 'Load More')
711
+ );
712
+ },
713
+
714
+ 'timeline': () => {
715
+ const events = [
716
+ { title: 'Project launched', status: 'completed', description: 'Initial release deployed to production.', date: 'Mar 1, 2026' },
717
+ { title: 'Beta testing', status: 'completed', description: 'Invited 500 users for beta feedback.', date: 'Feb 15, 2026' },
718
+ { title: 'Design review', status: 'completed', description: 'Final design review with stakeholders.', date: 'Feb 1, 2026' },
719
+ { title: 'Development started', status: null, description: 'Sprint 1 kicked off with core features.', date: 'Jan 15, 2026' }
720
+ ];
721
+
722
+ return div({ class: css('_flex _col _gap0 _p4 _borderL _ml4') },
723
+ ...events.map(e =>
724
+ div({ class: css('_flex _gap4 _pb6 _relative') },
725
+ div({ class: css('_absolute _left[-9px] _w[16px] _h[16px] _r[9999px] _bgprimary _b2 _bcborder') }),
726
+ div({ class: css('_flex _col _gap1 _pl4') },
727
+ div({ class: css('_flex _aic _gap2') },
728
+ h4({ class: css('_textsm _bold') }, e.title),
729
+ e.status ? Badge({ variant: e.status === 'completed' ? 'success' : 'default', size: 'sm' }, e.status) : null
730
+ ),
731
+ p({ class: css('_textsm _fgmuted') }, e.description),
732
+ time({ class: css('_textxs _fgmuted') }, e.date)
733
+ )
734
+ )
735
+ )
736
+ );
737
+ },
738
+
739
+ 'goal-tracker': () => {
740
+ const goals = [
741
+ { label: 'Revenue', current: 840000, target: 1000000, unit: '$', status: 'on-track', trend: [60, 68, 72, 78, 84] },
742
+ { label: 'New Users', current: 3200, target: 5000, unit: '', status: 'at-risk', trend: [20, 24, 26, 28, 32] },
743
+ { label: 'NPS Score', current: 72, target: 80, unit: '', status: 'on-track', trend: [65, 68, 70, 71, 72] }
744
+ ];
745
+
746
+ return div({ class: css('_flex _col _gap4 _p4') },
747
+ h3({ class: css('_heading4') }, 'Goal Progress'),
748
+ div({ class: css('_grid _gc3 _gap4') },
749
+ ...goals.map(g =>
750
+ div({ class: css('_flex _col _gap3 _p4 _b1 _r4') },
751
+ div({ class: css('_flex _aic _jcsb') },
752
+ span({ class: css('_textsm _bold') }, g.label),
753
+ Badge({ variant: g.status === 'on-track' ? 'success' : g.status === 'at-risk' ? 'warning' : 'error' }, g.status)
754
+ ),
755
+ Statistic({ label: 'Current', value: g.current, suffix: g.unit }),
756
+ Progress({ value: (g.current / g.target) * 100 }),
757
+ div({ class: css('_flex _aic _jcsb _textxs _fgmuted') },
758
+ span({}, `Target: ${g.target}${g.unit}`),
759
+ span({}, `${Math.round((g.current / g.target) * 100)}%`)
760
+ ),
761
+ g.trend ? (Sparkline ? Sparkline({ data: g.trend, height: 32 }) : sparklineFallback()) : null
762
+ )
763
+ )
764
+ )
765
+ );
766
+ },
767
+
768
+ 'pipeline-tracker': () => {
769
+ const [activeStage, setActiveStage] = createSignal('all');
770
+ const stages = [
771
+ { id: 'prospect', name: 'Prospect', value: 240000, count: 42 },
772
+ { id: 'qualified', name: 'Qualified', value: 180000, count: 28 },
773
+ { id: 'proposal', name: 'Proposal', value: 95000, count: 12 },
774
+ { id: 'negotiation', name: 'Negotiation', value: 62000, count: 6 },
775
+ { id: 'closed', name: 'Closed Won', value: 48000, count: 4 }
776
+ ];
777
+ const items = [
778
+ { name: 'Acme Corp', value: 45000, stage: 'proposal', owner: 'Alice' },
779
+ { name: 'Globex Inc', value: 32000, stage: 'qualified', owner: 'Bob' },
780
+ { name: 'Initech', value: 28000, stage: 'negotiation', owner: 'Carol' },
781
+ { name: 'Umbrella Co', value: 18000, stage: 'prospect', owner: 'Dan' }
782
+ ];
783
+
784
+ return div({ class: css('_flex _col _gap4 _p4') },
785
+ h3({ class: css('_heading4') }, 'Pipeline'),
786
+ div({ class: css('_flex _gap3 _overflow[auto]') },
787
+ ...stages.map(s =>
788
+ div({ class: css('_flex _col _gap1 _p3 _b1 _r4 _minw[140px] _tc _pointer'),
789
+ onclick: () => setActiveStage(s.id) },
790
+ span({ class: css('_textsm _fgmuted') }, s.name),
791
+ Statistic({ value: s.value, prefix: '$' }),
792
+ Badge({ variant: 'default' }, `${s.count} deals`)
793
+ )
794
+ )
795
+ ),
796
+ Chart
797
+ ? Chart({ type: 'funnel', data: stages, x: 'name', y: 'value', title: 'Conversion Funnel', height: 260 })
798
+ : chartFallback('Conversion Funnel'),
799
+ DataTable({
800
+ columns: [
801
+ { key: 'name', label: 'Deal', sortable: true },
802
+ { key: 'value', label: 'Value', sortable: true },
803
+ { key: 'stage', label: 'Stage' },
804
+ { key: 'owner', label: 'Owner' }
805
+ ],
806
+ data: items,
807
+ sortable: true, paginate: true
808
+ })
809
+ );
810
+ },
811
+
812
+ // ── Social / Recipe ──────────────────────────────────────────
813
+
814
+ 'recipe-card-grid': () => {
815
+ const recipes = [
816
+ { title: 'Spicy Thai Basil Chicken', desc: 'A quick and fiery stir-fry with holy basil, chilies, and garlic.', time: '25 min', servings: 4, tags: ['Thai', 'Spicy'], author: 'Chef Maria', forks: 142 },
817
+ { title: 'Classic Margherita Pizza', desc: 'Simple, fresh, and perfect — San Marzano tomatoes, mozzarella, basil.', time: '45 min', servings: 2, tags: ['Italian', 'Quick'], author: "Tom's Kitchen", forks: 89 },
818
+ { title: 'Lemon Herb Salmon', desc: 'Oven-baked salmon with a bright lemon-dill glaze and roasted vegetables.', time: '30 min', servings: 4, tags: ['Seafood', 'Healthy'], author: 'Lisa Bakes', forks: 63 },
819
+ { title: 'Chocolate Lava Cake', desc: 'Rich, molten-center chocolate cakes that are easier than you think.', time: '35 min', servings: 6, tags: ['Dessert', 'Chocolate'], author: 'Chef Maria', forks: 218 }
820
+ ];
821
+
822
+ return div({ class: css('_grid _gc3 _gap4 _p4') },
823
+ ...recipes.map(r =>
824
+ Card({},
825
+ Placeholder({ height: '180px', label: r.title }),
826
+ Card.Body({ class: css('_flex _col _gap2') },
827
+ h3({ class: css('_heading5') }, r.title),
828
+ p({ class: css('_textsm _fgmuted _clamp2') }, r.desc),
829
+ div({ class: css('_flex _gap2 _wrap') },
830
+ ...r.tags.map(t => Chip({ label: t, size: 'sm' }))
831
+ ),
832
+ div({ class: css('_flex _aic _gap3 _textsm _fgmuted') },
833
+ span({ class: css('_flex _aic _gap1') }, icon('clock', { size: 14 }), r.time),
834
+ span({ class: css('_flex _aic _gap1') }, icon('users', { size: 14 }), `${r.servings}`)
835
+ ),
836
+ Separator(),
837
+ div({ class: css('_flex _aic _jcsb') },
838
+ div({ class: css('_flex _aic _gap2') },
839
+ Avatar({ name: r.author, size: 'sm' }),
840
+ span({ class: css('_textsm') }, r.author)
841
+ ),
842
+ Badge({ variant: 'default' }, `${r.forks} forks`)
843
+ )
844
+ )
845
+ )
846
+ )
847
+ );
848
+ },
849
+
850
+ 'recipe-hero': () => {
851
+ return div({ class: css('_flex _col _gap0 _relative') },
852
+ div({ class: css('_relative') },
853
+ Placeholder({ height: '360px', label: 'Spicy Thai Basil Chicken' }),
854
+ div({ class: css('_absolute _top[16px] _left[16px] _flex _gap2') },
855
+ Button({ variant: 'outline', size: 'sm' }, icon('arrow-left'), 'Back')
856
+ ),
857
+ div({ class: css('_absolute _top[16px] _right[16px] _flex _gap2') },
858
+ Button({ variant: 'outline', size: 'sm' }, icon('edit'))
859
+ )
860
+ ),
861
+ div({ class: css('_flex _col _gap3 _p6') },
862
+ div({ class: css('_flex _gap2 _wrap') },
863
+ Chip({ label: 'Thai' }),
864
+ Chip({ label: 'Spicy' }),
865
+ Chip({ label: 'Quick' })
866
+ ),
867
+ h1({ class: css('_heading2') }, 'Spicy Thai Basil Chicken'),
868
+ p({ class: css('_body _fgmuted _mw[640px]') }, 'A quick and fiery stir-fry with holy basil, fresh chilies, and garlic served over jasmine rice. Ready in under 30 minutes.')
869
+ )
870
+ );
871
+ },
872
+
873
+ 'recipe-stats-bar': () => {
874
+ const stats = [
875
+ { label: 'Prep', value: '10 min', ic: 'clock' },
876
+ { label: 'Cook', value: '15 min', ic: 'flame' },
877
+ { label: 'Servings', value: '4', ic: 'users' },
878
+ { label: 'Difficulty', value: 'Medium', ic: 'bar-chart' }
879
+ ];
880
+
881
+ return div({ class: css('_flex _col _gap4 _p4') },
882
+ div({ class: css('_flex _aic _jcsb _wrap _gap4') },
883
+ div({ class: css('_flex _gap6') },
884
+ ...stats.map(s =>
885
+ div({ class: css('_flex _aic _gap2') },
886
+ icon(s.ic, { size: 16, class: css('_fgmuted') }),
887
+ div({ class: css('_flex _col') },
888
+ span({ class: css('_textxs _fgmuted') }, s.label),
889
+ span({ class: css('_textsm _bold') }, s.value)
890
+ )
891
+ )
892
+ )
893
+ ),
894
+ div({ class: css('_flex _gap2') },
895
+ Button({ variant: 'outline', size: 'sm' }, icon('git-fork'), 'Fork'),
896
+ Button({ variant: 'outline', size: 'sm' }, icon('bookmark'), 'Save'),
897
+ Button({ variant: 'outline', size: 'sm' }, icon('share'), 'Share')
898
+ )
899
+ ),
900
+ Separator()
901
+ );
902
+ },
903
+
904
+ 'recipe-ingredients': () => {
905
+ const ingredients = [
906
+ '500g chicken thigh, sliced',
907
+ '2 cups Thai holy basil leaves',
908
+ '4 cloves garlic, minced',
909
+ '3 Thai chilies, sliced',
910
+ '2 tbsp oyster sauce',
911
+ '1 tbsp fish sauce'
912
+ ];
913
+
914
+ return Card({},
915
+ Card.Header({},
916
+ div({ class: css('_flex _aic _gap2') },
917
+ icon('list', { size: 18 }),
918
+ h3({ class: css('_heading5') }, 'Ingredients')
919
+ )
920
+ ),
921
+ Card.Body({ class: css('_flex _col _gap2') },
922
+ ...ingredients.map(ing =>
923
+ Checkbox({ label: ing })
924
+ )
925
+ )
926
+ );
927
+ },
928
+
929
+ 'recipe-instructions': () => {
930
+ const steps = [
931
+ { text: 'Heat oil in a wok over high heat until smoking.', time: null },
932
+ { text: 'Add garlic and chilies, stir-fry for 30 seconds until fragrant.', time: '30 sec' },
933
+ { text: 'Add chicken and stir-fry until cooked through, about 5 minutes.', time: '5 min' },
934
+ { text: 'Add oyster sauce, fish sauce, and sugar. Toss to combine.', time: null },
935
+ { text: 'Remove from heat, fold in basil leaves until just wilted. Serve over rice.', time: null }
936
+ ];
937
+
938
+ return Card({},
939
+ Card.Header({},
940
+ div({ class: css('_flex _aic _gap2') },
941
+ icon('book-open', { size: 18 }),
942
+ h3({ class: css('_heading5') }, 'Instructions')
943
+ )
944
+ ),
945
+ Card.Body({ class: css('_flex _col _gap4') },
946
+ ...steps.map((step, i) =>
947
+ div({ class: css('_flex _gap3') },
948
+ Badge({ variant: 'primary' }, `${i + 1}`),
949
+ div({ class: css('_flex _col _gap1 _flex1') },
950
+ p({ class: css('_textsm') }, step.text),
951
+ step.time ? span({ class: css('_textxs _fgmuted _flex _aic _gap1') }, icon('clock', { size: 12 }), step.time) : null
952
+ )
953
+ )
954
+ )
955
+ )
956
+ );
957
+ },
958
+
959
+ 'nutrition-card': () => {
960
+ return Card({},
961
+ Card.Header({},
962
+ div({ class: css('_flex _aic _gap2') },
963
+ icon('activity', { size: 18 }),
964
+ h3({ class: css('_heading5') }, 'Nutrition per Serving')
965
+ )
966
+ ),
967
+ Card.Body({},
968
+ div({ class: css('_grid _gc2 _gap4') },
969
+ Statistic({ label: 'Calories', value: 420, suffix: ' kcal' }),
970
+ Statistic({ label: 'Protein', value: 38, suffix: 'g' }),
971
+ Statistic({ label: 'Carbs', value: 12, suffix: 'g' }),
972
+ Statistic({ label: 'Fat', value: 24, suffix: 'g' })
973
+ )
974
+ )
975
+ );
976
+ },
977
+
978
+ 'recipe-form-simple': () => {
979
+ const [ingredients, setIngredients] = createSignal(['', '', '']);
980
+ const [steps, setSteps] = createSignal(['', '']);
981
+
982
+ return div({ class: css('_flex _col _gap6 _p4 _mw[720px]') },
983
+ Upload({ label: 'Recipe Photo', accept: 'image/*' }),
984
+
985
+ Card({},
986
+ Card.Header({}, h3({ class: css('_heading5') }, 'Basic Info')),
987
+ Card.Body({ class: css('_flex _col _gap3') },
988
+ Input({ label: 'Recipe Title', placeholder: 'e.g. Spicy Thai Basil Chicken' }),
989
+ Textarea({ label: 'Description', placeholder: 'A short description of your recipe...', rows: 3 })
990
+ )
991
+ ),
992
+
993
+ Card({},
994
+ Card.Header({}, h3({ class: css('_heading5') }, 'Details')),
995
+ Card.Body({ class: css('_grid _gc3 _gap4') },
996
+ InputNumber({ label: 'Prep Time (min)', min: 0, value: 10 }),
997
+ InputNumber({ label: 'Cook Time (min)', min: 0, value: 20 }),
998
+ InputNumber({ label: 'Servings', min: 1, value: 4 }),
999
+ ),
1000
+ Card.Body({},
1001
+ Segmented({ value: 'medium', options: [
1002
+ { label: 'Easy', value: 'easy' },
1003
+ { label: 'Medium', value: 'medium' },
1004
+ { label: 'Hard', value: 'hard' }
1005
+ ] })
1006
+ )
1007
+ ),
1008
+
1009
+ Collapsible({ title: 'Backstory (optional)' },
1010
+ Textarea({ placeholder: 'Share the story behind this recipe...', rows: 4 })
1011
+ ),
1012
+
1013
+ Card({},
1014
+ Card.Header({ class: css('_flex _aic _jcsb') },
1015
+ h3({ class: css('_heading5') }, 'Ingredients'),
1016
+ Button({ variant: 'outline', size: 'sm', onclick: () => setIngredients([...ingredients(), '']) }, icon('plus'), 'Add')
1017
+ ),
1018
+ Card.Body({ class: css('_flex _col _gap2') },
1019
+ ...ingredients().map((_, i) =>
1020
+ Input({ placeholder: `Ingredient ${i + 1}`, value: ingredients()[i] })
1021
+ )
1022
+ )
1023
+ ),
1024
+
1025
+ Card({},
1026
+ Card.Header({ class: css('_flex _aic _jcsb') },
1027
+ h3({ class: css('_heading5') }, 'Instructions'),
1028
+ Button({ variant: 'outline', size: 'sm', onclick: () => setSteps([...steps(), '']) }, icon('plus'), 'Add Step')
1029
+ ),
1030
+ Card.Body({ class: css('_flex _col _gap3') },
1031
+ ...steps().map((_, i) =>
1032
+ div({ class: css('_flex _gap3 _aic') },
1033
+ Badge({ variant: 'primary' }, `${i + 1}`),
1034
+ Textarea({ placeholder: `Step ${i + 1}...`, rows: 2, class: css('_flex1') })
1035
+ )
1036
+ )
1037
+ )
1038
+ ),
1039
+
1040
+ div({ class: css('_flex _jcfe _gap3') },
1041
+ Button({ variant: 'outline' }, 'Save Draft'),
1042
+ Button({ variant: 'primary' }, icon('send'), 'Publish Recipe')
1043
+ )
1044
+ );
1045
+ },
1046
+
1047
+ 'recipe-form-chef': () => {
1048
+ const [ingredients, setIngredients] = createSignal([
1049
+ { qty: 500, unit: 'g', name: 'chicken thigh', prep: 'sliced' },
1050
+ { qty: 2, unit: 'cups', name: 'holy basil', prep: 'leaves only' },
1051
+ { qty: 4, unit: 'cloves', name: 'garlic', prep: 'minced' }
1052
+ ]);
1053
+ const [sections, setSections] = createSignal([
1054
+ { title: 'Preparation', steps: [{ text: 'Prep all ingredients', time: 10 }] },
1055
+ { title: 'Cooking', steps: [{ text: 'Heat wok over high heat', time: 1 }, { text: 'Stir-fry chicken', time: 5 }] }
1056
+ ]);
1057
+
1058
+ return div({ class: css('_flex _col _gap6 _p4 _mw[800px]') },
1059
+ Upload({ label: 'Hero Photo', accept: 'image/*' }),
1060
+
1061
+ Card({},
1062
+ Card.Header({}, h3({ class: css('_heading5') }, 'Basics')),
1063
+ Card.Body({ class: css('_flex _col _gap3') },
1064
+ Input({ label: 'Title', placeholder: 'Recipe title' }),
1065
+ Textarea({ label: 'Description', placeholder: 'Brief description...', rows: 2 }),
1066
+ div({ class: css('_grid _gc3 _gap4') },
1067
+ InputNumber({ label: 'Prep (min)', min: 0, value: 10 }),
1068
+ InputNumber({ label: 'Cook (min)', min: 0, value: 15 }),
1069
+ InputNumber({ label: 'Servings', min: 1, value: 4 })
1070
+ ),
1071
+ Segmented({ value: 'medium', options: [
1072
+ { label: 'Easy', value: 'easy' },
1073
+ { label: 'Medium', value: 'medium' },
1074
+ { label: 'Hard', value: 'hard' }
1075
+ ] })
1076
+ )
1077
+ ),
1078
+
1079
+ Card({},
1080
+ Card.Header({ class: css('_flex _aic _jcsb') },
1081
+ h3({ class: css('_heading5') }, 'Structured Ingredients'),
1082
+ Button({ variant: 'outline', size: 'sm', onclick: () => setIngredients([...ingredients(), { qty: 1, unit: '', name: '', prep: '' }]) }, icon('plus'), 'Add')
1083
+ ),
1084
+ Card.Body({ class: css('_flex _col _gap3') },
1085
+ div({ class: css('_grid _gc4 _gap2 _textsm _fgmuted _bold') },
1086
+ span({}, 'Qty'), span({}, 'Unit'), span({}, 'Ingredient'), span({}, 'Prep')
1087
+ ),
1088
+ ...ingredients().map(ing =>
1089
+ div({ class: css('_grid _gc4 _gap2') },
1090
+ InputNumber({ value: ing.qty, min: 0, size: 'sm' }),
1091
+ Select({ value: ing.unit, size: 'sm', options: [
1092
+ { label: 'g', value: 'g' }, { label: 'cups', value: 'cups' },
1093
+ { label: 'tbsp', value: 'tbsp' }, { label: 'tsp', value: 'tsp' },
1094
+ { label: 'cloves', value: 'cloves' }, { label: 'pcs', value: 'pcs' }
1095
+ ] }),
1096
+ Input({ value: ing.name, size: 'sm', placeholder: 'Name' }),
1097
+ Input({ value: ing.prep, size: 'sm', placeholder: 'Prep notes' })
1098
+ )
1099
+ )
1100
+ )
1101
+ ),
1102
+
1103
+ Card({},
1104
+ Card.Header({ class: css('_flex _aic _jcsb') },
1105
+ h3({ class: css('_heading5') }, 'Instructions by Section'),
1106
+ Button({ variant: 'outline', size: 'sm', onclick: () => setSections([...sections(), { title: '', steps: [{ text: '', time: 0 }] }]) }, icon('plus'), 'Add Section')
1107
+ ),
1108
+ Card.Body({ class: css('_flex _col _gap6') },
1109
+ ...sections().map((sec, si) =>
1110
+ div({ class: css('_flex _col _gap3 _pl4 _borderL') },
1111
+ Input({ value: sec.title, placeholder: 'Section title', size: 'sm', class: css('_bold') }),
1112
+ ...sec.steps.map((step, stepIdx) =>
1113
+ div({ class: css('_flex _gap3 _aic') },
1114
+ Badge({ variant: 'primary' }, `${si + 1}.${stepIdx + 1}`),
1115
+ Input({ value: step.text, placeholder: 'Step description...', class: css('_flex1') }),
1116
+ InputNumber({ value: step.time, min: 0, size: 'sm', class: css('_w[100px]') }),
1117
+ span({ class: css('_textxs _fgmuted') }, 'min')
1118
+ )
1119
+ )
1120
+ )
1121
+ )
1122
+ )
1123
+ ),
1124
+
1125
+ div({ class: css('_flex _jcfe _gap3') },
1126
+ Button({ variant: 'outline' }, 'Save Draft'),
1127
+ Button({ variant: 'primary' }, icon('send'), 'Publish')
1128
+ )
1129
+ );
1130
+ },
1131
+
1132
+ 'photo-to-recipe': () => {
1133
+ const [analyzing] = createSignal(false);
1134
+
1135
+ return div({ class: css('_grid _gc2 _gap6 _p4') },
1136
+ Card({},
1137
+ Card.Header({},
1138
+ div({ class: css('_flex _aic _gap2') },
1139
+ icon('camera', { size: 18 }),
1140
+ h3({ class: css('_heading5') }, 'Upload Photo')
1141
+ )
1142
+ ),
1143
+ Card.Body({ class: css('_flex _col _gap4') },
1144
+ Upload({ label: 'Drop a food photo here', accept: 'image/*' }),
1145
+ Button({ variant: 'primary', class: css('_wfull'), disabled: analyzing() },
1146
+ analyzing() ? Spinner({ size: 'sm' }) : icon('sparkles'),
1147
+ analyzing() ? 'Analyzing...' : 'Analyze Photo'
1148
+ )
1149
+ )
1150
+ ),
1151
+
1152
+ Card({},
1153
+ Card.Header({},
1154
+ div({ class: css('_flex _aic _gap2') },
1155
+ icon('sparkles', { size: 18 }),
1156
+ h3({ class: css('_heading5') }, 'AI-Generated Recipe')
1157
+ )
1158
+ ),
1159
+ Card.Body({ class: css('_flex _col _gap4') },
1160
+ div({ class: css('_flex _col _gap2 _fgmuted _tc _py8') },
1161
+ icon('image', { size: 32, class: css('_mxa _fgmuted') }),
1162
+ p({ class: css('_textsm') }, 'Upload a photo to generate a recipe')
1163
+ ),
1164
+ Separator(),
1165
+ Placeholder({ height: '200px', label: 'Generated recipe will appear here' })
1166
+ )
1167
+ )
1168
+ );
1169
+ },
1170
+
1171
+ 'chat-interface': () => {
1172
+ const [message, setMessage] = createSignal('');
1173
+ const messages = [
1174
+ { role: 'user', name: 'You', text: 'How do I make the sauce thicker?' },
1175
+ { role: 'assistant', name: 'Chef AI', text: 'You can thicken the sauce by adding a cornstarch slurry — mix 1 tbsp cornstarch with 2 tbsp cold water, then stir it into the sauce while simmering. It should thicken within 1-2 minutes.' },
1176
+ { role: 'user', name: 'You', text: 'Can I substitute chicken with tofu?' }
1177
+ ];
1178
+ const suggestions = ['Serving suggestions', 'Wine pairings', 'Make it spicier', 'Meal prep tips'];
1179
+
1180
+ return Card({ class: css('_flex _col _h[480px]') },
1181
+ Card.Header({},
1182
+ div({ class: css('_flex _aic _gap2') },
1183
+ icon('message-circle', { size: 18 }),
1184
+ h3({ class: css('_heading5') }, 'Recipe Assistant')
1185
+ )
1186
+ ),
1187
+ Card.Body({ class: css('_flex _col _flex1 _overflow[hidden]') },
1188
+ ScrollArea({ class: css('_flex1') },
1189
+ div({ class: css('_flex _col _gap4 _p2') },
1190
+ ...messages.map(msg =>
1191
+ div({ class: css(`_flex _gap3 ${msg.role === 'user' ? '_jcfe' : ''}`) },
1192
+ msg.role !== 'user' ? Avatar({ name: msg.name, size: 'sm' }) : null,
1193
+ div({ class: css(`_flex _col _gap1 _mw[75%] _p3 _r4 ${msg.role === 'user' ? '_bgprimary _fgprimary-fg' : '_bgmuted'}`) },
1194
+ span({ class: css('_textxs _bold') }, msg.name),
1195
+ p({ class: css('_textsm') }, msg.text)
1196
+ ),
1197
+ msg.role === 'user' ? Avatar({ name: msg.name, size: 'sm' }) : null
1198
+ )
1199
+ )
1200
+ )
1201
+ ),
1202
+ div({ class: css('_flex _gap2 _wrap _px2 _py2') },
1203
+ ...suggestions.map(s =>
1204
+ Chip({ label: s, variant: 'outline', size: 'sm' })
1205
+ )
1206
+ )
1207
+ ),
1208
+ Card.Footer({ class: css('_flex _gap2') },
1209
+ Input({ placeholder: 'Ask about the recipe...', value: message, onchange: e => setMessage(e.target.value), class: css('_flex1') }),
1210
+ Button({ variant: 'primary' }, icon('send'))
1211
+ )
1212
+ );
1213
+ },
1214
+
1215
+ 'cookbook-grid': () => {
1216
+ const cookbooks = [
1217
+ { title: 'Italian Classics', desc: 'Traditional recipes passed down through generations.', recipes: 24, visibility: 'public' },
1218
+ { title: 'Quick Weeknight Dinners', desc: 'Meals ready in 30 minutes or less.', recipes: 18, visibility: 'public' },
1219
+ { title: 'Baking Essentials', desc: 'Breads, pastries, and desserts for every occasion.', recipes: 32, visibility: 'private' },
1220
+ { title: 'Summer Grilling', desc: 'Fire up the grill with these crowd-pleasers.', recipes: 15, visibility: 'public' }
1221
+ ];
1222
+
1223
+ return div({ class: css('_grid _gc3 _gap4 _p4') },
1224
+ ...cookbooks.map(cb =>
1225
+ Card({},
1226
+ Placeholder({ height: '160px', label: cb.title }),
1227
+ Card.Body({ class: css('_flex _col _gap2') },
1228
+ div({ class: css('_flex _aic _jcsb') },
1229
+ h3({ class: css('_heading5') }, cb.title),
1230
+ Badge({ variant: cb.visibility === 'public' ? 'success' : 'default' }, cb.visibility)
1231
+ ),
1232
+ p({ class: css('_textsm _fgmuted _clamp2') }, cb.desc),
1233
+ span({ class: css('_textxs _fgmuted') }, `${cb.recipes} recipes`)
1234
+ )
1235
+ )
1236
+ )
1237
+ );
1238
+ },
1239
+
1240
+ 'cookbook-hero': () => {
1241
+ return div({ class: css('_flex _col _gap0 _relative') },
1242
+ div({ class: css('_relative') },
1243
+ Placeholder({ height: '280px', label: 'Italian Classics' }),
1244
+ div({ class: css('_absolute _top[16px] _right[16px] _flex _gap2') },
1245
+ Button({ variant: 'outline', size: 'sm' }, icon('edit'), 'Edit'),
1246
+ Button({ variant: 'outline', size: 'sm' }, icon('trash'))
1247
+ )
1248
+ ),
1249
+ div({ class: css('_flex _col _gap3 _p6') },
1250
+ div({ class: css('_flex _aic _gap3') },
1251
+ h1({ class: css('_heading2') }, 'Italian Classics'),
1252
+ Badge({ variant: 'success' }, 'Public')
1253
+ ),
1254
+ Badge({ variant: 'default' }, '24 recipes'),
1255
+ div({ class: css('_flex _aic _gap3 _mt2') },
1256
+ Avatar({ name: 'Chef Maria', size: 'sm' }),
1257
+ span({ class: css('_textsm') }, 'Curated by Chef Maria')
1258
+ )
1259
+ )
1260
+ );
1261
+ },
1262
+
1263
+ 'profile-header': () => {
1264
+ return div({ class: css('_flex _col _gap0') },
1265
+ Placeholder({ height: '200px', label: 'Cover Photo' }),
1266
+ div({ class: css('_flex _col _gap4 _px6 _pb6') },
1267
+ div({ class: css('_flex _aic _jcsb') },
1268
+ div({ class: css('_mt[-40px]') },
1269
+ Avatar({ name: 'Chef Maria', size: 'lg' })
1270
+ ),
1271
+ Button({ variant: 'outline', size: 'sm' }, icon('edit'), 'Edit Profile')
1272
+ ),
1273
+ div({ class: css('_flex _col _gap2') },
1274
+ h2({ class: css('_heading3') }, 'Chef Maria'),
1275
+ p({ class: css('_textsm _fgmuted _mw[480px]') }, 'Professional chef and recipe creator. Sharing my passion for Italian and Thai cuisine with the world.'),
1276
+ div({ class: css('_flex _gap4 _textsm _fgmuted') },
1277
+ span({ class: css('_flex _aic _gap1') }, icon('map-pin', { size: 14 }), 'San Francisco, CA'),
1278
+ span({ class: css('_flex _aic _gap1') }, icon('link', { size: 14 }), 'chefmaria.com')
1279
+ )
1280
+ )
1281
+ )
1282
+ );
1283
+ },
1284
+
1285
+ 'feature-grid': () => {
1286
+ const features = [
1287
+ { ic: 'book-open', title: 'Recipe Collections', desc: 'Organize recipes into themed cookbooks and share with friends.' },
1288
+ { ic: 'camera', title: 'Photo Recognition', desc: 'Snap a photo of any dish and get an instant AI-generated recipe.' },
1289
+ { ic: 'git-fork', title: 'Fork & Remix', desc: 'Fork any recipe to make it your own with personal modifications.' },
1290
+ { ic: 'users', title: 'Community', desc: 'Follow chefs, share tips, and discover trending recipes.' },
1291
+ { ic: 'message-circle', title: 'AI Assistant', desc: 'Ask questions about any recipe and get instant cooking advice.' },
1292
+ { ic: 'calendar', title: 'Meal Planning', desc: 'Plan your weekly meals and generate smart shopping lists.' }
1293
+ ];
1294
+
1295
+ return div({ class: css('_grid _gc3 _gap4 _p4') },
1296
+ ...features.map(f =>
1297
+ Card({},
1298
+ Card.Body({ class: css('_flex _col _gap3') },
1299
+ div({ class: css('_w[40px] _h[40px] _r[9999px] _bgprimary/10 _flex _aic _jcc') },
1300
+ icon(f.ic, { size: 20, class: css('_fgprimary') })
1301
+ ),
1302
+ h3({ class: css('_heading5') }, f.title),
1303
+ p({ class: css('_textsm _fgmuted') }, f.desc)
1304
+ )
1305
+ )
1306
+ )
1307
+ );
1308
+ }
1309
+ };
1310
+
1311
+ // ─── Aliases for consolidated v2 patterns ───────────────────────
1312
+ // Old domain-specific pattern IDs now map to generic patterns with presets.
1313
+ // We keep the old keys so the workbench can still render examples for both
1314
+ // the old IDs (referenced in existing archetypes) and the new generic IDs.
1315
+ PATTERN_EXAMPLES['card-grid'] = PATTERN_EXAMPLES['product-grid'];
1316
+ PATTERN_EXAMPLES['stats-bar'] = PATTERN_EXAMPLES['recipe-stats-bar'];
1317
+ PATTERN_EXAMPLES['checklist-card'] = PATTERN_EXAMPLES['recipe-ingredients'];
1318
+ PATTERN_EXAMPLES['steps-card'] = PATTERN_EXAMPLES['recipe-instructions'];
1319
+ PATTERN_EXAMPLES['stat-card'] = PATTERN_EXAMPLES['nutrition-card'];
1320
+
1321
+ // ─── Public API ─────────────────────────────────────────────────
1322
+
1323
+ export function renderPatternExample(patternId) {
1324
+ const renderer = PATTERN_EXAMPLES[patternId];
1325
+ if (!renderer) return null;
1326
+ try {
1327
+ return renderer();
1328
+ } catch (err) {
1329
+ return div({ class: css('_p4 _bgmuted/10 _radius _fgmutedfg _body') },
1330
+ `Preview unavailable: ${err.message}`
1331
+ );
1332
+ }
1333
+ }
1334
+
1335
+ export function hasPatternExample(patternId) {
1336
+ return patternId in PATTERN_EXAMPLES;
1337
+ }