@invisibleloop/pulse 0.20.11 → 0.20.20

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,313 +0,0 @@
1
- /**
2
- * Pulse — Swish example
3
- *
4
- * Demonstrates:
5
- * - Editorial light-theme branding (fashion / luxury ecommerce)
6
- * - Composing nav, section, container, grid, pullquote, button, footer, input
7
- * - Custom component helpers (look card, strip cell, journal card)
8
- * - Google Fonts integration via meta.styles
9
- * - Full-bleed hero with CSS background image + gradient overlay
10
- * - Responsive grid, lookbook strip, image-led journal cards
11
- * - Newsletter sign-up form using the input() component
12
- *
13
- * Run: node examples/dev.server.js → http://localhost:3001/swish
14
- */
15
-
16
- import {
17
- nav,
18
- section,
19
- container,
20
- grid,
21
- pullquote,
22
- button,
23
- footer,
24
- input,
25
- } from '../../../../src/ui/index.js'
26
- import { asset } from '../../lib/layout.js'
27
-
28
- // ---------------------------------------------------------------------------
29
- // Card / cell helpers
30
- // ---------------------------------------------------------------------------
31
-
32
- const look = ({ name, season, price, src }) => `
33
- <a class="swish-look" href="/collections/${encodeURIComponent(name.toLowerCase().replace(/\s+/g, '-'))}">
34
- <div class="swish-look__frame">
35
- <img src="${src}" alt="${name} — ${season}" loading="lazy" />
36
- </div>
37
- <div class="swish-look__name">${name}</div>
38
- <div class="swish-look__meta">${season} · ${price}</div>
39
- </a>
40
- `
41
-
42
- const stripCell = ({ src, alt, label }) => `
43
- <figure class="swish-strip__cell">
44
- <img src="${src}" alt="${alt}" loading="lazy" />
45
- <figcaption class="swish-strip__label">${label}</figcaption>
46
- </figure>
47
- `
48
-
49
- const journalCard = ({ category, title, deck, byline, src }) => `
50
- <article class="swish-journal-card">
51
- <div class="swish-journal-card__frame">
52
- <img src="${src}" alt="" loading="lazy" />
53
- </div>
54
- <div class="swish-journal-card__category">${category}</div>
55
- <h3 class="swish-journal-card__title">${title}</h3>
56
- <p class="swish-journal-card__deck">${deck}</p>
57
- <div class="swish-journal-card__byline">${byline}</div>
58
- </article>
59
- `
60
-
61
- // ---------------------------------------------------------------------------
62
- // Static data
63
- // ---------------------------------------------------------------------------
64
-
65
- const featured = [
66
- {
67
- name: 'The Marlene Coat',
68
- season: 'AW26',
69
- price: '£1,290',
70
- src: '/images/swish/1539109136881-3be0616acf4b.jpg',
71
- },
72
- {
73
- name: 'Atelier Knit',
74
- season: 'AW26',
75
- price: '£480',
76
- src: '/images/swish/1485518882345-15568b007407.jpg',
77
- },
78
- {
79
- name: 'Bias Slip Dress',
80
- season: 'AW26',
81
- price: '£695',
82
- src: '/images/swish/1572804013309-59a88b7e92f1.jpg',
83
- },
84
- {
85
- name: 'Wide-Leg Trouser',
86
- season: 'AW26',
87
- price: '£420',
88
- src: '/images/swish/1551803091-e20673f15770.jpg',
89
- },
90
- ]
91
-
92
- const lookbook = [
93
- {
94
- src: '/images/swish/1483985988355-763728e1935b.jpg',
95
- alt: 'Tailored editorial look in muted neutrals',
96
- label: 'Tailoring',
97
- },
98
- {
99
- src: '/images/swish/1469334031218-e382a71b716b.jpg',
100
- alt: 'Soft knitwear styled with wide trousers',
101
- label: 'Knitwear',
102
- },
103
- {
104
- src: '/images/swish/1502716119720-b23a93e5fe1b.jpg',
105
- alt: 'Evening dress under low light',
106
- label: 'Evening',
107
- },
108
- ]
109
-
110
- const journal = [
111
- {
112
- category: 'Atelier',
113
- title: 'Inside the Florence workshop',
114
- deck: 'Where every Marlene coat is cut, basted and finished by hand over twenty-three days.',
115
- byline: 'Words by Inez Vasquez',
116
- src: '/images/swish/1558769132-cb1aea458c5e.jpg',
117
- },
118
- {
119
- category: 'Conversation',
120
- title: 'A new vocabulary for womenswear',
121
- deck: 'Creative director Lila Chen on quiet power, ease, and clothes that outlive a season.',
122
- byline: 'Interview by The Editors',
123
- src: '/images/swish/1496747611176-843222e1e57c.jpg',
124
- },
125
- {
126
- category: 'Care',
127
- title: 'On keeping wool well',
128
- deck: 'A short guide to brushing, airing and storing the pieces meant to be with you for years.',
129
- byline: 'Words by Marta Engel',
130
- src: '/images/swish/1490481651871-ab68de25d43d.jpg',
131
- },
132
- ]
133
-
134
- // ---------------------------------------------------------------------------
135
- // Spec
136
- // ---------------------------------------------------------------------------
137
-
138
- export default {
139
- route: '/templates/swish',
140
-
141
- meta: {
142
- title: 'Swish — Considered womenswear, season by season',
143
- description: 'Swish is a ready-to-wear house designing collection-led womenswear, handfinished in Florence and Porto. Demonstrates editorial branding with Pulse.',
144
- theme: 'light',
145
- styles: [
146
- 'https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500;600&display=swap',
147
- '/pulse-ui.css',
148
- '/swish.css',
149
- ],
150
- ogTitle: 'Swish — Autumn/Winter 2026',
151
- ogImage: '/images/swish/1490481651871-ab68de25d43d.jpg',
152
- },
153
-
154
- view: () => `
155
- <a href="#main-content" class="skip-link">Skip to main content</a>
156
-
157
- ${nav({
158
- sticky: true,
159
- logo: `<span class="swish-mark">Swish</span>`,
160
- logoHref: '/swish',
161
- links: [
162
- { label: 'Collections', href: '/collections' },
163
- { label: 'Lookbook', href: '/lookbook' },
164
- { label: 'Atelier', href: '/atelier' },
165
- { label: 'Journal', href: '/journal' },
166
- { label: 'Stores', href: '/stores' },
167
- ],
168
- action: button({ label: 'Bag (0)', href: '/bag', variant: 'ghost', size: 'sm' }),
169
- })}
170
-
171
- <main id="main-content">
172
- <section class="swish-hero" aria-label="Autumn Winter 2026 collection">
173
- <div class="swish-hero__inner">
174
- <div class="swish-hero__eyebrow">AW26 · Issue Nº07</div>
175
- <h1 class="swish-hero__title">Quiet power, cut to last.</h1>
176
- <p class="swish-hero__deck">The Autumn/Winter collection — a study in tailoring, weight and restraint. Forty-two pieces, made in small runs across Florence and Porto.</p>
177
- <div class="u-flex u-gap-3 u-flex-wrap">
178
- ${button({ label: 'Shop the collection', href: '/collections/aw26', variant: 'primary', size: 'lg' })}
179
- ${button({ label: 'View lookbook', href: '/lookbook', variant: 'ghost', size: 'lg' })}
180
- </div>
181
- </div>
182
- </section>
183
-
184
- ${section({
185
- padding: 'lg',
186
- class: 'swish-quote-section',
187
- content: container({
188
- size: 'md',
189
- content: pullquote({
190
- quote: 'A new vocabulary for modern womenswear — disciplined, quietly luxurious, refreshingly unfussy.',
191
- cite: 'British Vogue',
192
- size: 'lg',
193
- }),
194
- }),
195
- })}
196
-
197
- ${section({
198
- padding: 'lg',
199
- content: container({
200
- content: `
201
- <div class="swish-issue">Featured pieces · AW26</div>
202
- ${grid({
203
- cols: 4,
204
- gap: 'lg',
205
- content: featured.map(look).join(''),
206
- })}
207
- `,
208
- }),
209
- })}
210
-
211
- ${section({
212
- variant: 'alt',
213
- padding: 'lg',
214
- content: container({
215
- content: `
216
- <div class="swish-split">
217
- <div class="swish-split__media">
218
- <img
219
- src="/images/swish/1558769132-cb1aea458c5e.jpg"
220
- alt="A tailor working at the Florence atelier" />
221
- </div>
222
- <div class="swish-split__body">
223
- <div class="swish-issue swish-issue--left">The Atelier</div>
224
- <h2 class="swish-split__title">Made slowly, in two small houses.</h2>
225
- <p>Tailoring is cut and finished in a converted convent in Oltrarno, Florence. Knitwear is shaped by a family-run mill outside Porto that has been spinning yarn for four generations.</p>
226
- <p>Each collection is produced in runs of fewer than three hundred pieces — quiet, deliberate, and intended to be lived in for years.</p>
227
- ${button({ label: 'Visit the atelier', href: '/atelier', variant: 'secondary' })}
228
- </div>
229
- </div>
230
- `,
231
- }),
232
- })}
233
-
234
- ${section({
235
- padding: 'lg',
236
- content: container({
237
- content: `
238
- <div class="swish-issue">Lookbook</div>
239
- <div class="swish-strip">${lookbook.map(stripCell).join('')}</div>
240
- `,
241
- }),
242
- })}
243
-
244
- ${section({
245
- variant: 'alt',
246
- padding: 'lg',
247
- content: container({
248
- content: `
249
- <div class="swish-issue">From the journal</div>
250
- ${grid({
251
- cols: 3,
252
- gap: 'lg',
253
- content: journal.map(journalCard).join(''),
254
- })}
255
- `,
256
- }),
257
- })}
258
-
259
- ${section({
260
- padding: 'lg',
261
- content: container({
262
- size: 'md',
263
- content: `
264
- <div class="swish-newsletter">
265
- <h2 class="swish-newsletter__title">Letters from Swish</h2>
266
- <p class="swish-newsletter__deck">Collection previews, atelier notes and private sale invitations — sent six times a year, never more.</p>
267
- <form class="swish-newsletter__form" action="/subscribe" method="post" novalidate>
268
- ${input({ name: 'email', label: 'Email address', type: 'email', placeholder: 'your@email.com', required: true, attrs: { autocomplete: 'email' } })}
269
- ${button({ label: 'Subscribe', type: 'submit', variant: 'primary' })}
270
- </form>
271
- </div>
272
- `,
273
- }),
274
- })}
275
- </main>
276
-
277
- ${footer({
278
- logo: `<span class="swish-mark">Swish</span>`,
279
- logoHref: '/swish',
280
- columns: [
281
- {
282
- title: 'Shop',
283
- links: [
284
- { label: 'AW26 Collection', href: '/collections/aw26' },
285
- { label: 'Tailoring', href: '/collections/tailoring' },
286
- { label: 'Knitwear', href: '/collections/knitwear' },
287
- { label: 'Evening', href: '/collections/evening' },
288
- ],
289
- },
290
- {
291
- title: 'House',
292
- links: [
293
- { label: 'Atelier', href: '/atelier' },
294
- { label: 'Journal', href: '/journal' },
295
- { label: 'Stores', href: '/stores' },
296
- { label: 'Careers', href: '/careers' },
297
- ],
298
- },
299
- {
300
- title: 'Help',
301
- links: [
302
- { label: 'Contact', href: '/contact' },
303
- { label: 'Shipping', href: '/shipping' },
304
- { label: 'Returns', href: '/returns' },
305
- { label: 'Care', href: '/care' },
306
- ],
307
- },
308
- ],
309
- legal: '© 2026 Swish Maison. All rights reserved.',
310
- })}
311
- <script src="${asset('/pulse-ui.js')}" defer></script>
312
- `,
313
- }