@invisibleloop/pulse 0.20.10 → 0.20.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -5
- package/public/.pulse-ui-version +1 -1
- package/scripts/build.js +26 -0
- package/scripts/release-version.js +17 -8
- package/scripts/strip-server.test.js +60 -0
- package/src/agent/checklist.md +8 -12
- package/src/agent/commands/pulse-start.md +2 -0
- package/src/agent/commands/verify.md +30 -8
- package/src/agent/guide-components.md +7 -25
- package/src/agent/guide-routing.md +0 -12
- package/src/agent/guide-server.md +102 -1
- package/src/agent/guide-spec.md +8 -1
- package/src/agent/guide-styles.md +23 -0
- package/src/agent/identity.md +9 -0
- package/src/agent/workflow.md +4 -23
- package/src/cli/check-bundles.js +113 -0
- package/src/cli/check-bundles.test.js +137 -0
- package/src/cli/cli.test.js +105 -0
- package/src/cli/dev.js +48 -3
- package/src/cli/diagnose.js +105 -0
- package/src/cli/diagnose.test.js +124 -0
- package/src/cli/discover.js +9 -2
- package/src/cli/index.js +171 -9
- package/src/cli/process-utils.js +57 -0
- package/src/cli/process-utils.test.js +114 -0
- package/src/cli/review.js +391 -0
- package/src/cli/review.test.js +143 -0
- package/src/cli/scaffold.js +9 -4
- package/src/cli/stamp.js +102 -0
- package/src/cli/stamp.test.js +149 -0
- package/src/cli/start.js +15 -2
- package/src/cli/validate.js +184 -0
- package/src/cli/validate.test.js +153 -0
- package/src/mcp/mcp.test.js +261 -30
- package/src/mcp/server.js +207 -515
- package/src/mcp/validate-worker.js +18 -1
- package/src/runtime/index.js +144 -11
- package/src/runtime/runtime.test.js +199 -0
- package/src/runtime/ssr.js +14 -7
- package/src/runtime/ssr.test.js +31 -0
- package/src/server/index.js +163 -10
- package/src/server/server.test.js +225 -2
- package/src/spec/schema.js +1 -1
- package/src/ui/index.js +0 -5
- package/src/ui/ui.test.js +0 -75
- package/types/ui.d.ts +0 -21
- package/.claude/commands/build-page.md +0 -63
- package/.claude/commands/new-doc-page.md +0 -45
- package/.claude/commands/pulse-start.md +0 -11
- package/.claude/commands/pulse-stop.md +0 -5
- package/.claude/commands/verify.md +0 -118
- package/.claude/pulse-checklist.md +0 -189
- package/.github/workflows/ci.yml +0 -25
- package/.github/workflows/publish.yml +0 -45
- package/.pulse/load-reports/home/1773432711417.json +0 -22
- package/.pulse-verified +0 -1
- package/AGENTS.md +0 -697
- package/CLAUDE.md +0 -697
- package/README.md +0 -95
- package/benchmark/public/dist/counter.boot-PO6SNSJZ.js +0 -8
- package/benchmark/public/dist/home.boot-YK6VAEQI.js +0 -6
- package/benchmark/public/dist/manifest.json +0 -5
- package/benchmark/public/dist/runtime-5Y7ESAIB.js +0 -2
- package/benchmark/public/dist/runtime-KO4BHUQ3.js +0 -49
- package/benchmark/results/2026-03-27-09-20.json +0 -188
- package/benchmark/results/2026-03-27-09-21.json +0 -188
- package/benchmark/results/2026-03-27-09-29.json +0 -188
- package/benchmark/results/2026-03-27-10-14.json +0 -188
- package/benchmark/results/2026-03-27-10-18.json +0 -232
- package/benchmark/src/pages/counter.js +0 -37
- package/benchmark/src/pages/home.js +0 -21
- package/docs/.claude/commands/pulse-build.md +0 -7
- package/docs/.claude/commands/pulse-dev.md +0 -7
- package/docs/.claude/commands/pulse-start.md +0 -9
- package/docs/.claude/commands/pulse-stop.md +0 -5
- package/docs/.claude/commands/verify.md +0 -84
- package/docs/.claude/coverage-check.js +0 -196
- package/docs/.claude/pulse-checklist.md +0 -189
- package/docs/public/.pulse-ui-version +0 -1
- package/docs/public/andypixel.css +0 -237
- package/docs/public/bookworms.css +0 -422
- package/docs/public/coutumes.css +0 -561
- package/docs/public/docs.css +0 -2388
- package/docs/public/favicon.svg +0 -4
- package/docs/public/home-brut.css +0 -982
- package/docs/public/images/bookworms/hero-books.jpg +0 -0
- package/docs/public/images/bookworms/how-we-read-now.jpg +0 -0
- package/docs/public/images/bookworms/lantern-keepers.jpg +0 -0
- package/docs/public/images/bookworms/provincial-bookseller.jpg +0 -0
- package/docs/public/images/journey/178.jpg +0 -0
- package/docs/public/images/journey/29.jpg +0 -0
- package/docs/public/images/retrowheels/hero.jpg +0 -0
- package/docs/public/images/shop/devbook-pro.jpg +0 -0
- package/docs/public/images/suzukibento/1015.jpg +0 -0
- package/docs/public/images/suzukibento/1025.jpg +0 -0
- package/docs/public/images/suzukibento/1043.jpg +0 -0
- package/docs/public/images/suzukibento/1059.jpg +0 -0
- package/docs/public/images/suzukibento/1062.jpg +0 -0
- package/docs/public/images/suzukibento/1084.jpg +0 -0
- package/docs/public/images/swish/1469334031218-e382a71b716b.jpg +0 -0
- package/docs/public/images/swish/1483985988355-763728e1935b.jpg +0 -0
- package/docs/public/images/swish/1485518882345-15568b007407.jpg +0 -0
- package/docs/public/images/swish/1490481651871-ab68de25d43d.jpg +0 -0
- package/docs/public/images/swish/1496747611176-843222e1e57c.jpg +0 -0
- package/docs/public/images/swish/1502716119720-b23a93e5fe1b.jpg +0 -0
- package/docs/public/images/swish/1539109136881-3be0616acf4b.jpg +0 -0
- package/docs/public/images/swish/1551803091-e20673f15770.jpg +0 -0
- package/docs/public/images/swish/1558769132-cb1aea458c5e.jpg +0 -0
- package/docs/public/images/swish/1572804013309-59a88b7e92f1.jpg +0 -0
- package/docs/public/img/heic1501a.jpg +0 -0
- package/docs/public/img/placeholder-landscape.svg +0 -18
- package/docs/public/img/placeholder-square.svg +0 -14
- package/docs/public/img/placeholder-tall.svg +0 -22
- package/docs/public/journey.css +0 -528
- package/docs/public/menu.js +0 -327
- package/docs/public/pulse-ui.css +0 -3028
- package/docs/public/pulse-ui.js +0 -257
- package/docs/public/retrowheels.css +0 -425
- package/docs/public/shop.css +0 -269
- package/docs/public/sparkys.css +0 -382
- package/docs/public/suzukibento.css +0 -517
- package/docs/public/swish.css +0 -394
- package/docs/public/theme.css +0 -52
- package/docs/public/themes/lumio.css +0 -50
- package/docs/public/themes/observatory.css +0 -104
- package/docs/server.js +0 -161
- package/docs/src/lib/component-page.js +0 -49
- package/docs/src/lib/highlight.js +0 -2
- package/docs/src/lib/layout.js +0 -190
- package/docs/src/lib/metrics-store.js +0 -6
- package/docs/src/lib/nav.js +0 -171
- package/docs/src/lib/stats.js +0 -81
- package/docs/src/pages/accessibility.js +0 -157
- package/docs/src/pages/actions.js +0 -194
- package/docs/src/pages/auth.js +0 -176
- package/docs/src/pages/caching.js +0 -95
- package/docs/src/pages/components/accordion.js +0 -48
- package/docs/src/pages/components/alert.js +0 -35
- package/docs/src/pages/components/app-badge.js +0 -41
- package/docs/src/pages/components/avatar.js +0 -35
- package/docs/src/pages/components/badge.js +0 -36
- package/docs/src/pages/components/breadcrumbs.js +0 -94
- package/docs/src/pages/components/button.js +0 -84
- package/docs/src/pages/components/card.js +0 -225
- package/docs/src/pages/components/charts.js +0 -283
- package/docs/src/pages/components/checkbox.js +0 -129
- package/docs/src/pages/components/cluster.js +0 -47
- package/docs/src/pages/components/container.js +0 -40
- package/docs/src/pages/components/cta.js +0 -54
- package/docs/src/pages/components/divider.js +0 -37
- package/docs/src/pages/components/empty.js +0 -36
- package/docs/src/pages/components/feature.js +0 -60
- package/docs/src/pages/components/fieldset.js +0 -65
- package/docs/src/pages/components/fileupload.js +0 -128
- package/docs/src/pages/components/footer.js +0 -58
- package/docs/src/pages/components/grid.js +0 -169
- package/docs/src/pages/components/heading.js +0 -107
- package/docs/src/pages/components/hero.js +0 -123
- package/docs/src/pages/components/icons.js +0 -410
- package/docs/src/pages/components/image.js +0 -71
- package/docs/src/pages/components/input.js +0 -51
- package/docs/src/pages/components/media.js +0 -51
- package/docs/src/pages/components/modal.js +0 -111
- package/docs/src/pages/components/nav.js +0 -136
- package/docs/src/pages/components/phone-frame.js +0 -74
- package/docs/src/pages/components/progress.js +0 -102
- package/docs/src/pages/components/prose.js +0 -111
- package/docs/src/pages/components/pullquote.js +0 -71
- package/docs/src/pages/components/radio.js +0 -194
- package/docs/src/pages/components/rating.js +0 -106
- package/docs/src/pages/components/search.js +0 -61
- package/docs/src/pages/components/section.js +0 -98
- package/docs/src/pages/components/segmented.js +0 -121
- package/docs/src/pages/components/select.js +0 -45
- package/docs/src/pages/components/slider.js +0 -115
- package/docs/src/pages/components/spinner.js +0 -73
- package/docs/src/pages/components/stack.js +0 -48
- package/docs/src/pages/components/stat.js +0 -55
- package/docs/src/pages/components/stepper.js +0 -66
- package/docs/src/pages/components/table.js +0 -45
- package/docs/src/pages/components/textarea.js +0 -31
- package/docs/src/pages/components/toggle.js +0 -84
- package/docs/src/pages/components/tooltip.js +0 -48
- package/docs/src/pages/components.js +0 -200
- package/docs/src/pages/config.js +0 -193
- package/docs/src/pages/constraints.js +0 -100
- package/docs/src/pages/deploy.js +0 -233
- package/docs/src/pages/error-pages.js +0 -99
- package/docs/src/pages/extending.js +0 -198
- package/docs/src/pages/faq.js +0 -99
- package/docs/src/pages/forms.js +0 -129
- package/docs/src/pages/getting-started.js +0 -147
- package/docs/src/pages/guard.js +0 -120
- package/docs/src/pages/home.js +0 -345
- package/docs/src/pages/how-it-works.js +0 -244
- package/docs/src/pages/hydration.js +0 -108
- package/docs/src/pages/images.js +0 -120
- package/docs/src/pages/markdown.js +0 -156
- package/docs/src/pages/meta.js +0 -193
- package/docs/src/pages/mutations.js +0 -107
- package/docs/src/pages/navigation.js +0 -85
- package/docs/src/pages/performance.js +0 -157
- package/docs/src/pages/persist.js +0 -89
- package/docs/src/pages/project-structure.js +0 -91
- package/docs/src/pages/prompt-examples.js +0 -186
- package/docs/src/pages/raw-responses.js +0 -122
- package/docs/src/pages/routing.js +0 -120
- package/docs/src/pages/server-api.js +0 -291
- package/docs/src/pages/server-data.js +0 -203
- package/docs/src/pages/slash-commands.js +0 -55
- package/docs/src/pages/spec.js +0 -207
- package/docs/src/pages/state.js +0 -102
- package/docs/src/pages/store.js +0 -203
- package/docs/src/pages/streaming.js +0 -107
- package/docs/src/pages/stripe.js +0 -194
- package/docs/src/pages/supabase.js +0 -324
- package/docs/src/pages/templates/andypixel.js +0 -260
- package/docs/src/pages/templates/blog-post.js +0 -226
- package/docs/src/pages/templates/bookworms.js +0 -227
- package/docs/src/pages/templates/coutumes.js +0 -237
- package/docs/src/pages/templates/journey.js +0 -186
- package/docs/src/pages/templates/mobile-app.js +0 -371
- package/docs/src/pages/templates/retrowheels.js +0 -355
- package/docs/src/pages/templates/shop.js +0 -268
- package/docs/src/pages/templates/sparkys.js +0 -264
- package/docs/src/pages/templates/suzukibento.js +0 -200
- package/docs/src/pages/templates/swish.js +0 -313
- package/docs/src/pages/templates.js +0 -534
- package/docs/src/pages/testing.js +0 -199
- package/docs/src/pages/validation.js +0 -138
- package/examples/.claude/commands/pulse-build.md +0 -7
- package/examples/.claude/commands/pulse-dev.md +0 -7
- package/examples/.claude/commands/pulse-start.md +0 -9
- package/examples/.claude/commands/pulse-stop.md +0 -5
- package/examples/.claude/commands/verify.md +0 -84
- package/examples/.claude/coverage-check.js +0 -196
- package/examples/.claude/pulse-checklist.md +0 -189
- package/examples/andypixel.css +0 -237
- package/examples/andypixel.js +0 -258
- package/examples/bookworms.css +0 -422
- package/examples/bookworms.js +0 -225
- package/examples/contact.js +0 -166
- package/examples/counter.js +0 -94
- package/examples/dev.server.js +0 -110
- package/examples/examples.test.js +0 -394
- package/examples/journey.css +0 -528
- package/examples/journey.js +0 -186
- package/examples/pricing.js +0 -262
- package/examples/products.js +0 -190
- package/examples/public/.pulse-ui-version +0 -1
- package/examples/public/pulse-ui.css +0 -3028
- package/examples/public/pulse-ui.js +0 -257
- package/examples/quiz.js +0 -208
- package/examples/retrowheels.css +0 -425
- package/examples/retrowheels.js +0 -353
- package/examples/shared.js +0 -84
- package/examples/suzukibento.css +0 -513
- package/examples/suzukibento.js +0 -200
- package/examples/swish.css +0 -394
- package/examples/swish.js +0 -311
- package/examples/todos.js +0 -162
- package/src/ui/fieldset.js +0 -37
- package/src/ui/phone-frame.js +0 -40
- package/src/ui/search.js +0 -78
- package/src/ui/segmented.js +0 -50
- package/src/ui/tooltip.js +0 -28
|
@@ -1,264 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Template: Sparkys — Local Trades Business
|
|
3
|
-
*
|
|
4
|
-
* A sharp, trustworthy landing page for a local electrical contractor.
|
|
5
|
-
* Navy + amber palette. Hero with split stats panel, services grid,
|
|
6
|
-
* trust signals, testimonial, and contact strip.
|
|
7
|
-
*
|
|
8
|
-
* Vibe: corporate Theme: light Palette: navy / amber
|
|
9
|
-
*
|
|
10
|
-
* component-free — creative override: the top utility strip, trust list, and
|
|
11
|
-
* contact strip are small raw-HTML sections (icon+label pairs / a two-column
|
|
12
|
-
* bar) that replaced the now-removed topStrip/trustList/contactRow components.
|
|
13
|
-
* Services use the kept card() component instead of the removed serviceCard().
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import {
|
|
17
|
-
nav, button, footer, card, grid,
|
|
18
|
-
iconZap, iconShield, iconCheck, iconPhone, iconMail, iconHome, iconClock, iconSettings, iconAlertTriangle, iconStar,
|
|
19
|
-
} from '../../../../src/ui/index.js'
|
|
20
|
-
import { asset } from '../../lib/layout.js'
|
|
21
|
-
|
|
22
|
-
const PHONE = '01623 555 014'
|
|
23
|
-
const PHONE_HREF = 'tel:+441623555014'
|
|
24
|
-
const EMAIL = 'hello@sparkys-electrical.co.uk'
|
|
25
|
-
|
|
26
|
-
const services = [
|
|
27
|
-
{
|
|
28
|
-
icon: iconHome({ size: 22 }),
|
|
29
|
-
title: 'Domestic rewiring & repairs',
|
|
30
|
-
body: 'Full or partial rewires, consumer unit upgrades, sockets, lighting, fault finding — done cleanly and on time.',
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
icon: iconZap({ size: 22 }),
|
|
34
|
-
title: 'EV charger installation',
|
|
35
|
-
body: 'OZEV-approved installers for home and workplace chargers. We handle the survey, install and DNO paperwork.',
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
icon: iconSettings({ size: 22 }),
|
|
39
|
-
title: 'Commercial fit-outs',
|
|
40
|
-
body: 'Offices, retail and light industrial — design, installation and certification under one roof.',
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
icon: iconShield({ size: 22 }),
|
|
44
|
-
title: 'EICR & landlord certificates',
|
|
45
|
-
body: 'Mandatory five-year electrical inspections for rental properties. Same-day digital report.',
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
icon: iconAlertTriangle({ size: 22 }),
|
|
49
|
-
title: 'Emergency 24/7 call-out',
|
|
50
|
-
body: 'Power down? Burning smell? Tripping breakers? An engineer on the road within 60 minutes, day or night.',
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
icon: iconCheck({ size: 22 }),
|
|
54
|
-
title: 'Inspections & testing',
|
|
55
|
-
body: 'Periodic testing, PAT testing, and remedial works to bring older installations up to current regulations.',
|
|
56
|
-
},
|
|
57
|
-
]
|
|
58
|
-
|
|
59
|
-
const why = [
|
|
60
|
-
{
|
|
61
|
-
title: 'Part P registered',
|
|
62
|
-
body: 'NICEIC Approved Contractor. Every job notified to building control — paperwork sorted on your behalf.',
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
title: 'Fixed, transparent pricing',
|
|
66
|
-
body: 'Written quotes before we start. No "while we\'re here" surprises and no callout fee on quoted work.',
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
title: '£10m public liability',
|
|
70
|
-
body: 'Fully insured for domestic and commercial work, with a 12-month workmanship guarantee on every job.',
|
|
71
|
-
},
|
|
72
|
-
]
|
|
73
|
-
|
|
74
|
-
const service = (s) => card({
|
|
75
|
-
content: `
|
|
76
|
-
<div class="ui-service-card-icon" aria-hidden="true">${s.icon}</div>
|
|
77
|
-
<h3 class="ui-service-card-title">${s.title}</h3>
|
|
78
|
-
<p class="ui-service-card-desc">${s.body}</p>
|
|
79
|
-
`,
|
|
80
|
-
class: 'service-card',
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
const whyItem = (w) => `
|
|
84
|
-
<div class="why__item">
|
|
85
|
-
<h3 class="why__title">${w.title}</h3>
|
|
86
|
-
<p class="why__body">${w.body}</p>
|
|
87
|
-
</div>
|
|
88
|
-
`
|
|
89
|
-
|
|
90
|
-
export default {
|
|
91
|
-
route: '/templates/sparkys',
|
|
92
|
-
|
|
93
|
-
meta: {
|
|
94
|
-
title: 'Sparkys — Trusted Electricians in Ashfield',
|
|
95
|
-
description: 'Sparkys are Part P registered electricians serving homes, landlords and businesses across Ashfield. Rewiring, EV chargers, EICRs and 24/7 emergency call-out.',
|
|
96
|
-
theme: 'light',
|
|
97
|
-
styles: ['/pulse-ui.css', '/sparkys.css'],
|
|
98
|
-
schema: {
|
|
99
|
-
'@context': 'https://schema.org',
|
|
100
|
-
'@type': 'Electrician',
|
|
101
|
-
name: 'Sparkys',
|
|
102
|
-
telephone: '+44 1623 555014',
|
|
103
|
-
email: EMAIL,
|
|
104
|
-
areaServed: 'Ashfield, Nottinghamshire',
|
|
105
|
-
address: {
|
|
106
|
-
'@type': 'PostalAddress',
|
|
107
|
-
streetAddress: '14 Forge Lane',
|
|
108
|
-
addressLocality: 'Sutton-in-Ashfield',
|
|
109
|
-
postalCode: 'NG17 4FX',
|
|
110
|
-
addressCountry: 'GB',
|
|
111
|
-
},
|
|
112
|
-
},
|
|
113
|
-
},
|
|
114
|
-
|
|
115
|
-
view: () => `
|
|
116
|
-
<div class="top-strip">
|
|
117
|
-
<div class="top-strip__left">Mon–Sat 7am–7pm · 24/7 emergency line</div>
|
|
118
|
-
<div class="top-strip__right"><a href="${PHONE_HREF}">Call ${PHONE}</a></div>
|
|
119
|
-
</div>
|
|
120
|
-
|
|
121
|
-
${nav({
|
|
122
|
-
logo: '<strong class="brand-mark">Sparkys</strong>',
|
|
123
|
-
logoHref: '/',
|
|
124
|
-
links: [
|
|
125
|
-
{ label: 'Services', href: '#services' },
|
|
126
|
-
{ label: 'Why Sparkys', href: '#why' },
|
|
127
|
-
{ label: 'Reviews', href: '#reviews' },
|
|
128
|
-
{ label: 'Contact', href: '#contact' },
|
|
129
|
-
],
|
|
130
|
-
action: button({
|
|
131
|
-
label: 'Get a free quote',
|
|
132
|
-
href: '#contact',
|
|
133
|
-
variant: 'primary',
|
|
134
|
-
size: 'sm',
|
|
135
|
-
}),
|
|
136
|
-
sticky: true,
|
|
137
|
-
})}
|
|
138
|
-
|
|
139
|
-
<main id="main-content">
|
|
140
|
-
<section class="hero-split" aria-labelledby="hero-title">
|
|
141
|
-
<div class="hero-split__text">
|
|
142
|
-
<p class="hero-split__eyebrow">Ashfield · Mansfield · Kirkby</p>
|
|
143
|
-
<h1 id="hero-title" class="hero-split__title">Ashfield's trusted electricians, since 2008.</h1>
|
|
144
|
-
<p class="hero-split__subtitle">From a dodgy socket to a full commercial fit-out — Sparkys are the local team homeowners, landlords and businesses call when the job has to be done properly.</p>
|
|
145
|
-
<div class="hero-split__actions">
|
|
146
|
-
${button({
|
|
147
|
-
label: 'Get a free quote',
|
|
148
|
-
href: '#contact',
|
|
149
|
-
variant: 'primary',
|
|
150
|
-
size: 'lg',
|
|
151
|
-
})}
|
|
152
|
-
${button({
|
|
153
|
-
label: `Call ${PHONE}`,
|
|
154
|
-
href: PHONE_HREF,
|
|
155
|
-
variant: 'secondary',
|
|
156
|
-
size: 'lg',
|
|
157
|
-
icon: iconPhone({ size: 16 }),
|
|
158
|
-
})}
|
|
159
|
-
</div>
|
|
160
|
-
<div class="hero-split__trust">
|
|
161
|
-
<ul class="trust-inline">
|
|
162
|
-
${['Part P registered', 'NICEIC approved', '£10m insured'].map(t => `
|
|
163
|
-
<li class="trust-inline__item">
|
|
164
|
-
<span class="trust-inline__icon" aria-hidden="true">${iconCheck({ size: 14 })}</span>
|
|
165
|
-
<span>${t}</span>
|
|
166
|
-
</li>
|
|
167
|
-
`).join('')}
|
|
168
|
-
</ul>
|
|
169
|
-
</div>
|
|
170
|
-
</div>
|
|
171
|
-
<aside class="hero-split__panel" aria-label="Sparkys at a glance">
|
|
172
|
-
<p class="hero-split__panel-title">Sparkys at a glance</p>
|
|
173
|
-
<dl class="hero-stats">
|
|
174
|
-
<div>
|
|
175
|
-
<dd class="hero-stat__value">24/7</dd>
|
|
176
|
-
<dt class="hero-stat__label">Emergency call-out</dt>
|
|
177
|
-
</div>
|
|
178
|
-
<div>
|
|
179
|
-
<dd class="hero-stat__value">1,200+</dd>
|
|
180
|
-
<dt class="hero-stat__label">Jobs completed</dt>
|
|
181
|
-
</div>
|
|
182
|
-
<div>
|
|
183
|
-
<dd class="hero-stat__value"><span class="hero-stat__rating">4.9${iconStar({ size: 22 })}</span></dd>
|
|
184
|
-
<dt class="hero-stat__label">Average review</dt>
|
|
185
|
-
</div>
|
|
186
|
-
<div>
|
|
187
|
-
<dd class="hero-stat__value">16 yrs</dd>
|
|
188
|
-
<dt class="hero-stat__label">Serving Ashfield</dt>
|
|
189
|
-
</div>
|
|
190
|
-
</dl>
|
|
191
|
-
</aside>
|
|
192
|
-
</section>
|
|
193
|
-
|
|
194
|
-
<section id="services" class="section">
|
|
195
|
-
<div class="section__inner">
|
|
196
|
-
<header class="section__head">
|
|
197
|
-
<p class="section__eyebrow">What we do</p>
|
|
198
|
-
<h2 class="section__title">Domestic, commercial and everything in between.</h2>
|
|
199
|
-
<p class="section__subtitle">One local team, certified for every job — so you don't end up juggling three different trades.</p>
|
|
200
|
-
</header>
|
|
201
|
-
${grid({ cols: 3, gap: 'md', content: services.map(service).join(''), class: 'services' })}
|
|
202
|
-
</div>
|
|
203
|
-
</section>
|
|
204
|
-
|
|
205
|
-
<section id="why" class="section section--alt">
|
|
206
|
-
<div class="section__inner">
|
|
207
|
-
<header class="section__head">
|
|
208
|
-
<p class="section__eyebrow">Why Sparkys</p>
|
|
209
|
-
<h2 class="section__title">Certified, insured, and on time.</h2>
|
|
210
|
-
<p class="section__subtitle">The boring stuff that actually matters when an electrician is in your home or business.</p>
|
|
211
|
-
</header>
|
|
212
|
-
<div class="why">
|
|
213
|
-
${why.map(whyItem).join('')}
|
|
214
|
-
</div>
|
|
215
|
-
</div>
|
|
216
|
-
</section>
|
|
217
|
-
|
|
218
|
-
<section id="reviews" class="section">
|
|
219
|
-
<div class="section__inner">
|
|
220
|
-
<figure class="quote">
|
|
221
|
-
<div class="quote__mark" aria-hidden="true">"</div>
|
|
222
|
-
<blockquote class="quote__text">Sparkys rewired our 1930s house top to bottom in five days, on the price they quoted, and tidied up after themselves every evening. Genuinely couldn't fault them.</blockquote>
|
|
223
|
-
<figcaption class="quote__cite">Helen M. · Sutton-in-Ashfield</figcaption>
|
|
224
|
-
</figure>
|
|
225
|
-
</div>
|
|
226
|
-
</section>
|
|
227
|
-
|
|
228
|
-
<section id="contact" class="contact-strip" aria-labelledby="contact-title">
|
|
229
|
-
<div class="contact-strip__inner">
|
|
230
|
-
<div>
|
|
231
|
-
<h2 id="contact-title" class="contact-strip__title">Need a sparky? Let's talk.</h2>
|
|
232
|
-
<p class="contact-strip__lead">Free, no-obligation quotes for homes and businesses across Ashfield, Mansfield and the wider NG postcodes.</p>
|
|
233
|
-
</div>
|
|
234
|
-
<div class="contact-strip__details">
|
|
235
|
-
<ul class="contact-inline">
|
|
236
|
-
${[
|
|
237
|
-
{ icon: iconPhone({ size: 18 }), label: PHONE, href: PHONE_HREF },
|
|
238
|
-
{ icon: iconMail({ size: 18 }), label: EMAIL, href: `mailto:${EMAIL}` },
|
|
239
|
-
{ icon: iconClock({ size: 18 }), label: 'Mon–Sat 7am–7pm · 24/7 emergencies' },
|
|
240
|
-
].map(({ icon, label, href }) => `
|
|
241
|
-
<li class="contact-inline__item">
|
|
242
|
-
<span class="contact-inline__icon" aria-hidden="true">${icon}</span>
|
|
243
|
-
${href ? `<a href="${href}">${label}</a>` : `<span>${label}</span>`}
|
|
244
|
-
</li>
|
|
245
|
-
`).join('')}
|
|
246
|
-
</ul>
|
|
247
|
-
</div>
|
|
248
|
-
</div>
|
|
249
|
-
</section>
|
|
250
|
-
</main>
|
|
251
|
-
|
|
252
|
-
${footer({
|
|
253
|
-
logo: '<span class="site-footer__brand">Sparkys</span>',
|
|
254
|
-
logoHref: '/',
|
|
255
|
-
links: [
|
|
256
|
-
{ label: 'Services', href: '#services' },
|
|
257
|
-
{ label: 'Reviews', href: '#reviews' },
|
|
258
|
-
{ label: 'Contact', href: '#contact' },
|
|
259
|
-
],
|
|
260
|
-
legal: '© 2026 Sparkys Electrical Ltd · NICEIC 60123456 · Reg. in England 09876543',
|
|
261
|
-
})}
|
|
262
|
-
<script src="${asset('/pulse-ui.js')}" defer></script>
|
|
263
|
-
`,
|
|
264
|
-
}
|
|
@@ -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
|
-
{ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[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: '/templates/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', '/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 & 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 — 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 & 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 & 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 & 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>© 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
|
-
}
|