@kamod-ch/preactpress 2.0.0 → 2.2.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/README.md +27 -8
- package/dist/node/config-helpers.d.ts +1 -0
- package/dist/node/config-helpers.d.ts.map +1 -1
- package/dist/node/config-helpers.js.map +1 -1
- package/dist/node/config.d.ts.map +1 -1
- package/dist/node/config.js +12 -12
- package/dist/node/config.js.map +1 -1
- package/dist/node/favicon.d.ts +9 -3
- package/dist/node/favicon.d.ts.map +1 -1
- package/dist/node/favicon.js +115 -9
- package/dist/node/favicon.js.map +1 -1
- package/dist/node/html.d.ts.map +1 -1
- package/dist/node/html.js +18 -11
- package/dist/node/html.js.map +1 -1
- package/dist/node/index.d.ts +1 -1
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/plugin.d.ts.map +1 -1
- package/dist/node/plugin.js +3 -2
- package/dist/node/plugin.js.map +1 -1
- package/dist/node/siteConfig.d.ts +57 -0
- package/dist/node/siteConfig.d.ts.map +1 -1
- package/dist/shared/pageReady.d.ts +19 -0
- package/dist/shared/pageReady.d.ts.map +1 -0
- package/dist/shared/pageReady.js +230 -0
- package/dist/shared/pageReady.js.map +1 -0
- package/package.json +3 -1
- package/src/shared/pageReady.ts +255 -0
- package/templates/docs/.preactpress/config.ts +1 -0
- package/templates/docs/components/MarketingHero.tsx +17 -3
- package/templates/docs/components/References.tsx +8 -3
- package/templates/docs/components/TemplateGallery.tsx +66 -9
- package/templates/docs/components/marketing.css +92 -2
- package/templates/docs/guide/advanced.md +1 -0
- package/templates/docs/guide/commands.md +2 -0
- package/templates/docs/guide/configuration.md +23 -0
- package/templates/docs/guide/getting-started.md +2 -0
- package/templates/docs/guide/templates.md +57 -0
- package/templates/docs/public/templates/.gitkeep +1 -0
- package/templates/docs/public/templates/default.webp +0 -0
- package/templates/docs/public/templates/docs.webp +0 -0
- package/templates/docs/public/templates/hono.webp +0 -0
- package/templates/docs/public/templates/magazine.webp +0 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { escapeHtml } from "./escapeHtml.js";
|
|
2
|
+
import type { PageReadyConfig, ResolvedPageReadyConfig } from "../node/siteConfig.js";
|
|
3
|
+
|
|
4
|
+
const DEFAULT_PRELOADER = `<div id="pp-preloader" role="status" aria-live="polite" aria-label="Loading">
|
|
5
|
+
<div class="pp-preloader-spinner" aria-hidden="true"></div>
|
|
6
|
+
</div>`;
|
|
7
|
+
|
|
8
|
+
const PAGE_READY_HEAD = `<style id="pp-page-ready">
|
|
9
|
+
html:not(.pp-ready) #app {
|
|
10
|
+
visibility: hidden;
|
|
11
|
+
}
|
|
12
|
+
html.pp-ready #pp-preloader {
|
|
13
|
+
display: none !important;
|
|
14
|
+
}
|
|
15
|
+
:root,
|
|
16
|
+
:root[data-theme="light"] {
|
|
17
|
+
--pp-preloader-bg: #ffffff;
|
|
18
|
+
--pp-preloader-track: #e5e7eb;
|
|
19
|
+
--pp-preloader-accent: #171717;
|
|
20
|
+
}
|
|
21
|
+
:root[data-theme="dark"],
|
|
22
|
+
html.dark {
|
|
23
|
+
--pp-preloader-bg: #1b1b1f;
|
|
24
|
+
--pp-preloader-track: #2f2f32;
|
|
25
|
+
--pp-preloader-accent: rgba(255, 255, 245, 0.86);
|
|
26
|
+
}
|
|
27
|
+
@media (prefers-color-scheme: dark) {
|
|
28
|
+
:root:not([data-theme]) {
|
|
29
|
+
--pp-preloader-bg: #1b1b1f;
|
|
30
|
+
--pp-preloader-track: #2f2f32;
|
|
31
|
+
--pp-preloader-accent: rgba(255, 255, 245, 0.86);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
#pp-preloader {
|
|
35
|
+
position: fixed;
|
|
36
|
+
inset: 0;
|
|
37
|
+
z-index: 2147483647;
|
|
38
|
+
display: flex;
|
|
39
|
+
align-items: center;
|
|
40
|
+
justify-content: center;
|
|
41
|
+
background: var(--pp-preloader-bg);
|
|
42
|
+
color-scheme: light dark;
|
|
43
|
+
}
|
|
44
|
+
.pp-preloader-spinner {
|
|
45
|
+
width: 2rem;
|
|
46
|
+
height: 2rem;
|
|
47
|
+
border: 3px solid var(--pp-preloader-track);
|
|
48
|
+
border-top-color: var(--pp-preloader-accent);
|
|
49
|
+
border-radius: 50%;
|
|
50
|
+
animation: pp-preloader-spin 0.7s linear infinite;
|
|
51
|
+
box-sizing: border-box;
|
|
52
|
+
}
|
|
53
|
+
@keyframes pp-preloader-spin {
|
|
54
|
+
to {
|
|
55
|
+
transform: rotate(360deg);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
</style>
|
|
59
|
+
<script>
|
|
60
|
+
document.documentElement.setAttribute("aria-busy", "true");
|
|
61
|
+
</script>
|
|
62
|
+
<noscript>
|
|
63
|
+
<style>
|
|
64
|
+
#pp-preloader {
|
|
65
|
+
display: none !important;
|
|
66
|
+
}
|
|
67
|
+
#app {
|
|
68
|
+
visibility: visible !important;
|
|
69
|
+
}
|
|
70
|
+
</style>
|
|
71
|
+
</noscript>`;
|
|
72
|
+
|
|
73
|
+
function escapeJsString(value: string): string {
|
|
74
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function normalizePreloaderHtml(html: string): string {
|
|
78
|
+
if (/id=["']pp-preloader["']/.test(html)) return html;
|
|
79
|
+
return `<div id="pp-preloader" role="status" aria-live="polite" aria-label="Loading">${html}</div>`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function buildThemeCssProbe(probe: string | false): string {
|
|
83
|
+
if (probe === false) return "";
|
|
84
|
+
const safeProbe = escapeJsString(probe);
|
|
85
|
+
return `var probe = getComputedStyle(document.documentElement).getPropertyValue("${safeProbe}");
|
|
86
|
+
if (probe && probe.trim() !== "") return true;
|
|
87
|
+
|
|
88
|
+
`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function buildPageReadyBootScript(config: ResolvedPageReadyConfig): string {
|
|
92
|
+
const themeProbe = buildThemeCssProbe(config.probe);
|
|
93
|
+
return `<script>
|
|
94
|
+
(function () {
|
|
95
|
+
var READY_CLASS = "pp-ready";
|
|
96
|
+
var FALLBACK_MS = ${config.fallbackMs};
|
|
97
|
+
var MAX_FRAMES = ${config.maxFrames};
|
|
98
|
+
var STABLE_FRAMES = ${config.stableFrames};
|
|
99
|
+
|
|
100
|
+
function markReady() {
|
|
101
|
+
if (document.documentElement.classList.contains(READY_CLASS)) return;
|
|
102
|
+
document.documentElement.classList.add(READY_CLASS);
|
|
103
|
+
document.documentElement.removeAttribute("aria-busy");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function themeCssApplied() {
|
|
107
|
+
${themeProbe}var links = document.querySelectorAll('link[rel="stylesheet"][href]');
|
|
108
|
+
if (!links.length) return true;
|
|
109
|
+
|
|
110
|
+
for (var i = 0; i < links.length; i++) {
|
|
111
|
+
var link = links[i];
|
|
112
|
+
if (link.media === "print") return false;
|
|
113
|
+
try {
|
|
114
|
+
if (!link.sheet) return false;
|
|
115
|
+
} catch (e) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function whenStylesReady() {
|
|
123
|
+
return new Promise(function (resolve) {
|
|
124
|
+
var settled = false;
|
|
125
|
+
var lastResourceCount = -1;
|
|
126
|
+
var stableFrames = 0;
|
|
127
|
+
|
|
128
|
+
function resourceCount() {
|
|
129
|
+
return (
|
|
130
|
+
document.querySelectorAll('link[rel="stylesheet"][href]').length +
|
|
131
|
+
document.querySelectorAll("style:not(#pp-page-ready)").length
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function settle() {
|
|
136
|
+
if (settled) return;
|
|
137
|
+
if (!themeCssApplied()) return;
|
|
138
|
+
var count = resourceCount();
|
|
139
|
+
if (count !== lastResourceCount) {
|
|
140
|
+
lastResourceCount = count;
|
|
141
|
+
stableFrames = 0;
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
stableFrames += 1;
|
|
145
|
+
if (stableFrames < STABLE_FRAMES) return;
|
|
146
|
+
settled = true;
|
|
147
|
+
observer.disconnect();
|
|
148
|
+
var app = document.getElementById("app");
|
|
149
|
+
if (app) void app.offsetHeight;
|
|
150
|
+
requestAnimationFrame(function () {
|
|
151
|
+
requestAnimationFrame(function () {
|
|
152
|
+
requestAnimationFrame(resolve);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
var observer = new MutationObserver(settle);
|
|
158
|
+
observer.observe(document.head, { childList: true, subtree: true });
|
|
159
|
+
|
|
160
|
+
var links = document.querySelectorAll('link[rel="stylesheet"][href]');
|
|
161
|
+
for (var i = 0; i < links.length; i++) {
|
|
162
|
+
links[i].addEventListener("load", settle);
|
|
163
|
+
links[i].addEventListener("error", settle);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
var frames = 0;
|
|
167
|
+
function poll() {
|
|
168
|
+
settle();
|
|
169
|
+
if (settled) return;
|
|
170
|
+
frames += 1;
|
|
171
|
+
if (frames >= MAX_FRAMES) {
|
|
172
|
+
settled = true;
|
|
173
|
+
observer.disconnect();
|
|
174
|
+
resolve();
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
requestAnimationFrame(poll);
|
|
178
|
+
}
|
|
179
|
+
poll();
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function start() {
|
|
184
|
+
whenStylesReady().then(markReady);
|
|
185
|
+
setTimeout(markReady, FALLBACK_MS);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (document.readyState === "loading") {
|
|
189
|
+
document.addEventListener("DOMContentLoaded", start);
|
|
190
|
+
} else {
|
|
191
|
+
start();
|
|
192
|
+
}
|
|
193
|
+
})();
|
|
194
|
+
</script>`;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function resolvePageReadyConfig(
|
|
198
|
+
input: false | PageReadyConfig | undefined,
|
|
199
|
+
): ResolvedPageReadyConfig | false {
|
|
200
|
+
if (input === false) return false;
|
|
201
|
+
const opts = input ?? {};
|
|
202
|
+
return {
|
|
203
|
+
preloader: normalizePreloaderHtml(opts.preloader ?? DEFAULT_PRELOADER),
|
|
204
|
+
fallbackMs: opts.fallbackMs ?? 5000,
|
|
205
|
+
probe: opts.probe === false ? false : (opts.probe ?? "--pp-bg"),
|
|
206
|
+
stableFrames: opts.stableFrames ?? 4,
|
|
207
|
+
maxFrames: opts.maxFrames ?? 300,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Render-blocking stylesheet link (ensures CSS applies before #app is revealed). */
|
|
212
|
+
export function renderStylesheetLink(href: string, opts: { crossorigin?: boolean } = {}): string {
|
|
213
|
+
const safeHref = escapeHtml(href);
|
|
214
|
+
const cross = opts.crossorigin ? ' crossorigin="anonymous"' : "";
|
|
215
|
+
return `<link rel="stylesheet" href="${safeHref}"${cross}>`;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function renderStylesheetLinks(
|
|
219
|
+
hrefs: string[],
|
|
220
|
+
opts: { crossorigin?: boolean } = {},
|
|
221
|
+
): string {
|
|
222
|
+
return hrefs.map((href) => renderStylesheetLink(href, opts)).join("\n ");
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** @deprecated Use renderStylesheetLink — async CSS caused FOUC when revealing #app. */
|
|
226
|
+
export function renderNonBlockingStylesheetLink(
|
|
227
|
+
href: string,
|
|
228
|
+
opts: { crossorigin?: boolean } = {},
|
|
229
|
+
): string {
|
|
230
|
+
return renderStylesheetLink(href, opts);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function renderNonBlockingStylesheetLinks(
|
|
234
|
+
hrefs: string[],
|
|
235
|
+
opts: { crossorigin?: boolean } = {},
|
|
236
|
+
): string {
|
|
237
|
+
return renderStylesheetLinks(hrefs, opts);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Inject inline preloader shell so first paint shows a spinner until stylesheets are ready. */
|
|
241
|
+
export function injectPageReadyShell(
|
|
242
|
+
html: string,
|
|
243
|
+
pageReady: ResolvedPageReadyConfig | false = resolvePageReadyConfig(undefined),
|
|
244
|
+
): string {
|
|
245
|
+
if (pageReady === false) return html;
|
|
246
|
+
if (html.includes('id="pp-page-ready"')) return html;
|
|
247
|
+
|
|
248
|
+
const bootScript = buildPageReadyBootScript(pageReady);
|
|
249
|
+
|
|
250
|
+
let out = html.replace(/<head>/i, `<head>\n ${PAGE_READY_HEAD}\n`);
|
|
251
|
+
|
|
252
|
+
out = out.replace(/<body([^>]*)>/i, `<body$1>\n ${pageReady.preloader}\n`);
|
|
253
|
+
|
|
254
|
+
return out.replace(/<\/body>/i, ` ${bootScript}\n </body>`);
|
|
255
|
+
}
|
|
@@ -50,6 +50,7 @@ export default defineConfig({
|
|
|
50
50
|
{ text: "Overview", link: "/" },
|
|
51
51
|
{ text: "What is PreactPress?", link: "/guide/what-is-preactpress" },
|
|
52
52
|
{ text: "Getting started", link: "/guide/getting-started" },
|
|
53
|
+
{ text: "Starter templates", link: "/guide/templates" },
|
|
53
54
|
{ text: "First five minutes", link: "/guide/first-five-minutes" },
|
|
54
55
|
],
|
|
55
56
|
},
|
|
@@ -12,6 +12,12 @@ const actions = [
|
|
|
12
12
|
},
|
|
13
13
|
];
|
|
14
14
|
|
|
15
|
+
const workflowLabels = [
|
|
16
|
+
{ text: "Markdown", href: "https://www.markdownguide.org/tools/github-pages/" },
|
|
17
|
+
{ text: "MDX", href: "https://mdxjs.com/" },
|
|
18
|
+
{ text: "Preact", href: "https://preactjs.com/" },
|
|
19
|
+
];
|
|
20
|
+
|
|
15
21
|
export default function MarketingHero() {
|
|
16
22
|
return (
|
|
17
23
|
<section class="pp-mkt-hero" aria-labelledby="preactpress-hero-title">
|
|
@@ -36,9 +42,17 @@ export default function MarketingHero() {
|
|
|
36
42
|
</div>
|
|
37
43
|
</div>
|
|
38
44
|
<div class="pp-mkt-hero-panel" aria-label="PreactPress workflow">
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
45
|
+
{workflowLabels.map((label) => (
|
|
46
|
+
<a
|
|
47
|
+
class="pp-mkt-panel-label"
|
|
48
|
+
href={label.href}
|
|
49
|
+
key={label.text}
|
|
50
|
+
rel="noopener noreferrer"
|
|
51
|
+
target="_blank"
|
|
52
|
+
>
|
|
53
|
+
{label.text}
|
|
54
|
+
</a>
|
|
55
|
+
))}
|
|
42
56
|
<span class="pp-mkt-panel-arrow" aria-hidden="true">
|
|
43
57
|
<svg
|
|
44
58
|
class="pp-mkt-panel-arrow-icon"
|
|
@@ -12,15 +12,20 @@ const references = [
|
|
|
12
12
|
description: "Preact + Tailwind components that can be documented and demonstrated with MDX.",
|
|
13
13
|
href: "https://ui.kamod.ch/",
|
|
14
14
|
},
|
|
15
|
+
{
|
|
16
|
+
name: "Kamod Icons",
|
|
17
|
+
description: "A lightweight, tree-shakeable SVG icon library for Preact apps.",
|
|
18
|
+
href: "https://github.com/kamod-ch/kamod-icons",
|
|
19
|
+
},
|
|
15
20
|
{
|
|
16
21
|
name: "Kamod Hooks",
|
|
17
22
|
description: "A Preact hooks library with documentation built around the same tooling stack.",
|
|
18
23
|
href: "https://github.com/kamod-ch/kamod-hooks",
|
|
19
24
|
},
|
|
20
25
|
{
|
|
21
|
-
name: "
|
|
22
|
-
description: "
|
|
23
|
-
href: "https://
|
|
26
|
+
name: "Syncding",
|
|
27
|
+
description: "Secure file sync and snapshot recovery between your devices and servers.",
|
|
28
|
+
href: "https://www.syncding.com/",
|
|
24
29
|
},
|
|
25
30
|
{
|
|
26
31
|
name: "More Kamod projects",
|
|
@@ -2,41 +2,98 @@
|
|
|
2
2
|
import { h } from "preact";
|
|
3
3
|
import CopyableCommand from "./CopyableCommand.tsx";
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
type TemplateCard = {
|
|
6
|
+
id: "docs" | "hono" | "magazine" | "default";
|
|
7
|
+
name: string;
|
|
8
|
+
useCase: string;
|
|
9
|
+
description: string;
|
|
10
|
+
highlights: string[];
|
|
11
|
+
command: string;
|
|
12
|
+
previewSrc: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const templates: TemplateCard[] = [
|
|
6
16
|
{
|
|
17
|
+
id: "docs",
|
|
7
18
|
name: "Documentation",
|
|
8
|
-
|
|
19
|
+
useCase: "Reference site",
|
|
20
|
+
description:
|
|
21
|
+
"The full reference starter used for this public demo — guides, examples, search, and i18n.",
|
|
22
|
+
highlights: ["Default docs theme", "Full guide + examples", "English / German locales"],
|
|
9
23
|
command: "pnpm dlx @kamod-ch/preactpress init my-docs --template docs",
|
|
24
|
+
previewSrc: "/templates/docs.webp",
|
|
10
25
|
},
|
|
11
26
|
{
|
|
27
|
+
id: "hono",
|
|
12
28
|
name: "Product + Docs",
|
|
13
|
-
|
|
29
|
+
useCase: "Product + docs",
|
|
30
|
+
description:
|
|
31
|
+
"A polished marketing landing page paired with a focused documentation area and custom theme.",
|
|
32
|
+
highlights: ["Custom Preact theme", "Product landing + docs", "Built-in i18n demo"],
|
|
14
33
|
command: "pnpm dlx @kamod-ch/preactpress init my-site --template hono",
|
|
34
|
+
previewSrc: "/templates/hono.webp",
|
|
15
35
|
},
|
|
16
36
|
{
|
|
37
|
+
id: "magazine",
|
|
17
38
|
name: "Magazine",
|
|
18
|
-
|
|
39
|
+
useCase: "Editorial",
|
|
40
|
+
description:
|
|
41
|
+
"A custom editorial theme for articles, tags, and content-heavy sites with teaser grids.",
|
|
42
|
+
highlights: ["Custom magazine theme", "Article teasers + tags", "Content loader home"],
|
|
19
43
|
command: "pnpm dlx @kamod-ch/preactpress init my-mag --template magazine",
|
|
44
|
+
previewSrc: "/templates/magazine.webp",
|
|
20
45
|
},
|
|
21
46
|
{
|
|
47
|
+
id: "default",
|
|
22
48
|
name: "Minimal",
|
|
23
|
-
|
|
24
|
-
|
|
49
|
+
useCase: "Quick start",
|
|
50
|
+
description:
|
|
51
|
+
"The smallest starter: a home page, an about page, and one guide — no custom theme.",
|
|
52
|
+
highlights: ["Default docs theme", "Tiny file tree", "Fastest way to try PreactPress"],
|
|
53
|
+
command: "pnpm dlx @kamod-ch/preactpress init my-site",
|
|
54
|
+
previewSrc: "/templates/default.webp",
|
|
25
55
|
},
|
|
26
56
|
];
|
|
27
57
|
|
|
58
|
+
function assetUrl(path: string): string {
|
|
59
|
+
const base = (import.meta.env.BASE_URL || "/").replace(/\/$/, "");
|
|
60
|
+
const normalized = path.startsWith("/") ? path : `/${path}`;
|
|
61
|
+
return `${base}${normalized}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
28
64
|
export default function TemplateGallery() {
|
|
29
65
|
return (
|
|
30
66
|
<section class="pp-mkt-section" aria-labelledby="templates-title">
|
|
31
67
|
<div class="pp-mkt-section-heading">
|
|
32
68
|
<p class="pp-mkt-eyebrow">Template gallery</p>
|
|
33
69
|
<h2 id="templates-title">Start with the shape of site you need</h2>
|
|
70
|
+
<p>
|
|
71
|
+
<a class="pp-mkt-section-link" href="/guide/templates">
|
|
72
|
+
Compare all templates →
|
|
73
|
+
</a>
|
|
74
|
+
</p>
|
|
34
75
|
</div>
|
|
35
76
|
<div class="pp-mkt-card-grid pp-mkt-card-grid-four">
|
|
36
77
|
{templates.map((template) => (
|
|
37
|
-
<article class="pp-mkt-card pp-mkt-template" key={template.
|
|
38
|
-
<
|
|
39
|
-
|
|
78
|
+
<article class="pp-mkt-card pp-mkt-template" key={template.id}>
|
|
79
|
+
<img
|
|
80
|
+
class="pp-mkt-template-preview"
|
|
81
|
+
src={assetUrl(template.previewSrc)}
|
|
82
|
+
alt={`${template.name} starter preview`}
|
|
83
|
+
loading="lazy"
|
|
84
|
+
width={640}
|
|
85
|
+
height={400}
|
|
86
|
+
/>
|
|
87
|
+
<div class="pp-mkt-template-body">
|
|
88
|
+
<p class="pp-mkt-template-tag">{template.useCase}</p>
|
|
89
|
+
<h3>{template.name}</h3>
|
|
90
|
+
<p>{template.description}</p>
|
|
91
|
+
<ul class="pp-mkt-template-highlights">
|
|
92
|
+
{template.highlights.map((item) => (
|
|
93
|
+
<li key={item}>{item}</li>
|
|
94
|
+
))}
|
|
95
|
+
</ul>
|
|
96
|
+
</div>
|
|
40
97
|
<CopyableCommand command={template.command} />
|
|
41
98
|
</article>
|
|
42
99
|
))}
|
|
@@ -162,6 +162,26 @@
|
|
|
162
162
|
font-weight: 800;
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
a.pp-mkt-panel-label {
|
|
166
|
+
color: inherit;
|
|
167
|
+
text-decoration: none;
|
|
168
|
+
transition:
|
|
169
|
+
border-color 160ms ease,
|
|
170
|
+
color 160ms ease,
|
|
171
|
+
transform 160ms ease;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
a.pp-mkt-panel-label:hover {
|
|
175
|
+
border-color: var(--pp-accent);
|
|
176
|
+
color: var(--pp-accent);
|
|
177
|
+
transform: translateY(-1px);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
a.pp-mkt-panel-label:focus-visible {
|
|
181
|
+
outline: 3px solid var(--pp-focus);
|
|
182
|
+
outline-offset: 3px;
|
|
183
|
+
}
|
|
184
|
+
|
|
165
185
|
.pp-mkt-panel-plus,
|
|
166
186
|
.pp-mkt-panel-arrow {
|
|
167
187
|
color: var(--pp-accent);
|
|
@@ -658,6 +678,11 @@
|
|
|
658
678
|
font-size: 1.04rem;
|
|
659
679
|
}
|
|
660
680
|
|
|
681
|
+
.pp-mkt-section-link {
|
|
682
|
+
color: var(--pp-accent);
|
|
683
|
+
font-weight: 650;
|
|
684
|
+
}
|
|
685
|
+
|
|
661
686
|
.pp-mkt-demo-grid {
|
|
662
687
|
display: grid;
|
|
663
688
|
grid-template-columns: minmax(0, 1fr) minmax(260px, 0.8fr);
|
|
@@ -778,10 +803,75 @@ a.pp-mkt-card:focus-visible {
|
|
|
778
803
|
letter-spacing: 0.08em;
|
|
779
804
|
}
|
|
780
805
|
|
|
806
|
+
.pp-mkt-section-link {
|
|
807
|
+
color: var(--pp-accent);
|
|
808
|
+
font-weight: 650;
|
|
809
|
+
}
|
|
810
|
+
|
|
781
811
|
.pp-mkt-template {
|
|
782
812
|
display: grid;
|
|
783
|
-
|
|
784
|
-
|
|
813
|
+
grid-template-rows: auto 1fr auto;
|
|
814
|
+
align-content: stretch;
|
|
815
|
+
gap: 0;
|
|
816
|
+
min-height: 0;
|
|
817
|
+
padding: 0;
|
|
818
|
+
overflow: hidden;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
.pp-mkt-template-preview {
|
|
822
|
+
display: block;
|
|
823
|
+
width: 100%;
|
|
824
|
+
aspect-ratio: 16 / 10;
|
|
825
|
+
object-fit: cover;
|
|
826
|
+
object-position: top center;
|
|
827
|
+
background: color-mix(in srgb, var(--pp-code-bg) 70%, var(--pp-bg));
|
|
828
|
+
border-bottom: 1px solid var(--pp-border);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
.pp-mkt-template-body {
|
|
832
|
+
padding: 18px 22px 0;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
.pp-mkt-template-tag {
|
|
836
|
+
margin: 0 0 8px;
|
|
837
|
+
color: var(--pp-accent);
|
|
838
|
+
font-size: 0.78rem;
|
|
839
|
+
font-weight: 800;
|
|
840
|
+
letter-spacing: 0.1em;
|
|
841
|
+
text-transform: uppercase;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
.pp-mkt-template-highlights {
|
|
845
|
+
margin: 14px 0 0;
|
|
846
|
+
padding: 0;
|
|
847
|
+
list-style: none;
|
|
848
|
+
color: var(--pp-muted);
|
|
849
|
+
font-size: 0.9rem;
|
|
850
|
+
line-height: 1.45;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
.pp-mkt-template-highlights li {
|
|
854
|
+
position: relative;
|
|
855
|
+
padding-left: 1rem;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
.pp-mkt-template-highlights li + li {
|
|
859
|
+
margin-top: 6px;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
.pp-mkt-template-highlights li::before {
|
|
863
|
+
content: "";
|
|
864
|
+
position: absolute;
|
|
865
|
+
top: 0.55em;
|
|
866
|
+
left: 0;
|
|
867
|
+
width: 0.35rem;
|
|
868
|
+
height: 0.35rem;
|
|
869
|
+
border-radius: 999px;
|
|
870
|
+
background: var(--pp-accent);
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
.pp-mkt-template .pp-mkt-copyable {
|
|
874
|
+
margin: 16px 22px 22px;
|
|
785
875
|
}
|
|
786
876
|
|
|
787
877
|
.pp-mkt-copyable {
|
|
@@ -21,6 +21,7 @@ export default defineConfig(async () => ({
|
|
|
21
21
|
- `transformHead(ctx)` returns additional head tags.
|
|
22
22
|
- `transformPageData(page, ctx)` changes page data before rendering.
|
|
23
23
|
- `transformHtml(html, ctx)` changes the final HTML document.
|
|
24
|
+
- `pageReady` customizes or disables the built-in loading overlay (see [Configuration](/guide/configuration#page-ready-preloader)).
|
|
24
25
|
- `buildEnd(ctx)` runs after production output is complete.
|
|
25
26
|
|
|
26
27
|
Hooks may be asynchronous. Keep output deterministic so incremental builds and CI remain reproducible.
|
|
@@ -16,6 +16,8 @@ Run commands from the site root or pass a root path as the final argument.
|
|
|
16
16
|
| `preactpress build [root]` | Emit static production files |
|
|
17
17
|
| `preactpress preview [root]` | Serve `outDir` locally |
|
|
18
18
|
|
|
19
|
+
See [Starter templates](/guide/templates) for a comparison of the four official init starters.
|
|
20
|
+
|
|
19
21
|
## Recommended workflow
|
|
20
22
|
|
|
21
23
|
```bash
|
|
@@ -34,6 +34,29 @@ export default defineConfig({
|
|
|
34
34
|
| `head` | Global meta, link, and script tuples |
|
|
35
35
|
| `vite` | User Vite configuration merged into the internal config |
|
|
36
36
|
| `build` | Sitemap, robots, and feed output |
|
|
37
|
+
| `pageReady` | Built-in loading overlay while CSS becomes ready; `false` disables |
|
|
38
|
+
|
|
39
|
+
## Page-ready preloader
|
|
40
|
+
|
|
41
|
+
PreactPress injects a small inline preloader into every HTML document so the first paint does not flash unstyled content. Customize or disable it in config instead of editing HTML with `transformHtml`.
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
export default defineConfig({
|
|
45
|
+
// Disable the built-in preloader entirely
|
|
46
|
+
pageReady: false,
|
|
47
|
+
|
|
48
|
+
// Or customize the overlay
|
|
49
|
+
pageReady: {
|
|
50
|
+
preloader: '<img src="/brand-mark.svg" alt="" width="48" height="48" />',
|
|
51
|
+
fallbackMs: 3000,
|
|
52
|
+
probe: "--pp-bg", // CSS variable that must be set before reveal
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`preloader` accepts either a full element with `id="pp-preloader"` or inner HTML only (wrapped automatically). The built-in spinner follows the default theme: it reacts to `data-theme`, the `dark` class, and `prefers-color-scheme`. Custom markup needs its own light/dark styles unless you reuse the `pp-preloader-spinner` class.
|
|
58
|
+
|
|
59
|
+
`probe: false` skips the CSS-variable check and waits for stylesheet links only.
|
|
37
60
|
|
|
38
61
|
## Site metadata
|
|
39
62
|
|
|
@@ -44,6 +44,8 @@ Open **http://localhost:5173** to see the starter site.
|
|
|
44
44
|
> **Note**
|
|
45
45
|
> `preactpress init` copies the built-in starter template and writes `@kamod-ch/preactpress` to `devDependencies`. You do not need to install PreactPress separately before running the initializer.
|
|
46
46
|
|
|
47
|
+
Not sure which starter to use? Compare the four official options on [Starter templates](/guide/templates).
|
|
48
|
+
|
|
47
49
|
## File structure
|
|
48
50
|
|
|
49
51
|
After initialization, the project looks like this:
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Starter templates
|
|
3
|
+
description: Compare the built-in PreactPress init templates and choose the right starting shape.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Starter templates
|
|
7
|
+
|
|
8
|
+
PreactPress ships four official starters. Use `preactpress init` (or `pnpm dlx @kamod-ch/preactpress init`) and optionally `--template <name>` to scaffold one.
|
|
9
|
+
|
|
10
|
+
See the [template gallery on the home page](/#templates-title) for screenshots and copyable commands.
|
|
11
|
+
|
|
12
|
+
## Which template should I use?
|
|
13
|
+
|
|
14
|
+
| Template | CLI name | Theme | Best for | Notable extras |
|
|
15
|
+
| -------------- | ----------------------------- | ---------------------- | ---------------------------------------- | ----------------------------------------------- |
|
|
16
|
+
| Documentation | `docs` | Default docs theme | Full product or library docs (this site) | Guides, examples, search, EN/DE locales |
|
|
17
|
+
| Product + Docs | `hono` | Custom Preact theme | Product marketing page plus a docs area | Landing layout, focused guide subset, i18n demo |
|
|
18
|
+
| Magazine | `magazine` | Custom editorial theme | Articles, tags, content-heavy sites | Teaser grid, tag pages, content loader |
|
|
19
|
+
| Minimal | `default` (omit `--template`) | Default docs theme | Trying PreactPress quickly | Smallest file tree |
|
|
20
|
+
|
|
21
|
+
## Scaffold commands
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Documentation (canonical reference starter)
|
|
25
|
+
pnpm dlx @kamod-ch/preactpress init my-docs --template docs
|
|
26
|
+
|
|
27
|
+
# Product landing + docs
|
|
28
|
+
pnpm dlx @kamod-ch/preactpress init my-site --template hono
|
|
29
|
+
|
|
30
|
+
# Editorial / magazine
|
|
31
|
+
pnpm dlx @kamod-ch/preactpress init my-mag --template magazine
|
|
32
|
+
|
|
33
|
+
# Minimal (default)
|
|
34
|
+
pnpm dlx @kamod-ch/preactpress init my-site
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
After scaffolding:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
cd my-site
|
|
41
|
+
pnpm install
|
|
42
|
+
pnpm run dev
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## How they differ
|
|
46
|
+
|
|
47
|
+
- **Documentation** is the largest starter and the public demo. Prefer it when you want the full guide surface, examples, and default theme features out of the box.
|
|
48
|
+
- **Product + Docs** shows how far a custom theme can go for a marketing site while keeping a documentation section. Use it as a theme reference more than as the canonical API docs source.
|
|
49
|
+
- **Magazine** focuses on editorial patterns: article teasers, tags, and a content-loader driven home page.
|
|
50
|
+
- **Minimal** is the default `init` target. Start here if you want the fewest files and will grow the site yourself.
|
|
51
|
+
|
|
52
|
+
## Related
|
|
53
|
+
|
|
54
|
+
- [Getting started](/guide/getting-started)
|
|
55
|
+
- [CLI and validation](/guide/commands)
|
|
56
|
+
- [Custom themes](/guide/custom-themes)
|
|
57
|
+
- [Custom theme example](/examples/custom-theme)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Generated by scripts/capture-template-previews.mjs
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|