@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.
- 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 +13 -6
- package/src/runtime/ssr.test.js +31 -0
- package/src/server/index.js +162 -9
- 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 -350
- 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,260 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pulse — Andy Pixel example
|
|
3
|
-
*
|
|
4
|
-
* Demonstrates:
|
|
5
|
-
* - Blog article layout — editorial light-theme, prose-width reading column
|
|
6
|
-
* - Composing nav, badge, avatar, prose, input, button, footer
|
|
7
|
-
* - Custom code block component (dark pre with labelled header)
|
|
8
|
-
* - Blockquote / pullquote styling
|
|
9
|
-
* - Async newsletter subscribe action with success/error state
|
|
10
|
-
* - Author bio strip below article content
|
|
11
|
-
* - iconCode, iconCheck from icons.js
|
|
12
|
-
* - Orange accent palette with WCAG contrast fixes for badge + inline code
|
|
13
|
-
*
|
|
14
|
-
* Run: node examples/dev.server.js → http://localhost:3001/andypixel
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import {
|
|
18
|
-
nav,
|
|
19
|
-
footer,
|
|
20
|
-
badge,
|
|
21
|
-
button,
|
|
22
|
-
avatar,
|
|
23
|
-
prose,
|
|
24
|
-
input,
|
|
25
|
-
} from '../../../../src/ui/index.js'
|
|
26
|
-
|
|
27
|
-
import { iconCode, iconCheck } from '../../../../src/ui/icons.js'
|
|
28
|
-
import { asset } from '../../lib/layout.js'
|
|
29
|
-
|
|
30
|
-
// ---------------------------------------------------------------------------
|
|
31
|
-
// Code block helper
|
|
32
|
-
// ---------------------------------------------------------------------------
|
|
33
|
-
|
|
34
|
-
function codeBlock({ filename, code }) {
|
|
35
|
-
return `
|
|
36
|
-
<div class="ap-code-block" role="figure" aria-label="Code example: ${filename}">
|
|
37
|
-
<div class="ap-code-block__header">
|
|
38
|
-
${iconCode({ size: 14 })}
|
|
39
|
-
<span>${filename}</span>
|
|
40
|
-
</div>
|
|
41
|
-
<pre class="ap-code-block__pre"><code class="ap-code-block__code">${code}</code></pre>
|
|
42
|
-
</div>
|
|
43
|
-
`
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// ---------------------------------------------------------------------------
|
|
47
|
-
// Spec
|
|
48
|
-
// ---------------------------------------------------------------------------
|
|
49
|
-
|
|
50
|
-
export default {
|
|
51
|
-
route: '/templates/andypixel',
|
|
52
|
-
|
|
53
|
-
meta: {
|
|
54
|
-
title: 'JavaScript Proxies: The Hidden Superpower \u2014 Andy Pixel',
|
|
55
|
-
description: 'Discover how JavaScript Proxies let you intercept and redefine fundamental operations on objects.',
|
|
56
|
-
styles: ['/pulse-ui.css', '/andypixel.css'],
|
|
57
|
-
theme: 'light',
|
|
58
|
-
},
|
|
59
|
-
|
|
60
|
-
state: {
|
|
61
|
-
status: 'idle',
|
|
62
|
-
},
|
|
63
|
-
|
|
64
|
-
view: (state) => `
|
|
65
|
-
<main id="main-content">
|
|
66
|
-
|
|
67
|
-
${nav({
|
|
68
|
-
logo: 'Andy Pixel',
|
|
69
|
-
links: [
|
|
70
|
-
{ label: 'Blog', href: '#blog' },
|
|
71
|
-
{ label: 'Projects', href: '#projects' },
|
|
72
|
-
{ label: 'About', href: '#about' },
|
|
73
|
-
],
|
|
74
|
-
})}
|
|
75
|
-
|
|
76
|
-
<article>
|
|
77
|
-
|
|
78
|
-
<header class="ap-article-header">
|
|
79
|
-
<div class="ap-reading-col">
|
|
80
|
-
|
|
81
|
-
<div class="ap-article-meta">
|
|
82
|
-
${badge({ label: 'JavaScript' })}
|
|
83
|
-
${badge({ label: 'Advanced' })}
|
|
84
|
-
<span class="u-text-muted u-text-sm">\u00b7 May 28, 2026 \u00b7 8 min read</span>
|
|
85
|
-
</div>
|
|
86
|
-
|
|
87
|
-
<h1 class="ap-article-title">
|
|
88
|
-
JavaScript Proxies: The Hidden Superpower You\u2019re Not Using
|
|
89
|
-
</h1>
|
|
90
|
-
|
|
91
|
-
<p class="ap-article-subtitle">
|
|
92
|
-
Intercept, transform, and observe objects at a fundamental level \u2014
|
|
93
|
-
without touching the source.
|
|
94
|
-
</p>
|
|
95
|
-
|
|
96
|
-
<div class="ap-article-author-strip">
|
|
97
|
-
${avatar({ name: 'Andy Pixel', size: 'sm' })}
|
|
98
|
-
<div>
|
|
99
|
-
<p class="u-text-sm u-font-bold">Andy Pixel</p>
|
|
100
|
-
<p class="u-text-sm u-text-muted">Frontend Engineer</p>
|
|
101
|
-
</div>
|
|
102
|
-
</div>
|
|
103
|
-
|
|
104
|
-
</div>
|
|
105
|
-
</header>
|
|
106
|
-
|
|
107
|
-
<div class="ap-article-body ap-reading-col">
|
|
108
|
-
|
|
109
|
-
${prose({ content: `
|
|
110
|
-
<p class="ap-article-lead">If you\u2019ve ever wanted to validate object properties automatically,
|
|
111
|
-
track changes reactively, or build a mock API client that feels real \u2014 the
|
|
112
|
-
<code>Proxy</code> object is your tool. Yet somehow it flies under the radar
|
|
113
|
-
for most developers.</p>
|
|
114
|
-
|
|
115
|
-
<p>Introduced in ES2015, <code>Proxy</code> wraps an object and lets you intercept
|
|
116
|
-
fundamental operations \u2014 property reads, writes, deletions, and more \u2014 via
|
|
117
|
-
\u201ctraps\u201d defined on a handler object.</p>
|
|
118
|
-
|
|
119
|
-
<h2>The anatomy of a Proxy</h2>
|
|
120
|
-
|
|
121
|
-
<p>A Proxy takes two arguments: the <strong>target</strong> (the object being wrapped)
|
|
122
|
-
and the <strong>handler</strong> (an object defining which operations to intercept).
|
|
123
|
-
Every interceptable operation has a corresponding trap method on the handler.</p>
|
|
124
|
-
` })}
|
|
125
|
-
|
|
126
|
-
${codeBlock({
|
|
127
|
-
filename: 'proxy-basics.js',
|
|
128
|
-
code: `const handler = {
|
|
129
|
-
get(target, key) {
|
|
130
|
-
return key in target
|
|
131
|
-
? target[key]
|
|
132
|
-
: \`Property "\${key}" doesn\u2019t exist\`
|
|
133
|
-
},
|
|
134
|
-
set(target, key, value) {
|
|
135
|
-
if (typeof value !== 'number') {
|
|
136
|
-
throw new TypeError('Only numbers allowed')
|
|
137
|
-
}
|
|
138
|
-
target[key] = value
|
|
139
|
-
return true
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const proxy = new Proxy({}, handler)
|
|
144
|
-
proxy.score = 42 // \u2713 works
|
|
145
|
-
proxy.score = 'oops' // \u2717 TypeError`,
|
|
146
|
-
})}
|
|
147
|
-
|
|
148
|
-
${prose({ content: `
|
|
149
|
-
<p>The <code>get</code> trap runs on every property access. The <code>set</code> trap
|
|
150
|
-
intercepts every assignment. Return <code>true</code> from <code>set</code> to signal
|
|
151
|
-
success \u2014 otherwise you\u2019ll get a <code>TypeError</code> in strict mode.</p>
|
|
152
|
-
|
|
153
|
-
<h2>Real-world use cases</h2>
|
|
154
|
-
|
|
155
|
-
<p>The real power isn\u2019t abstract. Proxy unlocks patterns that are otherwise
|
|
156
|
-
painful or impossible.</p>
|
|
157
|
-
` })}
|
|
158
|
-
|
|
159
|
-
<blockquote class="ap-pullquote">
|
|
160
|
-
<p>\u201cA Proxy doesn\u2019t change what your object <em>is</em> \u2014 it changes what your
|
|
161
|
-
object <em>does</em> when the runtime touches it.\u201d</p>
|
|
162
|
-
</blockquote>
|
|
163
|
-
|
|
164
|
-
${prose({ content: `
|
|
165
|
-
<h3>Reactive state</h3>
|
|
166
|
-
<p>Vue 3\u2019s reactivity system is built entirely on Proxy. When you access a reactive
|
|
167
|
-
property, Vue\u2019s <code>get</code> trap tracks the dependency. When you mutate it,
|
|
168
|
-
the <code>set</code> trap schedules re-renders. No syntax overhead. Just assignment.</p>
|
|
169
|
-
|
|
170
|
-
<h3>Input validation</h3>
|
|
171
|
-
<p>Instead of scattering guard clauses through your codebase, centralise validation
|
|
172
|
-
in a Proxy handler. The contract lives in one place. Every consumer benefits
|
|
173
|
-
automatically.</p>
|
|
174
|
-
|
|
175
|
-
<h3>API mocking</h3>
|
|
176
|
-
<p>Wrap a plain object in a Proxy that intercepts property access and returns async
|
|
177
|
-
functions. You get a fully-typed mock client that mirrors your real API surface \u2014
|
|
178
|
-
without any code generation.</p>
|
|
179
|
-
|
|
180
|
-
<h2>Performance considerations</h2>
|
|
181
|
-
|
|
182
|
-
<p>Proxies are not free. Every trap fires a function call. In tight loops over large
|
|
183
|
-
collections, this adds up. Benchmark before you commit. For most use cases \u2014 form
|
|
184
|
-
state, reactive stores, config validation \u2014 the overhead is completely negligible.</p>
|
|
185
|
-
|
|
186
|
-
<p>The V8 team has steadily optimised Proxy performance since ES2015. Don\u2019t avoid
|
|
187
|
-
Proxy out of dated fear \u2014 measure it first.</p>
|
|
188
|
-
` })}
|
|
189
|
-
|
|
190
|
-
</div>
|
|
191
|
-
|
|
192
|
-
</article>
|
|
193
|
-
|
|
194
|
-
<div class="ap-author-bio-wrap">
|
|
195
|
-
<div class="ap-reading-col">
|
|
196
|
-
<div class="ap-author-bio">
|
|
197
|
-
${avatar({ name: 'Andy Pixel', size: 'lg' })}
|
|
198
|
-
<div class="ap-author-bio__text">
|
|
199
|
-
<p class="ap-author-bio__name">Andy Pixel</p>
|
|
200
|
-
<p class="u-text-muted u-text-sm">
|
|
201
|
-
Frontend engineer obsessed with the web platform. Writing about JavaScript,
|
|
202
|
-
CSS, and creative code. Building things at the edge of what browsers can do.
|
|
203
|
-
</p>
|
|
204
|
-
</div>
|
|
205
|
-
</div>
|
|
206
|
-
</div>
|
|
207
|
-
</div>
|
|
208
|
-
|
|
209
|
-
${ state.status === 'success'
|
|
210
|
-
? `<div class="ap-newsletter-success">
|
|
211
|
-
<p class="u-font-bold u-text-lg">${iconCheck({ size: 20, class: 'u-inline' })} You\u2019re in!</p>
|
|
212
|
-
<p class="u-text-muted u-mt-2">Next article lands in your inbox.</p>
|
|
213
|
-
</div>`
|
|
214
|
-
: `<div class="ap-newsletter-section">
|
|
215
|
-
<p class="ap-newsletter-eyebrow">Stay in the loop</p>
|
|
216
|
-
<h2 class="ap-newsletter-title">More like this, in your inbox.</h2>
|
|
217
|
-
<p class="u-text-muted">
|
|
218
|
-
Deep-dives on JavaScript, the web platform, and frontend engineering \u2014
|
|
219
|
-
whenever I publish. No noise.
|
|
220
|
-
</p>
|
|
221
|
-
${ state.status === 'error'
|
|
222
|
-
? `<p class="ap-newsletter-error u-mt-3">Something went wrong. Please try again.</p>`
|
|
223
|
-
: '' }
|
|
224
|
-
<form class="ap-newsletter-form" data-action="subscribe">
|
|
225
|
-
${input({ type: 'email', name: 'email', placeholder: 'you@example.com', label: 'Email address', attrs: { autocomplete: 'email' } })}
|
|
226
|
-
${button({ label: 'Subscribe', variant: 'primary', attrs: { type: 'submit' } })}
|
|
227
|
-
</form>
|
|
228
|
-
</div>`
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
${footer({
|
|
232
|
-
logo: 'Andy Pixel',
|
|
233
|
-
links: [
|
|
234
|
-
{ label: 'Blog', href: '#blog' },
|
|
235
|
-
{ label: 'Projects', href: '#projects' },
|
|
236
|
-
{ label: 'About', href: '#about' },
|
|
237
|
-
{ label: 'RSS', href: '#feed' },
|
|
238
|
-
],
|
|
239
|
-
})}
|
|
240
|
-
|
|
241
|
-
</main>
|
|
242
|
-
<script src="${asset('/pulse-ui.js')}" defer></script>
|
|
243
|
-
`,
|
|
244
|
-
|
|
245
|
-
actions: {
|
|
246
|
-
subscribe: {
|
|
247
|
-
onStart: (state, formData) => ({
|
|
248
|
-
status: 'loading',
|
|
249
|
-
}),
|
|
250
|
-
run: async (state, serverState, formData) => {
|
|
251
|
-
// Newsletter subscription would call an API here
|
|
252
|
-
},
|
|
253
|
-
onSuccess: (state) => ({ status: 'success' }),
|
|
254
|
-
onError: (state, err) => ({
|
|
255
|
-
status: 'error',
|
|
256
|
-
errors: err?.validation ?? [{ message: err.message }],
|
|
257
|
-
}),
|
|
258
|
-
},
|
|
259
|
-
},
|
|
260
|
-
}
|
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Template: Blog Post — Observatory
|
|
3
|
-
*
|
|
4
|
-
* A full blog post page for an astronomy publication.
|
|
5
|
-
* Includes sticky nav, article hero, prose body, newsletter signup, and footer.
|
|
6
|
-
* Dark theme using the Observatory palette (prussian blue / pacific blue).
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { asset } from '../../lib/layout.js'
|
|
10
|
-
import {
|
|
11
|
-
nav as uiNav,
|
|
12
|
-
hero,
|
|
13
|
-
section,
|
|
14
|
-
container,
|
|
15
|
-
prose,
|
|
16
|
-
heading,
|
|
17
|
-
card,
|
|
18
|
-
input,
|
|
19
|
-
button,
|
|
20
|
-
footer,
|
|
21
|
-
iconTelescope,
|
|
22
|
-
iconCheckCircle,
|
|
23
|
-
iconCalendar,
|
|
24
|
-
iconMapPin,
|
|
25
|
-
} from '../../../../src/ui/index.js'
|
|
26
|
-
|
|
27
|
-
// ── Navigation ───────────────────────────────────���────────────────────────────
|
|
28
|
-
|
|
29
|
-
const pageNav = uiNav({
|
|
30
|
-
logo: `${iconTelescope({ size: 20 })}<span class="u-font-bold" style="letter-spacing:-0.02em">Observatory</span>`,
|
|
31
|
-
links: [
|
|
32
|
-
{ label: 'Articles', href: '#' },
|
|
33
|
-
{ label: 'Topics', href: '#' },
|
|
34
|
-
{ label: 'Archive', href: '#' },
|
|
35
|
-
],
|
|
36
|
-
sticky: true,
|
|
37
|
-
background: 'rgba(10, 34, 57, 0.85)',
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
// ── Article hero ──────────────────────────────────────────────────────────────
|
|
41
|
-
|
|
42
|
-
const articleHero = hero({
|
|
43
|
-
eyebrow: 'Deep Sky Objects',
|
|
44
|
-
title: 'The Pillars of Creation: Stellar Nurseries Caught in the Act',
|
|
45
|
-
subtitle: 'Six trillion miles tall and sculpted by the light of newborn stars — the Eagle Nebula\'s most famous feature is one of the most photographed objects in the cosmos. Here\'s what three decades of observation have revealed.',
|
|
46
|
-
actions: `<p class="u-text-sm u-text-muted">By Dr. Elena Vasquez · 14 January 2026 · 9 min read</p>`,
|
|
47
|
-
align: 'center',
|
|
48
|
-
size: 'sm',
|
|
49
|
-
background: 'linear-gradient(160deg, #0a2239 0%, #132e32 60%, #0d2b38 100%)',
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
// ── Article body ──────────────────────────────────────────────────────────────
|
|
53
|
-
|
|
54
|
-
const articleBody = `
|
|
55
|
-
<p>In April 1995, astronomers pointed the Hubble Space Telescope at a seemingly unremarkable patch of sky in the constellation Serpens. What they captured would become one of the most iconic images in the history of science: three towering columns of gas and dust, backlit by the fierce radiation of newly-formed stars.</p>
|
|
56
|
-
|
|
57
|
-
<p>These are the Pillars of Creation — a region of active star formation nestled inside the Eagle Nebula, some 6,500 light-years from Earth. The tallest pillar stretches four light-years from base to tip, roughly equivalent to the distance between our Sun and its nearest stellar neighbour, Proxima Centauri.</p>
|
|
58
|
-
|
|
59
|
-
<figure>
|
|
60
|
-
<img
|
|
61
|
-
src="/img/heic1501a.jpg"
|
|
62
|
-
alt="Three towering columns of gas and dust, backlit by brilliant blue stars, rise from a field of glowing hydrogen in the Eagle Nebula — the Pillars of Creation as seen by Hubble."
|
|
63
|
-
loading="lazy">
|
|
64
|
-
<figcaption>The Pillars of Creation, Eagle Nebula (M16) — Hubble Space Telescope, 2015. Credit: NASA, ESA/Hubble and the Hubble Heritage Team</figcaption>
|
|
65
|
-
</figure>
|
|
66
|
-
|
|
67
|
-
<h2 id="what-are-the-pillars">What are the Pillars, exactly?</h2>
|
|
68
|
-
|
|
69
|
-
<p>The Pillars are a type of structure known as an evaporating gaseous globule, or EGG. They form when intense ultraviolet radiation from hot, massive young stars bombards surrounding clouds of molecular hydrogen. The radiation ionises the outer layers of the cloud, causing them to evaporate and stream away. But denser knots of gas resist this erosion, acting as shields that protect the material behind them.</p>
|
|
70
|
-
|
|
71
|
-
<p>The result is a column: a finger of denser gas pointing away from the radiation source, its tip rounded and hardened by the very light trying to destroy it. The pillars we see in the Eagle Nebula are essentially the remnants of a much larger cloud, sculpted over millions of years by the stars that formed within it.</p>
|
|
72
|
-
|
|
73
|
-
<h2 id="nursery-in-destruction">A nursery in the act of destruction</h2>
|
|
74
|
-
|
|
75
|
-
<p>Look closely at the tips of the pillars and you'll see small, dark bumps — protrusions that break away from the column like droplets from a slow-moving waterfall. Each of these is a potential solar system in the making. Inside every bump, gravity is pulling gas and dust into an ever-tighter knot. Given time — another hundred thousand years, perhaps — each one may collapse under its own weight and ignite as a new star.</p>
|
|
76
|
-
|
|
77
|
-
<p>The irony is profound. The very stars that are destroying the pillars through photoevaporation were themselves born from the same cloud. The Pillars of Creation are, simultaneously, a stellar nursery and a funeral pyre.</p>
|
|
78
|
-
|
|
79
|
-
<h2 id="how-spitzer-changed">How Spitzer changed the story</h2>
|
|
80
|
-
|
|
81
|
-
<p>In 2007, the Spitzer Space Telescope — sensitive to infrared light invisible to human eyes — revealed something unexpected. The Eagle Nebula appeared to have already been hit by the shockwave from a supernova explosion. If correct, the pillars we see in visible light may already be gone. Light from their destruction would still be en route to us, not expected to arrive for another thousand years.</p>
|
|
82
|
-
|
|
83
|
-
<p>When it does, the Eagle Nebula will briefly outshine everything in the night sky except the Moon. Then, as the light-years between us absorb the news, it will fade — and one of the most recognisable objects in the cosmos will exist only in our archives.</p>
|
|
84
|
-
|
|
85
|
-
<h2 id="the-webb-revision">The Webb revision</h2>
|
|
86
|
-
|
|
87
|
-
<p>In 2022, the James Webb Space Telescope turned its golden mirror on the Pillars and returned images that made 1995 look like a rough draft. Webb's near-infrared camera penetrated the dust to reveal thousands of previously hidden young stars, some still wrapped in their accretion discs. Orange jets of superheated material punch out from several of the protostars, signatures of the violent magnetic fields that accompany stellar birth.</p>
|
|
88
|
-
|
|
89
|
-
<p>Webb also confirmed that the Pillars are not as serene as they appear. Turbulence ripples through the gas. Temperature gradients steepen where radiation meets denser material. The whole structure is in slow, continuous motion — a three-dimensional sculpture being revised in real time by forces we are only beginning to understand.</p>
|
|
90
|
-
|
|
91
|
-
<h2 id="observing-from-earth">Observing from Earth</h2>
|
|
92
|
-
|
|
93
|
-
<p>The Eagle Nebula is visible from Earth with modest equipment. From mid-northern latitudes, it rises in the southeast during summer evenings and reaches its highest point around midnight in July. A modest telescope at low magnification will reveal the nebulosity surrounding the cluster M16. The pillars themselves, however, require long-exposure imaging — they lie within the brighter core of the nebula and are impossible to resolve visually.</p>
|
|
94
|
-
|
|
95
|
-
<p>If you point a camera at M16 on a dark night and stack several hours of exposures, the pillars will emerge from the noise — dim, dark columns against a field of glowing hydrogen. You'll be photographing light that left the nebula when the Western Roman Empire was still intact. Every image you take is a time machine.</p>
|
|
96
|
-
`
|
|
97
|
-
|
|
98
|
-
// ── Article aside ────────────────────────────────────────────────────────────
|
|
99
|
-
|
|
100
|
-
const articleAside = `
|
|
101
|
-
<aside aria-label="Article sidebar">
|
|
102
|
-
<div class="obs-aside-section">
|
|
103
|
-
<p class="u-text-sm u-font-semibold u-mb-3">In this article</p>
|
|
104
|
-
<nav aria-label="Article sections">
|
|
105
|
-
<a class="obs-aside-nav-link" href="#what-are-the-pillars">What are the Pillars?</a>
|
|
106
|
-
<a class="obs-aside-nav-link" href="#nursery-in-destruction">A nursery in the act of destruction</a>
|
|
107
|
-
<a class="obs-aside-nav-link" href="#how-spitzer-changed">How Spitzer changed the story</a>
|
|
108
|
-
<a class="obs-aside-nav-link" href="#the-webb-revision">The Webb revision</a>
|
|
109
|
-
<a class="obs-aside-nav-link" href="#observing-from-earth">Observing from Earth</a>
|
|
110
|
-
</nav>
|
|
111
|
-
</div>
|
|
112
|
-
<div class="obs-aside-section">
|
|
113
|
-
${card({
|
|
114
|
-
content: `
|
|
115
|
-
<p class="u-text-xs u-font-semibold u-text-accent u-mb-2">UPCOMING EVENT</p>
|
|
116
|
-
${heading({ level: 3, text: 'Dark Sky Night 2026' })}
|
|
117
|
-
<p class="u-text-sm u-text-muted u-mt-2 u-mb-4">A guided tour of the summer sky with expert astronomers. Telescopes provided. Suitable for all ages.</p>
|
|
118
|
-
<div class="u-flex u-flex-col u-gap-1 u-mb-4">
|
|
119
|
-
<span class="u-flex u-items-center u-gap-2 u-text-sm u-text-muted">
|
|
120
|
-
${iconCalendar({ size: 14 })} 19 July 2026
|
|
121
|
-
</span>
|
|
122
|
-
<span class="u-flex u-items-center u-gap-2 u-text-sm u-text-muted">
|
|
123
|
-
${iconMapPin({ size: 14 })} Galloway Forest Park, Scotland
|
|
124
|
-
</span>
|
|
125
|
-
</div>
|
|
126
|
-
${button({ label: 'Book a place', href: '#', size: 'sm', full: true })}
|
|
127
|
-
`,
|
|
128
|
-
})}
|
|
129
|
-
</div>
|
|
130
|
-
</aside>`
|
|
131
|
-
|
|
132
|
-
// ── Newsletter ────────────────────────────────────────────────────────────────
|
|
133
|
-
|
|
134
|
-
const newsletterSection = (state) => section({
|
|
135
|
-
id: 'newsletter',
|
|
136
|
-
variant: 'alt',
|
|
137
|
-
content: container({
|
|
138
|
-
size: 'sm',
|
|
139
|
-
content: `
|
|
140
|
-
<div class="u-text-center">
|
|
141
|
-
${heading({ level: 2, text: 'Never miss a discovery' })}
|
|
142
|
-
<p class="u-text-muted u-mt-2 u-mb-6">Weekly dispatches from the cosmos — deep sky objects, new research, and what to observe this month.</p>
|
|
143
|
-
${state.status === 'success'
|
|
144
|
-
? `<div class="u-flex u-items-center u-justify-center u-gap-2">
|
|
145
|
-
${iconCheckCircle({ size: 20 })}
|
|
146
|
-
<span>You're subscribed. Clear skies ahead.</span>
|
|
147
|
-
</div>`
|
|
148
|
-
: `<form data-action="subscribe" aria-label="Newsletter signup" novalidate>
|
|
149
|
-
<div class="u-flex u-gap-3 u-flex-wrap u-justify-center u-items-end">
|
|
150
|
-
${input({ label: 'Email address', name: 'email', type: 'email', placeholder: 'your@email.com', required: true })}
|
|
151
|
-
${button({
|
|
152
|
-
label: state.status === 'loading' ? 'Subscribing\u2026' : 'Subscribe',
|
|
153
|
-
type: 'submit',
|
|
154
|
-
attrs: state.status === 'loading' ? { 'aria-busy': 'true', disabled: '' } : {},
|
|
155
|
-
})}
|
|
156
|
-
</div>
|
|
157
|
-
</form>`
|
|
158
|
-
}
|
|
159
|
-
</div>
|
|
160
|
-
`,
|
|
161
|
-
}),
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
// ── Footer ────────────────────────────────────────────────────────────────────
|
|
165
|
-
|
|
166
|
-
const pageFooter = footer({
|
|
167
|
-
logo: `${iconTelescope({ size: 18 })}<span class="u-font-bold" style="letter-spacing:-0.02em">Observatory</span>`,
|
|
168
|
-
logoHref: '/',
|
|
169
|
-
links: [
|
|
170
|
-
{ label: 'About', href: '#' },
|
|
171
|
-
{ label: 'Archive', href: '#' },
|
|
172
|
-
{ label: 'Topics', href: '#' },
|
|
173
|
-
{ label: 'Privacy', href: '#' },
|
|
174
|
-
{ label: 'RSS', href: '#' },
|
|
175
|
-
],
|
|
176
|
-
legal: `\u00a9 ${new Date().getFullYear()} Observatory. All rights reserved.`,
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
// ── Spec ─────────────────────────────────────────────────────────────────��────
|
|
180
|
-
|
|
181
|
-
export default {
|
|
182
|
-
route: '/templates/blog-post',
|
|
183
|
-
|
|
184
|
-
meta: {
|
|
185
|
-
title: 'The Pillars of Creation \u2014 Observatory',
|
|
186
|
-
description: 'Six trillion miles tall and sculpted by the light of newborn stars \u2014 the Eagle Nebula\'s Pillars of Creation remain one of the most studied and photographed objects in the cosmos.',
|
|
187
|
-
styles: ['/pulse-ui.css', '/themes/observatory.css'],
|
|
188
|
-
},
|
|
189
|
-
|
|
190
|
-
state: {
|
|
191
|
-
status: 'idle',
|
|
192
|
-
},
|
|
193
|
-
|
|
194
|
-
actions: {
|
|
195
|
-
subscribe: {
|
|
196
|
-
onStart: (state) => ({ status: 'loading' }),
|
|
197
|
-
run: async (state, serverState, formData) => {
|
|
198
|
-
const email = formData.get('email')
|
|
199
|
-
if (!email) throw new Error('Email address is required')
|
|
200
|
-
// Replace with your email API call
|
|
201
|
-
await new Promise(r => setTimeout(r, 600))
|
|
202
|
-
return { email }
|
|
203
|
-
},
|
|
204
|
-
onSuccess: (state) => ({
|
|
205
|
-
status: 'success',
|
|
206
|
-
_toast: { message: 'Welcome to Observatory! Clear skies ahead.', variant: 'success' },
|
|
207
|
-
}),
|
|
208
|
-
onError: (state, err) => ({
|
|
209
|
-
status: 'error',
|
|
210
|
-
_toast: { message: err.message || 'Something went wrong. Please try again.', variant: 'error' },
|
|
211
|
-
}),
|
|
212
|
-
},
|
|
213
|
-
},
|
|
214
|
-
|
|
215
|
-
view: (state) => `
|
|
216
|
-
<a href="#main-content" class="skip-link" style="position:absolute;left:-9999px;top:1rem;z-index:9999;padding:0.5rem 1rem;background:var(--ui-accent);color:var(--ui-accent-text);border-radius:0.25rem;text-decoration:none">Skip to main content</a>
|
|
217
|
-
<div class="obs-reading-progress" aria-hidden="true"><div class="obs-reading-progress-bar"></div></div>
|
|
218
|
-
${pageNav}
|
|
219
|
-
<main id="main-content">
|
|
220
|
-
${articleHero}
|
|
221
|
-
${section({ content: container({ content: `<div class="obs-article">${prose({ content: articleBody })}${articleAside}</div>` }) })}
|
|
222
|
-
${newsletterSection(state)}
|
|
223
|
-
</main>
|
|
224
|
-
${pageFooter}
|
|
225
|
-
<script src="${asset('/pulse-ui.js')}" defer></script>`,
|
|
226
|
-
}
|