create-spark-html-app 0.11.1 → 0.12.0

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 (33) hide show
  1. package/bin/index.js +7 -5
  2. package/package.json +1 -1
  3. package/template/README.md +9 -68
  4. package/template/_gitignore +0 -2
  5. package/template/index.html +4 -204
  6. package/template/package.json +2 -12
  7. package/template/public/components/hero.html +12 -64
  8. package/template/src/main.js +2 -61
  9. package/template/src/style.css +32 -0
  10. package/template-prerender/README.md +71 -8
  11. package/template-prerender/_gitignore +2 -0
  12. package/template-prerender/index.html +206 -5
  13. package/template-prerender/package.json +12 -3
  14. package/template-prerender/public/components/hero.html +71 -10
  15. package/template-prerender/spark.config.js +68 -5
  16. package/template-prerender/src/main.js +61 -2
  17. package/template-ssr/public/style.css +113 -54
  18. package/template-ssr/spark.json +1 -1
  19. package/template/spark.config.js +0 -72
  20. package/template-prerender/src/style.css +0 -3
  21. /package/{template → template-prerender}/public/components/about.html +0 -0
  22. /package/{template → template-prerender}/public/components/demo-await.html +0 -0
  23. /package/{template → template-prerender}/public/components/demo-image.html +0 -0
  24. /package/{template → template-prerender}/public/components/demo-persist.html +0 -0
  25. /package/{template → template-prerender}/public/components/demo-props.html +0 -0
  26. /package/{template → template-prerender}/public/components/demo-todo.html +0 -0
  27. /package/{template → template-prerender}/public/components/feature-card.html +0 -0
  28. /package/{template → template-prerender}/public/components/footer.html +0 -0
  29. /package/{template → template-prerender}/public/components/home.html +0 -0
  30. /package/{template → template-prerender}/public/components/nav.html +0 -0
  31. /package/{template → template-prerender}/public/icon.png +0 -0
  32. /package/{template → template-prerender}/public/lib/format.js +0 -0
  33. /package/{template → template-prerender}/public/sample.jpg +0 -0
@@ -1,13 +1,214 @@
1
1
  <!doctype html>
2
2
  <html lang="en">
3
3
  <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1" />
6
- <title>Spark Static App</title>
7
- <link rel="stylesheet" href="/src/style.css" />
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Spark App</title>
7
+ <meta name="description" content="A reactive app built with Spark — single-file HTML components, no compiler, no virtual DOM." />
8
+ <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⚡</text></svg>" />
9
+ <!-- Fonts are injected by spark-html-font/bun (see spark.config.js):
10
+ preconnects, the stylesheet, and a size-adjusted fallback face so the
11
+ swap never moves the page. -->
12
+ <style>
13
+ /* Design system — the same palette and monospace type as the Spark
14
+ website. Tokens + base + a shared card/button/input system live here in
15
+ <head> so they apply globally before any component boots. Light & dark
16
+ are driven by [data-theme] (spark-html-theme writes it). */
17
+ *,
18
+ *::before,
19
+ *::after {
20
+ box-sizing: border-box;
21
+ margin: 0;
22
+ padding: 0;
23
+ }
24
+ :root {
25
+ --bg: #000;
26
+ --surface: #0a0a0a;
27
+ --surface-2: #101014;
28
+ --border: #1a1a1a;
29
+ --border-strong: #333;
30
+ --text: #fff;
31
+ --muted: #888;
32
+ --muted-dim: #555;
33
+ --spark: #ffd24a;
34
+ --spark-ink: #ffd24a;
35
+ --danger: #ff6b6b;
36
+ --radius: 12px;
37
+ /* the injected --font-jetbrains-mono stack includes the fallback face;
38
+ the var() fallback list covers a config without the font step */
39
+ --font: var(--font-jetbrains-mono, "JetBrains Mono", ui-monospace, monospace);
40
+ }
41
+ [data-theme="light"] {
42
+ --bg: #fff;
43
+ --surface: #fafafa;
44
+ --surface-2: #f4f4f5;
45
+ --border: #ededed;
46
+ --border-strong: #d4d4d4;
47
+ --text: #1a1a1a;
48
+ --muted: #666;
49
+ --muted-dim: #999;
50
+ --spark: #ffd24a;
51
+ --spark-ink: #9a6a00;
52
+ --danger: #d63b3b;
53
+ }
54
+ html {
55
+ scroll-behavior: smooth;
56
+ }
57
+ body {
58
+ font-family: var(--font);
59
+ background: var(--bg);
60
+ color: var(--text);
61
+ line-height: 1.6;
62
+ -webkit-font-smoothing: antialiased;
63
+ min-height: 100vh;
64
+ display: flex;
65
+ flex-direction: column;
66
+ }
67
+ ::selection {
68
+ background: var(--spark);
69
+ color: #000;
70
+ }
71
+ [data-theme="light"] ::selection {
72
+ background: #ffe9a8;
73
+ color: #111;
74
+ }
75
+ a {
76
+ color: var(--spark-ink);
77
+ text-decoration: none;
78
+ }
79
+ [hidden] {
80
+ display: none !important;
81
+ }
82
+ .routes {
83
+ flex: 1;
84
+ width: 100%;
85
+ max-width: 960px;
86
+ margin: 0 auto;
87
+ padding: 0 24px;
88
+ }
89
+
90
+ /* Shared component system — components lean on these instead of each
91
+ redefining a card/button/input. */
92
+ .card {
93
+ background: var(--surface);
94
+ border: 1px solid var(--border);
95
+ border-radius: var(--radius);
96
+ padding: 22px;
97
+ }
98
+ .card h2 {
99
+ font-size: 16px;
100
+ font-weight: 700;
101
+ margin-bottom: 4px;
102
+ letter-spacing: -0.01em;
103
+ display: flex;
104
+ align-items: center;
105
+ gap: 8px;
106
+ flex-wrap: wrap;
107
+ }
108
+ .tag {
109
+ font-size: 10px;
110
+ font-weight: 500;
111
+ color: var(--spark-ink);
112
+ border: 1px solid var(--border-strong);
113
+ padding: 1px 7px;
114
+ border-radius: 999px;
115
+ }
116
+ .hint {
117
+ font-size: 12.5px;
118
+ color: var(--muted);
119
+ margin-bottom: 16px;
120
+ line-height: 1.5;
121
+ }
122
+ code {
123
+ background: var(--surface-2);
124
+ color: var(--spark-ink);
125
+ padding: 1px 6px;
126
+ border-radius: 4px;
127
+ font-size: 12px;
128
+ }
129
+ .row {
130
+ display: flex;
131
+ gap: 8px;
132
+ align-items: center;
133
+ flex-wrap: wrap;
134
+ }
135
+
136
+ button,
137
+ .btn {
138
+ font-family: inherit;
139
+ font-size: 13px;
140
+ cursor: pointer;
141
+ padding: 8px 15px;
142
+ border-radius: 8px;
143
+ border: 1px solid var(--border-strong);
144
+ background: var(--surface-2);
145
+ color: var(--text);
146
+ transition:
147
+ border-color 0.12s,
148
+ background 0.12s,
149
+ color 0.12s,
150
+ transform 0.08s;
151
+ }
152
+ button:hover:not(:disabled) {
153
+ border-color: var(--spark);
154
+ color: var(--spark-ink);
155
+ }
156
+ button:active:not(:disabled) {
157
+ transform: scale(0.97);
158
+ }
159
+ button:disabled {
160
+ opacity: 0.4;
161
+ cursor: not-allowed;
162
+ }
163
+ button.primary {
164
+ background: var(--spark);
165
+ color: #000;
166
+ border-color: var(--spark);
167
+ font-weight: 700;
168
+ }
169
+ button.primary:hover:not(:disabled) {
170
+ color: #000;
171
+ filter: brightness(1.06);
172
+ }
173
+
174
+ label {
175
+ display: flex;
176
+ flex-direction: column;
177
+ gap: 4px;
178
+ font-size: 12px;
179
+ color: var(--muted);
180
+ }
181
+ input,
182
+ textarea {
183
+ font-family: inherit;
184
+ font-size: 14px;
185
+ width: 100%;
186
+ background: var(--bg);
187
+ color: var(--text);
188
+ border: 1px solid var(--border-strong);
189
+ border-radius: 8px;
190
+ padding: 9px 12px;
191
+ }
192
+ input:focus,
193
+ textarea:focus {
194
+ outline: none;
195
+ border-color: var(--spark);
196
+ }
197
+ </style>
8
198
  </head>
9
199
  <body>
10
- <div import="components/hero"></div>
200
+ <div import="components/nav"></div>
201
+ <main class="routes">
202
+ <!-- @spark:router -->
203
+ <template route="/"><div import="components/home"></div></template>
204
+ <template route="/about"><div import="components/about"></div></template>
205
+ <!-- @spark:end -->
206
+ <!-- @spark:!router -->
207
+ <div import="components/home"></div>
208
+ <!-- @spark:end -->
209
+ </main>
210
+ <div import="components/footer"></div>
211
+
11
212
  <script type="module" src="/src/main.js"></script>
12
213
  </body>
13
214
  </html>
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "spark-static-app",
2
+ "name": "spark-app",
3
3
  "private": true,
4
4
  "version": "0.0.0",
5
5
  "type": "module",
@@ -9,10 +9,19 @@
9
9
  "preview": "spark preview"
10
10
  },
11
11
  "dependencies": {
12
- "spark-html": "latest"
12
+ "spark-html": "latest",
13
+ "spark-html-router": "latest",
14
+ "spark-html-theme": "latest",
15
+ "spark-html-head": "latest",
16
+ "spark-html-persist": "latest",
17
+ "spark-html-sri": "latest",
18
+ "spark-html-manifest": "latest"
13
19
  },
14
20
  "devDependencies": {
15
21
  "spark-html-bun": "latest",
16
- "spark-prerender": "latest"
22
+ "spark-prerender": "latest",
23
+ "spark-html-devtools": "latest",
24
+ "spark-html-image": "latest",
25
+ "spark-html-font": "latest"
17
26
  }
18
27
  }
@@ -1,14 +1,75 @@
1
- <main>
2
- <h1>⚡ {greeting}</h1>
3
- <p>This page is prerendered at build time — view-source on the deployed
4
- site and the content is right there. Edit this component and save.</p>
5
- <button onclick={cheer}>Clicked {count} times</button>
6
- </main>
1
+ <header class="hero">
2
+ <span class="bolt" :class="igniting ? 'bolt lit' : 'bolt'" onclick="{ignite}" title="Strike the bolt">⚡</span>
3
+
4
+ <h1>HTML that <span class="grad">reacts</span>.</h1>
5
+ <p class="tagline">
6
+ Single-file reactive components — no compiler, no virtual DOM, no build step.
7
+ Everything below is live the moment the page loads.
8
+ </p>
9
+
10
+ <div class="counter">
11
+ <button class="round" onclick="{count = Math.max(0, count - 1)}" :disabled="count <= 0" aria-label="decrement">–</button>
12
+ <div class="readout">
13
+ <span class="num">{count}</span>
14
+ <span class="sub">doubled is {doubled} · {capitalize(mood)}</span>
15
+ </div>
16
+ <button class="round plus" onclick="{count++}" aria-label="increment">+</button>
17
+ </div>
18
+
19
+ <p class="store-line">
20
+ You've struck the bolt <strong>{app.sparks}</strong>
21
+ {pluralize(app.sparks, 'time')} — that lives in a shared store.
22
+ </p>
23
+ </header>
7
24
 
8
25
  <script>
9
- let greeting = 'HTML that reacts';
26
+ import { capitalize, pluralize } from '../lib/format.js';
27
+
10
28
  let count = 0;
11
- let pageTitle = 'Spark Static App';
12
- let pageDescription = 'A prerendered spark-html starter — SEO-ready static HTML that hydrates into a reactive app.';
13
- function cheer() { count++; }
29
+ let igniting = false;
30
+
31
+ $: doubled = count * 2;
32
+ $: mood = count === 0 ? 'a calm start' : count < 5 ? 'warming up' : 'on fire';
33
+
34
+ const app = useStore('app');
35
+
36
+ function ignite() {
37
+ app.sparks++;
38
+ igniting = true;
39
+ setTimeout(() => { igniting = false; }, 450);
40
+ }
14
41
  </script>
42
+
43
+ <style>
44
+ .hero { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 16px; padding-top: 32px; }
45
+ .bolt {
46
+ font-size: 32px; line-height: 1; cursor: pointer; user-select: none;
47
+ filter: drop-shadow(0 0 14px rgba(255, 210, 74, .45));
48
+ transition: transform .2s ease, filter .2s ease;
49
+ }
50
+ .bolt:hover { transform: translateY(-1px) scale(1.06); }
51
+ .bolt.lit { animation: zap .45s ease; }
52
+ @keyframes zap {
53
+ 0% { transform: scale(1); filter: drop-shadow(0 0 14px rgba(255, 210, 74, .45)); }
54
+ 40% { transform: scale(1.35) rotate(-8deg); filter: drop-shadow(0 0 34px rgba(255, 210, 74, .95)); }
55
+ 100% { transform: scale(1); filter: drop-shadow(0 0 14px rgba(255, 210, 74, .45)); }
56
+ }
57
+
58
+ h1 { font-size: clamp(32px, 6vw, 48px); font-weight: 800; letter-spacing: -.03em; }
59
+ .grad {
60
+ background: linear-gradient(110deg, var(--text), var(--spark));
61
+ -webkit-background-clip: text; background-clip: text; color: transparent;
62
+ }
63
+ .tagline { max-width: 480px; color: var(--muted); font-size: 14px; }
64
+
65
+ .counter { display: flex; align-items: center; justify-content: center; gap: 22px; margin-top: 8px; }
66
+ .round {
67
+ width: 44px; height: 44px; border-radius: 50%; font-size: 22px; padding: 0;
68
+ }
69
+ .round.plus { background: var(--surface-2); }
70
+ .readout { min-width: 130px; }
71
+ .num { display: block; font-size: 42px; font-weight: 800; font-variant-numeric: tabular-nums; line-height: 1.1; }
72
+ .sub { font-size: 11px; color: var(--muted); }
73
+ .store-line { font-size: 13px; color: var(--muted); margin-top: 4px; }
74
+ .store-line strong { color: var(--spark-ink); }
75
+ </style>
@@ -1,9 +1,72 @@
1
1
  import prerender from 'spark-prerender/bun';
2
+ // @spark:theme
3
+ import theme from 'spark-html-theme/bun';
4
+ // @spark:end
5
+ // @spark:font
6
+ import font from 'spark-html-font/bun';
7
+ // @spark:end
8
+ // @spark:image
9
+ import image from 'spark-html-image/bun';
10
+ // @spark:end
11
+ // @spark:pwa
12
+ import manifest from 'spark-html-manifest/bun';
13
+ // @spark:end
14
+ // @spark:sri
15
+ import sri from 'spark-html-sri/bun';
16
+ // @spark:end
2
17
 
3
- // Static site, the Spark way: `spark dev` serves components raw with HMR;
4
- // `spark build` copies public/, bundles the entry, then prerender() runs the
5
- // REAL app at build time and writes fully-rendered HTML into dist/ crawlers
6
- // and AI tools read real content, the browser hydrates over it.
18
+ // Spark needs no build step spark-html-bun is just a fast dev server and a
19
+ // bundler for your app shell. `spark dev` serves component fragments raw and
20
+ // hot-reloads only the edited component; components live in public/ so they
21
+ // ship verbatim to the production build too.
22
+ //
23
+ // The `pipeline` runs in order after `spark build` copies public/ and bundles
24
+ // the entry. Order matters: prerender() first (it writes one HTML file per
25
+ // route), then the steps that rewrite those pages — sri() must be last so it
26
+ // hashes the final bytes.
27
+ //
28
+ // `prerender()` makes `bun run build` SEO-friendly: it runs your app at build
29
+ // time and writes fully-rendered HTML into dist/ (crawlers and AI tools read
30
+ // real content; the browser still hydrates over it), plus sitemap.xml +
31
+ // robots.txt. Remove it if you don't need SEO.
7
32
  export default {
8
- pipeline: [prerender({ pages: ['index.html'] })],
33
+ pipeline: [
34
+ prerender({ pages: ['index.html'] }),
35
+ // @spark:theme
36
+ // Bakes the tiny no-flash script into each page (dev too) so the saved /
37
+ // OS theme is on <html> before first paint — no wrong-theme flash on load.
38
+ theme(),
39
+ // @spark:end
40
+ // @spark:font
41
+ // Preconnect + the Google stylesheet + a size-adjusted local fallback
42
+ // face, baked into each page (dev too) — the font swap never moves or
43
+ // visibly reflows the page.
44
+ font({
45
+ fallback: ['ui-monospace', 'monospace'],
46
+ fonts: [{ family: 'JetBrains Mono', google: true, weights: [300, 400, 500, 600, 700, 800] }],
47
+ }),
48
+ // @spark:end
49
+ // @spark:image
50
+ // Every <img> in pages and components: converted to webp/avif at multiple
51
+ // widths, srcset + width/height added (no layout shift), loading="lazy".
52
+ // Zero config.
53
+ image(),
54
+ // @spark:end
55
+ // @spark:pwa
56
+ // One config → manifest.webmanifest + resized icons + <head> tags + an
57
+ // offline app-shell service worker (registered automatically).
58
+ manifest({
59
+ name: 'Spark App',
60
+ themeColor: '#ffd24a',
61
+ icon: 'public/icon.png',
62
+ offline: true,
63
+ }),
64
+ // @spark:end
65
+ // @spark:sri
66
+ // Hashes every built asset + component, stamps integrity/crossorigin onto
67
+ // script/link tags, and bakes the verify manifest into each page. Keep it
68
+ // LAST so it sees the final pages.
69
+ sri(),
70
+ // @spark:end
71
+ ],
9
72
  };
@@ -1,2 +1,61 @@
1
- import { mount } from 'spark-html';
2
- mount();
1
+ import { store } from "spark-html";
2
+ // @spark:!router
3
+ import { mount } from "spark-html";
4
+ // @spark:end
5
+ // @spark:router
6
+ import { router } from "spark-html-router";
7
+ // @spark:end
8
+ // @spark:theme
9
+ import { theme } from "spark-html-theme";
10
+ // @spark:end
11
+ import { head } from "spark-html-head";
12
+ import { persist } from "spark-html-persist";
13
+ // @spark:sri
14
+ import { sri } from "spark-html-sri";
15
+ // @spark:end
16
+ import { devtools } from "spark-html-devtools";
17
+
18
+ const dev = import.meta.env?.DEV;
19
+ if (dev) devtools(); // dev only
20
+
21
+ // @spark:sri
22
+ // Subresource Integrity: verifies every component fetch against the
23
+ // manifest the build baked into the page, and allow-lists remote URL
24
+ // imports (TOFU). Fails open on localhost, enforces in production.
25
+ sri();
26
+ // @spark:end
27
+
28
+ head({
29
+ title: { "/": "Home", "/about": "About", "*": "Not found" },
30
+ titleTemplate: (t) => `${t} · My Site`,
31
+ meta: { description: (path) => `The ${path} page` },
32
+ });
33
+
34
+ // Shared stores connect components without providers or prop drilling.
35
+ store("app", { sparks: 0 });
36
+
37
+ // A store that survives reloads — hydrates from localStorage on boot,
38
+ // saves on every change (see components/demo-persist.html).
39
+ persist("prefs", { name: "", visits: 0 });
40
+
41
+ // @spark:theme
42
+ // One-line dark/light/system theming (the ⚡ logo toggles it).
43
+ theme();
44
+ // @spark:end
45
+
46
+ // @spark:pwa
47
+ // PWA: manifest.webmanifest, icons, and the offline app-shell worker are
48
+ // generated by spark-html-manifest/bun (see spark.config.js) — the worker
49
+ // registration is injected into every built page automatically.
50
+ // Want offline-capable CDN component imports too? See spark-html-offline
51
+ // (a page registers one worker per scope, so pick the one you need).
52
+ // @spark:end
53
+
54
+ // @spark:router
55
+ // Client-side router: reads <template route> blocks, intercepts <a> clicks,
56
+ // and manages SPA navigation. Call it once — replaces mount().
57
+ router({ devOverlay: dev, quiet: !dev });
58
+ // @spark:end
59
+ // @spark:!router
60
+ mount(document.body);
61
+ // @spark:end