@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,513 +0,0 @@
1
- /*
2
- * suzukibento.css — Susuki Bento artist portfolio example
3
- *
4
- * Brutalist gallery palette: near-black ground, bone-white text, vermilion accent.
5
- * Dark by default — palette set as bare token overrides on :root so they
6
- * cascade into --ui-bg, --ui-text, --ui-accent etc. via pulse-ui.css.
7
- * Zero border-radius throughout (--radius: 0px).
8
- * Typography: Inter → system-ui (no external font requests).
9
- *
10
- * Token responsibility:
11
- * :root — dark palette + zero-radius + font stack
12
- * Component styles — layout only, var() references throughout
13
- */
14
-
15
- /* ==========================================================================
16
- 1. Token overrides (hex values live here)
17
- ========================================================================== */
18
-
19
- /*
20
- * Override the bare cascade tokens that --ui-* variables reference.
21
- * See pulse-ui.css :root: --ui-bg: var(--bg, ...) etc.
22
- * Setting --bg, --surface, --text, --accent etc. on :root is enough to
23
- * retheme the entire UI without touching --ui-* directly.
24
- */
25
- :root {
26
- --bg: #0a0a0a;
27
- --surface: #131313;
28
- --surface-2: #1c1c1c;
29
- --border: #2a2a2a;
30
- --text: #f5f3ef;
31
- --muted: #8a8780;
32
- --accent: #ff4a2a; /* vermilion — 4.5:1 on #0a0a0a */
33
- --accent-hover: #ff6b4f;
34
- --accent-dim: rgba(255, 74, 42, 0.14);
35
- --accent-text: #0a0a0a;
36
-
37
- /* Zero-radius brutalist design language */
38
- --radius: 0px;
39
- --radius-sm: 0px;
40
-
41
- /* Font stack — system Inter, no HTTP request */
42
- --font: 'Inter', system-ui, sans-serif;
43
- --font-display: 'Inter', system-ui, sans-serif;
44
- }
45
-
46
- /* ==========================================================================
47
- 2. Component styles (var() only — no hex values below this line)
48
- ========================================================================== */
49
-
50
- *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
51
-
52
- body {
53
- font-family: var(--ui-font, system-ui, sans-serif);
54
- line-height: 1.5;
55
- -webkit-font-smoothing: antialiased;
56
- }
57
-
58
- /* --------------------------------------------------------------------------
59
- Skip link
60
- -------------------------------------------------------------------------- */
61
- .skip-link {
62
- position: absolute;
63
- left: -9999px;
64
- top: 0;
65
- background: var(--ui-accent);
66
- color: var(--ui-accent-text);
67
- padding: var(--ui-space-2) var(--ui-space-4);
68
- z-index: 100;
69
- }
70
-
71
- .skip-link:focus { left: var(--ui-space-2); }
72
-
73
- /* --------------------------------------------------------------------------
74
- Sticky top nav (hand-rolled — no nav() component)
75
- -------------------------------------------------------------------------- */
76
- .sb-nav {
77
- position: sticky;
78
- top: 0;
79
- z-index: 50;
80
- display: flex;
81
- justify-content: space-between;
82
- align-items: center;
83
- padding: var(--ui-space-5) var(--ui-space-8);
84
- background: var(--ui-bg);
85
- border-bottom: 1px solid var(--ui-border);
86
- font-size: var(--ui-text-sm);
87
- text-transform: uppercase;
88
- letter-spacing: 0.08em;
89
- }
90
-
91
- .sb-nav__brand {
92
- font-weight: 700;
93
- color: var(--ui-text);
94
- text-decoration: none;
95
- display: inline-flex;
96
- align-items: center;
97
- gap: 0.3em;
98
- }
99
-
100
- .sb-nav__dot {
101
- display: inline-block;
102
- width: 0.45em;
103
- height: 0.45em;
104
- border-radius: 50%;
105
- background: var(--ui-accent);
106
- flex-shrink: 0;
107
- }
108
-
109
- .sb-nav__links {
110
- display: flex;
111
- gap: var(--ui-space-6);
112
- list-style: none;
113
- }
114
-
115
- .sb-nav__links a {
116
- color: var(--ui-muted);
117
- text-decoration: none;
118
- }
119
-
120
- .sb-nav__links a:hover,
121
- .sb-nav__links a:focus { color: var(--ui-accent); }
122
-
123
- /* Burger — hidden on desktop */
124
- .sb-nav__toggle-input { display: none; }
125
- .sb-nav__burger { display: none; }
126
-
127
- @media (max-width: 600px) {
128
- .sb-nav {
129
- flex-wrap: wrap;
130
- padding: var(--ui-space-4) var(--ui-space-5);
131
- }
132
-
133
- .sb-nav__burger {
134
- display: flex;
135
- flex-direction: column;
136
- justify-content: space-between;
137
- width: 22px;
138
- height: 16px;
139
- cursor: pointer;
140
- }
141
-
142
- .sb-nav__burger span {
143
- display: block;
144
- height: 2px;
145
- background: var(--ui-text);
146
- transition: transform 0.2s, opacity 0.2s;
147
- }
148
-
149
- .sb-nav__toggle-input:checked ~ .sb-nav__burger span:nth-child(1) {
150
- transform: translateY(7px) rotate(45deg);
151
- }
152
- .sb-nav__toggle-input:checked ~ .sb-nav__burger span:nth-child(2) {
153
- opacity: 0;
154
- }
155
- .sb-nav__toggle-input:checked ~ .sb-nav__burger span:nth-child(3) {
156
- transform: translateY(-7px) rotate(-45deg);
157
- }
158
-
159
- .sb-nav nav {
160
- display: none;
161
- position: absolute;
162
- top: 100%;
163
- left: 0;
164
- right: 0;
165
- background: var(--ui-bg);
166
- border-bottom: 1px solid var(--ui-border);
167
- z-index: 49;
168
- }
169
-
170
- .sb-nav__toggle-input:checked ~ nav {
171
- display: block;
172
- }
173
-
174
- .sb-nav__links {
175
- flex-direction: column;
176
- gap: 0;
177
- padding: var(--ui-space-4) var(--ui-space-5);
178
- }
179
-
180
- .sb-nav__links li { border-bottom: 1px solid var(--ui-border); }
181
-
182
- .sb-nav__links a {
183
- display: block;
184
- padding: var(--ui-space-3) 0;
185
- color: var(--ui-text);
186
- }
187
- }
188
-
189
- /* --------------------------------------------------------------------------
190
- Hero
191
- -------------------------------------------------------------------------- */
192
- .sb-hero {
193
- padding: var(--ui-space-24) var(--ui-space-8) var(--ui-space-16);
194
- border-bottom: 1px solid var(--ui-border);
195
- }
196
-
197
- .sb-hero__eyebrow {
198
- display: inline-block;
199
- font-size: var(--ui-text-sm);
200
- text-transform: uppercase;
201
- letter-spacing: 0.18em;
202
- color: var(--ui-accent);
203
- margin-bottom: var(--ui-space-6);
204
- }
205
-
206
- .sb-hero__title {
207
- font-size: clamp(3.5rem, 12vw, 11rem);
208
- font-weight: 800;
209
- line-height: 0.9;
210
- letter-spacing: -0.05em;
211
- color: var(--ui-text);
212
- }
213
-
214
- .sb-hero__title em {
215
- font-style: normal;
216
- color: var(--ui-accent);
217
- }
218
-
219
- .sb-hero__meta {
220
- margin-top: var(--ui-space-10);
221
- display: flex;
222
- flex-wrap: wrap;
223
- gap: var(--ui-space-8);
224
- }
225
-
226
- .sb-hero__meta-label {
227
- display: block;
228
- font-size: var(--ui-text-sm);
229
- text-transform: uppercase;
230
- letter-spacing: 0.12em;
231
- color: var(--ui-muted);
232
- }
233
-
234
- .sb-hero__meta strong {
235
- display: block;
236
- color: var(--ui-text);
237
- font-weight: 600;
238
- margin-top: var(--ui-space-1);
239
- letter-spacing: 0.04em;
240
- font-size: var(--ui-text-sm);
241
- text-transform: uppercase;
242
- }
243
-
244
- /* --------------------------------------------------------------------------
245
- Section scaffolding
246
- -------------------------------------------------------------------------- */
247
- .sb-section {
248
- padding: var(--ui-space-20) var(--ui-space-8);
249
- border-bottom: 1px solid var(--ui-border);
250
- }
251
-
252
- .sb-section__head {
253
- display: flex;
254
- justify-content: space-between;
255
- align-items: baseline;
256
- margin-bottom: var(--ui-space-12);
257
- gap: var(--ui-space-6);
258
- flex-wrap: wrap;
259
- }
260
-
261
- .sb-section__label {
262
- font-size: var(--ui-text-sm);
263
- text-transform: uppercase;
264
- letter-spacing: 0.18em;
265
- color: var(--ui-accent);
266
- }
267
-
268
- .sb-section__title {
269
- font-size: clamp(1.75rem, 4vw, 2.5rem);
270
- font-weight: 700;
271
- letter-spacing: -0.02em;
272
- color: var(--ui-text);
273
- }
274
-
275
- /* --------------------------------------------------------------------------
276
- Works grid (12-col asymmetric layout)
277
- -------------------------------------------------------------------------- */
278
- .sb-works {
279
- display: grid;
280
- grid-template-columns: repeat(12, 1fr);
281
- grid-auto-rows: 120px;
282
- gap: var(--ui-space-4);
283
- }
284
-
285
- .sb-work {
286
- position: relative;
287
- overflow: hidden;
288
- background: var(--ui-surface);
289
- }
290
-
291
- .sb-work img {
292
- width: 100%;
293
- height: 100%;
294
- object-fit: cover;
295
- display: block;
296
- filter: grayscale(15%) contrast(1.05);
297
- transition: filter 0.4s ease, transform 0.6s ease;
298
- }
299
-
300
- .sb-work:hover img {
301
- filter: none;
302
- transform: scale(1.02);
303
- }
304
-
305
- .sb-work__caption {
306
- position: absolute;
307
- left: var(--ui-space-3);
308
- bottom: var(--ui-space-3);
309
- background: var(--ui-bg);
310
- color: var(--ui-text);
311
- padding: var(--ui-space-1) var(--ui-space-3);
312
- font-size: var(--ui-text-xs);
313
- text-transform: uppercase;
314
- letter-spacing: 0.1em;
315
- }
316
-
317
- .sb-work__caption span {
318
- color: var(--ui-muted);
319
- margin-left: var(--ui-space-2);
320
- }
321
-
322
- /* Explicit positions — gap-free brickwork layout */
323
- .sb-work--a { grid-column: 1 / 8; grid-row: 1 / 5; }
324
- .sb-work--b { grid-column: 8 / 13; grid-row: 1 / 4; }
325
- .sb-work--c { grid-column: 8 / 13; grid-row: 4 / 8; }
326
- .sb-work--d { grid-column: 1 / 5; grid-row: 5 / 8; }
327
- .sb-work--e { grid-column: 5 / 8; grid-row: 5 / 8; }
328
- .sb-work--f { grid-column: 1 / 13; grid-row: 8 / 12; }
329
-
330
- @media (max-width: 720px) {
331
- .sb-works { grid-auto-rows: 80px; }
332
- .sb-work--a, .sb-work--b, .sb-work--c,
333
- .sb-work--d, .sb-work--e, .sb-work--f {
334
- grid-column: span 12;
335
- grid-row: span 3;
336
- }
337
- }
338
-
339
- /* --------------------------------------------------------------------------
340
- Artist statement
341
- -------------------------------------------------------------------------- */
342
- .sb-statement {
343
- max-width: 56rem;
344
- display: grid;
345
- grid-template-columns: 1fr 2fr;
346
- gap: var(--ui-space-12);
347
- }
348
-
349
- .sb-statement p {
350
- font-size: var(--ui-text-xl);
351
- line-height: 1.6;
352
- color: var(--ui-text);
353
- margin-bottom: var(--ui-space-5);
354
- }
355
-
356
- .sb-statement p + p {
357
- color: var(--ui-muted);
358
- font-size: var(--ui-text-lg);
359
- }
360
-
361
- .sb-statement__bio {
362
- font-size: var(--ui-text-sm);
363
- color: var(--ui-muted);
364
- line-height: 1.7;
365
- }
366
-
367
- .sb-statement__bio dt {
368
- text-transform: uppercase;
369
- letter-spacing: 0.12em;
370
- color: var(--ui-accent);
371
- font-size: var(--ui-text-xs);
372
- margin-top: var(--ui-space-4);
373
- }
374
-
375
- .sb-statement__bio dd { color: var(--ui-text); }
376
-
377
- @media (max-width: 720px) {
378
- .sb-statement { grid-template-columns: 1fr; }
379
- }
380
-
381
- /* --------------------------------------------------------------------------
382
- Exhibition table
383
- -------------------------------------------------------------------------- */
384
- .sb-exhibitions {
385
- list-style: none;
386
- border-top: 1px solid var(--ui-border);
387
- }
388
-
389
- .sb-exhibition {
390
- display: grid;
391
- grid-template-columns: 80px 1fr 1fr 120px;
392
- gap: var(--ui-space-6);
393
- padding: var(--ui-space-5) 0;
394
- border-bottom: 1px solid var(--ui-border);
395
- align-items: baseline;
396
- }
397
-
398
- .sb-exhibition__year {
399
- font-weight: 700;
400
- color: var(--ui-accent);
401
- font-variant-numeric: tabular-nums;
402
- }
403
-
404
- .sb-exhibition__title { color: var(--ui-text); font-weight: 500; }
405
- .sb-exhibition__venue { color: var(--ui-muted); }
406
-
407
- .sb-exhibition__tag {
408
- font-size: var(--ui-text-xs);
409
- text-transform: uppercase;
410
- letter-spacing: 0.1em;
411
- color: var(--ui-muted);
412
- text-align: right;
413
- }
414
-
415
- .sb-exhibition__tag--solo { color: var(--ui-accent); }
416
-
417
- @media (max-width: 720px) {
418
- .sb-exhibition { grid-template-columns: 60px 1fr; }
419
- .sb-exhibition__venue,
420
- .sb-exhibition__tag { grid-column: 2; text-align: left; }
421
- }
422
-
423
- /* --------------------------------------------------------------------------
424
- Press list with dot separator
425
- -------------------------------------------------------------------------- */
426
- .sb-press {
427
- display: flex;
428
- flex-wrap: wrap;
429
- gap: var(--ui-space-8);
430
- font-size: var(--ui-text-lg);
431
- font-weight: 500;
432
- color: var(--ui-muted);
433
- }
434
-
435
- .sb-press span { white-space: nowrap; }
436
-
437
- .sb-press span + span::before {
438
- content: "\00b7";
439
- margin-right: var(--ui-space-8);
440
- color: var(--ui-accent);
441
- }
442
-
443
- @media (max-width: 600px) {
444
- .sb-press {
445
- flex-direction: column;
446
- gap: var(--ui-space-3);
447
- font-size: var(--ui-text-base);
448
- }
449
-
450
- .sb-press span + span::before { display: none; }
451
- }
452
-
453
- /* --------------------------------------------------------------------------
454
- Contact
455
- -------------------------------------------------------------------------- */
456
- .sb-contact {
457
- display: grid;
458
- grid-template-columns: 1fr 1fr;
459
- gap: var(--ui-space-12);
460
- align-items: start;
461
- }
462
-
463
- .sb-contact h3 {
464
- font-size: var(--ui-text-sm);
465
- text-transform: uppercase;
466
- letter-spacing: 0.18em;
467
- color: var(--ui-accent);
468
- margin-bottom: var(--ui-space-4);
469
- }
470
-
471
- .sb-contact p,
472
- .sb-contact a {
473
- font-size: var(--ui-text-lg);
474
- color: var(--ui-text);
475
- line-height: 1.6;
476
- text-decoration: none;
477
- display: block;
478
- }
479
-
480
- .sb-contact a:hover { color: var(--ui-accent); }
481
-
482
- @media (max-width: 720px) {
483
- .sb-contact { grid-template-columns: 1fr; gap: var(--ui-space-8); }
484
- }
485
-
486
- /* --------------------------------------------------------------------------
487
- Footer
488
- -------------------------------------------------------------------------- */
489
- .sb-foot {
490
- display: flex;
491
- justify-content: space-between;
492
- padding: var(--ui-space-6) var(--ui-space-8);
493
- font-size: var(--ui-text-xs);
494
- text-transform: uppercase;
495
- letter-spacing: 0.12em;
496
- color: var(--ui-muted);
497
- border-top: 1px solid var(--ui-border);
498
- }
499
-
500
- @media (max-width: 600px) {
501
- .sb-foot {
502
- flex-direction: column;
503
- gap: var(--ui-space-2);
504
- padding: var(--ui-space-5);
505
- }
506
- }
507
-
508
- .sb-foot__link {
509
- color: var(--ui-muted);
510
- text-decoration: none;
511
- }
512
-
513
- .sb-foot__link:hover { color: var(--ui-text); }
@@ -1,200 +0,0 @@
1
- /**
2
- * Pulse — Susuki Bento artist portfolio example
3
- *
4
- * Demonstrates:
5
- * - Dark-by-default brutalist gallery palette (near-black, bone, vermilion)
6
- * - Zero-radius design language (--radius: 0px overrides Pulse defaults)
7
- * - Asymmetric CSS grid works gallery (12-col with mixed span sizes)
8
- * - Pure vanilla HTML throughout — no Pulse UI components used.
9
- * Shows that Pulse views are just template literals; you can write
10
- * raw HTML for fully custom layouts without any component overhead.
11
- * - Custom sticky nav and simple footer hand-rolled in the view
12
- * - HTML-escaped data with a utility function (safe pattern for user data)
13
- * - Hover effect with CSS filter: grayscale + scale on artwork images
14
- * - Exhibition table using CSS grid (year / title / venue / tag columns)
15
- * - Press list with CSS ::before dot separator
16
- * - Two-column contact grid
17
- * - System font stack only (Inter → system-ui fallback) — no HTTP requests
18
- * - picsum.photos for artwork images (placeholder seeded by id)
19
- *
20
- * Run: node examples/dev.server.js → http://localhost:3001/suzukibento
21
- */
22
-
23
- // ---------------------------------------------------------------------------
24
- // Static data
25
- // ---------------------------------------------------------------------------
26
-
27
- const works = [
28
- { id: 1043, cls: 'sb-work--a', title: 'Erosion (Shibuya)', year: '2025', medium: 'Oil, ash, dust on canvas — 220×180cm' },
29
- { id: 1025, cls: 'sb-work--b', title: 'Fragments No. 14', year: '2024', medium: 'Mixed media — 140×140cm' },
30
- { id: 1015, cls: 'sb-work--c', title: 'Displaced', year: '2024', medium: 'Acrylic, collage — 160×120cm' },
31
- { id: 1059, cls: 'sb-work--d', title: 'Residue', year: '2023', medium: 'Oil on linen — 100×100cm' },
32
- { id: 1062, cls: 'sb-work--e', title: 'Mother Tongue', year: '2023', medium: 'Oil, gold leaf — 180×140cm' },
33
- { id: 1084, cls: 'sb-work--f', title: 'Untitled (Berlin VI)', year: '2025', medium: 'Concrete, pigment — 300×200cm' },
34
- ]
35
-
36
- const exhibitions = [
37
- { year: '2026', title: 'Soft Architectures', venue: 'Kunsthalle Basel, CH', tag: 'Solo', solo: true },
38
- { year: '2025', title: 'The Weight of Surface', venue: 'KW Institute, Berlin', tag: 'Solo', solo: true },
39
- { year: '2025', title: 'Asia Now', venue: 'Monnaie de Paris', tag: 'Group', solo: false },
40
- { year: '2024', title: 'Memory as Material', venue: 'Hauser & Wirth, Hong Kong', tag: 'Solo', solo: true },
41
- { year: '2024', title: 'Berlin Biennale 13', venue: 'Hamburger Bahnhof', tag: 'Group', solo: false },
42
- { year: '2023', title: 'Quiet Geographies', venue: 'Mori Art Museum, Tokyo', tag: 'Solo', solo: true },
43
- { year: '2022', title: 'New Painting / New Asia', venue: 'Tate St Ives', tag: 'Group', solo: false },
44
- ]
45
-
46
- const press = [
47
- 'Frieze', 'Artforum', 'ArtReview', 'S\u00fcddeutsche Zeitung',
48
- 'The Japan Times', 'Mousse', 'e-flux',
49
- ]
50
-
51
- // ---------------------------------------------------------------------------
52
- // Helpers
53
- // ---------------------------------------------------------------------------
54
-
55
- const esc = (s) => String(s).replace(/[&<>"']/g, (c) => (
56
- { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]
57
- ))
58
-
59
- const work = (w) => `
60
- <figure class="sb-work ${w.cls}">
61
- <img src="/images/suzukibento/${w.id}.jpg" alt="${esc(w.title)}, ${esc(w.year)}" loading="lazy" width="1200" height="900">
62
- <figcaption class="sb-work__caption">${esc(w.title)} <span>${esc(w.year)}</span></figcaption>
63
- </figure>
64
- `
65
-
66
- const exhibition = (e) => `
67
- <li class="sb-exhibition">
68
- <span class="sb-exhibition__year">${esc(e.year)}</span>
69
- <span class="sb-exhibition__title">${esc(e.title)}</span>
70
- <span class="sb-exhibition__venue">${esc(e.venue)}</span>
71
- <span class="sb-exhibition__tag${e.solo ? ' sb-exhibition__tag--solo' : ''}">${esc(e.tag)}</span>
72
- </li>
73
- `
74
-
75
- // ---------------------------------------------------------------------------
76
- // Spec
77
- // ---------------------------------------------------------------------------
78
-
79
- export default {
80
- route: '/suzukibento',
81
-
82
- meta: {
83
- title: 'Susuki Bento \u2014 Painter',
84
- description: 'Tokyo-born, Berlin-based mixed-media artist exploring memory, displacement, and urban erosion through large-scale painting.',
85
- theme: 'dark',
86
- styles: ['/pulse-ui.css', '/examples/suzukibento.css'],
87
- },
88
-
89
- view: () => `
90
- <a href="#main-content" class="skip-link">Skip to main content</a>
91
-
92
- <header class="sb-nav">
93
- <a href="/suzukibento" class="sb-nav__brand" aria-label="Susuki Bento — home">Susuki Bento<span class="sb-nav__dot" aria-hidden="true"></span></a>
94
- <input type="checkbox" id="sb-menu-toggle" class="sb-nav__toggle-input" aria-hidden="true">
95
- <label for="sb-menu-toggle" class="sb-nav__burger" aria-label="Toggle menu">
96
- <span></span><span></span><span></span>
97
- </label>
98
- <nav aria-label="Primary">
99
- <ul class="sb-nav__links">
100
- <li><a href="#works">Works</a></li>
101
- <li><a href="#about">About</a></li>
102
- <li><a href="#exhibitions">Exhibitions</a></li>
103
- <li><a href="#contact">Contact</a></li>
104
- </ul>
105
- </nav>
106
- </header>
107
-
108
- <main id="main-content">
109
- <section class="sb-hero">
110
- <span class="sb-hero__eyebrow">ペインタ \u2014 b. Tokyo, 1984 \u00b7 lives Berlin</span>
111
- <h1 class="sb-hero__title">Susuki<br>Bento<em>.</em></h1>
112
- <div class="sb-hero__meta">
113
- <div>
114
- <span class="sb-hero__meta-label">Now showing</span>
115
- <strong>The Weight of Surface \u2014 KW Institute, Berlin</strong>
116
- </div>
117
- <div>
118
- <span class="sb-hero__meta-label">Closing</span>
119
- <strong>14 September 2026</strong>
120
- </div>
121
- <div>
122
- <span class="sb-hero__meta-label">Represented by</span>
123
- <strong>Hauser &amp; Wirth</strong>
124
- </div>
125
- </div>
126
- </section>
127
-
128
- <section id="works" class="sb-section">
129
- <div class="sb-section__head">
130
- <span class="sb-section__label">01 \u2014 Selected works</span>
131
- <h2 class="sb-section__title">Recent paintings, 2023\u20132025</h2>
132
- </div>
133
- <div class="sb-works">
134
- ${works.map(work).join('')}
135
- </div>
136
- </section>
137
-
138
- <section id="about" class="sb-section">
139
- <div class="sb-section__head">
140
- <span class="sb-section__label">02 \u2014 Statement</span>
141
- <h2 class="sb-section__title">On surface, memory, dust</h2>
142
- </div>
143
- <div class="sb-statement">
144
- <dl class="sb-statement__bio">
145
- <dt>Born</dt><dd>Tokyo, 1984</dd>
146
- <dt>Lives</dt><dd>Berlin-Wedding</dd>
147
- <dt>Education</dt><dd>Tokyo University of the Arts, MFA<br>St\u00e4delschule, Frankfurt</dd>
148
- <dt>Studio</dt><dd>Soldiner Stra\u00dfe 31, 13359 Berlin</dd>
149
- </dl>
150
- <div>
151
- <p>The work begins with surface. I scrape, layer, bury and uncover until the paint behaves like architecture &mdash; weight, weather, decay. Each canvas is a record of what a place could not hold onto.</p>
152
- <p>For two decades I have moved between Tokyo and Berlin, two cities defined by the things they have erased. My paintings collect this erasure: dust from demolition sites, charcoal from cleared lots, the colour of a wall before it was repainted. They are quiet objects made from loud absences.</p>
153
- </div>
154
- </div>
155
- </section>
156
-
157
- <section id="exhibitions" class="sb-section">
158
- <div class="sb-section__head">
159
- <span class="sb-section__label">03 \u2014 Exhibition history</span>
160
- <h2 class="sb-section__title">Selected solo &amp; group, 2022\u20132026</h2>
161
- </div>
162
- <ul class="sb-exhibitions">
163
- ${exhibitions.map(exhibition).join('')}
164
- </ul>
165
- </section>
166
-
167
- <section class="sb-section">
168
- <div class="sb-section__head">
169
- <span class="sb-section__label">04 \u2014 Press</span>
170
- <h2 class="sb-section__title">In print</h2>
171
- </div>
172
- <p class="sb-press">${press.map((p) => `<span>${esc(p)}</span>`).join('')}</p>
173
- </section>
174
-
175
- <section id="contact" class="sb-section">
176
- <div class="sb-section__head">
177
- <span class="sb-section__label">05 \u2014 Contact</span>
178
- <h2 class="sb-section__title">Studio &amp; representation</h2>
179
- </div>
180
- <div class="sb-contact">
181
- <div>
182
- <h3>Studio enquiries</h3>
183
- <a href="mailto:studio@susukibento.art">studio@susukibento.art</a>
184
- <p>Soldiner Stra\u00dfe 31<br>13359 Berlin</p>
185
- </div>
186
- <div>
187
- <h3>Gallery representation</h3>
188
- <a href="mailto:bento@hauserwirth.com">bento@hauserwirth.com</a>
189
- <p>Hauser &amp; Wirth<br>Z\u00fcrich \u00b7 London \u00b7 Hong Kong</p>
190
- </div>
191
- </div>
192
- </section>
193
- </main>
194
-
195
- <footer class="sb-foot">
196
- <span>&copy; Susuki Bento Studio 2026</span>
197
- <span>Site by <a href="https://invisibleloop.com" class="sb-foot__link" target="_blank" rel="noopener">invisibleloop</a></span>
198
- </footer>
199
- `,
200
- }