@invisibleloop/pulse 0.12.3 → 0.14.2

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 (181) hide show
  1. package/.claude/commands/build-page.md +5 -1
  2. package/.claude/commands/verify.md +31 -17
  3. package/.claude/pulse-checklist.md +21 -1
  4. package/.github/workflows/publish.yml +1 -0
  5. package/.pulse-verified +1 -0
  6. package/CLAUDE.md +56 -15
  7. package/docs/.claude/commands/verify.md +4 -0
  8. package/docs/.claude/pulse-checklist.md +20 -2
  9. package/docs/public/.pulse-ui-version +1 -1
  10. package/docs/public/andypixel.css +237 -0
  11. package/docs/public/bookworms.css +422 -0
  12. package/docs/public/coutumes.css +584 -0
  13. package/docs/public/images/bookworms/hero-books.jpg +0 -0
  14. package/docs/public/images/bookworms/how-we-read-now.jpg +0 -0
  15. package/docs/public/images/bookworms/lantern-keepers.jpg +0 -0
  16. package/docs/public/images/bookworms/provincial-bookseller.jpg +0 -0
  17. package/docs/public/images/journey/178.jpg +0 -0
  18. package/docs/public/images/journey/29.jpg +0 -0
  19. package/docs/public/images/retrowheels/hero.jpg +0 -0
  20. package/docs/public/images/shop/devbook-pro.jpg +0 -0
  21. package/docs/public/images/suzukibento/1015.jpg +0 -0
  22. package/docs/public/images/suzukibento/1025.jpg +0 -0
  23. package/docs/public/images/suzukibento/1043.jpg +0 -0
  24. package/docs/public/images/suzukibento/1059.jpg +0 -0
  25. package/docs/public/images/suzukibento/1062.jpg +0 -0
  26. package/docs/public/images/suzukibento/1084.jpg +0 -0
  27. package/docs/public/images/swish/1469334031218-e382a71b716b.jpg +0 -0
  28. package/docs/public/images/swish/1483985988355-763728e1935b.jpg +0 -0
  29. package/docs/public/images/swish/1485518882345-15568b007407.jpg +0 -0
  30. package/docs/public/images/swish/1490481651871-ab68de25d43d.jpg +0 -0
  31. package/docs/public/images/swish/1496747611176-843222e1e57c.jpg +0 -0
  32. package/docs/public/images/swish/1502716119720-b23a93e5fe1b.jpg +0 -0
  33. package/docs/public/images/swish/1539109136881-3be0616acf4b.jpg +0 -0
  34. package/docs/public/images/swish/1551803091-e20673f15770.jpg +0 -0
  35. package/docs/public/images/swish/1558769132-cb1aea458c5e.jpg +0 -0
  36. package/docs/public/images/swish/1572804013309-59a88b7e92f1.jpg +0 -0
  37. package/docs/public/journey.css +528 -0
  38. package/docs/public/pulse-ui.css +1036 -22
  39. package/docs/public/retrowheels.css +402 -0
  40. package/docs/public/shop.css +235 -0
  41. package/docs/public/sparkys.css +349 -0
  42. package/docs/public/suzukibento.css +517 -0
  43. package/docs/public/swish.css +394 -0
  44. package/docs/server.js +19 -4
  45. package/docs/src/lib/nav.js +14 -3
  46. package/docs/src/pages/components/decorate.js +74 -0
  47. package/docs/src/pages/components/gallery.js +102 -0
  48. package/docs/src/pages/components/hero.js +1 -0
  49. package/docs/src/pages/components/marquee.js +76 -0
  50. package/docs/src/pages/components/photo-card.js +74 -0
  51. package/docs/src/pages/components/section.js +40 -1
  52. package/docs/src/pages/meta.js +26 -0
  53. package/docs/src/pages/templates/andypixel.js +260 -0
  54. package/docs/src/pages/templates/bookworms.js +227 -0
  55. package/docs/src/pages/templates/coutumes.js +226 -0
  56. package/docs/src/pages/templates/journey.js +186 -0
  57. package/docs/src/pages/templates/retrowheels.js +342 -0
  58. package/docs/src/pages/templates/shop.js +257 -0
  59. package/docs/src/pages/templates/sparkys.js +249 -0
  60. package/docs/src/pages/templates/suzukibento.js +200 -0
  61. package/docs/src/pages/templates/swish.js +313 -0
  62. package/docs/src/pages/templates.js +313 -2
  63. package/docs/src/pages/testing.js +2 -1
  64. package/examples/.claude/commands/pulse-build.md +7 -0
  65. package/examples/.claude/commands/pulse-dev.md +7 -0
  66. package/examples/.claude/commands/pulse-start.md +9 -0
  67. package/examples/.claude/commands/pulse-stop.md +5 -0
  68. package/examples/.claude/commands/verify.md +84 -0
  69. package/examples/.claude/coverage-check.js +127 -0
  70. package/examples/.claude/pulse-checklist.md +177 -0
  71. package/examples/andypixel.css +237 -0
  72. package/examples/andypixel.js +258 -0
  73. package/examples/bookworms.css +422 -0
  74. package/examples/bookworms.js +225 -0
  75. package/examples/contact.js +1 -0
  76. package/examples/counter.js +1 -0
  77. package/examples/dev.server.js +17 -0
  78. package/examples/journey.css +528 -0
  79. package/examples/journey.js +186 -0
  80. package/examples/pricing.js +1 -0
  81. package/examples/public/.pulse-ui-version +1 -0
  82. package/examples/public/pulse-ui.css +4031 -0
  83. package/examples/public/pulse-ui.js +257 -0
  84. package/examples/quiz.js +1 -0
  85. package/examples/retrowheels.css +402 -0
  86. package/examples/retrowheels.js +340 -0
  87. package/examples/shared.js +6 -0
  88. package/examples/suzukibento.css +513 -0
  89. package/examples/suzukibento.js +200 -0
  90. package/examples/swish.css +394 -0
  91. package/examples/swish.js +311 -0
  92. package/examples/todos.js +1 -0
  93. package/package.json +2 -3
  94. package/public/.pulse-ui-version +1 -1
  95. package/public/images/bookworms/hero-books.jpg +0 -0
  96. package/public/images/bookworms/how-we-read-now.jpg +0 -0
  97. package/public/images/bookworms/lantern-keepers.jpg +0 -0
  98. package/public/images/bookworms/provincial-bookseller.jpg +0 -0
  99. package/public/images/journey/178.jpg +0 -0
  100. package/public/images/journey/29.jpg +0 -0
  101. package/public/images/retrowheels/hero.jpg +0 -0
  102. package/public/images/shop/devbook-pro.jpg +0 -0
  103. package/public/images/suzukibento/1015.jpg +0 -0
  104. package/public/images/suzukibento/1025.jpg +0 -0
  105. package/public/images/suzukibento/1043.jpg +0 -0
  106. package/public/images/suzukibento/1059.jpg +0 -0
  107. package/public/images/suzukibento/1062.jpg +0 -0
  108. package/public/images/suzukibento/1084.jpg +0 -0
  109. package/public/images/swish/1469334031218-e382a71b716b.jpg +0 -0
  110. package/public/images/swish/1483985988355-763728e1935b.jpg +0 -0
  111. package/public/images/swish/1485518882345-15568b007407.jpg +0 -0
  112. package/public/images/swish/1490481651871-ab68de25d43d.jpg +0 -0
  113. package/public/images/swish/1496747611176-843222e1e57c.jpg +0 -0
  114. package/public/images/swish/1502716119720-b23a93e5fe1b.jpg +0 -0
  115. package/public/images/swish/1539109136881-3be0616acf4b.jpg +0 -0
  116. package/public/images/swish/1551803091-e20673f15770.jpg +0 -0
  117. package/public/images/swish/1558769132-cb1aea458c5e.jpg +0 -0
  118. package/public/images/swish/1572804013309-59a88b7e92f1.jpg +0 -0
  119. package/public/pulse-ui.css +1080 -22
  120. package/scripts/build.js +72 -18
  121. package/src/agent/checklist.md +22 -2
  122. package/src/agent/commands/verify.md +4 -0
  123. package/src/agent/guide-components.md +146 -8
  124. package/src/agent/guide-design-gallery.md +288 -0
  125. package/src/agent/guide-design-references.md +172 -0
  126. package/src/agent/guide-explore.md +313 -0
  127. package/src/agent/guide-styles.md +212 -11
  128. package/src/agent/guide-templates.md +223 -10
  129. package/src/agent/guide.md +9 -4
  130. package/src/agent/identity.md +2 -1
  131. package/src/agent/skills/verify/SKILL.md +64 -28
  132. package/src/agent/workflow.md +247 -61
  133. package/src/cli/dev.js +39 -14
  134. package/src/cli/fmt.js +113 -0
  135. package/src/cli/index.js +60 -33
  136. package/src/cli/logger.js +145 -0
  137. package/src/cli/scaffold.js +57 -3
  138. package/src/html.js +6 -1
  139. package/src/mcp/server.js +2561 -99
  140. package/src/mcp/validate-worker.js +49 -5
  141. package/src/runtime/ssr.js +4 -1
  142. package/src/server/index.js +29 -5
  143. package/src/spec/schema.js +21 -0
  144. package/src/spec/schema.test.js +28 -0
  145. package/src/testing/html.js +130 -4
  146. package/src/testing/testing.test.js +58 -0
  147. package/src/ui/banner.js +24 -9
  148. package/src/ui/brand-statement.js +58 -0
  149. package/src/ui/button.js +13 -2
  150. package/src/ui/card.js +20 -1
  151. package/src/ui/colophon.js +29 -0
  152. package/src/ui/contact-row.js +41 -0
  153. package/src/ui/cta.js +18 -4
  154. package/src/ui/decorate.js +55 -0
  155. package/src/ui/display-heading.js +44 -0
  156. package/src/ui/drop-cap.js +29 -0
  157. package/src/ui/editorial-hero.js +63 -0
  158. package/src/ui/entry-list.js +36 -0
  159. package/src/ui/feature.js +20 -3
  160. package/src/ui/footer.js +78 -9
  161. package/src/ui/gallery.js +56 -0
  162. package/src/ui/hero.js +68 -18
  163. package/src/ui/image-pair.js +72 -0
  164. package/src/ui/index.js +20 -0
  165. package/src/ui/input.js +1 -0
  166. package/src/ui/marquee.js +48 -0
  167. package/src/ui/nav.js +19 -9
  168. package/src/ui/photo-card.js +44 -0
  169. package/src/ui/product-card.js +65 -0
  170. package/src/ui/pullquote.js +14 -3
  171. package/src/ui/search.js +1 -0
  172. package/src/ui/section-label.js +38 -0
  173. package/src/ui/section.js +1 -1
  174. package/src/ui/service-card.js +47 -0
  175. package/src/ui/stat-grid.js +40 -0
  176. package/src/ui/stat.js +16 -2
  177. package/src/ui/testimonial.js +17 -6
  178. package/src/ui/textarea.js +1 -0
  179. package/src/ui/top-strip.js +47 -0
  180. package/src/ui/trust-list.js +46 -0
  181. package/src/ui/ui.test.js +35 -0
@@ -0,0 +1,422 @@
1
+ /*
2
+ * bookworms.css — BookWorms independent bookshop example
3
+ *
4
+ * Palette: warm off-white, deep ink, oxblood accent.
5
+ * Font: Fraunces (display headings, loaded via Google Fonts in meta.styles)
6
+ *
7
+ * Split into two logical sections:
8
+ * 1. Token overrides — hex values, palette definitions ([data-theme="light"])
9
+ * 2. Component styles — layout, typography, custom elements (var() only)
10
+ */
11
+
12
+ /* ==========================================================================
13
+ 1. Token overrides (hex values live here)
14
+ ========================================================================== */
15
+
16
+ /*
17
+ * Override --font and --font-display so --ui-font / --ui-font-display
18
+ * cascade to Fraunces throughout components and custom elements.
19
+ */
20
+ :root {
21
+ --font: 'Fraunces', Georgia, 'Times New Roman', serif;
22
+ --font-display: 'Fraunces', Georgia, 'Times New Roman', serif;
23
+ }
24
+
25
+ [data-theme="light"] {
26
+ --ui-bg: #f6f2ec;
27
+ --ui-surface: #ffffff;
28
+ --ui-surface-2: #efe9df;
29
+ --ui-border: #d8cfc1;
30
+ --ui-text: #1a1a1a;
31
+ --ui-muted: #5a5043;
32
+ --ui-accent: #6b2a2a;
33
+ --ui-accent-hover: #4f1d1d;
34
+ --ui-accent-dim: #f0e3e3;
35
+ --ui-accent-text: #ffffff;
36
+
37
+ /* BookWorms-specific token */
38
+ --bw-bg-alt: #efe9df;
39
+ }
40
+
41
+ /* ==========================================================================
42
+ 2. Component styles (var() only — no hex values below this line)
43
+ ========================================================================== */
44
+
45
+ body { line-height: 1.6; }
46
+
47
+ /* --------------------------------------------------------------------------
48
+ Shell (centred max-width wrapper)
49
+ -------------------------------------------------------------------------- */
50
+ .bw-shell {
51
+ max-width: 1180px;
52
+ margin: 0 auto;
53
+ padding: 0 var(--ui-space-6);
54
+ }
55
+
56
+ /* --------------------------------------------------------------------------
57
+ Hero
58
+ -------------------------------------------------------------------------- */
59
+ .bw-hero {
60
+ padding: var(--ui-space-20) 0 var(--ui-space-16);
61
+ border-bottom: 1px solid var(--ui-border);
62
+ overflow: hidden;
63
+ }
64
+
65
+ .bw-hero__inner {
66
+ display: grid;
67
+ grid-template-columns: 1fr 1fr;
68
+ gap: var(--ui-space-12);
69
+ align-items: center;
70
+ }
71
+
72
+ .bw-hero__content {
73
+ min-width: 0;
74
+ }
75
+
76
+ .bw-hero__image {
77
+ position: relative;
78
+ border-radius: var(--ui-radius-lg);
79
+ overflow: hidden;
80
+ aspect-ratio: 3 / 4;
81
+ }
82
+
83
+ .bw-hero__image img {
84
+ width: 100%;
85
+ height: 100%;
86
+ object-fit: cover;
87
+ display: block;
88
+ }
89
+
90
+ @media (max-width: 700px) {
91
+ .bw-hero__inner {
92
+ grid-template-columns: 1fr;
93
+ }
94
+ .bw-hero__image {
95
+ aspect-ratio: 4 / 3;
96
+ order: -1;
97
+ }
98
+ }
99
+
100
+ .bw-eyebrow {
101
+ font-size: var(--ui-text-xs);
102
+ letter-spacing: 0.18em;
103
+ text-transform: uppercase;
104
+ color: var(--ui-muted);
105
+ margin-bottom: var(--ui-space-6);
106
+ }
107
+
108
+ .bw-hero-title {
109
+ font-family: var(--ui-font-display);
110
+ font-size: clamp(2.5rem, 7vw, 5.5rem);
111
+ font-weight: 500;
112
+ line-height: 1.02;
113
+ letter-spacing: -0.025em;
114
+ color: var(--ui-text);
115
+ margin-bottom: var(--ui-space-8);
116
+ max-width: 18ch;
117
+ }
118
+
119
+ .bw-hero-lede {
120
+ font-size: var(--ui-text-xl);
121
+ line-height: 1.55;
122
+ color: var(--ui-muted);
123
+ max-width: 60ch;
124
+ margin-bottom: var(--ui-space-8);
125
+ }
126
+
127
+ /* --------------------------------------------------------------------------
128
+ Section template
129
+ -------------------------------------------------------------------------- */
130
+ .bw-section {
131
+ padding: var(--ui-space-16) 0;
132
+ border-bottom: 1px solid var(--ui-border);
133
+ }
134
+
135
+ .bw-section-label {
136
+ font-size: var(--ui-text-xs);
137
+ letter-spacing: 0.18em;
138
+ text-transform: uppercase;
139
+ color: var(--ui-muted);
140
+ margin-bottom: var(--ui-space-8);
141
+ }
142
+
143
+ .bw-section-title {
144
+ font-family: var(--ui-font-display);
145
+ font-size: clamp(1.75rem, 3.2vw, 2.5rem);
146
+ font-weight: 500;
147
+ line-height: 1.1;
148
+ letter-spacing: -0.02em;
149
+ color: var(--ui-text);
150
+ margin-bottom: var(--ui-space-10);
151
+ max-width: 24ch;
152
+ }
153
+
154
+ /* --------------------------------------------------------------------------
155
+ Staff picks grid
156
+ -------------------------------------------------------------------------- */
157
+ .bw-picks {
158
+ display: grid;
159
+ grid-template-columns: repeat(3, 1fr);
160
+ gap: var(--ui-space-10);
161
+ }
162
+
163
+ @media (max-width: 800px) {
164
+ .bw-picks {
165
+ grid-template-columns: 1fr;
166
+ gap: var(--ui-space-8);
167
+ }
168
+ }
169
+
170
+ .bw-pick-cover-wrap {
171
+ position: relative;
172
+ overflow: hidden;
173
+ margin-bottom: var(--ui-space-6);
174
+ box-shadow: 0 2px 12px rgb(0 0 0 / 0.08);
175
+ }
176
+
177
+ .bw-pick-cover {
178
+ display: block;
179
+ width: 100%;
180
+ height: auto;
181
+ aspect-ratio: 2 / 3;
182
+ object-fit: cover;
183
+ background: var(--bw-bg-alt);
184
+ }
185
+
186
+ .bw-pick-overlay {
187
+ position: absolute;
188
+ inset: 0;
189
+ display: flex;
190
+ flex-direction: column;
191
+ justify-content: flex-end;
192
+ padding: var(--ui-space-6);
193
+ background: linear-gradient(to top, rgba(0,0,0,0.72) 0%, rgba(0,0,0,0.2) 50%, transparent 100%);
194
+ }
195
+
196
+ /* First book only — text at top */
197
+ .bw-picks article:first-child .bw-pick-overlay {
198
+ justify-content: flex-start;
199
+ background: linear-gradient(to bottom, rgba(0,0,0,0.72) 0%, rgba(0,0,0,0.2) 50%, transparent 100%);
200
+ }
201
+
202
+ .bw-picks article:first-child .bw-pick-overlay-title {
203
+ font-family: Arial, sans-serif;
204
+ font-weight: 700;
205
+ text-transform: uppercase;
206
+ }
207
+
208
+ /* Second book — solid semi-transparent block behind text */
209
+ .bw-picks article:nth-child(2) .bw-pick-overlay {
210
+ inset: auto 0 0 0;
211
+ background: rgba(0, 0, 0, 0.55);
212
+ padding: var(--ui-space-5) var(--ui-space-6);
213
+ }
214
+
215
+ /* Third book — Arial, no italic on author */
216
+ .bw-picks article:nth-child(3) .bw-pick-overlay-title {
217
+ font-family: Arial, sans-serif;
218
+ font-size: 3.75rem;
219
+ }
220
+
221
+ .bw-picks article:nth-child(3) .bw-pick-overlay-author {
222
+ font-family: Arial, sans-serif;
223
+ font-style: normal;
224
+ }
225
+
226
+ .bw-pick-overlay-tag {
227
+ font-size: var(--ui-text-xs);
228
+ letter-spacing: 0.14em;
229
+ text-transform: uppercase;
230
+ color: rgba(255,255,255,0.7);
231
+ margin-bottom: var(--ui-space-2);
232
+ }
233
+
234
+ .bw-pick-overlay-title {
235
+ font-family: var(--ui-font);
236
+ font-size: var(--ui-text-2xl);
237
+ font-weight: 600;
238
+ line-height: 1.15;
239
+ color: #fff;
240
+ margin-bottom: var(--ui-space-1);
241
+ }
242
+
243
+ .bw-pick-overlay-author {
244
+ font-style: italic;
245
+ font-family: var(--ui-font);
246
+ font-size: var(--ui-text-sm);
247
+ color: rgba(255,255,255,0.7);
248
+ }
249
+
250
+ .bw-pick-meta {
251
+ font-size: var(--ui-text-xs);
252
+ letter-spacing: 0.14em;
253
+ text-transform: uppercase;
254
+ color: var(--ui-accent);
255
+ margin-bottom: var(--ui-space-3);
256
+ }
257
+
258
+ .bw-pick-title {
259
+ font-family: var(--ui-font-display);
260
+ font-size: var(--ui-text-2xl);
261
+ font-weight: 500;
262
+ line-height: 1.2;
263
+ margin-bottom: var(--ui-space-2);
264
+ color: var(--ui-text);
265
+ }
266
+
267
+ .bw-pick-author {
268
+ font-style: italic;
269
+ color: var(--ui-muted);
270
+ margin-bottom: var(--ui-space-4);
271
+ }
272
+
273
+ .bw-pick-note {
274
+ font-size: var(--ui-text-base);
275
+ line-height: 1.6;
276
+ color: var(--ui-text);
277
+ }
278
+
279
+ /* --------------------------------------------------------------------------
280
+ Pull-quote
281
+ -------------------------------------------------------------------------- */
282
+ .bw-pull {
283
+ padding: var(--ui-space-20) 0;
284
+ text-align: center;
285
+ border-bottom: 1px solid var(--ui-border);
286
+ }
287
+
288
+ .bw-pull-quote {
289
+ font-family: var(--ui-font-display);
290
+ font-size: clamp(1.75rem, 4vw, 2.75rem);
291
+ font-weight: 400;
292
+ font-style: italic;
293
+ line-height: 1.25;
294
+ letter-spacing: -0.015em;
295
+ color: var(--ui-text);
296
+ max-width: 32ch;
297
+ margin: 0 auto var(--ui-space-6);
298
+ }
299
+
300
+ .bw-pull-cite {
301
+ font-size: var(--ui-text-sm);
302
+ letter-spacing: 0.14em;
303
+ text-transform: uppercase;
304
+ color: var(--ui-muted);
305
+ }
306
+
307
+ /* --------------------------------------------------------------------------
308
+ Feature grid (what we do)
309
+ -------------------------------------------------------------------------- */
310
+ .bw-features {
311
+ display: grid;
312
+ grid-template-columns: 1fr 1fr;
313
+ gap: var(--ui-space-12) var(--ui-space-16);
314
+ }
315
+
316
+ @media (max-width: 800px) {
317
+ .bw-features {
318
+ grid-template-columns: 1fr;
319
+ gap: var(--ui-space-10);
320
+ }
321
+ }
322
+
323
+ .bw-feature-title {
324
+ font-family: var(--ui-font-display);
325
+ font-size: var(--ui-text-xl);
326
+ font-weight: 500;
327
+ margin-bottom: var(--ui-space-2);
328
+ color: var(--ui-text);
329
+ }
330
+
331
+ .bw-feature-body {
332
+ color: var(--ui-muted);
333
+ line-height: 1.6;
334
+ }
335
+
336
+ /* --------------------------------------------------------------------------
337
+ Events list
338
+ -------------------------------------------------------------------------- */
339
+ .bw-events {
340
+ display: flex;
341
+ flex-direction: column;
342
+ }
343
+
344
+ .bw-event {
345
+ display: grid;
346
+ grid-template-columns: 140px 1fr auto;
347
+ align-items: baseline;
348
+ gap: var(--ui-space-8);
349
+ padding: var(--ui-space-6) 0;
350
+ border-top: 1px solid var(--ui-border);
351
+ }
352
+
353
+ .bw-event:last-child {
354
+ border-bottom: 1px solid var(--ui-border);
355
+ }
356
+
357
+ @media (max-width: 700px) {
358
+ .bw-event {
359
+ grid-template-columns: 1fr;
360
+ gap: var(--ui-space-2);
361
+ }
362
+ }
363
+
364
+ .bw-event-date {
365
+ font-family: var(--ui-font-display);
366
+ font-size: var(--ui-text-lg);
367
+ color: var(--ui-accent);
368
+ letter-spacing: 0.02em;
369
+ }
370
+
371
+ .bw-event-title {
372
+ font-family: var(--ui-font-display);
373
+ font-size: var(--ui-text-xl);
374
+ font-weight: 500;
375
+ color: var(--ui-text);
376
+ }
377
+
378
+ .bw-event-host {
379
+ color: var(--ui-muted);
380
+ font-style: italic;
381
+ }
382
+
383
+ /* --------------------------------------------------------------------------
384
+ CTA section
385
+ -------------------------------------------------------------------------- */
386
+ .bw-cta {
387
+ padding: var(--ui-space-24) 0;
388
+ text-align: center;
389
+ }
390
+
391
+ .bw-cta-title {
392
+ font-family: var(--ui-font-display);
393
+ font-size: clamp(2rem, 5vw, 3.5rem);
394
+ font-weight: 500;
395
+ line-height: 1.05;
396
+ letter-spacing: -0.02em;
397
+ margin-bottom: var(--ui-space-6);
398
+ color: var(--ui-text);
399
+ max-width: 20ch;
400
+ margin-left: auto;
401
+ margin-right: auto;
402
+ }
403
+
404
+ .bw-cta-sub {
405
+ color: var(--ui-muted);
406
+ font-size: var(--ui-text-lg);
407
+ margin-bottom: var(--ui-space-8);
408
+ max-width: 50ch;
409
+ margin-left: auto;
410
+ margin-right: auto;
411
+ }
412
+
413
+ .bw-cta-actions {
414
+ display: flex;
415
+ gap: var(--ui-space-4);
416
+ justify-content: center;
417
+ flex-wrap: wrap;
418
+ }
419
+
420
+ .bw-cta-actions--start {
421
+ justify-content: flex-start;
422
+ }
@@ -0,0 +1,225 @@
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
+
22
+ // ---------------------------------------------------------------------------
23
+ // Static data
24
+ // ---------------------------------------------------------------------------
25
+
26
+ const picks = [
27
+ {
28
+ tag: 'Fiction · Staff pick',
29
+ title: 'The Lantern Keepers',
30
+ author: 'Iris Aldenmoor',
31
+ note: 'A quiet novel about three sisters, an unreliable lighthouse, and the long winter that follows a single decision.',
32
+ cover: '/images/bookworms/lantern-keepers.jpg',
33
+ },
34
+ {
35
+ tag: 'Non-fiction · This week',
36
+ title: 'How We Read Now',
37
+ author: 'Dr. Samuel Okafor',
38
+ note: 'A short, generous history of attention — from monastic libraries to the open browser tab.',
39
+ cover: '/images/bookworms/how-we-read-now.jpg',
40
+ },
41
+ {
42
+ tag: 'Rare find · Just arrived',
43
+ title: 'Letters from a Provincial Bookseller',
44
+ author: 'M. Halloran (1927)',
45
+ note: 'First edition, lightly foxed. A wry account of running a shop through a quiet decade.',
46
+ cover: '/images/bookworms/provincial-bookseller.jpg',
47
+ },
48
+ ]
49
+
50
+ const features = [
51
+ {
52
+ title: 'Curated, not algorithmic',
53
+ 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.',
54
+ },
55
+ {
56
+ title: 'Browse by mood',
57
+ body: 'Looking for something restorative? Something difficult? Our shelves are arranged the way readers actually think.',
58
+ },
59
+ {
60
+ title: 'Author evenings & book clubs',
61
+ body: 'Small, considered events with writers we admire. Wine, conversation, and signed copies — no panels of four.',
62
+ },
63
+ {
64
+ title: 'Free delivery over £25',
65
+ body: 'Wrapped by hand, delivered locally by bicycle. Posted further afield by the next morning.',
66
+ },
67
+ {
68
+ title: 'Gift cards & reader subscriptions',
69
+ body: 'A monthly book chosen for one specific reader, with a note from the bookseller who picked it.',
70
+ },
71
+ {
72
+ title: 'Open seven days',
73
+ body: 'Old Market Lane, opposite the bakery. Coffee from nine, books from ten.',
74
+ },
75
+ ]
76
+
77
+ const events = [
78
+ { date: '12 June', title: 'An evening with Iris Aldenmoor', host: 'In conversation with Priya Joshi' },
79
+ { date: '21 June', title: 'Slow Reading Club — Middlemarch, part one', host: 'Hosted by the BookWorms team' },
80
+ { date: '03 July', title: 'New voices in non-fiction', host: 'Three debut authors read and answer questions' },
81
+ ]
82
+
83
+ const navLinks = [
84
+ { label: 'Browse', href: '#browse' },
85
+ { label: 'Staff picks', href: '#picks' },
86
+ { label: 'Events', href: '#events' },
87
+ { label: 'Visit', href: '#visit' },
88
+ ]
89
+
90
+ // ---------------------------------------------------------------------------
91
+ // Spec
92
+ // ---------------------------------------------------------------------------
93
+
94
+ export default {
95
+ route: '/bookworms',
96
+
97
+ meta: {
98
+ title: 'BookWorms — an independent bookstore for curious readers',
99
+ 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.',
100
+ theme: 'light',
101
+ styles: [
102
+ 'https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,400;0,9..144,500;1,9..144,400&display=swap',
103
+ '/pulse-ui.css',
104
+ '/examples/bookworms.css',
105
+ ],
106
+ },
107
+
108
+ view: () => `
109
+ ${nav({
110
+ logo: 'BookWorms',
111
+ links: navLinks,
112
+ action: button({ label: 'Visit the shop', href: '#visit', variant: 'primary', size: 'sm' }),
113
+ sticky: true,
114
+ })}
115
+
116
+ <main id="main-content">
117
+ <section class="bw-hero">
118
+ <div class="bw-shell bw-hero__inner">
119
+ <div class="bw-hero__content">
120
+ <p class="bw-eyebrow">Independent bookstore · Old Market Lane</p>
121
+ <h1 class="bw-hero-title">Books, handpicked for the curious.</h1>
122
+ <p class="bw-hero-lede">
123
+ BookWorms is a small shop with a long memory. Every title on our shelves has been read by
124
+ someone who works here — and is waiting for the right reader to find it.
125
+ </p>
126
+ <div class="bw-cta-actions bw-cta-actions--start">
127
+ ${button({ label: 'See this week\u2019s picks', href: '#picks', variant: 'primary' })}
128
+ ${button({ label: 'Upcoming events', href: '#events', variant: 'secondary' })}
129
+ </div>
130
+ </div>
131
+ <div class="bw-hero__image" aria-hidden="true">
132
+ <img src="/images/bookworms/hero-books.jpg" alt="" width="820" height="1080" />
133
+ </div>
134
+ </div>
135
+ </section>
136
+
137
+ <section class="bw-section" id="picks">
138
+ <div class="bw-shell">
139
+ <p class="bw-section-label">This week</p>
140
+ <h2 class="bw-section-title">Three books we\u2019re pressing into people\u2019s hands.</h2>
141
+ <div class="bw-picks">
142
+ ${picks.map((p, i) => `
143
+ <article>
144
+ <div class="bw-pick-cover-wrap">
145
+ <img class="bw-pick-cover" src="${p.cover}" alt="Cover of ${p.title} by ${p.author}" width="400" height="600" loading="lazy" />
146
+ <div class="bw-pick-overlay">
147
+ <h3 class="bw-pick-overlay-title">${p.title}</h3>
148
+ <p class="bw-pick-overlay-author">${i === 2 ? p.author.replace(/\s*\(\d{4}\)/, '') : 'by ' + p.author}</p>
149
+ </div>
150
+ </div>
151
+ <p class="bw-pick-meta">${p.tag}</p>
152
+ <h3 class="bw-pick-title">${p.title}</h3>
153
+ <p class="bw-pick-author">by ${p.author}</p>
154
+ <p class="bw-pick-note">${p.note}</p>
155
+ </article>
156
+ `).join('')}
157
+ </div>
158
+ </div>
159
+ </section>
160
+
161
+ <section class="bw-pull">
162
+ <div class="bw-shell">
163
+ <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>
164
+ <p class="bw-pull-cite">\u2014 The Quarterly Review</p>
165
+ </div>
166
+ </section>
167
+
168
+ <section class="bw-section" id="browse">
169
+ <div class="bw-shell">
170
+ <p class="bw-section-label">What we do</p>
171
+ <h2 class="bw-section-title">A bookstore that still reads.</h2>
172
+ <div class="bw-features">
173
+ ${features.map(f => `
174
+ <div>
175
+ <h3 class="bw-feature-title">${f.title}</h3>
176
+ <p class="bw-feature-body">${f.body}</p>
177
+ </div>
178
+ `).join('')}
179
+ </div>
180
+ </div>
181
+ </section>
182
+
183
+ <section class="bw-section" id="events">
184
+ <div class="bw-shell">
185
+ <p class="bw-section-label">Upcoming</p>
186
+ <h2 class="bw-section-title">Evenings worth turning up for.</h2>
187
+ <div class="bw-events">
188
+ ${events.map(e => `
189
+ <article class="bw-event">
190
+ <p class="bw-event-date">${e.date}</p>
191
+ <h3 class="bw-event-title">${e.title}</h3>
192
+ <p class="bw-event-host">${e.host}</p>
193
+ </article>
194
+ `).join('')}
195
+ </div>
196
+ </div>
197
+ </section>
198
+
199
+ <section class="bw-cta" id="visit">
200
+ <div class="bw-shell">
201
+ <h2 class="bw-cta-title">Come and browse.</h2>
202
+ <p class="bw-cta-sub">
203
+ 14 Old Market Lane, opposite the bakery. Open Monday to Sunday, 10am\u20137pm.
204
+ Or sign up to receive one handpicked book each month.
205
+ </p>
206
+ <div class="bw-cta-actions">
207
+ ${button({ label: 'Plan a visit', href: '#visit', variant: 'primary' })}
208
+ ${button({ label: 'Reader subscription', href: '#visit', variant: 'secondary' })}
209
+ </div>
210
+ </div>
211
+ </section>
212
+ </main>
213
+
214
+ ${footer({
215
+ logo: 'BookWorms',
216
+ links: [
217
+ { label: 'Browse', href: '#browse' },
218
+ { label: 'Events', href: '#events' },
219
+ { label: 'Visit', href: '#visit' },
220
+ { label: 'Contact', href: '#visit' },
221
+ ],
222
+ legal: '\u00a9 2026 BookWorms \u00b7 14 Old Market Lane',
223
+ })}
224
+ `,
225
+ }
@@ -33,6 +33,7 @@ export default {
33
33
  title: 'Contact — Pulse',
34
34
  description: 'A contact form built with Pulse. Demonstrates server data, validation, and async action lifecycle.',
35
35
  styles: ['/pulse-ui.css'],
36
+ theme: 'dark',
36
37
  },
37
38
 
38
39
  server: {
@@ -20,6 +20,7 @@ export default {
20
20
  title: 'Counter — Pulse',
21
21
  description: 'An interactive counter built with Pulse. Demonstrates mutations, constraints, and data-event binding.',
22
22
  styles: ['/pulse-ui.css'],
23
+ theme: 'dark',
23
24
  },
24
25
 
25
26
  state: {
@@ -12,6 +12,12 @@
12
12
  * http://localhost:3001/quiz — multi-step state machine
13
13
  * http://localhost:3001/products — server data + search/filter/sort
14
14
  * http://localhost:3001/pricing — landing page components + billing toggle
15
+ * http://localhost:3001/swish — editorial fashion branding + light theme
16
+ * http://localhost:3001/bookworms — independent bookshop, Fraunces font, picsum covers
17
+ * http://localhost:3001/retrowheels — vintage bicycle shop, slab-serif, booking form
18
+ * http://localhost:3001/suzukibento — artist portfolio, dark brutalist, asymmetric grid
19
+ * http://localhost:3001/andypixel — blog article, editorial light, newsletter action
20
+ * http://localhost:3001/journey — app landing page, dark split-screen, sticky timeline
15
21
  */
16
22
 
17
23
  import fs from 'fs'
@@ -81,6 +87,12 @@ await createServer(
81
87
  new URL('./quiz.js', import.meta.url),
82
88
  new URL('./products.js', import.meta.url),
83
89
  new URL('./pricing.js', import.meta.url),
90
+ new URL('./swish.js', import.meta.url),
91
+ new URL('./bookworms.js', import.meta.url),
92
+ new URL('./retrowheels.js', import.meta.url),
93
+ new URL('./suzukibento.js', import.meta.url),
94
+ new URL('./andypixel.js', import.meta.url),
95
+ new URL('./journey.js', import.meta.url),
84
96
  ],
85
97
  {
86
98
  port: 3001,
@@ -89,5 +101,10 @@ await createServer(
89
101
  root: new URL('..', import.meta.url), // pulse2/ — specs served at /examples/*
90
102
  onRequest: staticHandler,
91
103
  extraBody: themeScript,
104
+ csp: {
105
+ 'style-src': ['https://fonts.googleapis.com'],
106
+ 'font-src': ['https://fonts.gstatic.com'],
107
+ 'img-src': ['https://images.unsplash.com', 'https://picsum.photos', 'https://fastly.picsum.photos'],
108
+ },
92
109
  }
93
110
  )