@invisibleloop/pulse 0.20.10 → 0.20.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (266) hide show
  1. package/package.json +6 -5
  2. package/public/.pulse-ui-version +1 -1
  3. package/scripts/build.js +26 -0
  4. package/scripts/release-version.js +17 -8
  5. package/scripts/strip-server.test.js +60 -0
  6. package/src/agent/checklist.md +8 -12
  7. package/src/agent/commands/pulse-start.md +2 -0
  8. package/src/agent/commands/verify.md +30 -8
  9. package/src/agent/guide-components.md +7 -25
  10. package/src/agent/guide-routing.md +0 -12
  11. package/src/agent/guide-server.md +102 -1
  12. package/src/agent/guide-spec.md +8 -1
  13. package/src/agent/guide-styles.md +23 -0
  14. package/src/agent/identity.md +9 -0
  15. package/src/agent/workflow.md +4 -23
  16. package/src/cli/check-bundles.js +113 -0
  17. package/src/cli/check-bundles.test.js +137 -0
  18. package/src/cli/cli.test.js +105 -0
  19. package/src/cli/dev.js +48 -3
  20. package/src/cli/diagnose.js +105 -0
  21. package/src/cli/diagnose.test.js +124 -0
  22. package/src/cli/discover.js +9 -2
  23. package/src/cli/index.js +171 -9
  24. package/src/cli/process-utils.js +57 -0
  25. package/src/cli/process-utils.test.js +114 -0
  26. package/src/cli/review.js +391 -0
  27. package/src/cli/review.test.js +143 -0
  28. package/src/cli/scaffold.js +9 -4
  29. package/src/cli/stamp.js +102 -0
  30. package/src/cli/stamp.test.js +149 -0
  31. package/src/cli/start.js +15 -2
  32. package/src/cli/validate.js +184 -0
  33. package/src/cli/validate.test.js +153 -0
  34. package/src/mcp/mcp.test.js +261 -30
  35. package/src/mcp/server.js +207 -515
  36. package/src/mcp/validate-worker.js +18 -1
  37. package/src/runtime/index.js +144 -11
  38. package/src/runtime/runtime.test.js +199 -0
  39. package/src/runtime/ssr.js +14 -7
  40. package/src/runtime/ssr.test.js +31 -0
  41. package/src/server/index.js +163 -10
  42. package/src/server/server.test.js +225 -2
  43. package/src/spec/schema.js +1 -1
  44. package/src/ui/index.js +0 -5
  45. package/src/ui/ui.test.js +0 -75
  46. package/types/ui.d.ts +0 -21
  47. package/.claude/commands/build-page.md +0 -63
  48. package/.claude/commands/new-doc-page.md +0 -45
  49. package/.claude/commands/pulse-start.md +0 -11
  50. package/.claude/commands/pulse-stop.md +0 -5
  51. package/.claude/commands/verify.md +0 -118
  52. package/.claude/pulse-checklist.md +0 -189
  53. package/.github/workflows/ci.yml +0 -25
  54. package/.github/workflows/publish.yml +0 -45
  55. package/.pulse/load-reports/home/1773432711417.json +0 -22
  56. package/.pulse-verified +0 -1
  57. package/AGENTS.md +0 -697
  58. package/CLAUDE.md +0 -697
  59. package/README.md +0 -95
  60. package/benchmark/public/dist/counter.boot-PO6SNSJZ.js +0 -8
  61. package/benchmark/public/dist/home.boot-YK6VAEQI.js +0 -6
  62. package/benchmark/public/dist/manifest.json +0 -5
  63. package/benchmark/public/dist/runtime-5Y7ESAIB.js +0 -2
  64. package/benchmark/public/dist/runtime-KO4BHUQ3.js +0 -49
  65. package/benchmark/results/2026-03-27-09-20.json +0 -188
  66. package/benchmark/results/2026-03-27-09-21.json +0 -188
  67. package/benchmark/results/2026-03-27-09-29.json +0 -188
  68. package/benchmark/results/2026-03-27-10-14.json +0 -188
  69. package/benchmark/results/2026-03-27-10-18.json +0 -232
  70. package/benchmark/src/pages/counter.js +0 -37
  71. package/benchmark/src/pages/home.js +0 -21
  72. package/docs/.claude/commands/pulse-build.md +0 -7
  73. package/docs/.claude/commands/pulse-dev.md +0 -7
  74. package/docs/.claude/commands/pulse-start.md +0 -9
  75. package/docs/.claude/commands/pulse-stop.md +0 -5
  76. package/docs/.claude/commands/verify.md +0 -84
  77. package/docs/.claude/coverage-check.js +0 -196
  78. package/docs/.claude/pulse-checklist.md +0 -189
  79. package/docs/public/.pulse-ui-version +0 -1
  80. package/docs/public/andypixel.css +0 -237
  81. package/docs/public/bookworms.css +0 -422
  82. package/docs/public/coutumes.css +0 -561
  83. package/docs/public/docs.css +0 -2388
  84. package/docs/public/favicon.svg +0 -4
  85. package/docs/public/home-brut.css +0 -982
  86. package/docs/public/images/bookworms/hero-books.jpg +0 -0
  87. package/docs/public/images/bookworms/how-we-read-now.jpg +0 -0
  88. package/docs/public/images/bookworms/lantern-keepers.jpg +0 -0
  89. package/docs/public/images/bookworms/provincial-bookseller.jpg +0 -0
  90. package/docs/public/images/journey/178.jpg +0 -0
  91. package/docs/public/images/journey/29.jpg +0 -0
  92. package/docs/public/images/retrowheels/hero.jpg +0 -0
  93. package/docs/public/images/shop/devbook-pro.jpg +0 -0
  94. package/docs/public/images/suzukibento/1015.jpg +0 -0
  95. package/docs/public/images/suzukibento/1025.jpg +0 -0
  96. package/docs/public/images/suzukibento/1043.jpg +0 -0
  97. package/docs/public/images/suzukibento/1059.jpg +0 -0
  98. package/docs/public/images/suzukibento/1062.jpg +0 -0
  99. package/docs/public/images/suzukibento/1084.jpg +0 -0
  100. package/docs/public/images/swish/1469334031218-e382a71b716b.jpg +0 -0
  101. package/docs/public/images/swish/1483985988355-763728e1935b.jpg +0 -0
  102. package/docs/public/images/swish/1485518882345-15568b007407.jpg +0 -0
  103. package/docs/public/images/swish/1490481651871-ab68de25d43d.jpg +0 -0
  104. package/docs/public/images/swish/1496747611176-843222e1e57c.jpg +0 -0
  105. package/docs/public/images/swish/1502716119720-b23a93e5fe1b.jpg +0 -0
  106. package/docs/public/images/swish/1539109136881-3be0616acf4b.jpg +0 -0
  107. package/docs/public/images/swish/1551803091-e20673f15770.jpg +0 -0
  108. package/docs/public/images/swish/1558769132-cb1aea458c5e.jpg +0 -0
  109. package/docs/public/images/swish/1572804013309-59a88b7e92f1.jpg +0 -0
  110. package/docs/public/img/heic1501a.jpg +0 -0
  111. package/docs/public/img/placeholder-landscape.svg +0 -18
  112. package/docs/public/img/placeholder-square.svg +0 -14
  113. package/docs/public/img/placeholder-tall.svg +0 -22
  114. package/docs/public/journey.css +0 -528
  115. package/docs/public/menu.js +0 -327
  116. package/docs/public/pulse-ui.css +0 -3028
  117. package/docs/public/pulse-ui.js +0 -257
  118. package/docs/public/retrowheels.css +0 -425
  119. package/docs/public/shop.css +0 -269
  120. package/docs/public/sparkys.css +0 -382
  121. package/docs/public/suzukibento.css +0 -517
  122. package/docs/public/swish.css +0 -394
  123. package/docs/public/theme.css +0 -52
  124. package/docs/public/themes/lumio.css +0 -50
  125. package/docs/public/themes/observatory.css +0 -104
  126. package/docs/server.js +0 -161
  127. package/docs/src/lib/component-page.js +0 -49
  128. package/docs/src/lib/highlight.js +0 -2
  129. package/docs/src/lib/layout.js +0 -190
  130. package/docs/src/lib/metrics-store.js +0 -6
  131. package/docs/src/lib/nav.js +0 -171
  132. package/docs/src/lib/stats.js +0 -81
  133. package/docs/src/pages/accessibility.js +0 -157
  134. package/docs/src/pages/actions.js +0 -194
  135. package/docs/src/pages/auth.js +0 -176
  136. package/docs/src/pages/caching.js +0 -95
  137. package/docs/src/pages/components/accordion.js +0 -48
  138. package/docs/src/pages/components/alert.js +0 -35
  139. package/docs/src/pages/components/app-badge.js +0 -41
  140. package/docs/src/pages/components/avatar.js +0 -35
  141. package/docs/src/pages/components/badge.js +0 -36
  142. package/docs/src/pages/components/breadcrumbs.js +0 -94
  143. package/docs/src/pages/components/button.js +0 -84
  144. package/docs/src/pages/components/card.js +0 -225
  145. package/docs/src/pages/components/charts.js +0 -283
  146. package/docs/src/pages/components/checkbox.js +0 -129
  147. package/docs/src/pages/components/cluster.js +0 -47
  148. package/docs/src/pages/components/container.js +0 -40
  149. package/docs/src/pages/components/cta.js +0 -54
  150. package/docs/src/pages/components/divider.js +0 -37
  151. package/docs/src/pages/components/empty.js +0 -36
  152. package/docs/src/pages/components/feature.js +0 -60
  153. package/docs/src/pages/components/fieldset.js +0 -65
  154. package/docs/src/pages/components/fileupload.js +0 -128
  155. package/docs/src/pages/components/footer.js +0 -58
  156. package/docs/src/pages/components/grid.js +0 -169
  157. package/docs/src/pages/components/heading.js +0 -107
  158. package/docs/src/pages/components/hero.js +0 -123
  159. package/docs/src/pages/components/icons.js +0 -410
  160. package/docs/src/pages/components/image.js +0 -71
  161. package/docs/src/pages/components/input.js +0 -51
  162. package/docs/src/pages/components/media.js +0 -51
  163. package/docs/src/pages/components/modal.js +0 -111
  164. package/docs/src/pages/components/nav.js +0 -136
  165. package/docs/src/pages/components/phone-frame.js +0 -74
  166. package/docs/src/pages/components/progress.js +0 -102
  167. package/docs/src/pages/components/prose.js +0 -111
  168. package/docs/src/pages/components/pullquote.js +0 -71
  169. package/docs/src/pages/components/radio.js +0 -194
  170. package/docs/src/pages/components/rating.js +0 -106
  171. package/docs/src/pages/components/search.js +0 -61
  172. package/docs/src/pages/components/section.js +0 -98
  173. package/docs/src/pages/components/segmented.js +0 -121
  174. package/docs/src/pages/components/select.js +0 -45
  175. package/docs/src/pages/components/slider.js +0 -115
  176. package/docs/src/pages/components/spinner.js +0 -73
  177. package/docs/src/pages/components/stack.js +0 -48
  178. package/docs/src/pages/components/stat.js +0 -55
  179. package/docs/src/pages/components/stepper.js +0 -66
  180. package/docs/src/pages/components/table.js +0 -45
  181. package/docs/src/pages/components/textarea.js +0 -31
  182. package/docs/src/pages/components/toggle.js +0 -84
  183. package/docs/src/pages/components/tooltip.js +0 -48
  184. package/docs/src/pages/components.js +0 -200
  185. package/docs/src/pages/config.js +0 -193
  186. package/docs/src/pages/constraints.js +0 -100
  187. package/docs/src/pages/deploy.js +0 -233
  188. package/docs/src/pages/error-pages.js +0 -99
  189. package/docs/src/pages/extending.js +0 -198
  190. package/docs/src/pages/faq.js +0 -99
  191. package/docs/src/pages/forms.js +0 -129
  192. package/docs/src/pages/getting-started.js +0 -147
  193. package/docs/src/pages/guard.js +0 -120
  194. package/docs/src/pages/home.js +0 -345
  195. package/docs/src/pages/how-it-works.js +0 -244
  196. package/docs/src/pages/hydration.js +0 -108
  197. package/docs/src/pages/images.js +0 -120
  198. package/docs/src/pages/markdown.js +0 -156
  199. package/docs/src/pages/meta.js +0 -193
  200. package/docs/src/pages/mutations.js +0 -107
  201. package/docs/src/pages/navigation.js +0 -85
  202. package/docs/src/pages/performance.js +0 -157
  203. package/docs/src/pages/persist.js +0 -89
  204. package/docs/src/pages/project-structure.js +0 -91
  205. package/docs/src/pages/prompt-examples.js +0 -186
  206. package/docs/src/pages/raw-responses.js +0 -122
  207. package/docs/src/pages/routing.js +0 -120
  208. package/docs/src/pages/server-api.js +0 -291
  209. package/docs/src/pages/server-data.js +0 -203
  210. package/docs/src/pages/slash-commands.js +0 -55
  211. package/docs/src/pages/spec.js +0 -207
  212. package/docs/src/pages/state.js +0 -102
  213. package/docs/src/pages/store.js +0 -203
  214. package/docs/src/pages/streaming.js +0 -107
  215. package/docs/src/pages/stripe.js +0 -194
  216. package/docs/src/pages/supabase.js +0 -324
  217. package/docs/src/pages/templates/andypixel.js +0 -260
  218. package/docs/src/pages/templates/blog-post.js +0 -226
  219. package/docs/src/pages/templates/bookworms.js +0 -227
  220. package/docs/src/pages/templates/coutumes.js +0 -237
  221. package/docs/src/pages/templates/journey.js +0 -186
  222. package/docs/src/pages/templates/mobile-app.js +0 -371
  223. package/docs/src/pages/templates/retrowheels.js +0 -355
  224. package/docs/src/pages/templates/shop.js +0 -268
  225. package/docs/src/pages/templates/sparkys.js +0 -264
  226. package/docs/src/pages/templates/suzukibento.js +0 -200
  227. package/docs/src/pages/templates/swish.js +0 -313
  228. package/docs/src/pages/templates.js +0 -534
  229. package/docs/src/pages/testing.js +0 -199
  230. package/docs/src/pages/validation.js +0 -138
  231. package/examples/.claude/commands/pulse-build.md +0 -7
  232. package/examples/.claude/commands/pulse-dev.md +0 -7
  233. package/examples/.claude/commands/pulse-start.md +0 -9
  234. package/examples/.claude/commands/pulse-stop.md +0 -5
  235. package/examples/.claude/commands/verify.md +0 -84
  236. package/examples/.claude/coverage-check.js +0 -196
  237. package/examples/.claude/pulse-checklist.md +0 -189
  238. package/examples/andypixel.css +0 -237
  239. package/examples/andypixel.js +0 -258
  240. package/examples/bookworms.css +0 -422
  241. package/examples/bookworms.js +0 -225
  242. package/examples/contact.js +0 -166
  243. package/examples/counter.js +0 -94
  244. package/examples/dev.server.js +0 -110
  245. package/examples/examples.test.js +0 -394
  246. package/examples/journey.css +0 -528
  247. package/examples/journey.js +0 -186
  248. package/examples/pricing.js +0 -262
  249. package/examples/products.js +0 -190
  250. package/examples/public/.pulse-ui-version +0 -1
  251. package/examples/public/pulse-ui.css +0 -3028
  252. package/examples/public/pulse-ui.js +0 -257
  253. package/examples/quiz.js +0 -208
  254. package/examples/retrowheels.css +0 -425
  255. package/examples/retrowheels.js +0 -353
  256. package/examples/shared.js +0 -84
  257. package/examples/suzukibento.css +0 -513
  258. package/examples/suzukibento.js +0 -200
  259. package/examples/swish.css +0 -394
  260. package/examples/swish.js +0 -311
  261. package/examples/todos.js +0 -162
  262. package/src/ui/fieldset.js +0 -37
  263. package/src/ui/phone-frame.js +0 -40
  264. package/src/ui/search.js +0 -78
  265. package/src/ui/segmented.js +0 -50
  266. package/src/ui/tooltip.js +0 -28
@@ -1,534 +0,0 @@
1
- /**
2
- * Pulse Docs — Templates
3
- *
4
- * Showcase of ready-made Pulse templates.
5
- */
6
-
7
- import { renderLayout, h1, lead, section } from '../lib/layout.js'
8
- import { prevNext } from '../lib/nav.js'
9
- import { badge } from '../../../src/ui/index.js'
10
-
11
- function esc(s) {
12
- return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;')
13
- }
14
-
15
- function statPill(label, value) {
16
- return `<span style="display:inline-flex;align-items:center;gap:0.3rem;padding:0.2rem 0.6rem;border-radius:999px;font-size:0.72rem;font-weight:500;background:rgba(255,255,255,0.08);border:1px solid rgba(255,255,255,0.14);color:rgba(255,255,255,0.7)">
17
- <span style="color:#a78bfa">●</span> ${esc(label)}: <strong style="color:#fff;font-weight:600">${esc(String(value))}</strong>
18
- </span>`
19
- }
20
-
21
- function templateCard({ name, route, description, tags = [], stats = {}, preview = '' }) {
22
- const tagHtml = tags.map(t =>
23
- `<span style="display:inline-block;padding:0.2rem 0.6rem;background:rgba(255,255,255,0.1);border:1px solid rgba(255,255,255,0.16);border-radius:4px;font-size:0.72rem;font-weight:500;color:rgba(255,255,255,0.7)">${esc(t)}</span>`
24
- ).join('')
25
-
26
- const statHtml = Object.entries(stats).map(([k, v]) => statPill(k, v)).join('')
27
-
28
- // This card is a deliberately dark, self-contained surface (macOS-style
29
- // preview window + dark body) regardless of the page's own theme — it
30
- // mirrors the code-window pattern elsewhere. Uses a real dark background
31
- // (not a translucent white tint over the page background), so it reads
32
- // correctly on both the light and dark docs themes and keeps the
33
- // white-tinted text/borders it was designed with at proper contrast.
34
- return `
35
- <div style="border:1px solid rgba(255,255,255,0.1);border-radius:0.875rem;overflow:hidden;display:flex;flex-direction:column;background:#171512">
36
-
37
- <a href="${esc(route)}" aria-label="Open ${esc(name)} template" style="display:block;background:#0d0d12;border-bottom:1px solid rgba(255,255,255,0.08);text-decoration:none;overflow:hidden">
38
- <div style="padding:0.5rem 0.75rem;background:#16161e;display:flex;align-items:center;gap:0.375rem;border-bottom:1px solid rgba(255,255,255,0.06)">
39
- <span style="width:8px;height:8px;border-radius:50%;background:#ff5f57;display:inline-block"></span>
40
- <span style="width:8px;height:8px;border-radius:50%;background:#febc2e;display:inline-block"></span>
41
- <span style="width:8px;height:8px;border-radius:50%;background:#28c840;display:inline-block"></span>
42
- <span style="flex:1;height:14px;background:rgba(255,255,255,0.06);border-radius:3px;margin-left:0.5rem"></span>
43
- </div>
44
- <div style="padding:1rem 1.25rem 1.25rem;min-height:180px">
45
- ${preview}
46
- </div>
47
- </a>
48
-
49
- <div style="padding:1.25rem 1.25rem 1rem;flex:1;display:flex;flex-direction:column;gap:0.875rem;color:#fff">
50
- <div>
51
- <div style="display:flex;flex-wrap:wrap;align-items:center;gap:0.5rem;margin-bottom:0.5rem">
52
- <h2 style="margin:0;font-size:1rem;font-weight:700;line-height:1.3;color:#fff">${esc(name)}</h2>
53
- ${tagHtml}
54
- </div>
55
- <p style="margin:0;font-size:0.85rem;line-height:1.6;color:rgba(255,255,255,0.7)">${esc(description)}</p>
56
- </div>
57
- <div style="display:flex;flex-wrap:wrap;gap:0.3rem;padding-top:0.625rem;border-top:1px solid rgba(255,255,255,0.08)">${statHtml}</div>
58
- </div>
59
-
60
- <div style="padding:0.875rem 1.25rem;border-top:1px solid rgba(255,255,255,0.08)">
61
- <a href="${esc(route)}" style="display:inline-flex;align-items:center;gap:0.375rem;padding:0.5rem 1.1rem;background:#7c3aed;border-radius:0.4rem;font-size:0.85rem;font-weight:600;text-decoration:none !important;color:#fff !important">
62
- View template
63
- <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.5" aria-hidden="true"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
64
- </a>
65
- </div>
66
- </div>`
67
- }
68
-
69
- // Mini CSS page-layout previews ────────────────────────────────────────────────
70
-
71
- const bar = (w, h, bg = 'rgba(255,255,255,0.12)', r = 3) =>
72
- `<div style="width:${w};height:${h}px;background:${bg};border-radius:${r}px;flex-shrink:0"></div>`
73
-
74
- const mobileAppPreview = `
75
- <div style="display:flex;flex-direction:column;gap:6px;font-size:0">
76
-
77
- <!-- nav -->
78
- <div style="display:flex;align-items:center;gap:6px">
79
- ${bar('28px', 6, 'var(--color-accent)')}
80
- ${bar('40px', 4)} ${bar('30px', 4)} ${bar('30px', 4)}
81
- <div style="flex:1"></div>
82
- ${bar('48px', 18, 'var(--color-accent)', 4)}
83
- </div>
84
-
85
- <!-- hero -->
86
- <div style="display:flex;gap:10px;margin-top:4px;align-items:center">
87
- <div style="flex:1;display:flex;flex-direction:column;gap:5px">
88
- ${bar('50px', 3, 'var(--color-accent)')}
89
- ${bar('100%', 10, 'rgba(255,255,255,0.18)', 2)}
90
- ${bar('85%', 10, 'rgba(255,255,255,0.18)', 2)}
91
- ${bar('70%', 6, 'rgba(255,255,255,0.08)', 2)}
92
- <div style="display:flex;gap:5px;margin-top:4px">
93
- ${bar('52px', 16, 'rgba(255,255,255,0.15)', 4)}
94
- ${bar('52px', 16, 'rgba(255,255,255,0.15)', 4)}
95
- </div>
96
- </div>
97
- <div style="width:52px;height:90px;background:rgba(255,255,255,0.08);border-radius:8px;border:1px solid rgba(255,255,255,0.12);flex-shrink:0;display:flex;flex-direction:column;gap:4px;padding:6px">
98
- ${bar('100%', 20, 'var(--color-accent)', 4)}
99
- ${bar('100%', 12, 'rgba(255,255,255,0.1)', 3)}
100
- ${bar('100%', 12, 'rgba(255,255,255,0.1)', 3)}
101
- </div>
102
- </div>
103
-
104
- <!-- stats row -->
105
- <div style="display:flex;gap:5px;margin-top:2px">
106
- ${[1,2,3,4].map(() => `<div style="flex:1;height:22px;background:rgba(255,255,255,0.06);border-radius:3px"></div>`).join('')}
107
- </div>
108
-
109
- <!-- features grid -->
110
- <div style="display:flex;gap:5px">
111
- ${[1,2,3].map(() => `<div style="flex:1;height:36px;background:rgba(255,255,255,0.05);border-radius:3px;padding:5px;display:flex;flex-direction:column;gap:3px">${bar('40%',4,'var(--color-accent)')}${bar('80%',3)}${bar('90%',3)}</div>`).join('')}
112
- </div>
113
-
114
- <!-- pricing row -->
115
- <div style="display:flex;gap:5px">
116
- ${[1,2,3].map((i) => `<div style="flex:1;height:42px;background:${i===2?'rgba(139,92,246,0.2)':'rgba(255,255,255,0.05)'};border-radius:3px;border:${i===2?'1px solid rgba(139,92,246,0.5)':'1px solid transparent'};padding:5px;display:flex;flex-direction:column;gap:3px">${bar('60%',4,i===2?'var(--color-accent)':'rgba(255,255,255,0.15)')}${bar('40%',6,i===2?'rgba(255,255,255,0.6)':'rgba(255,255,255,0.2)')}${bar('80%',3)}</div>`).join('')}
117
- </div>
118
- </div>`
119
-
120
- const blogPostPreview = `
121
- <div style="display:flex;flex-direction:column;gap:6px;font-size:0;background:#0a2239;border-radius:4px;padding:8px">
122
-
123
- <!-- nav -->
124
- <div style="display:flex;align-items:center;gap:6px">
125
- ${bar('20px', 6, '#53a2be')}
126
- ${bar('36px', 4, '#53a2be', 2)}
127
- <div style="flex:1"></div>
128
- ${bar('28px', 4)} ${bar('28px', 4)} ${bar('28px', 4)}
129
- </div>
130
-
131
- <!-- hero -->
132
- <div style="display:flex;flex-direction:column;align-items:center;gap:4px;padding:8px 0 6px;background:linear-gradient(160deg,#0a2239 0%,#132e32 100%);border-radius:3px;margin-top:2px">
133
- ${bar('40px', 3, '#53a2be')}
134
- ${bar('90%', 8, 'rgba(255,255,255,0.7)', 2)}
135
- ${bar('75%', 8, 'rgba(255,255,255,0.7)', 2)}
136
- ${bar('65%', 4, 'rgba(83,162,190,0.6)', 2)}
137
- ${bar('50%', 3, 'rgba(83,162,190,0.4)', 2)}
138
- </div>
139
-
140
- <!-- article + aside -->
141
- <div style="display:flex;gap:6px;margin-top:2px">
142
- <div style="flex:1;display:flex;flex-direction:column;gap:4px">
143
- ${bar('100%', 48, 'rgba(83,162,190,0.15)', 3)}
144
- ${bar('100%', 4, 'rgba(255,255,255,0.5)', 1)}
145
- ${bar('90%', 4, 'rgba(255,255,255,0.5)', 1)}
146
- ${bar('95%', 4, 'rgba(255,255,255,0.5)', 1)}
147
- ${bar('60%', 4, 'rgba(255,255,255,0.5)', 1)}
148
- ${bar('55%', 6, 'rgba(255,255,255,0.7)', 1)}
149
- ${bar('100%', 3, 'rgba(255,255,255,0.4)', 1)}
150
- ${bar('85%', 3, 'rgba(255,255,255,0.4)', 1)}
151
- </div>
152
- <div style="width:38px;display:flex;flex-direction:column;gap:3px">
153
- ${bar('100%', 4, 'rgba(255,255,255,0.5)', 1)}
154
- ${bar('100%', 3, 'rgba(83,162,190,0.6)', 1)}
155
- ${bar('100%', 3, 'rgba(83,162,190,0.6)', 1)}
156
- ${bar('100%', 3, 'rgba(83,162,190,0.6)', 1)}
157
- <div style="margin-top:3px;background:#132e32;border-radius:3px;padding:3px;display:flex;flex-direction:column;gap:2px">
158
- ${bar('60%', 3, '#53a2be')}
159
- ${bar('100%', 5, 'rgba(255,255,255,0.6)', 1)}
160
- ${bar('100%', 5, 'rgba(255,255,255,0.6)', 1)}
161
- ${bar('100%', 10, 'rgba(83,162,190,0.5)', 2)}
162
- </div>
163
- </div>
164
- </div>
165
- </div>`
166
-
167
- const TEMPLATES = [
168
- {
169
- name: 'Mobile App Landing Page',
170
- route: '/templates/mobile-app',
171
- description: 'A complete marketing site for a mobile app — nav, hero with phone mockup, feature grid, social proof stats, testimonials, pricing cards, FAQ accordion, download CTA, and footer.',
172
- tags: ['Landing Page', 'Marketing', 'SaaS'],
173
- preview: mobileAppPreview,
174
- stats: {
175
- 'Accessibility': '100',
176
- 'Best Practices': '100',
177
- 'SEO': '100',
178
- 'CLS': '0.00',
179
- 'JS': '~4.2 kB',
180
- },
181
- },
182
- {
183
- name: 'Blog Post',
184
- route: '/templates/blog-post',
185
- description: 'A full editorial article page — sticky nav, article hero, prose with image, two-column layout with table-of-contents sidebar, newsletter signup, and footer. Dark astronomy theme.',
186
- tags: ['Blog', 'Editorial'],
187
- preview: blogPostPreview,
188
- stats: {
189
- 'Accessibility': '100',
190
- 'Best Practices': '100',
191
- 'SEO': '100',
192
- 'CLS': '0.00',
193
- 'JS': '~4.2 kB',
194
- },
195
- },
196
- {
197
- name: 'Sparkys — Local Trades',
198
- route: '/templates/sparkys',
199
- description: 'A sharp, trustworthy landing page for a local electrical contractor. Navy + amber palette with a split hero, stats panel, services grid, why-us section, testimonial, and contact strip.',
200
- tags: ['Local Business', 'Trades', 'Light Theme'],
201
- preview: `<div style="background:#f7f9fc;padding:1.5rem;border-radius:8px;font-family:system-ui,sans-serif">
202
- <div style="height:8px;background:#062247;border-radius:3px;width:100%;margin-bottom:0.75rem"></div>
203
- <div style="display:grid;grid-template-columns:1.2fr 1fr;gap:0.75rem;min-height:80px">
204
- <div style="display:flex;flex-direction:column;gap:5px;justify-content:center">
205
- <div style="height:5px;background:#0a3160;border-radius:3px;width:50%"></div>
206
- <div style="height:10px;background:rgba(15,23,42,0.8);border-radius:3px;width:90%"></div>
207
- <div style="height:10px;background:rgba(15,23,42,0.8);border-radius:3px;width:75%"></div>
208
- <div style="height:6px;background:rgba(15,23,42,0.3);border-radius:3px;width:80%;margin-bottom:4px"></div>
209
- <div style="display:flex;gap:5px">
210
- <div style="height:18px;background:#0a3160;border-radius:4px;width:60px"></div>
211
- <div style="height:18px;background:rgba(10,49,96,0.15);border:1px solid rgba(10,49,96,0.3);border-radius:4px;width:50px"></div>
212
- </div>
213
- </div>
214
- <div style="background:linear-gradient(160deg,#0a3160,#062247);border-radius:6px;padding:0.6rem;display:grid;grid-template-columns:1fr 1fr;gap:5px">
215
- ${[1,2,3,4].map(() => `<div style="display:flex;flex-direction:column;gap:2px">
216
- <div style="height:9px;background:rgba(255,255,255,0.7);border-radius:2px;width:70%"></div>
217
- <div style="height:5px;background:rgba(244,167,33,0.5);border-radius:2px;width:90%"></div>
218
- </div>`).join('')}
219
- </div>
220
- </div>
221
- <div style="display:grid;grid-template-columns:repeat(3,1fr);gap:5px;margin-top:0.75rem">
222
- ${[1,2,3].map(() => `<div style="background:#fff;border:1px solid #d6deea;border-radius:5px;padding:0.4rem;display:flex;flex-direction:column;gap:3px">
223
- <div style="width:18px;height:18px;background:#e3ecf7;border-radius:3px"></div>
224
- <div style="height:5px;background:rgba(15,23,42,0.6);border-radius:2px;width:80%"></div>
225
- <div style="height:4px;background:rgba(15,23,42,0.25);border-radius:2px;width:100%"></div>
226
- <div style="height:4px;background:rgba(15,23,42,0.2);border-radius:2px;width:70%"></div>
227
- </div>`).join('')}
228
- </div>
229
- </div>`,
230
- stats: {
231
- 'Accessibility': '100',
232
- 'Best Practices': '100',
233
- 'SEO': '100',
234
- 'CLS': '0.00',
235
- 'Theme': 'light',
236
- },
237
- },
238
- {
239
- name: 'Local Business',
240
- route: '/templates/local-business',
241
- description: 'A warm, photo-forward landing page for a local service business — dog groomer, bakery, physio, tradesperson. Services grid, about section, testimonials, hours, and booking contact form.',
242
- tags: ['Local Business', 'Services', 'Light Theme'],
243
- preview: `<div style="background:linear-gradient(135deg,#fdf6ee,#fdebd0);padding:1.5rem;border-radius:8px;font-family:system-ui,sans-serif">
244
- <div style="height:10px;background:#e8956d;border-radius:6px;width:60%;margin-bottom:0.75rem"></div>
245
- <div style="height:22px;background:rgba(61,43,30,0.85);border-radius:4px;width:85%;margin-bottom:0.5rem"></div>
246
- <div style="height:10px;background:rgba(61,43,30,0.35);border-radius:4px;width:75%;margin-bottom:1rem"></div>
247
- <div style="display:flex;gap:0.5rem;margin-bottom:1.25rem">
248
- <div style="height:30px;background:#c4632a;border-radius:14px;width:100px;flex-shrink:0"></div>
249
- <div style="height:30px;background:rgba(61,43,30,0.15);border:1px solid rgba(61,43,30,0.2);border-radius:14px;width:90px;flex-shrink:0"></div>
250
- </div>
251
- <div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:0.5rem">
252
- ${[1,2,3].map(() => `<div style="background:#fff;border-radius:10px;padding:0.5rem">
253
- <div style="height:8px;background:#e8956d;border-radius:4px;width:70%;margin-bottom:0.35rem"></div>
254
- <div style="height:6px;background:rgba(0,0,0,0.12);border-radius:3px;width:90%"></div>
255
- <div style="height:6px;background:rgba(0,0,0,0.08);border-radius:3px;width:70%;margin-top:0.25rem"></div>
256
- </div>`).join('')}
257
- </div>
258
- </div>`,
259
- stats: {
260
- 'Vibe': 'warm',
261
- 'Theme': 'light',
262
- 'CLS': '0.00',
263
- },
264
- },
265
- {
266
- name: 'Portfolio',
267
- route: '/templates/portfolio',
268
- description: 'A minimal dark portfolio for a creative professional — designer, developer, photographer, or architect. Work grid, about section with skill badges, and a clean contact panel.',
269
- tags: ['Portfolio', 'Personal', 'Minimal'],
270
- preview: `<div style="background:#0d0d10;padding:1.5rem;border-radius:8px;font-family:system-ui,sans-serif">
271
- <div style="height:12px;background:rgba(255,255,255,0.7);border-radius:3px;width:55%;margin-bottom:0.75rem;font-size:0;"></div>
272
- <div style="height:20px;background:rgba(255,255,255,0.85);border-radius:3px;width:80%;margin-bottom:0.5rem"></div>
273
- <div style="height:8px;background:rgba(255,255,255,0.25);border-radius:3px;width:65%;margin-bottom:1.25rem"></div>
274
- <div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:0.5rem">
275
- ${[1,2,3].map(i => `<div style="background:rgba(255,255,255,0.05);border-radius:6px;overflow:hidden">
276
- <div style="height:48px;background:rgba(255,255,255,0.08);"></div>
277
- <div style="padding:0.4rem">
278
- <div style="height:7px;background:rgba(255,255,255,0.4);border-radius:3px;width:80%;margin-bottom:0.3rem"></div>
279
- <div style="height:6px;background:rgba(255,255,255,0.2);border-radius:3px;width:60%"></div>
280
- </div>
281
- </div>`).join('')}
282
- </div>
283
- </div>`,
284
- stats: {
285
- 'Vibe': 'minimal',
286
- 'Theme': 'dark',
287
- 'CLS': '0.00',
288
- },
289
- },
290
- {
291
- name: 'Event / Conference',
292
- route: '/templates/event',
293
- description: 'A high-energy landing page for a conference, festival, or summit — speaker grid, day-by-day schedule accordion, venue section, sponsor logo marquee, and ticket CTA.',
294
- tags: ['Event', 'Conference', 'Bold'],
295
- preview: `<div style="background:linear-gradient(180deg,#05050a,#0d0829);padding:1.5rem;border-radius:8px;font-family:system-ui,sans-serif">
296
- <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:1.25rem">
297
- <div style="height:10px;background:rgba(255,255,255,0.8);border-radius:3px;width:60px"></div>
298
- <div style="height:24px;background:#6366f1;border-radius:6px;width:80px"></div>
299
- </div>
300
- <div style="height:8px;background:rgba(99,102,241,0.6);border-radius:4px;width:50%;margin-bottom:0.5rem"></div>
301
- <div style="height:20px;background:rgba(255,255,255,0.9);border-radius:4px;width:85%;margin-bottom:0.75rem"></div>
302
- <div style="height:8px;background:rgba(255,255,255,0.3);border-radius:4px;width:70%;margin-bottom:1.25rem"></div>
303
- <div style="display:grid;grid-template-columns:repeat(4,1fr);gap:0.4rem">
304
- ${[1,2,3,4].map(() => `<div style="background:rgba(255,255,255,0.06);border-radius:6px;padding:0.4rem;text-align:center">
305
- <div style="height:14px;background:rgba(99,102,241,0.8);border-radius:3px;width:70%;margin:0 auto 0.25rem"></div>
306
- <div style="height:6px;background:rgba(255,255,255,0.2);border-radius:3px;width:80%;margin:0 auto"></div>
307
- </div>`).join('')}
308
- </div>
309
- </div>`,
310
- stats: {
311
- 'Vibe': 'bold',
312
- 'Theme': 'dark',
313
- 'CLS': '0.00',
314
- },
315
- },
316
- {
317
- name: 'Editorial / Magazine',
318
- route: '/templates/editorial',
319
- description: 'A publication homepage — featured article hero, pullquote, article grid with category badges, newsletter signup, and footer. Serif typography throughout via the editorial vibe.',
320
- tags: ['Editorial', 'Publication', 'Blog'],
321
- preview: `<div style="background:#0a0a0c;padding:1.5rem;border-radius:8px;font-family:Georgia,serif">
322
- <div style="height:8px;background:rgba(180,100,60,0.7);border-radius:2px;width:40%;margin-bottom:0.75rem"></div>
323
- <div style="height:20px;background:rgba(255,255,255,0.9);border-radius:2px;width:90%;margin-bottom:0.35rem"></div>
324
- <div style="height:20px;background:rgba(255,255,255,0.75);border-radius:2px;width:70%;margin-bottom:0.75rem"></div>
325
- <div style="height:8px;background:rgba(255,255,255,0.3);border-radius:2px;width:80%;margin-bottom:1.25rem"></div>
326
- <div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:0.5rem">
327
- ${[1,2,3].map(() => `<div style="background:rgba(255,255,255,0.04);border-radius:2px;overflow:hidden">
328
- <div style="height:44px;background:rgba(255,255,255,0.08);"></div>
329
- <div style="padding:0.4rem">
330
- <div style="height:6px;background:rgba(180,100,60,0.6);border-radius:2px;width:40%;margin-bottom:0.3rem"></div>
331
- <div style="height:8px;background:rgba(255,255,255,0.5);border-radius:2px;width:90%;margin-bottom:0.25rem"></div>
332
- <div style="height:8px;background:rgba(255,255,255,0.3);border-radius:2px;width:70%"></div>
333
- </div>
334
- </div>`).join('')}
335
- </div>
336
- </div>`,
337
- stats: {
338
- 'Vibe': 'editorial',
339
- 'Theme': 'dark',
340
- 'CLS': '0.00',
341
- },
342
- },
343
- {
344
- name: 'Swish — Fashion Ecommerce',
345
- route: '/templates/swish',
346
- description: 'An editorial light-theme fashion brand — full-bleed hero with gradient overlay, lookbook grid, product strip, journal cards, and newsletter. Composites nav, section, grid, pullquote, and input.',
347
- tags: ['Ecommerce', 'Fashion', 'Editorial', 'Light Theme'],
348
- preview: `<div style="background:#faf9f7;padding:1.5rem;border-radius:8px;font-family:system-ui,sans-serif">
349
- <div style="height:80px;background:linear-gradient(160deg,#2a2118,#5c4a3a);border-radius:6px;margin-bottom:0.75rem;display:flex;align-items:flex-end;padding:0.5rem">
350
- <div>
351
- <div style="height:6px;background:rgba(255,255,255,0.5);border-radius:2px;width:60px;margin-bottom:4px"></div>
352
- <div style="height:12px;background:#fff;border-radius:2px;width:120px;margin-bottom:6px"></div>
353
- <div style="height:20px;background:rgba(255,255,255,0.9);border-radius:14px;width:80px"></div>
354
- </div>
355
- </div>
356
- <div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:0.4rem">
357
- ${[1,2,3].map(() => `<div style="background:#ede9e3;border-radius:4px;overflow:hidden">
358
- <div style="height:44px;background:rgba(90,70,50,0.15);"></div>
359
- <div style="padding:0.3rem">
360
- <div style="height:6px;background:rgba(0,0,0,0.4);border-radius:2px;width:80%;margin-bottom:3px"></div>
361
- <div style="height:5px;background:rgba(0,0,0,0.2);border-radius:2px;width:50%"></div>
362
- </div>
363
- </div>`).join('')}
364
- </div>
365
- </div>`,
366
- stats: {
367
- 'Vibe': 'editorial',
368
- 'Theme': 'light',
369
- 'CLS': '0.00',
370
- },
371
- },
372
- {
373
- name: 'BookWorms — Independent Bookshop',
374
- route: '/templates/bookworms',
375
- description: 'A warm editorial site for an independent bookshop — staff picks grid, press pullquote, two-column features, event list, and a centred CTA. Fraunces serif headings, light cream palette.',
376
- tags: ['Local Business', 'Retail', 'Editorial', 'Light Theme'],
377
- preview: `<div style="background:#fdf8f2;padding:1.5rem;border-radius:8px;font-family:Georgia,serif">
378
- <div style="height:8px;background:#8b4513;border-radius:2px;width:30%;margin-bottom:0.75rem"></div>
379
- <div style="height:18px;background:rgba(60,30,10,0.85);border-radius:2px;width:85%;margin-bottom:0.5rem"></div>
380
- <div style="height:8px;background:rgba(60,30,10,0.3);border-radius:2px;width:70%;margin-bottom:1rem"></div>
381
- <div style="display:grid;grid-template-columns:1fr 1fr 1fr 1fr;gap:0.4rem">
382
- ${[1,2,3,4].map(() => `<div style="background:#f0e8da;border-radius:3px;overflow:hidden">
383
- <div style="height:50px;background:rgba(139,69,19,0.2);"></div>
384
- <div style="padding:0.3rem">
385
- <div style="height:5px;background:rgba(60,30,10,0.5);border-radius:2px;width:90%;margin-bottom:3px"></div>
386
- <div style="height:4px;background:rgba(60,30,10,0.2);border-radius:2px;width:70%"></div>
387
- </div>
388
- </div>`).join('')}
389
- </div>
390
- </div>`,
391
- stats: {
392
- 'Vibe': 'paper',
393
- 'Theme': 'light',
394
- 'CLS': '0.00',
395
- },
396
- },
397
- {
398
- name: 'Retro Wheels Co. — Classic Cars',
399
- route: '/templates/retrowheels',
400
- description: 'A vintage 1970s enamel-sign aesthetic for a classic car dealer. Stat strip, testimonials, async booking enquiry form with form state, and Rockwell system fonts. Zero extra HTTP requests.',
401
- tags: ['Retro', 'Automotive', 'Form', 'Light Theme'],
402
- preview: `<div style="background:#f5f0e8;padding:1.5rem;border-radius:8px;font-family:'Courier New',monospace">
403
- <div style="height:70px;background:linear-gradient(160deg,#1a1a2e,#2d2d4e);border-radius:4px;margin-bottom:0.75rem;display:flex;align-items:center;justify-content:center">
404
- <div style="height:14px;background:rgba(205,160,60,0.9);border-radius:2px;width:100px"></div>
405
- </div>
406
- <div style="background:#1a1a2e;border-radius:4px;padding:0.5rem;display:grid;grid-template-columns:1fr 1fr 1fr;gap:0.4rem;margin-bottom:0.75rem">
407
- ${[1,2,3].map(() => `<div style="text-align:center">
408
- <div style="height:10px;background:rgba(205,160,60,0.8);border-radius:2px;width:60%;margin:0 auto 3px"></div>
409
- <div style="height:6px;background:rgba(255,255,255,0.3);border-radius:2px;width:80%;margin:0 auto"></div>
410
- </div>`).join('')}
411
- </div>
412
- <div style="display:grid;grid-template-columns:1fr 1fr;gap:0.4rem">
413
- ${[1,2].map(() => `<div style="background:#fff;border:2px solid #1a1a2e;border-radius:3px;padding:0.4rem">
414
- <div style="height:7px;background:rgba(26,26,46,0.7);border-radius:2px;width:90%;margin-bottom:4px"></div>
415
- <div style="height:5px;background:rgba(26,26,46,0.3);border-radius:2px;width:70%"></div>
416
- </div>`).join('')}
417
- </div>
418
- </div>`,
419
- stats: {
420
- 'Vibe': 'retro',
421
- 'Theme': 'light',
422
- 'CLS': '0.00',
423
- },
424
- },
425
- {
426
- name: 'Susuki Bento — Artist Portfolio',
427
- route: '/templates/suzukibento',
428
- description: 'A dark brutalist gallery for a fine-art painter. Zero border-radius, asymmetric 12-column works grid, exhibition table, and press list. Pure HTML — no Pulse UI components. Mode B creative override.',
429
- tags: ['Portfolio', 'Art', 'Brutalist', 'Dark Theme'],
430
- preview: `<div style="background:#0e0e0e;padding:1.5rem;border-radius:0;font-family:system-ui,sans-serif;border:1px solid #222">
431
- <div style="height:10px;background:#f2efe8;width:80%;margin-bottom:0.75rem"></div>
432
- <div style="height:6px;background:#cc3300;width:40%;margin-bottom:1rem"></div>
433
- <div style="display:grid;grid-template-columns:1.5fr 1fr 1fr;grid-template-rows:60px 40px;gap:3px">
434
- <div style="background:rgba(242,239,232,0.1);grid-row:1/3"></div>
435
- <div style="background:rgba(242,239,232,0.08)"></div>
436
- <div style="background:rgba(204,51,0,0.15)"></div>
437
- <div style="background:rgba(242,239,232,0.06);grid-column:2/4"></div>
438
- </div>
439
- <div style="margin-top:0.75rem;display:flex;gap:0.4rem">
440
- ${[1,2,3].map(() => `<div style="height:4px;background:rgba(242,239,232,0.2);border-radius:0;flex:1"></div>`).join('')}
441
- </div>
442
- </div>`,
443
- stats: {
444
- 'Vibe': 'brutalist',
445
- 'Theme': 'dark',
446
- 'CLS': '0.00',
447
- 'Mode': 'B — raw HTML',
448
- },
449
- },
450
- {
451
- name: 'Andy Pixel — Dev Blog',
452
- route: '/templates/andypixel',
453
- description: 'An editorial blog article page — prose-width reading column, badge, avatar, custom code block, blockquote, author bio, and async newsletter subscribe. Orange accent on light cream.',
454
- tags: ['Blog', 'Editorial', 'Article', 'Light Theme'],
455
- preview: `<div style="background:#fdfaf6;padding:1.5rem;border-radius:8px;font-family:Georgia,serif">
456
- <div style="display:flex;align-items:center;gap:0.5rem;margin-bottom:0.75rem">
457
- <div style="width:24px;height:24px;background:#e65c00;border-radius:50%;flex-shrink:0"></div>
458
- <div style="height:6px;background:rgba(0,0,0,0.3);border-radius:3px;width:80px"></div>
459
- <div style="height:14px;background:#e65c00;border-radius:10px;width:40px;margin-left:auto"></div>
460
- </div>
461
- <div style="height:18px;background:rgba(0,0,0,0.8);border-radius:2px;width:90%;margin-bottom:0.5rem"></div>
462
- <div style="height:18px;background:rgba(0,0,0,0.6);border-radius:2px;width:70%;margin-bottom:0.75rem"></div>
463
- <div style="background:#1a1a1a;border-radius:6px;padding:0.5rem;margin-bottom:0.75rem">
464
- <div style="height:5px;background:rgba(230,92,0,0.5);border-radius:2px;width:50%;margin-bottom:5px"></div>
465
- ${[1,2,3].map(() => `<div style="height:4px;background:rgba(255,255,255,0.3);border-radius:2px;width:${70+Math.random()*20|0}%;margin-bottom:3px"></div>`).join('')}
466
- </div>
467
- <div style="height:6px;background:rgba(0,0,0,0.2);border-radius:2px;width:85%"></div>
468
- </div>`,
469
- stats: {
470
- 'Vibe': 'editorial',
471
- 'Theme': 'light',
472
- 'CLS': '0.00',
473
- },
474
- },
475
- {
476
- name: 'Journey — App Landing Page',
477
- route: '/templates/journey',
478
- description: 'A dark split-screen app landing page — sticky hero left, scrollable timeline right, frosted glass nav, gold accent, App Store badge. Pure HTML throughout. Mode B creative override.',
479
- tags: ['Landing Page', 'App', 'Dark Theme', 'Split-screen'],
480
- preview: `<div style="background:#0a0a0f;padding:0;border-radius:8px;overflow:hidden;display:grid;grid-template-columns:1fr 1fr;min-height:130px">
481
- <div style="padding:1rem;display:flex;flex-direction:column;justify-content:center;gap:0.4rem;border-right:1px solid rgba(201,169,110,0.2)">
482
- <div style="height:5px;background:rgba(201,169,110,0.5);border-radius:2px;width:50%;margin-bottom:2px"></div>
483
- <div style="height:14px;background:rgba(240,237,232,0.9);border-radius:2px;width:90%"></div>
484
- <div style="height:14px;background:rgba(240,237,232,0.6);border-radius:2px;width:70%"></div>
485
- <div style="height:8px;background:rgba(240,237,232,0.25);border-radius:2px;width:80%;margin-top:2px"></div>
486
- <div style="height:20px;background:rgba(201,169,110,0.8);border-radius:4px;width:80px;margin-top:4px"></div>
487
- </div>
488
- <div style="padding:0.75rem;display:flex;flex-direction:column;gap:0.4rem">
489
- ${[1,2,3].map(() => `<div style="background:rgba(255,255,255,0.04);border-radius:4px;padding:0.35rem;border-left:2px solid rgba(201,169,110,0.4)">
490
- <div style="height:5px;background:rgba(201,169,110,0.5);border-radius:2px;width:40%;margin-bottom:3px"></div>
491
- <div style="height:4px;background:rgba(240,237,232,0.2);border-radius:2px;width:85%"></div>
492
- </div>`).join('')}
493
- </div>
494
- </div>`,
495
- stats: {
496
- 'Vibe': 'bold',
497
- 'Theme': 'dark',
498
- 'CLS': '0.00',
499
- 'Mode': 'B — raw HTML',
500
- },
501
- },
502
- ]
503
-
504
- const content = `
505
- ${h1('Templates')}
506
- ${lead('Production-ready Pulse templates — from local business to editorial to app landing pages. Every template ships with 100/100/100 Lighthouse scores and zero CLS.')}
507
-
508
- <div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:1.5rem;margin-top:2rem">
509
- ${TEMPLATES.map(templateCard).join('')}
510
- </div>
511
-
512
- ${section('design-directions', 'Design directions')}
513
- <p>Templates give you the structure. The <a href="/meta#vibe">meta.vibe</a> system gives you the personality. Combine a template with a vibe to get a page that feels right for the product — not just correct.</p>
514
- <p style="margin-top:0.75rem">When building a new site, ask: <em>what kind of business is this?</em> A dog groomer and a SaaS dashboard have the same sections but completely different aesthetics. Use the design directions guide (<code>pulse://guide/design-references</code>) to make that decision consciously.</p>
515
-
516
- ${section('adding', 'Suggesting a template')}
517
- <p>Have an idea for a template? <a href="https://github.com/invisibleloop/pulse-framework/issues" target="_blank" rel="noopener noreferrer">Open an issue on GitHub</a> with the layout you have in mind and we'll consider adding it to the library.</p>
518
- `
519
-
520
- export default {
521
- route: '/templates',
522
-
523
- meta: {
524
- title: 'Templates — Pulse',
525
- description: 'Production-ready Pulse templates. Every template uses only Pulse UI components and ships with 100/100/100 Lighthouse scores.',
526
- theme: 'light',
527
- styles: ['/pulse-ui.css', '/theme.css', '/docs.css'],
528
- },
529
-
530
- view: () => {
531
- const { prev, next } = prevNext('/templates')
532
- return renderLayout({ currentHref: '/templates', content, prev, next })
533
- },
534
- }