@invisibleloop/pulse 0.20.11 → 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 +13 -6
  40. package/src/runtime/ssr.test.js +31 -0
  41. package/src/server/index.js +162 -9
  42. package/src/server/server.test.js +225 -2
  43. package/src/spec/schema.js +1 -1
  44. package/src/ui/index.js +0 -5
  45. package/src/ui/ui.test.js +0 -75
  46. package/types/ui.d.ts +0 -21
  47. package/.claude/commands/build-page.md +0 -63
  48. package/.claude/commands/new-doc-page.md +0 -45
  49. package/.claude/commands/pulse-start.md +0 -11
  50. package/.claude/commands/pulse-stop.md +0 -5
  51. package/.claude/commands/verify.md +0 -118
  52. package/.claude/pulse-checklist.md +0 -189
  53. package/.github/workflows/ci.yml +0 -25
  54. package/.github/workflows/publish.yml +0 -45
  55. package/.pulse/load-reports/home/1773432711417.json +0 -22
  56. package/.pulse-verified +0 -1
  57. package/AGENTS.md +0 -697
  58. package/CLAUDE.md +0 -697
  59. package/README.md +0 -95
  60. package/benchmark/public/dist/counter.boot-PO6SNSJZ.js +0 -8
  61. package/benchmark/public/dist/home.boot-YK6VAEQI.js +0 -6
  62. package/benchmark/public/dist/manifest.json +0 -5
  63. package/benchmark/public/dist/runtime-5Y7ESAIB.js +0 -2
  64. package/benchmark/public/dist/runtime-KO4BHUQ3.js +0 -49
  65. package/benchmark/results/2026-03-27-09-20.json +0 -188
  66. package/benchmark/results/2026-03-27-09-21.json +0 -188
  67. package/benchmark/results/2026-03-27-09-29.json +0 -188
  68. package/benchmark/results/2026-03-27-10-14.json +0 -188
  69. package/benchmark/results/2026-03-27-10-18.json +0 -232
  70. package/benchmark/src/pages/counter.js +0 -37
  71. package/benchmark/src/pages/home.js +0 -21
  72. package/docs/.claude/commands/pulse-build.md +0 -7
  73. package/docs/.claude/commands/pulse-dev.md +0 -7
  74. package/docs/.claude/commands/pulse-start.md +0 -9
  75. package/docs/.claude/commands/pulse-stop.md +0 -5
  76. package/docs/.claude/commands/verify.md +0 -84
  77. package/docs/.claude/coverage-check.js +0 -196
  78. package/docs/.claude/pulse-checklist.md +0 -189
  79. package/docs/public/.pulse-ui-version +0 -1
  80. package/docs/public/andypixel.css +0 -237
  81. package/docs/public/bookworms.css +0 -422
  82. package/docs/public/coutumes.css +0 -561
  83. package/docs/public/docs.css +0 -2388
  84. package/docs/public/favicon.svg +0 -4
  85. package/docs/public/home-brut.css +0 -982
  86. package/docs/public/images/bookworms/hero-books.jpg +0 -0
  87. package/docs/public/images/bookworms/how-we-read-now.jpg +0 -0
  88. package/docs/public/images/bookworms/lantern-keepers.jpg +0 -0
  89. package/docs/public/images/bookworms/provincial-bookseller.jpg +0 -0
  90. package/docs/public/images/journey/178.jpg +0 -0
  91. package/docs/public/images/journey/29.jpg +0 -0
  92. package/docs/public/images/retrowheels/hero.jpg +0 -0
  93. package/docs/public/images/shop/devbook-pro.jpg +0 -0
  94. package/docs/public/images/suzukibento/1015.jpg +0 -0
  95. package/docs/public/images/suzukibento/1025.jpg +0 -0
  96. package/docs/public/images/suzukibento/1043.jpg +0 -0
  97. package/docs/public/images/suzukibento/1059.jpg +0 -0
  98. package/docs/public/images/suzukibento/1062.jpg +0 -0
  99. package/docs/public/images/suzukibento/1084.jpg +0 -0
  100. package/docs/public/images/swish/1469334031218-e382a71b716b.jpg +0 -0
  101. package/docs/public/images/swish/1483985988355-763728e1935b.jpg +0 -0
  102. package/docs/public/images/swish/1485518882345-15568b007407.jpg +0 -0
  103. package/docs/public/images/swish/1490481651871-ab68de25d43d.jpg +0 -0
  104. package/docs/public/images/swish/1496747611176-843222e1e57c.jpg +0 -0
  105. package/docs/public/images/swish/1502716119720-b23a93e5fe1b.jpg +0 -0
  106. package/docs/public/images/swish/1539109136881-3be0616acf4b.jpg +0 -0
  107. package/docs/public/images/swish/1551803091-e20673f15770.jpg +0 -0
  108. package/docs/public/images/swish/1558769132-cb1aea458c5e.jpg +0 -0
  109. package/docs/public/images/swish/1572804013309-59a88b7e92f1.jpg +0 -0
  110. package/docs/public/img/heic1501a.jpg +0 -0
  111. package/docs/public/img/placeholder-landscape.svg +0 -18
  112. package/docs/public/img/placeholder-square.svg +0 -14
  113. package/docs/public/img/placeholder-tall.svg +0 -22
  114. package/docs/public/journey.css +0 -528
  115. package/docs/public/menu.js +0 -327
  116. package/docs/public/pulse-ui.css +0 -3028
  117. package/docs/public/pulse-ui.js +0 -257
  118. package/docs/public/retrowheels.css +0 -425
  119. package/docs/public/shop.css +0 -269
  120. package/docs/public/sparkys.css +0 -382
  121. package/docs/public/suzukibento.css +0 -517
  122. package/docs/public/swish.css +0 -394
  123. package/docs/public/theme.css +0 -52
  124. package/docs/public/themes/lumio.css +0 -50
  125. package/docs/public/themes/observatory.css +0 -104
  126. package/docs/server.js +0 -161
  127. package/docs/src/lib/component-page.js +0 -49
  128. package/docs/src/lib/highlight.js +0 -2
  129. package/docs/src/lib/layout.js +0 -190
  130. package/docs/src/lib/metrics-store.js +0 -6
  131. package/docs/src/lib/nav.js +0 -171
  132. package/docs/src/lib/stats.js +0 -81
  133. package/docs/src/pages/accessibility.js +0 -157
  134. package/docs/src/pages/actions.js +0 -194
  135. package/docs/src/pages/auth.js +0 -176
  136. package/docs/src/pages/caching.js +0 -95
  137. package/docs/src/pages/components/accordion.js +0 -48
  138. package/docs/src/pages/components/alert.js +0 -35
  139. package/docs/src/pages/components/app-badge.js +0 -41
  140. package/docs/src/pages/components/avatar.js +0 -35
  141. package/docs/src/pages/components/badge.js +0 -36
  142. package/docs/src/pages/components/breadcrumbs.js +0 -94
  143. package/docs/src/pages/components/button.js +0 -84
  144. package/docs/src/pages/components/card.js +0 -225
  145. package/docs/src/pages/components/charts.js +0 -283
  146. package/docs/src/pages/components/checkbox.js +0 -129
  147. package/docs/src/pages/components/cluster.js +0 -47
  148. package/docs/src/pages/components/container.js +0 -40
  149. package/docs/src/pages/components/cta.js +0 -54
  150. package/docs/src/pages/components/divider.js +0 -37
  151. package/docs/src/pages/components/empty.js +0 -36
  152. package/docs/src/pages/components/feature.js +0 -60
  153. package/docs/src/pages/components/fieldset.js +0 -65
  154. package/docs/src/pages/components/fileupload.js +0 -128
  155. package/docs/src/pages/components/footer.js +0 -58
  156. package/docs/src/pages/components/grid.js +0 -169
  157. package/docs/src/pages/components/heading.js +0 -107
  158. package/docs/src/pages/components/hero.js +0 -123
  159. package/docs/src/pages/components/icons.js +0 -410
  160. package/docs/src/pages/components/image.js +0 -71
  161. package/docs/src/pages/components/input.js +0 -51
  162. package/docs/src/pages/components/media.js +0 -51
  163. package/docs/src/pages/components/modal.js +0 -111
  164. package/docs/src/pages/components/nav.js +0 -136
  165. package/docs/src/pages/components/phone-frame.js +0 -74
  166. package/docs/src/pages/components/progress.js +0 -102
  167. package/docs/src/pages/components/prose.js +0 -111
  168. package/docs/src/pages/components/pullquote.js +0 -71
  169. package/docs/src/pages/components/radio.js +0 -194
  170. package/docs/src/pages/components/rating.js +0 -106
  171. package/docs/src/pages/components/search.js +0 -61
  172. package/docs/src/pages/components/section.js +0 -98
  173. package/docs/src/pages/components/segmented.js +0 -121
  174. package/docs/src/pages/components/select.js +0 -45
  175. package/docs/src/pages/components/slider.js +0 -115
  176. package/docs/src/pages/components/spinner.js +0 -73
  177. package/docs/src/pages/components/stack.js +0 -48
  178. package/docs/src/pages/components/stat.js +0 -55
  179. package/docs/src/pages/components/stepper.js +0 -66
  180. package/docs/src/pages/components/table.js +0 -45
  181. package/docs/src/pages/components/textarea.js +0 -31
  182. package/docs/src/pages/components/toggle.js +0 -84
  183. package/docs/src/pages/components/tooltip.js +0 -48
  184. package/docs/src/pages/components.js +0 -200
  185. package/docs/src/pages/config.js +0 -193
  186. package/docs/src/pages/constraints.js +0 -100
  187. package/docs/src/pages/deploy.js +0 -233
  188. package/docs/src/pages/error-pages.js +0 -99
  189. package/docs/src/pages/extending.js +0 -198
  190. package/docs/src/pages/faq.js +0 -99
  191. package/docs/src/pages/forms.js +0 -129
  192. package/docs/src/pages/getting-started.js +0 -147
  193. package/docs/src/pages/guard.js +0 -120
  194. package/docs/src/pages/home.js +0 -350
  195. package/docs/src/pages/how-it-works.js +0 -244
  196. package/docs/src/pages/hydration.js +0 -108
  197. package/docs/src/pages/images.js +0 -120
  198. package/docs/src/pages/markdown.js +0 -156
  199. package/docs/src/pages/meta.js +0 -193
  200. package/docs/src/pages/mutations.js +0 -107
  201. package/docs/src/pages/navigation.js +0 -85
  202. package/docs/src/pages/performance.js +0 -157
  203. package/docs/src/pages/persist.js +0 -89
  204. package/docs/src/pages/project-structure.js +0 -91
  205. package/docs/src/pages/prompt-examples.js +0 -186
  206. package/docs/src/pages/raw-responses.js +0 -122
  207. package/docs/src/pages/routing.js +0 -120
  208. package/docs/src/pages/server-api.js +0 -291
  209. package/docs/src/pages/server-data.js +0 -203
  210. package/docs/src/pages/slash-commands.js +0 -55
  211. package/docs/src/pages/spec.js +0 -207
  212. package/docs/src/pages/state.js +0 -102
  213. package/docs/src/pages/store.js +0 -203
  214. package/docs/src/pages/streaming.js +0 -107
  215. package/docs/src/pages/stripe.js +0 -194
  216. package/docs/src/pages/supabase.js +0 -324
  217. package/docs/src/pages/templates/andypixel.js +0 -260
  218. package/docs/src/pages/templates/blog-post.js +0 -226
  219. package/docs/src/pages/templates/bookworms.js +0 -227
  220. package/docs/src/pages/templates/coutumes.js +0 -237
  221. package/docs/src/pages/templates/journey.js +0 -186
  222. package/docs/src/pages/templates/mobile-app.js +0 -371
  223. package/docs/src/pages/templates/retrowheels.js +0 -355
  224. package/docs/src/pages/templates/shop.js +0 -268
  225. package/docs/src/pages/templates/sparkys.js +0 -264
  226. package/docs/src/pages/templates/suzukibento.js +0 -200
  227. package/docs/src/pages/templates/swish.js +0 -313
  228. package/docs/src/pages/templates.js +0 -534
  229. package/docs/src/pages/testing.js +0 -199
  230. package/docs/src/pages/validation.js +0 -138
  231. package/examples/.claude/commands/pulse-build.md +0 -7
  232. package/examples/.claude/commands/pulse-dev.md +0 -7
  233. package/examples/.claude/commands/pulse-start.md +0 -9
  234. package/examples/.claude/commands/pulse-stop.md +0 -5
  235. package/examples/.claude/commands/verify.md +0 -84
  236. package/examples/.claude/coverage-check.js +0 -196
  237. package/examples/.claude/pulse-checklist.md +0 -189
  238. package/examples/andypixel.css +0 -237
  239. package/examples/andypixel.js +0 -258
  240. package/examples/bookworms.css +0 -422
  241. package/examples/bookworms.js +0 -225
  242. package/examples/contact.js +0 -166
  243. package/examples/counter.js +0 -94
  244. package/examples/dev.server.js +0 -110
  245. package/examples/examples.test.js +0 -394
  246. package/examples/journey.css +0 -528
  247. package/examples/journey.js +0 -186
  248. package/examples/pricing.js +0 -262
  249. package/examples/products.js +0 -190
  250. package/examples/public/.pulse-ui-version +0 -1
  251. package/examples/public/pulse-ui.css +0 -3028
  252. package/examples/public/pulse-ui.js +0 -257
  253. package/examples/quiz.js +0 -208
  254. package/examples/retrowheels.css +0 -425
  255. package/examples/retrowheels.js +0 -353
  256. package/examples/shared.js +0 -84
  257. package/examples/suzukibento.css +0 -513
  258. package/examples/suzukibento.js +0 -200
  259. package/examples/swish.css +0 -394
  260. package/examples/swish.js +0 -311
  261. package/examples/todos.js +0 -162
  262. package/src/ui/fieldset.js +0 -37
  263. package/src/ui/phone-frame.js +0 -40
  264. package/src/ui/search.js +0 -78
  265. package/src/ui/segmented.js +0 -50
  266. package/src/ui/tooltip.js +0 -28
@@ -1,227 +0,0 @@
1
- /**
2
- * Pulse — BookWorms example
3
- *
4
- * Demonstrates:
5
- * - Light-theme editorial branding for a physical/independent business
6
- * - nav() with logo text, anchor links, and a CTA action button
7
- * - Pure HTML sections — no Pulse section/container wrappers (shows the
8
- * framework is just strings; you can write vanilla HTML where it fits)
9
- * - Book card grid (staff picks) with cover images from picsum.photos
10
- * - Pull-quote / press quote breakout
11
- * - Two-column feature grid
12
- * - Bordered event list with date / title / host columns
13
- * - Centred CTA section
14
- * - footer() with flat link list
15
- * - Google Fonts (Fraunces) for display headings
16
- *
17
- * Run: node examples/dev.server.js → http://localhost:3001/bookworms
18
- */
19
-
20
- import { nav, button, footer } from '../../../../src/ui/index.js'
21
- import { asset } from '../../lib/layout.js'
22
-
23
- // ---------------------------------------------------------------------------
24
- // Static data
25
- // ---------------------------------------------------------------------------
26
-
27
- const picks = [
28
- {
29
- tag: 'Fiction · Staff pick',
30
- title: 'The Lantern Keepers',
31
- author: 'Iris Aldenmoor',
32
- note: 'A quiet novel about three sisters, an unreliable lighthouse, and the long winter that follows a single decision.',
33
- cover: '/images/bookworms/lantern-keepers.jpg',
34
- },
35
- {
36
- tag: 'Non-fiction · This week',
37
- title: 'How We Read Now',
38
- author: 'Dr. Samuel Okafor',
39
- note: 'A short, generous history of attention — from monastic libraries to the open browser tab.',
40
- cover: '/images/bookworms/how-we-read-now.jpg',
41
- },
42
- {
43
- tag: 'Rare find · Just arrived',
44
- title: 'Letters from a Provincial Bookseller',
45
- author: 'M. Halloran (1927)',
46
- note: 'First edition, lightly foxed. A wry account of running a shop through a quiet decade.',
47
- cover: '/images/bookworms/provincial-bookseller.jpg',
48
- },
49
- ]
50
-
51
- const features = [
52
- {
53
- title: 'Curated, not algorithmic',
54
- body: 'Every recommendation comes from one of our booksellers. No bestseller lists, no engagement metrics — just books we\'ve read and would lend you ourselves.',
55
- },
56
- {
57
- title: 'Browse by mood',
58
- body: 'Looking for something restorative? Something difficult? Our shelves are arranged the way readers actually think.',
59
- },
60
- {
61
- title: 'Author evenings & book clubs',
62
- body: 'Small, considered events with writers we admire. Wine, conversation, and signed copies — no panels of four.',
63
- },
64
- {
65
- title: 'Free delivery over £25',
66
- body: 'Wrapped by hand, delivered locally by bicycle. Posted further afield by the next morning.',
67
- },
68
- {
69
- title: 'Gift cards & reader subscriptions',
70
- body: 'A monthly book chosen for one specific reader, with a note from the bookseller who picked it.',
71
- },
72
- {
73
- title: 'Open seven days',
74
- body: 'Old Market Lane, opposite the bakery. Coffee from nine, books from ten.',
75
- },
76
- ]
77
-
78
- const events = [
79
- { date: '12 June', title: 'An evening with Iris Aldenmoor', host: 'In conversation with Priya Joshi' },
80
- { date: '21 June', title: 'Slow Reading Club — Middlemarch, part one', host: 'Hosted by the BookWorms team' },
81
- { date: '03 July', title: 'New voices in non-fiction', host: 'Three debut authors read and answer questions' },
82
- ]
83
-
84
- const navLinks = [
85
- { label: 'Browse', href: '#browse' },
86
- { label: 'Staff picks', href: '#picks' },
87
- { label: 'Events', href: '#events' },
88
- { label: 'Visit', href: '#visit' },
89
- ]
90
-
91
- // ---------------------------------------------------------------------------
92
- // Spec
93
- // ---------------------------------------------------------------------------
94
-
95
- export default {
96
- route: '/templates/bookworms',
97
-
98
- meta: {
99
- title: 'BookWorms — an independent bookstore for curious readers',
100
- description: 'Handpicked fiction, non-fiction, and rare finds. Weekly staff picks, author evenings, and a slow reading club. Open seven days on Old Market Lane.',
101
- theme: 'light',
102
- styles: [
103
- 'https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,400;0,9..144,500;1,9..144,400&display=swap',
104
- '/pulse-ui.css',
105
- '/bookworms.css',
106
- ],
107
- },
108
-
109
- view: () => `
110
- ${nav({
111
- logo: 'BookWorms',
112
- links: navLinks,
113
- action: button({ label: 'Visit the shop', href: '#visit', variant: 'primary', size: 'sm' }),
114
- sticky: true,
115
- })}
116
-
117
- <main id="main-content">
118
- <section class="bw-hero">
119
- <div class="bw-shell bw-hero__inner">
120
- <div class="bw-hero__content">
121
- <p class="bw-eyebrow">Independent bookstore · Old Market Lane</p>
122
- <h1 class="bw-hero-title">Books, handpicked for the curious.</h1>
123
- <p class="bw-hero-lede">
124
- BookWorms is a small shop with a long memory. Every title on our shelves has been read by
125
- someone who works here — and is waiting for the right reader to find it.
126
- </p>
127
- <div class="bw-cta-actions bw-cta-actions--start">
128
- ${button({ label: 'See this week\u2019s picks', href: '#picks', variant: 'primary' })}
129
- ${button({ label: 'Upcoming events', href: '#events', variant: 'secondary' })}
130
- </div>
131
- </div>
132
- <div class="bw-hero__image" aria-hidden="true">
133
- <img src="/images/bookworms/hero-books.jpg" alt="" width="820" height="1080" />
134
- </div>
135
- </div>
136
- </section>
137
-
138
- <section class="bw-section" id="picks">
139
- <div class="bw-shell">
140
- <p class="bw-section-label">This week</p>
141
- <h2 class="bw-section-title">Three books we\u2019re pressing into people\u2019s hands.</h2>
142
- <div class="bw-picks">
143
- ${picks.map((p, i) => `
144
- <article>
145
- <div class="bw-pick-cover-wrap">
146
- <img class="bw-pick-cover" src="${p.cover}" alt="Cover of ${p.title} by ${p.author}" width="400" height="600" loading="lazy" />
147
- <div class="bw-pick-overlay">
148
- <h3 class="bw-pick-overlay-title">${p.title}</h3>
149
- <p class="bw-pick-overlay-author">${i === 2 ? p.author.replace(/\s*\(\d{4}\)/, '') : 'by ' + p.author}</p>
150
- </div>
151
- </div>
152
- <p class="bw-pick-meta">${p.tag}</p>
153
- <h3 class="bw-pick-title">${p.title}</h3>
154
- <p class="bw-pick-author">by ${p.author}</p>
155
- <p class="bw-pick-note">${p.note}</p>
156
- </article>
157
- `).join('')}
158
- </div>
159
- </div>
160
- </section>
161
-
162
- <section class="bw-pull">
163
- <div class="bw-shell">
164
- <p class="bw-pull-quote">\u201cThe kind of shop where you walk in for one book and leave with three you didn\u2019t know you needed.\u201d</p>
165
- <p class="bw-pull-cite">\u2014 The Quarterly Review</p>
166
- </div>
167
- </section>
168
-
169
- <section class="bw-section" id="browse">
170
- <div class="bw-shell">
171
- <p class="bw-section-label">What we do</p>
172
- <h2 class="bw-section-title">A bookstore that still reads.</h2>
173
- <div class="bw-features">
174
- ${features.map(f => `
175
- <div>
176
- <h3 class="bw-feature-title">${f.title}</h3>
177
- <p class="bw-feature-body">${f.body}</p>
178
- </div>
179
- `).join('')}
180
- </div>
181
- </div>
182
- </section>
183
-
184
- <section class="bw-section" id="events">
185
- <div class="bw-shell">
186
- <p class="bw-section-label">Upcoming</p>
187
- <h2 class="bw-section-title">Evenings worth turning up for.</h2>
188
- <div class="bw-events">
189
- ${events.map(e => `
190
- <article class="bw-event">
191
- <p class="bw-event-date">${e.date}</p>
192
- <h3 class="bw-event-title">${e.title}</h3>
193
- <p class="bw-event-host">${e.host}</p>
194
- </article>
195
- `).join('')}
196
- </div>
197
- </div>
198
- </section>
199
-
200
- <section class="bw-cta" id="visit">
201
- <div class="bw-shell">
202
- <h2 class="bw-cta-title">Come and browse.</h2>
203
- <p class="bw-cta-sub">
204
- 14 Old Market Lane, opposite the bakery. Open Monday to Sunday, 10am\u20137pm.
205
- Or sign up to receive one handpicked book each month.
206
- </p>
207
- <div class="bw-cta-actions">
208
- ${button({ label: 'Plan a visit', href: '#visit', variant: 'primary' })}
209
- ${button({ label: 'Reader subscription', href: '#visit', variant: 'secondary' })}
210
- </div>
211
- </div>
212
- </section>
213
- </main>
214
-
215
- ${footer({
216
- logo: 'BookWorms',
217
- links: [
218
- { label: 'Browse', href: '#browse' },
219
- { label: 'Events', href: '#events' },
220
- { label: 'Visit', href: '#visit' },
221
- { label: 'Contact', href: '#visit' },
222
- ],
223
- legal: '\u00a9 2026 BookWorms \u00b7 14 Old Market Lane',
224
- })}
225
- <script src="${asset('/pulse-ui.js')}" defer></script>
226
- `,
227
- }
@@ -1,237 +0,0 @@
1
- // Recreation of coutumes.com for Pulse capability testing
2
- //
3
- // component-free — creative override: this editorial fashion recreation relies
4
- // on several highly custom sections (dual-image blend-mode hero, hover-reveal
5
- // product cards, 50/50 image pair, large serif brand statement, dismissible
6
- // announcement bar) that have no generic component equivalent — the former
7
- // editorialHero/productCard/imagePair/brandStatement/banner components were
8
- // cut from the library as narrow single-purpose wrappers an agent can
9
- // hand-roll; this file now hand-rolls them directly with scoped CSS in
10
- // coutumes.css, using var() tokens throughout. nav(), pullquote() (variant:
11
- // 'editorial'), grid(), uiImage(), and footer() (rich layout) remain kept
12
- // components and are unchanged.
13
- //
14
- // Substituted fonts: Bebas Neue (→ Plaak 3), Cormorant Garamond (→ Exposure Regular)
15
- // All photography: placeholder images (originals are proprietary Coutumes photos)
16
-
17
- import { nav, pullquote, grid, uiImage, footer } from '@invisibleloop/pulse/ui'
18
- import { asset } from '../../lib/layout.js'
19
-
20
- export const spec = {
21
- route: '/templates/coutumes',
22
- meta: {
23
- title: 'Coutumes — Recreation Test',
24
- description: 'Pulse recreation of coutumes.com for capability testing.',
25
- styles: [
26
- 'https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Cormorant+Garamond:ital,wght@0,400;0,500;1,400&family=Barlow:wght@400;500&display=optional',
27
- '/pulse-ui.css',
28
- '/coutumes.css',
29
- ],
30
- },
31
-
32
- state: { bannerVisible: true },
33
-
34
- mutations: {
35
- dismissBanner: (state) => ({ bannerVisible: false }),
36
- },
37
-
38
- view: (state) => `
39
- <main id="main-content">
40
-
41
- <!-- Announcement bar — fixed to bottom, dismissible. Raw HTML: the
42
- removed banner() component previously rendered this; the dismiss
43
- button still wires to the existing dismissBanner mutation. -->
44
- ${state.bannerVisible ? `
45
- <div class="coutumes-announce" role="banner">
46
- <span>SILVER LANDS&nbsp;•&nbsp;NEW CAPSULE COLLECTION</span>
47
- <button class="coutumes-announce-close" data-event="click:dismissBanner" aria-label="Close announcement">×</button>
48
- </div>
49
- ` : ''}
50
-
51
- <!-- Navigation — nav() supports a second utilityLinks group natively. -->
52
- ${nav({
53
- logo: 'COUTUMES',
54
- logoHref: '/',
55
- links: [
56
- { label: 'Jewelry', href: '#' },
57
- { label: 'Accessories', href: '#' },
58
- { label: 'Souvenirs Club', href: '#' },
59
- { label: 'About', href: '#' },
60
- ],
61
- utilityLinks: [
62
- { label: 'Search', href: '#' },
63
- { label: 'Magazine', href: '#' },
64
- { label: 'FAQ', href: '#' },
65
- { label: 'Cart (0)', href: '#' },
66
- ],
67
- sticky: true,
68
- background: 'var(--coutumes-white)',
69
- color: 'var(--coutumes-black)',
70
- })}
71
-
72
- <!-- Hero — dual split-image with overlapping blend-mode type. Raw HTML:
73
- the removed editorialHero() component previously rendered this
74
- mix-blend-mode overlay technique; a real h1 heading element carries
75
- the accessible heading while the overlay lines are decorative (aria-hidden). -->
76
- <section class="coutumes-hero">
77
- <h1 class="coutumes-sr-only">Silver Lands Capsule Collection</h1>
78
- <div class="coutumes-hero-images">
79
- <div class="coutumes-hero-img">
80
- <img src="https://images.unsplash.com/photo-1617038260897-41a1f14a8ca0?w=700&q=80" alt="Hand holding a cream hat, wearing a dark ring" width="700" loading="eager" fetchpriority="high">
81
- </div>
82
- <div class="coutumes-hero-img">
83
- <img src="https://images.unsplash.com/photo-1552374196-1ab2a1c593e8?w=700&q=80" alt="Man leaning against a Land Rover in the countryside" width="700" loading="eager">
84
- </div>
85
- </div>
86
- <div class="coutumes-hero-text" aria-hidden="true">
87
- ${['INTRODUCING', 'SILVER LANDS', 'CAPSULE', 'COLLECTION'].map(l => `<span>${l}</span>`).join('')}
88
- </div>
89
- </section>
90
-
91
- <!-- Editorial quote — pullquote() variant='editorial' (centred, no border). -->
92
- <section id="collection">
93
- ${pullquote({
94
- quote: 'Open roads with no precise destination, passing landscapes, a few days spent outside with friends. Pieces designed to travel for a long time.',
95
- cite: 'Silver Lands Capsule Collection',
96
- variant: 'editorial',
97
- size: 'lg',
98
- })}
99
- </section>
100
-
101
- <!-- Product grid — raw anchor cards (full-bleed image + below-caption
102
- name/price), replacing the removed productCard() component.
103
- Laid out with the kept grid() component. -->
104
- ${grid({
105
- cols: 3,
106
- gap: 'sm',
107
- content: [
108
- {
109
- img: 'https://images.unsplash.com/photo-1611085583191-a3b181a88401?w=600&q=80',
110
- alt: 'White beaded bracelet on dark wrist',
111
- name: 'Silver Tube Bracelet',
112
- price: '£67.00',
113
- badge: 'NEW',
114
- },
115
- {
116
- img: 'https://images.unsplash.com/photo-1535632066927-ab7c9ab60908?w=600&q=80',
117
- alt: 'Close portrait of young man with curly hair wearing gold earring',
118
- name: 'Kali Hoop Earring',
119
- price: '£31.00',
120
- badge: 'NEW',
121
- },
122
- {
123
- img: 'https://picsum.photos/seed/cuff/600/750',
124
- alt: 'Silver cuff bracelet on wrist',
125
- name: 'Land Cuff',
126
- price: '£62.00',
127
- badge: 'NEW',
128
- },
129
- ].map(p => `
130
- <a href="#" class="coutumes-product-card">
131
- <img src="${p.img}" alt="${p.alt}" loading="lazy" decoding="async">
132
- ${p.badge ? `<span class="coutumes-product-badge">${p.badge}</span>` : ''}
133
- <div class="coutumes-product-caption">
134
- <p class="coutumes-product-name">${p.name}</p>
135
- <p class="coutumes-product-price">${p.price}</p>
136
- </div>
137
- </a>
138
- `).join(''),
139
- class: 'coutumes-product-grid',
140
- })}
141
-
142
- <!-- 50/50 split editorial images — raw HTML two-column grid, replacing
143
- the removed imagePair() component. media() is image + text and
144
- is not a fit for this text-free two-photo layout. -->
145
- <div class="coutumes-split-images" aria-label="Craftmanship and boutique">
146
- <div class="coutumes-split-col">
147
- <div class="coutumes-split-img">
148
- <img src="https://picsum.photos/seed/craft/700/900" alt="Craftsman working with tools in a jewelry workshop" loading="lazy" decoding="async">
149
- </div>
150
- <a href="#craftmanship" class="coutumes-split-link">Craftmanship →</a>
151
- </div>
152
- <div class="coutumes-split-col">
153
- <div class="coutumes-split-img">
154
- <img src="https://images.unsplash.com/photo-1441984904996-e0b6ba687e04?w=700&q=80" alt="Elegant boutique storefront with warm lighting" loading="lazy" decoding="async">
155
- </div>
156
- <a href="#store" class="coutumes-split-link">Store →</a>
157
- </div>
158
- </div>
159
-
160
- <!-- Large editorial statement — raw HTML, replacing the removed
161
- brandStatement() component. pullquote() is for shorter attributed
162
- quotes; this is an unattributed full-width brand-voice paragraph. -->
163
- <div class="coutumes-statement">
164
- <div class="coutumes-statement-inner">
165
- <p>pieces that are both eclectic and everyday. Subtly precious, expressive, and refined, our jewelry complements an outfit or disrupts it, introducing new rituals into the modern man&rsquo;s wardrobe.</p>
166
- </div>
167
- </div>
168
-
169
- <!-- Portrait — uiImage() works well here with maxWidth constraint -->
170
- ${uiImage({
171
- src: 'https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?w=600&q=80',
172
- alt: 'Young man holding a Kodak camera, wearing a silver ring',
173
- ratio: '3/4',
174
- maxWidth: '600px',
175
- class: 'coutumes-portrait',
176
- })}
177
-
178
- <!-- Footer — footer() already supports the rich layout (columns,
179
- subscribe, wordmark, background/color) used here; no changes needed. -->
180
- ${footer({
181
- background: 'var(--coutumes-amber)',
182
- color: 'var(--coutumes-black)',
183
- subscribe: `
184
- <p class="coutumes-footer-subscribe-label">JOIN THE COUTUMES CLUB</p>
185
- <form class="coutumes-footer-form" aria-label="Newsletter subscribe">
186
- <label for="footer-email" class="coutumes-sr-only">Email address</label>
187
- <input id="footer-email" type="email" name="email" placeholder="Email" autocomplete="email">
188
- <button type="submit">■ SUBSCRIBE</button>
189
- </form>
190
- <p class="coutumes-footer-legal">By clicking Subscribe, I accept the <a href="#">Privacy Policy</a>.</p>
191
- `,
192
- columns: [
193
- {
194
- title: 'Our Brand',
195
- links: [
196
- { label: 'Craftmanship', href: '#' },
197
- { label: 'About Us', href: '#' },
198
- { label: 'Our Store', href: '#' },
199
- { label: 'Magazine', href: '#' },
200
- { label: 'Instagram', href: '#' },
201
- ],
202
- },
203
- {
204
- title: 'Our Collections',
205
- links: [
206
- { label: 'Best sellers', href: '#' },
207
- { label: 'Silver Lands', href: '#' },
208
- { label: 'Classics', href: '#' },
209
- { label: 'Stones', href: '#' },
210
- { label: 'Denim Collection',href: '#' },
211
- { label: 'Souvenirs Club', href: '#' },
212
- ],
213
- },
214
- {
215
- title: 'Help',
216
- links: [
217
- { label: 'Shipping Policy', href: '#' },
218
- { label: 'Return', href: '#' },
219
- { label: 'FAQ', href: '#' },
220
- { label: 'Contact', href: '#' },
221
- ],
222
- },
223
- ],
224
- wordmark: 'COUTUMES',
225
- links: [
226
- { label: 'Privacy Policy', href: '#' },
227
- { label: 'Terms of service', href: '#' },
228
- ],
229
- legal: 'Site by Coutumes',
230
- })}
231
-
232
- </main>
233
- <script src="${asset('/pulse-ui.js')}" defer></script>
234
- `,
235
- }
236
-
237
- export default spec
@@ -1,186 +0,0 @@
1
- /**
2
- * Pulse — Journey example
3
- *
4
- * Demonstrates:
5
- * - Mode B creative override — raw HTML throughout, no structural components
6
- * - Split-screen layout: sticky hero left / scrollable timeline right (100svh grid)
7
- * - appBadge component from @invisibleloop/pulse/ui
8
- * - Deep-night palette (#0A0A0F) with gold (#C9A96E) accent
9
- * - Alternating left/right timeline cards with centred thread + connector dots
10
- * - calc(50% - var(--ui-space-8)) pattern for half-width timeline cards
11
- * - Sticky positioned timeline column with overflow-y: auto + scrollbar-width: none
12
- * - Frosted glass nav: rgba translucency + backdrop-filter blur
13
- * - Skip link, visually-hidden headings, section aria-labelledby — full a11y
14
- * - Mobile responsive: stacks vertically, thread moves left, full-width cards
15
- * - iconTag, iconPlus, iconSend, iconArrowDown, iconFeather from icons.js
16
- * - picsum.photos/id/* for stable prototype images
17
- *
18
- * Run: node examples/dev.server.js → http://localhost:3001/journey
19
- */
20
-
21
- import { appBadge } from '../../../../src/ui/index.js'
22
- import { iconTag, iconPlus, iconSend, iconArrowDown, iconFeather } from '../../../../src/ui/icons.js'
23
-
24
- // ---------------------------------------------------------------------------
25
- // Timeline data
26
- // ---------------------------------------------------------------------------
27
-
28
- const timelineEvents = [
29
- {
30
- year: '2019',
31
- title: 'First Day in the City',
32
- body: 'Packed everything into one suitcase. The apartment was smaller than expected. The feeling was not.',
33
- tag: 'Milestone',
34
- image: { src: '/images/journey/29.jpg', alt: 'City street view' },
35
- },
36
- {
37
- year: '2020',
38
- title: 'Learning to Stay Still',
39
- body: 'The world paused. Finished three books. Started painting. Discovered what quiet actually sounds like.',
40
- tag: 'Reflection',
41
- },
42
- {
43
- year: '2021',
44
- title: 'The Road Trip',
45
- body: '4,200 miles across eleven states. Every diner looks the same after midnight. Every sunrise does not.',
46
- tag: 'Adventure',
47
- image: { src: '/images/journey/178.jpg', alt: 'Open road through a landscape' },
48
- },
49
- {
50
- year: '2022',
51
- title: 'Said Yes',
52
- body: 'On a Tuesday. In the kitchen. No ring yet, just the question and the answer and the rest of our lives.',
53
- tag: 'Love',
54
- },
55
- {
56
- year: '2024',
57
- title: 'This Is Home Now',
58
- body: 'The boxes are gone. The walls have pictures. The cat has a name. The journey continues.',
59
- tag: 'Present',
60
- },
61
- ]
62
-
63
- // ---------------------------------------------------------------------------
64
- // Timeline card helper
65
- // ---------------------------------------------------------------------------
66
-
67
- const tlCard = ({ year, title, body, tag, image }) => `
68
- <div class="j-tl-card${image ? ' j-tl-card--image' : ''}">
69
- ${image ? `<img class="j-tl-image" src="${image.src}" alt="${image.alt}" width="600" height="340" loading="lazy" decoding="async">` : ''}
70
- <div class="j-tl-content">
71
- <p class="j-tl-year">${year}</p>
72
- <h3 class="j-tl-title">${title}</h3>
73
- <p class="j-tl-body">${body}</p>
74
- <span class="j-tl-tag">${iconTag({ size: 10 })}${tag}</span>
75
- </div>
76
- </div>
77
- `
78
-
79
- // ---------------------------------------------------------------------------
80
- // Spec
81
- // ---------------------------------------------------------------------------
82
-
83
- export default {
84
- route: '/templates/journey',
85
-
86
- meta: {
87
- title: 'Journey \u2014 Your story, in order',
88
- description: 'Plot your life\u2019s journey. Map events, milestones, and memories on a beautiful timeline.',
89
- styles: ['/pulse-ui.css', '/journey.css'],
90
- theme: 'dark',
91
- },
92
-
93
- view: () => `
94
- <a href="#main-content" class="j-skip-link">Skip to main content</a>
95
-
96
- <header role="banner">
97
- <nav class="j-nav" aria-label="Site navigation">
98
- <a href="/journey" class="j-nav-wordmark" aria-label="Journey \u2014 home">Journey</a>
99
- ${appBadge({ store: 'apple', href: '#download' })}
100
- </nav>
101
- </header>
102
-
103
- <main id="main-content">
104
-
105
- <!-- Hero + Timeline split -->
106
- <div class="j-hero-split">
107
-
108
- <!-- Hero -->
109
- <section class="j-hero" aria-labelledby="journey-hero-title">
110
- <p class="j-hero-eyebrow" aria-hidden="true">Your story \u00b7 In order</p>
111
- <h1 class="j-hero-title" id="journey-hero-title">
112
- Every<br>moment<br><em>matters.</em>
113
- </h1>
114
- <p class="j-hero-sub">
115
- Journey is a beautiful timeline for your life. Plot events, mark milestones,
116
- and explore the story only you can tell.
117
- </p>
118
- <div class="j-hero-cta">
119
- <a href="#download" class="j-btn-primary">
120
- Download free
121
- </a>
122
- <a href="#journey-timeline" class="j-btn-ghost">
123
- See how it works ${iconArrowDown({ size: 14 })}
124
- </a>
125
- </div>
126
- <div class="j-hero-scroll" aria-hidden="true">
127
- <span class="j-hero-line"></span>
128
- <span>Scroll to explore</span>
129
- </div>
130
- </section>
131
-
132
- <!-- Timeline demo -->
133
- <section class="j-timeline-section" id="journey-timeline" aria-labelledby="journey-timeline-heading">
134
- <p class="j-section-label" aria-hidden="true">01 \u2014 Your timeline</p>
135
- <h2 class="j-visually-hidden" id="journey-timeline-heading">Example journey timeline</h2>
136
- <div class="j-timeline" role="list">
137
- ${timelineEvents.map(ev => `
138
- <div class="j-tl-item" role="listitem">
139
- ${tlCard(ev)}
140
- </div>
141
- `).join('')}
142
- </div>
143
- </section>
144
-
145
- </div><!-- end j-hero-split -->
146
-
147
- <!-- Features -->
148
- <section class="j-features" aria-labelledby="journey-features-heading">
149
- <h2 class="j-visually-hidden" id="journey-features-heading">Features</h2>
150
- <div>
151
- <p class="j-feature-label">${iconPlus({ size: 12 })}Plot your path</p>
152
- <p class="j-feature-body">Add any event \u2014 big or small \u2014 and watch your story take shape along a living timeline.</p>
153
- </div>
154
- <div>
155
- <p class="j-feature-label">${iconTag({ size: 12 })}Tag &amp; explore</p>
156
- <p class="j-feature-body">Organise moments by theme, mood, or chapter. Filter your timeline to find any memory instantly.</p>
157
- </div>
158
- <div>
159
- <p class="j-feature-label">${iconSend({ size: 12 })}Share your journey</p>
160
- <p class="j-feature-body">Export a beautiful visual timeline to share with the people who made it worth living.</p>
161
- </div>
162
- </section>
163
-
164
- <!-- CTA / Download -->
165
- <section class="j-cta" id="download" aria-labelledby="journey-cta-heading">
166
- <p class="j-cta-eyebrow">Available now \u00b7 iOS</p>
167
- <h2 class="j-cta-title" id="journey-cta-heading">
168
- Begin your<br><em>journey</em> today.
169
- </h2>
170
- <div class="j-cta-badges">
171
- ${appBadge({ store: 'apple', href: '#' })}
172
- </div>
173
- </section>
174
-
175
- </main>
176
-
177
- <footer class="j-footer" role="contentinfo">
178
- <span class="j-footer-word">${iconFeather({ size: 12 })} Journey &copy; 2025</span>
179
- <nav class="j-footer-links" aria-label="Footer navigation">
180
- <a href="#privacy">Privacy</a>
181
- <a href="#terms">Terms</a>
182
- <a href="#contact">Contact</a>
183
- </nav>
184
- </footer>
185
- `,
186
- }