@kamod-ch/preactpress 2.1.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.
Files changed (34) hide show
  1. package/README.md +8 -8
  2. package/dist/node/config-helpers.d.ts +1 -1
  3. package/dist/node/config-helpers.d.ts.map +1 -1
  4. package/dist/node/config.d.ts.map +1 -1
  5. package/dist/node/config.js +2 -0
  6. package/dist/node/config.js.map +1 -1
  7. package/dist/node/html.d.ts.map +1 -1
  8. package/dist/node/html.js +12 -11
  9. package/dist/node/html.js.map +1 -1
  10. package/dist/node/index.d.ts +1 -1
  11. package/dist/node/index.d.ts.map +1 -1
  12. package/dist/node/siteConfig.d.ts +32 -0
  13. package/dist/node/siteConfig.d.ts.map +1 -1
  14. package/dist/shared/pageReady.d.ts +19 -0
  15. package/dist/shared/pageReady.d.ts.map +1 -0
  16. package/dist/shared/pageReady.js +230 -0
  17. package/dist/shared/pageReady.js.map +1 -0
  18. package/package.json +3 -1
  19. package/src/shared/pageReady.ts +255 -0
  20. package/templates/docs/.preactpress/config.ts +1 -0
  21. package/templates/docs/components/MarketingHero.tsx +17 -3
  22. package/templates/docs/components/References.tsx +8 -3
  23. package/templates/docs/components/TemplateGallery.tsx +66 -9
  24. package/templates/docs/components/marketing.css +92 -2
  25. package/templates/docs/guide/advanced.md +1 -0
  26. package/templates/docs/guide/commands.md +2 -0
  27. package/templates/docs/guide/configuration.md +23 -0
  28. package/templates/docs/guide/getting-started.md +2 -0
  29. package/templates/docs/guide/templates.md +57 -0
  30. package/templates/docs/public/templates/.gitkeep +1 -0
  31. package/templates/docs/public/templates/default.webp +0 -0
  32. package/templates/docs/public/templates/docs.webp +0 -0
  33. package/templates/docs/public/templates/hono.webp +0 -0
  34. 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
- <span class="pp-mkt-panel-label">Markdown</span>
40
- <span class="pp-mkt-panel-label">MDX</span>
41
- <span class="pp-mkt-panel-label">Preact</span>
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: "Hono starter template",
22
- description: "A product-and-docs starter shipped with PreactPress.",
23
- href: "https://github.com/kamod-ch/preactpress/tree/main/templates/hono",
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
- const templates = [
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
- description: "The full reference starter used for this public demo.",
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
- description: "A polished product landing page with a complete documentation area.",
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
- description: "A custom editorial theme for articles, tags and content-heavy sites.",
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
- description: "A small starter with a home page, an about page and one guide.",
24
- command: "pnpm dlx @kamod-ch/preactpress init my-docs",
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.name}>
38
- <h3>{template.name}</h3>
39
- <p>{template.description}</p>
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
- align-content: space-between;
784
- min-height: 250px;
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