create-volt 0.45.1 → 0.47.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.
- package/CHANGELOG.md +22 -0
- package/index.js +28 -1
- package/package.json +1 -1
- package/templates/blog/setup/index.html +23 -6
- package/templates/blog/setup/setup.js +6 -2
- package/templates/default/setup/index.html +23 -6
- package/templates/default/setup/setup.js +6 -2
- package/templates/docs/setup/index.html +23 -6
- package/templates/docs/setup/setup.js +6 -2
- package/templates/starter/setup/index.html +23 -6
- package/templates/starter/setup/setup.js +6 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,26 @@ All notable changes to `create-volt` are documented here. The format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/), and this project adheres to
|
|
5
5
|
[Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [0.47.0] - 2026-06-29
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- **Config shows the create-volt version** at the top, always; with `(X available)`
|
|
11
|
+
+ an Upgrade button when behind, `(up to date)` otherwise.
|
|
12
|
+
- **Light-mode switcher** in the config wizard (top-right; persists; defaults to dark).
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- **Content-editor textarea** showed a template placeholder instead of the page
|
|
16
|
+
body — now binds the body via the value property.
|
|
17
|
+
|
|
18
|
+
## [0.46.0] - 2026-06-29
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
- **`update` self-heals `server.js` startup-log encoding.** Older scaffolds had
|
|
22
|
+
byte-corrupted bolt/arrow/ellipsis/dash characters in their console logs;
|
|
23
|
+
`create-volt update` (and the config wizard Upgrade button, which runs it) now
|
|
24
|
+
surgically repairs them — rewriting the brand log lines to plain ASCII and
|
|
25
|
+
swapping the corrupted byte-runs — with no change to your logic.
|
|
26
|
+
|
|
7
27
|
## [0.45.1] - 2026-06-29
|
|
8
28
|
|
|
9
29
|
### Fixed
|
|
@@ -600,6 +620,8 @@ All notable changes to `create-volt` are documented here. The format follows
|
|
|
600
620
|
watching and full-page hot reload. Supports `--skip-install` and `--force`,
|
|
601
621
|
and auto-detects npm / pnpm / yarn / bun for the install step.
|
|
602
622
|
|
|
623
|
+
[0.47.0]: https://github.com/MIR-2025/volt/releases/tag/v0.47.0
|
|
624
|
+
[0.46.0]: https://github.com/MIR-2025/volt/releases/tag/v0.46.0
|
|
603
625
|
[0.45.1]: https://github.com/MIR-2025/volt/releases/tag/v0.45.1
|
|
604
626
|
[0.45.0]: https://github.com/MIR-2025/volt/releases/tag/v0.45.0
|
|
605
627
|
[0.44.0]: https://github.com/MIR-2025/volt/releases/tag/v0.44.0
|
package/index.js
CHANGED
|
@@ -270,8 +270,35 @@ if (positionals[0] === "update") {
|
|
|
270
270
|
}
|
|
271
271
|
fs.mkdirSync(path.join(cwd, ".volt"), { recursive: true });
|
|
272
272
|
fs.writeFileSync(path.join(cwd, ".volt", "version"), pkg.version + "\n");
|
|
273
|
+
|
|
274
|
+
// Self-heal: older scaffolds have byte-corrupted ⚡/→/…/— in server.js's startup
|
|
275
|
+
// logs (a tooling bug). Repair is SURGICAL — it only swaps the corrupted byte-runs
|
|
276
|
+
// and rewrites the brand console.log lines to plain ASCII; no logic is touched.
|
|
277
|
+
const srv = path.join(cwd, "server.js");
|
|
278
|
+
if (fs.existsSync(srv)) {
|
|
279
|
+
let code = fs.readFileSync(srv, "utf8");
|
|
280
|
+
const orig = code;
|
|
281
|
+
code = code.replace(/`[^`]*Volt[^`]*http:\/\/localhost/g, "`Volt at http://localhost");
|
|
282
|
+
code = code.replace(/`\\n[^`]*Volt setup[^`]*\$\{url\}/g, "`\\nVolt setup at ${url}");
|
|
283
|
+
code = code.replace(/`\\n[^`]*Volt Studio[^`]*\$\{url\}/g, "`\\nVolt Studio at ${url}");
|
|
284
|
+
const cc = (...a) => String.fromCharCode(...a);
|
|
285
|
+
const moji = [
|
|
286
|
+
[cc(195, 162, 194, 128, 194, 148), "—"], // em-dash
|
|
287
|
+
[cc(195, 162, 194, 134, 194, 144), "→"], // arrow
|
|
288
|
+
[cc(195, 162, 194, 134, 194, 146), "→"], // arrow
|
|
289
|
+
[cc(195, 162, 194, 128, 194, 166), "…"], // ellipsis
|
|
290
|
+
[cc(195, 131, 194, 160), "—"],
|
|
291
|
+
[cc(226, 128, 148), "—"], // single-encoded em-dash
|
|
292
|
+
];
|
|
293
|
+
for (const [from, to] of moji) code = code.split(from).join(to);
|
|
294
|
+
if (code !== orig) {
|
|
295
|
+
fs.writeFileSync(srv, code);
|
|
296
|
+
done.push("server.js (repaired startup-log encoding)");
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
273
300
|
console.log(`\n${green("✔")} Updated to create-volt ${pkg.version}: ${done.join(", ")}.`);
|
|
274
|
-
console.log(dim(`
|
|
301
|
+
console.log(dim(` server.js logic is untouched (re-scaffold to adopt entry-point changes). Restart the app.`));
|
|
275
302
|
process.exit(0);
|
|
276
303
|
}
|
|
277
304
|
|
package/package.json
CHANGED
|
@@ -7,15 +7,19 @@
|
|
|
7
7
|
<title>Set up your Volt app</title>
|
|
8
8
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" />
|
|
9
9
|
<style>
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
.
|
|
14
|
-
.
|
|
15
|
-
|
|
10
|
+
:root { --bg: #0f1115; --fg: #e7e9ee; --card: #161a22; --border: #232a36; --accent: #ffd24a; --field: #0f1115; }
|
|
11
|
+
[data-theme="light"] { --bg: #f6f7f9; --fg: #1b1f27; --card: #ffffff; --border: #d8dce3; --accent: #b07d00; --field: #ffffff; }
|
|
12
|
+
body { background: var(--bg); color: var(--fg); }
|
|
13
|
+
.accent { color: var(--accent); }
|
|
14
|
+
.card-x { background: var(--card); border: 1px solid var(--border); border-radius: 14px; }
|
|
15
|
+
.form-control, .form-select { background: var(--field); color: var(--fg); border-color: var(--border); }
|
|
16
|
+
.form-control:focus, .form-select:focus { background: var(--field); color: var(--fg); border-color: var(--accent); box-shadow: none; }
|
|
17
|
+
code { color: var(--accent); }
|
|
18
|
+
#theme-toggle { position: fixed; top: 12px; right: 14px; z-index: 10; }
|
|
16
19
|
</style>
|
|
17
20
|
</head>
|
|
18
21
|
<body>
|
|
22
|
+
<button id="theme-toggle" class="btn btn-sm btn-outline-secondary">Light mode</button>
|
|
19
23
|
<main class="container py-5" style="max-width: 640px;">
|
|
20
24
|
<header class="mb-4">
|
|
21
25
|
<h1 class="h3"><span class="accent"><img src="/logo.webp" alt="" style="height:1em;vertical-align:-.15em" /> Set up your Volt app</span></h1>
|
|
@@ -23,6 +27,19 @@
|
|
|
23
27
|
</header>
|
|
24
28
|
<div id="app"></div>
|
|
25
29
|
</main>
|
|
30
|
+
<script>
|
|
31
|
+
(function () {
|
|
32
|
+
const root = document.documentElement;
|
|
33
|
+
const btn = document.getElementById("theme-toggle");
|
|
34
|
+
const apply = (t) => { root.setAttribute("data-theme", t); btn.textContent = t === "light" ? "Dark mode" : "Light mode"; };
|
|
35
|
+
apply(localStorage.getItem("volt-setup-theme") || "dark");
|
|
36
|
+
btn.addEventListener("click", () => {
|
|
37
|
+
const next = root.getAttribute("data-theme") === "light" ? "dark" : "light";
|
|
38
|
+
apply(next);
|
|
39
|
+
localStorage.setItem("volt-setup-theme", next);
|
|
40
|
+
});
|
|
41
|
+
})();
|
|
42
|
+
</script>
|
|
26
43
|
<script type="module" src="/setup.js"></script>
|
|
27
44
|
</body>
|
|
28
45
|
</html>
|
|
@@ -342,7 +342,7 @@ const editorPanel = () => {
|
|
|
342
342
|
const e = editing(); // inputs are uncontrolled (read on Save) so typing never re-renders
|
|
343
343
|
return html`<div class="p-3 mb-2" style="border:1px solid #232a36;border-radius:10px">
|
|
344
344
|
<div class="d-flex gap-2 mb-2"><input id="mg-slug" class="form-control" placeholder="slug" value=${e.slug} readonly=${!e.isNew} /><span class="align-self-center small text-muted">${e.type === "post" ? "posts/" : "pages/"}</span></div>
|
|
345
|
-
<textarea id="mg-body" class="form-control" rows="16" style="font-family:ui-monospace,monospace;font-size:13px"
|
|
345
|
+
<textarea id="mg-body" class="form-control" rows="16" style="font-family:ui-monospace,monospace;font-size:13px" value=${e.body}></textarea>
|
|
346
346
|
<div class="mt-2 d-flex gap-2"><button class="btn btn-primary btn-sm" onclick=${saveItem}>Save</button><button class="btn btn-outline-secondary btn-sm" onclick=${() => editing(null)}>Cancel</button></div>
|
|
347
347
|
</div>`;
|
|
348
348
|
};
|
|
@@ -353,7 +353,11 @@ const manageView = () =>
|
|
|
353
353
|
</div>`;
|
|
354
354
|
|
|
355
355
|
const configView = () =>
|
|
356
|
-
html`${() =>
|
|
356
|
+
html`${() => {
|
|
357
|
+
const u = upgrade();
|
|
358
|
+
if (!u || !u.current || u.current === "?") return "";
|
|
359
|
+
return html`<div class="card-x p-3 mb-3 d-flex justify-content-between align-items-center"><span class="small">create-volt <strong>${u.current}</strong> ${u.available ? html`<span class="accent">(${u.latest} available)</span>` : u.latest && u.latest !== "?" ? html`<span class="text-muted">(up to date)</span>` : ""}</span>${u.available ? html`<button class="btn btn-sm btn-primary" onclick=${doUpgrade}>Upgrade</button>` : ""}</div>`;
|
|
360
|
+
}}
|
|
357
361
|
${() => (aiCredits()?.ok ? html`<div class="card-x p-3 mb-3"><div class="d-flex justify-content-between align-items-center mb-2"><strong>AI credits</strong><span class="small text-muted">${aiCredits().tier}${typeof aiCredits().creditBalanceUsd === "number" ? ` · $${aiCredits().creditBalanceUsd.toFixed(2)} left` : ""}</span></div>${aiCredits().payments ? html`<div class="d-flex gap-2 align-items-center"><span class="small text-muted me-1">Top up:</span>${[10, 25, 50].map((a) => html`<button class="btn btn-sm btn-outline-primary" onclick=${() => buyCredits(a)}>$${a}</button>`)}</div>` : html`<div class="small text-muted">Pay-as-you-go isn't enabled on the gateway yet — using the free tier.</div>`}</div>` : "")}
|
|
358
362
|
${available.length ? html`<div class="card-x p-4 mb-3"><h2 class="h6 mb-3">Features</h2>${available.map(addonRow)}<p class="small text-muted mb-0">Enabling a feature wires its backend automatically. Frontend UI (login form, chat) is yours to build — or start from <code>--template guestbook</code>.</p></div>` : ""}
|
|
359
363
|
<div class="card-x p-4 mb-3">
|
|
@@ -7,15 +7,19 @@
|
|
|
7
7
|
<title>Set up your Volt app</title>
|
|
8
8
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" />
|
|
9
9
|
<style>
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
.
|
|
14
|
-
.
|
|
15
|
-
|
|
10
|
+
:root { --bg: #0f1115; --fg: #e7e9ee; --card: #161a22; --border: #232a36; --accent: #ffd24a; --field: #0f1115; }
|
|
11
|
+
[data-theme="light"] { --bg: #f6f7f9; --fg: #1b1f27; --card: #ffffff; --border: #d8dce3; --accent: #b07d00; --field: #ffffff; }
|
|
12
|
+
body { background: var(--bg); color: var(--fg); }
|
|
13
|
+
.accent { color: var(--accent); }
|
|
14
|
+
.card-x { background: var(--card); border: 1px solid var(--border); border-radius: 14px; }
|
|
15
|
+
.form-control, .form-select { background: var(--field); color: var(--fg); border-color: var(--border); }
|
|
16
|
+
.form-control:focus, .form-select:focus { background: var(--field); color: var(--fg); border-color: var(--accent); box-shadow: none; }
|
|
17
|
+
code { color: var(--accent); }
|
|
18
|
+
#theme-toggle { position: fixed; top: 12px; right: 14px; z-index: 10; }
|
|
16
19
|
</style>
|
|
17
20
|
</head>
|
|
18
21
|
<body>
|
|
22
|
+
<button id="theme-toggle" class="btn btn-sm btn-outline-secondary">Light mode</button>
|
|
19
23
|
<main class="container py-5" style="max-width: 640px;">
|
|
20
24
|
<header class="mb-4">
|
|
21
25
|
<h1 class="h3"><span class="accent"><img src="/logo.webp" alt="" style="height:1em;vertical-align:-.15em" /> Set up your Volt app</span></h1>
|
|
@@ -23,6 +27,19 @@
|
|
|
23
27
|
</header>
|
|
24
28
|
<div id="app"></div>
|
|
25
29
|
</main>
|
|
30
|
+
<script>
|
|
31
|
+
(function () {
|
|
32
|
+
const root = document.documentElement;
|
|
33
|
+
const btn = document.getElementById("theme-toggle");
|
|
34
|
+
const apply = (t) => { root.setAttribute("data-theme", t); btn.textContent = t === "light" ? "Dark mode" : "Light mode"; };
|
|
35
|
+
apply(localStorage.getItem("volt-setup-theme") || "dark");
|
|
36
|
+
btn.addEventListener("click", () => {
|
|
37
|
+
const next = root.getAttribute("data-theme") === "light" ? "dark" : "light";
|
|
38
|
+
apply(next);
|
|
39
|
+
localStorage.setItem("volt-setup-theme", next);
|
|
40
|
+
});
|
|
41
|
+
})();
|
|
42
|
+
</script>
|
|
26
43
|
<script type="module" src="/setup.js"></script>
|
|
27
44
|
</body>
|
|
28
45
|
</html>
|
|
@@ -342,7 +342,7 @@ const editorPanel = () => {
|
|
|
342
342
|
const e = editing(); // inputs are uncontrolled (read on Save) so typing never re-renders
|
|
343
343
|
return html`<div class="p-3 mb-2" style="border:1px solid #232a36;border-radius:10px">
|
|
344
344
|
<div class="d-flex gap-2 mb-2"><input id="mg-slug" class="form-control" placeholder="slug" value=${e.slug} readonly=${!e.isNew} /><span class="align-self-center small text-muted">${e.type === "post" ? "posts/" : "pages/"}</span></div>
|
|
345
|
-
<textarea id="mg-body" class="form-control" rows="16" style="font-family:ui-monospace,monospace;font-size:13px"
|
|
345
|
+
<textarea id="mg-body" class="form-control" rows="16" style="font-family:ui-monospace,monospace;font-size:13px" value=${e.body}></textarea>
|
|
346
346
|
<div class="mt-2 d-flex gap-2"><button class="btn btn-primary btn-sm" onclick=${saveItem}>Save</button><button class="btn btn-outline-secondary btn-sm" onclick=${() => editing(null)}>Cancel</button></div>
|
|
347
347
|
</div>`;
|
|
348
348
|
};
|
|
@@ -353,7 +353,11 @@ const manageView = () =>
|
|
|
353
353
|
</div>`;
|
|
354
354
|
|
|
355
355
|
const configView = () =>
|
|
356
|
-
html`${() =>
|
|
356
|
+
html`${() => {
|
|
357
|
+
const u = upgrade();
|
|
358
|
+
if (!u || !u.current || u.current === "?") return "";
|
|
359
|
+
return html`<div class="card-x p-3 mb-3 d-flex justify-content-between align-items-center"><span class="small">create-volt <strong>${u.current}</strong> ${u.available ? html`<span class="accent">(${u.latest} available)</span>` : u.latest && u.latest !== "?" ? html`<span class="text-muted">(up to date)</span>` : ""}</span>${u.available ? html`<button class="btn btn-sm btn-primary" onclick=${doUpgrade}>Upgrade</button>` : ""}</div>`;
|
|
360
|
+
}}
|
|
357
361
|
${() => (aiCredits()?.ok ? html`<div class="card-x p-3 mb-3"><div class="d-flex justify-content-between align-items-center mb-2"><strong>AI credits</strong><span class="small text-muted">${aiCredits().tier}${typeof aiCredits().creditBalanceUsd === "number" ? ` · $${aiCredits().creditBalanceUsd.toFixed(2)} left` : ""}</span></div>${aiCredits().payments ? html`<div class="d-flex gap-2 align-items-center"><span class="small text-muted me-1">Top up:</span>${[10, 25, 50].map((a) => html`<button class="btn btn-sm btn-outline-primary" onclick=${() => buyCredits(a)}>$${a}</button>`)}</div>` : html`<div class="small text-muted">Pay-as-you-go isn't enabled on the gateway yet — using the free tier.</div>`}</div>` : "")}
|
|
358
362
|
${available.length ? html`<div class="card-x p-4 mb-3"><h2 class="h6 mb-3">Features</h2>${available.map(addonRow)}<p class="small text-muted mb-0">Enabling a feature wires its backend automatically. Frontend UI (login form, chat) is yours to build — or start from <code>--template guestbook</code>.</p></div>` : ""}
|
|
359
363
|
<div class="card-x p-4 mb-3">
|
|
@@ -7,15 +7,19 @@
|
|
|
7
7
|
<title>Set up your Volt app</title>
|
|
8
8
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" />
|
|
9
9
|
<style>
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
.
|
|
14
|
-
.
|
|
15
|
-
|
|
10
|
+
:root { --bg: #0f1115; --fg: #e7e9ee; --card: #161a22; --border: #232a36; --accent: #ffd24a; --field: #0f1115; }
|
|
11
|
+
[data-theme="light"] { --bg: #f6f7f9; --fg: #1b1f27; --card: #ffffff; --border: #d8dce3; --accent: #b07d00; --field: #ffffff; }
|
|
12
|
+
body { background: var(--bg); color: var(--fg); }
|
|
13
|
+
.accent { color: var(--accent); }
|
|
14
|
+
.card-x { background: var(--card); border: 1px solid var(--border); border-radius: 14px; }
|
|
15
|
+
.form-control, .form-select { background: var(--field); color: var(--fg); border-color: var(--border); }
|
|
16
|
+
.form-control:focus, .form-select:focus { background: var(--field); color: var(--fg); border-color: var(--accent); box-shadow: none; }
|
|
17
|
+
code { color: var(--accent); }
|
|
18
|
+
#theme-toggle { position: fixed; top: 12px; right: 14px; z-index: 10; }
|
|
16
19
|
</style>
|
|
17
20
|
</head>
|
|
18
21
|
<body>
|
|
22
|
+
<button id="theme-toggle" class="btn btn-sm btn-outline-secondary">Light mode</button>
|
|
19
23
|
<main class="container py-5" style="max-width: 640px;">
|
|
20
24
|
<header class="mb-4">
|
|
21
25
|
<h1 class="h3"><span class="accent"><img src="/logo.webp" alt="" style="height:1em;vertical-align:-.15em" /> Set up your Volt app</span></h1>
|
|
@@ -23,6 +27,19 @@
|
|
|
23
27
|
</header>
|
|
24
28
|
<div id="app"></div>
|
|
25
29
|
</main>
|
|
30
|
+
<script>
|
|
31
|
+
(function () {
|
|
32
|
+
const root = document.documentElement;
|
|
33
|
+
const btn = document.getElementById("theme-toggle");
|
|
34
|
+
const apply = (t) => { root.setAttribute("data-theme", t); btn.textContent = t === "light" ? "Dark mode" : "Light mode"; };
|
|
35
|
+
apply(localStorage.getItem("volt-setup-theme") || "dark");
|
|
36
|
+
btn.addEventListener("click", () => {
|
|
37
|
+
const next = root.getAttribute("data-theme") === "light" ? "dark" : "light";
|
|
38
|
+
apply(next);
|
|
39
|
+
localStorage.setItem("volt-setup-theme", next);
|
|
40
|
+
});
|
|
41
|
+
})();
|
|
42
|
+
</script>
|
|
26
43
|
<script type="module" src="/setup.js"></script>
|
|
27
44
|
</body>
|
|
28
45
|
</html>
|
|
@@ -342,7 +342,7 @@ const editorPanel = () => {
|
|
|
342
342
|
const e = editing(); // inputs are uncontrolled (read on Save) so typing never re-renders
|
|
343
343
|
return html`<div class="p-3 mb-2" style="border:1px solid #232a36;border-radius:10px">
|
|
344
344
|
<div class="d-flex gap-2 mb-2"><input id="mg-slug" class="form-control" placeholder="slug" value=${e.slug} readonly=${!e.isNew} /><span class="align-self-center small text-muted">${e.type === "post" ? "posts/" : "pages/"}</span></div>
|
|
345
|
-
<textarea id="mg-body" class="form-control" rows="16" style="font-family:ui-monospace,monospace;font-size:13px"
|
|
345
|
+
<textarea id="mg-body" class="form-control" rows="16" style="font-family:ui-monospace,monospace;font-size:13px" value=${e.body}></textarea>
|
|
346
346
|
<div class="mt-2 d-flex gap-2"><button class="btn btn-primary btn-sm" onclick=${saveItem}>Save</button><button class="btn btn-outline-secondary btn-sm" onclick=${() => editing(null)}>Cancel</button></div>
|
|
347
347
|
</div>`;
|
|
348
348
|
};
|
|
@@ -353,7 +353,11 @@ const manageView = () =>
|
|
|
353
353
|
</div>`;
|
|
354
354
|
|
|
355
355
|
const configView = () =>
|
|
356
|
-
html`${() =>
|
|
356
|
+
html`${() => {
|
|
357
|
+
const u = upgrade();
|
|
358
|
+
if (!u || !u.current || u.current === "?") return "";
|
|
359
|
+
return html`<div class="card-x p-3 mb-3 d-flex justify-content-between align-items-center"><span class="small">create-volt <strong>${u.current}</strong> ${u.available ? html`<span class="accent">(${u.latest} available)</span>` : u.latest && u.latest !== "?" ? html`<span class="text-muted">(up to date)</span>` : ""}</span>${u.available ? html`<button class="btn btn-sm btn-primary" onclick=${doUpgrade}>Upgrade</button>` : ""}</div>`;
|
|
360
|
+
}}
|
|
357
361
|
${() => (aiCredits()?.ok ? html`<div class="card-x p-3 mb-3"><div class="d-flex justify-content-between align-items-center mb-2"><strong>AI credits</strong><span class="small text-muted">${aiCredits().tier}${typeof aiCredits().creditBalanceUsd === "number" ? ` · $${aiCredits().creditBalanceUsd.toFixed(2)} left` : ""}</span></div>${aiCredits().payments ? html`<div class="d-flex gap-2 align-items-center"><span class="small text-muted me-1">Top up:</span>${[10, 25, 50].map((a) => html`<button class="btn btn-sm btn-outline-primary" onclick=${() => buyCredits(a)}>$${a}</button>`)}</div>` : html`<div class="small text-muted">Pay-as-you-go isn't enabled on the gateway yet — using the free tier.</div>`}</div>` : "")}
|
|
358
362
|
${available.length ? html`<div class="card-x p-4 mb-3"><h2 class="h6 mb-3">Features</h2>${available.map(addonRow)}<p class="small text-muted mb-0">Enabling a feature wires its backend automatically. Frontend UI (login form, chat) is yours to build — or start from <code>--template guestbook</code>.</p></div>` : ""}
|
|
359
363
|
<div class="card-x p-4 mb-3">
|
|
@@ -7,15 +7,19 @@
|
|
|
7
7
|
<title>Set up your Volt app</title>
|
|
8
8
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" />
|
|
9
9
|
<style>
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
.
|
|
14
|
-
.
|
|
15
|
-
|
|
10
|
+
:root { --bg: #0f1115; --fg: #e7e9ee; --card: #161a22; --border: #232a36; --accent: #ffd24a; --field: #0f1115; }
|
|
11
|
+
[data-theme="light"] { --bg: #f6f7f9; --fg: #1b1f27; --card: #ffffff; --border: #d8dce3; --accent: #b07d00; --field: #ffffff; }
|
|
12
|
+
body { background: var(--bg); color: var(--fg); }
|
|
13
|
+
.accent { color: var(--accent); }
|
|
14
|
+
.card-x { background: var(--card); border: 1px solid var(--border); border-radius: 14px; }
|
|
15
|
+
.form-control, .form-select { background: var(--field); color: var(--fg); border-color: var(--border); }
|
|
16
|
+
.form-control:focus, .form-select:focus { background: var(--field); color: var(--fg); border-color: var(--accent); box-shadow: none; }
|
|
17
|
+
code { color: var(--accent); }
|
|
18
|
+
#theme-toggle { position: fixed; top: 12px; right: 14px; z-index: 10; }
|
|
16
19
|
</style>
|
|
17
20
|
</head>
|
|
18
21
|
<body>
|
|
22
|
+
<button id="theme-toggle" class="btn btn-sm btn-outline-secondary">Light mode</button>
|
|
19
23
|
<main class="container py-5" style="max-width: 640px;">
|
|
20
24
|
<header class="mb-4">
|
|
21
25
|
<h1 class="h3"><span class="accent"><img src="/logo.webp" alt="" style="height:1em;vertical-align:-.15em" /> Set up your Volt app</span></h1>
|
|
@@ -23,6 +27,19 @@
|
|
|
23
27
|
</header>
|
|
24
28
|
<div id="app"></div>
|
|
25
29
|
</main>
|
|
30
|
+
<script>
|
|
31
|
+
(function () {
|
|
32
|
+
const root = document.documentElement;
|
|
33
|
+
const btn = document.getElementById("theme-toggle");
|
|
34
|
+
const apply = (t) => { root.setAttribute("data-theme", t); btn.textContent = t === "light" ? "Dark mode" : "Light mode"; };
|
|
35
|
+
apply(localStorage.getItem("volt-setup-theme") || "dark");
|
|
36
|
+
btn.addEventListener("click", () => {
|
|
37
|
+
const next = root.getAttribute("data-theme") === "light" ? "dark" : "light";
|
|
38
|
+
apply(next);
|
|
39
|
+
localStorage.setItem("volt-setup-theme", next);
|
|
40
|
+
});
|
|
41
|
+
})();
|
|
42
|
+
</script>
|
|
26
43
|
<script type="module" src="/setup.js"></script>
|
|
27
44
|
</body>
|
|
28
45
|
</html>
|
|
@@ -342,7 +342,7 @@ const editorPanel = () => {
|
|
|
342
342
|
const e = editing(); // inputs are uncontrolled (read on Save) so typing never re-renders
|
|
343
343
|
return html`<div class="p-3 mb-2" style="border:1px solid #232a36;border-radius:10px">
|
|
344
344
|
<div class="d-flex gap-2 mb-2"><input id="mg-slug" class="form-control" placeholder="slug" value=${e.slug} readonly=${!e.isNew} /><span class="align-self-center small text-muted">${e.type === "post" ? "posts/" : "pages/"}</span></div>
|
|
345
|
-
<textarea id="mg-body" class="form-control" rows="16" style="font-family:ui-monospace,monospace;font-size:13px"
|
|
345
|
+
<textarea id="mg-body" class="form-control" rows="16" style="font-family:ui-monospace,monospace;font-size:13px" value=${e.body}></textarea>
|
|
346
346
|
<div class="mt-2 d-flex gap-2"><button class="btn btn-primary btn-sm" onclick=${saveItem}>Save</button><button class="btn btn-outline-secondary btn-sm" onclick=${() => editing(null)}>Cancel</button></div>
|
|
347
347
|
</div>`;
|
|
348
348
|
};
|
|
@@ -353,7 +353,11 @@ const manageView = () =>
|
|
|
353
353
|
</div>`;
|
|
354
354
|
|
|
355
355
|
const configView = () =>
|
|
356
|
-
html`${() =>
|
|
356
|
+
html`${() => {
|
|
357
|
+
const u = upgrade();
|
|
358
|
+
if (!u || !u.current || u.current === "?") return "";
|
|
359
|
+
return html`<div class="card-x p-3 mb-3 d-flex justify-content-between align-items-center"><span class="small">create-volt <strong>${u.current}</strong> ${u.available ? html`<span class="accent">(${u.latest} available)</span>` : u.latest && u.latest !== "?" ? html`<span class="text-muted">(up to date)</span>` : ""}</span>${u.available ? html`<button class="btn btn-sm btn-primary" onclick=${doUpgrade}>Upgrade</button>` : ""}</div>`;
|
|
360
|
+
}}
|
|
357
361
|
${() => (aiCredits()?.ok ? html`<div class="card-x p-3 mb-3"><div class="d-flex justify-content-between align-items-center mb-2"><strong>AI credits</strong><span class="small text-muted">${aiCredits().tier}${typeof aiCredits().creditBalanceUsd === "number" ? ` · $${aiCredits().creditBalanceUsd.toFixed(2)} left` : ""}</span></div>${aiCredits().payments ? html`<div class="d-flex gap-2 align-items-center"><span class="small text-muted me-1">Top up:</span>${[10, 25, 50].map((a) => html`<button class="btn btn-sm btn-outline-primary" onclick=${() => buyCredits(a)}>$${a}</button>`)}</div>` : html`<div class="small text-muted">Pay-as-you-go isn't enabled on the gateway yet — using the free tier.</div>`}</div>` : "")}
|
|
358
362
|
${available.length ? html`<div class="card-x p-4 mb-3"><h2 class="h6 mb-3">Features</h2>${available.map(addonRow)}<p class="small text-muted mb-0">Enabling a feature wires its backend automatically. Frontend UI (login form, chat) is yours to build — or start from <code>--template guestbook</code>.</p></div>` : ""}
|
|
359
363
|
<div class="card-x p-4 mb-3">
|