create-zudo-doc 0.2.14 → 0.2.16
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/dist/cli.d.ts +1 -0
- package/dist/constants.js +7 -0
- package/dist/features/dynamic-page-transition.d.ts +21 -0
- package/dist/features/dynamic-page-transition.js +240 -0
- package/dist/features/index.js +2 -0
- package/dist/scaffold.js +11 -7
- package/dist/settings-gen.js +13 -0
- package/dist/zfb-config-gen.js +8 -0
- package/package.json +1 -1
- package/templates/base/pages/lib/_body-end-islands.tsx +0 -19
- package/templates/base/src/styles/global.css +4 -98
- /package/templates/{base → features/dynamicPageTransition/files}/src/components/client-router-bootstrap.tsx +0 -0
package/dist/cli.d.ts
CHANGED
package/dist/constants.js
CHANGED
|
@@ -199,6 +199,13 @@ export const FEATURES = [
|
|
|
199
199
|
default: true,
|
|
200
200
|
cliFlag: "image-enlarge",
|
|
201
201
|
},
|
|
202
|
+
{
|
|
203
|
+
value: "dynamicPageTransition",
|
|
204
|
+
label: "Dynamic page transition",
|
|
205
|
+
hint: "SPA-style page transition with history handling",
|
|
206
|
+
default: true,
|
|
207
|
+
cliFlag: "dynamic-page-transition",
|
|
208
|
+
},
|
|
202
209
|
{
|
|
203
210
|
value: "footerCopyright",
|
|
204
211
|
label: "Footer copyright",
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { FeatureModule } from "../compose.js";
|
|
2
|
+
/**
|
|
3
|
+
* Dynamic page transition feature.
|
|
4
|
+
*
|
|
5
|
+
* When enabled, wires the SPA client-side router (ClientRouterBootstrap
|
|
6
|
+
* island) and the page-loading overlay (PageLoadingOverlay, pure SSR) into
|
|
7
|
+
* body-end-islands, and injects the View-Transitions CSS + page-loading
|
|
8
|
+
* overlay CSS into global.css.
|
|
9
|
+
*
|
|
10
|
+
* Bug fix (#2265): the page-loading overlay CSS (.page-loading-overlay /
|
|
11
|
+
* .page-loading-spinner / @keyframes page-loading-spin / [data-zd-nav-pending])
|
|
12
|
+
* was never reaching scaffolded projects because it lived unconditionally in
|
|
13
|
+
* the host global.css but had no corresponding injection for the template.
|
|
14
|
+
* Gating this feature makes both the JS island and the CSS land together.
|
|
15
|
+
*
|
|
16
|
+
* The ClientRouterBootstrap component lives in
|
|
17
|
+
* templates/features/dynamicPageTransition/files/src/components/ and is
|
|
18
|
+
* byte-identical to the host src/components/client-router-bootstrap.tsx.
|
|
19
|
+
* The feature-template drift checker enforces this parity automatically.
|
|
20
|
+
*/
|
|
21
|
+
export declare const dynamicPageTransitionFeature: FeatureModule;
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dynamic page transition feature.
|
|
3
|
+
*
|
|
4
|
+
* When enabled, wires the SPA client-side router (ClientRouterBootstrap
|
|
5
|
+
* island) and the page-loading overlay (PageLoadingOverlay, pure SSR) into
|
|
6
|
+
* body-end-islands, and injects the View-Transitions CSS + page-loading
|
|
7
|
+
* overlay CSS into global.css.
|
|
8
|
+
*
|
|
9
|
+
* Bug fix (#2265): the page-loading overlay CSS (.page-loading-overlay /
|
|
10
|
+
* .page-loading-spinner / @keyframes page-loading-spin / [data-zd-nav-pending])
|
|
11
|
+
* was never reaching scaffolded projects because it lived unconditionally in
|
|
12
|
+
* the host global.css but had no corresponding injection for the template.
|
|
13
|
+
* Gating this feature makes both the JS island and the CSS land together.
|
|
14
|
+
*
|
|
15
|
+
* The ClientRouterBootstrap component lives in
|
|
16
|
+
* templates/features/dynamicPageTransition/files/src/components/ and is
|
|
17
|
+
* byte-identical to the host src/components/client-router-bootstrap.tsx.
|
|
18
|
+
* The feature-template drift checker enforces this parity automatically.
|
|
19
|
+
*/
|
|
20
|
+
export const dynamicPageTransitionFeature = () => ({
|
|
21
|
+
name: "dynamicPageTransition",
|
|
22
|
+
injections: [
|
|
23
|
+
// 1. Import ClientRouterBootstrap and PageLoadingOverlay.
|
|
24
|
+
// Inserted AFTER the `// @slot:body-end-islands:imports` anchor.
|
|
25
|
+
{
|
|
26
|
+
file: "pages/lib/_body-end-islands.tsx",
|
|
27
|
+
anchor: "// @slot:body-end-islands:imports",
|
|
28
|
+
position: "after",
|
|
29
|
+
content: `import ClientRouterBootstrap from "@/components/client-router-bootstrap";
|
|
30
|
+
import { PageLoadingOverlay } from "@takazudo/zudo-doc/page-loading";`,
|
|
31
|
+
},
|
|
32
|
+
// 2. Stable island marker name for ClientRouterBootstrap.
|
|
33
|
+
// Inserted AFTER the `// @slot:body-end-islands:display-names` anchor.
|
|
34
|
+
{
|
|
35
|
+
file: "pages/lib/_body-end-islands.tsx",
|
|
36
|
+
anchor: "// @slot:body-end-islands:display-names",
|
|
37
|
+
position: "after",
|
|
38
|
+
content: `(ClientRouterBootstrap as { displayName?: string }).displayName =
|
|
39
|
+
"ClientRouterBootstrap";`,
|
|
40
|
+
},
|
|
41
|
+
// 3. Gated island mount + PageLoadingOverlay.
|
|
42
|
+
// Inserted AFTER the `{/* @slot:body-end-islands:extra-islands */}` anchor.
|
|
43
|
+
// Gated on `settings.dynamicPageTransition` — mirrors the designTokenPanel
|
|
44
|
+
// and aiAssistant gating patterns in the same file.
|
|
45
|
+
// ClientRouterBootstrap: hydrates on "load" so the SPA-router click
|
|
46
|
+
// intercept is registered as soon as the islands runtime mounts the marker
|
|
47
|
+
// (zudolab/zudo-doc#1524 W7A fix). PageLoadingOverlay: pure SSR — the
|
|
48
|
+
// component emits its overlay div and the inline script that wires
|
|
49
|
+
// zfb:before-preparation / zfb:after-swap listeners at runtime.
|
|
50
|
+
{
|
|
51
|
+
file: "pages/lib/_body-end-islands.tsx",
|
|
52
|
+
anchor: "{/* @slot:body-end-islands:extra-islands */}",
|
|
53
|
+
position: "after",
|
|
54
|
+
content: ` {settings.dynamicPageTransition ? (
|
|
55
|
+
<>
|
|
56
|
+
{/* Pure SSR — no Island wrap. The component emits its overlay div,
|
|
57
|
+
inline styles, and a small inline script that self-wires
|
|
58
|
+
zfb:before-preparation / zfb:after-swap listeners at runtime. */}
|
|
59
|
+
<PageLoadingOverlay />
|
|
60
|
+
{Island({
|
|
61
|
+
when: "load",
|
|
62
|
+
children: <ClientRouterBootstrap />,
|
|
63
|
+
}) as unknown as VNode}
|
|
64
|
+
</>
|
|
65
|
+
) : null}`,
|
|
66
|
+
},
|
|
67
|
+
// 4. View-Transitions CSS (chrome extraction + cross-fade + reduced motion).
|
|
68
|
+
// Injected AFTER `/* @slot:global-css:feature-styles */` in global.css.
|
|
69
|
+
// These rules are required for SPA page transitions via ClientRouter.
|
|
70
|
+
{
|
|
71
|
+
file: "src/styles/global.css",
|
|
72
|
+
anchor: "/* @slot:global-css:feature-styles */",
|
|
73
|
+
position: "after",
|
|
74
|
+
content: `/* Root cross-fade for same-document View Transitions (Strategy B).
|
|
75
|
+
*
|
|
76
|
+
* @view-transition { navigation: auto; } (Strategy A, cross-document) is
|
|
77
|
+
* deleted — Strategy B uses <ClientRouter /> which drives same-document
|
|
78
|
+
* transitions via document.startViewTransition on each navigation.
|
|
79
|
+
*
|
|
80
|
+
* Chrome extraction via view-transition-name (Strategy B+, zudolab/zudo-doc#1558):
|
|
81
|
+
* <header>, <aside id="desktop-sidebar">, <footer>, and the desktop-sidebar-toggle
|
|
82
|
+
* button each carry a data-zfb-transition-persist attribute whose value is keyed
|
|
83
|
+
* by locale and nav-section. The four attribute selectors below assign a stable
|
|
84
|
+
* view-transition-name based on the attribute value prefix, extracting those
|
|
85
|
+
* elements from the root snapshot into their own named layers:
|
|
86
|
+
* - header-{lang} → zfb-header
|
|
87
|
+
* - sidebar-{locale}-… → zfb-sidebar
|
|
88
|
+
* - footer-{lang} → zfb-footer
|
|
89
|
+
* - desktop-sidebar-toggle → zfb-sidebar-toggle
|
|
90
|
+
*
|
|
91
|
+
* Once extracted, the root cross-fade animates only non-chrome content (main,
|
|
92
|
+
* article, TOC, etc.). The twelve ::view-transition-{old,new,group}(<name>)
|
|
93
|
+
* rules disable animation for all four chrome layers. The group pseudo must be
|
|
94
|
+
* neutralised too — even when old/new are static the group container can still
|
|
95
|
+
* produce a geometry-morph animation when snapshot size/position differs.
|
|
96
|
+
*
|
|
97
|
+
* Entry/exit cross-fade (zudolab/zudo-doc#2072): animation: none is only
|
|
98
|
+
* correct when the chrome element exists on BOTH pages of a navigation.
|
|
99
|
+
* When it exists on one side only (docs page with sidebar → top page
|
|
100
|
+
* without), the named group holds a single snapshot — frozen at full
|
|
101
|
+
* opacity for the whole transition, then dropped abruptly at finish. The
|
|
102
|
+
* :only-child rules below detect that one-sided case and cross-fade the
|
|
103
|
+
* lone snapshot in sync with the root content fade (spec-standard
|
|
104
|
+
* entry/exit pattern; :only-child outranks the animation: none rules via
|
|
105
|
+
* the extra pseudo-class specificity). */
|
|
106
|
+
|
|
107
|
+
/* Chrome extraction — assign view-transition-name from data-zfb-transition-persist */
|
|
108
|
+
[data-zfb-transition-persist^="header-"] { view-transition-name: zfb-header; }
|
|
109
|
+
[data-zfb-transition-persist^="sidebar-"] { view-transition-name: zfb-sidebar; }
|
|
110
|
+
[data-zfb-transition-persist^="footer-"] { view-transition-name: zfb-footer; }
|
|
111
|
+
[data-zfb-transition-persist="desktop-sidebar-toggle"] { view-transition-name: zfb-sidebar-toggle; }
|
|
112
|
+
|
|
113
|
+
/* Disable animation for all four chrome layers (old, new, and group) */
|
|
114
|
+
::view-transition-old(zfb-header),
|
|
115
|
+
::view-transition-new(zfb-header),
|
|
116
|
+
::view-transition-group(zfb-header),
|
|
117
|
+
::view-transition-old(zfb-sidebar),
|
|
118
|
+
::view-transition-new(zfb-sidebar),
|
|
119
|
+
::view-transition-group(zfb-sidebar),
|
|
120
|
+
::view-transition-old(zfb-footer),
|
|
121
|
+
::view-transition-new(zfb-footer),
|
|
122
|
+
::view-transition-group(zfb-footer),
|
|
123
|
+
::view-transition-old(zfb-sidebar-toggle),
|
|
124
|
+
::view-transition-new(zfb-sidebar-toggle),
|
|
125
|
+
::view-transition-group(zfb-sidebar-toggle) { animation: none; }
|
|
126
|
+
|
|
127
|
+
/* Entry/exit: chrome element present on one side only (#2072).
|
|
128
|
+
* Exit — lone old snapshot fades out with the content cross-fade. */
|
|
129
|
+
::view-transition-old(zfb-header):only-child,
|
|
130
|
+
::view-transition-old(zfb-sidebar):only-child,
|
|
131
|
+
::view-transition-old(zfb-footer):only-child,
|
|
132
|
+
::view-transition-old(zfb-sidebar-toggle):only-child {
|
|
133
|
+
animation: 150ms ease-in both contentFadeOut;
|
|
134
|
+
}
|
|
135
|
+
/* Entry — lone new snapshot fades in with the content cross-fade. */
|
|
136
|
+
::view-transition-new(zfb-header):only-child,
|
|
137
|
+
::view-transition-new(zfb-sidebar):only-child,
|
|
138
|
+
::view-transition-new(zfb-footer):only-child,
|
|
139
|
+
::view-transition-new(zfb-sidebar-toggle):only-child {
|
|
140
|
+
animation: 300ms ease-out both contentFadeIn;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/* Root cross-fade rules — animate only the non-chrome snapshot */
|
|
144
|
+
::view-transition-old(root) {
|
|
145
|
+
animation: 150ms ease-in both contentFadeOut;
|
|
146
|
+
}
|
|
147
|
+
::view-transition-new(root) {
|
|
148
|
+
animation: 300ms ease-out both contentFadeIn;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/* Reduced motion: collapse all view-transition animation to an instant
|
|
152
|
+
* swap (zudolab/zudo-doc#2086). With every snapshot animation at none the
|
|
153
|
+
* transition settles immediately — no cross-fade, no translateY slide.
|
|
154
|
+
* Covers the root cross-fade and the #2072 :only-child entry/exit rules;
|
|
155
|
+
* the both-sides chrome layers are already animation: none above. Equal
|
|
156
|
+
* specificity per selector, but later in source order, so these win.
|
|
157
|
+
* ::view-transition-group(root) must be neutralized too (zudolab/zzmod#845):
|
|
158
|
+
* the UA animates the root group for its full duration even when the old/new
|
|
159
|
+
* root snapshots are at animation: none, leaving a brief frozen,
|
|
160
|
+
* non-interactive frame. The chrome groups are already animation: none in the
|
|
161
|
+
* unconditional chrome block above, so only the root group is added here. */
|
|
162
|
+
@media (prefers-reduced-motion: reduce) {
|
|
163
|
+
::view-transition-old(root),
|
|
164
|
+
::view-transition-new(root),
|
|
165
|
+
::view-transition-group(root),
|
|
166
|
+
::view-transition-old(zfb-header):only-child,
|
|
167
|
+
::view-transition-old(zfb-sidebar):only-child,
|
|
168
|
+
::view-transition-old(zfb-footer):only-child,
|
|
169
|
+
::view-transition-old(zfb-sidebar-toggle):only-child,
|
|
170
|
+
::view-transition-new(zfb-header):only-child,
|
|
171
|
+
::view-transition-new(zfb-sidebar):only-child,
|
|
172
|
+
::view-transition-new(zfb-footer):only-child,
|
|
173
|
+
::view-transition-new(zfb-sidebar-toggle):only-child {
|
|
174
|
+
animation: none;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/* Page-loading overlay CSS — moved out of PageLoadingOverlay's inline
|
|
179
|
+
* <style> block to avoid the <style>-inside-<body> HTML5 content-model
|
|
180
|
+
* violation flagged by html-validate (same pattern as version-switcher
|
|
181
|
+
* fix in zudolab/zudo-doc#1505; see W2A confirm zudolab/zudo-doc#1543). */
|
|
182
|
+
.page-loading-overlay {
|
|
183
|
+
position: fixed;
|
|
184
|
+
inset: 0;
|
|
185
|
+
/* Full-screen blocking load overlay — modal tier (was a raw z-index:9999).
|
|
186
|
+
It sits below the transient \`drag\` tier (sidebar-resizer ghost), which is
|
|
187
|
+
fine: a full page-load overlay and an active pointer-drag never coexist. */
|
|
188
|
+
z-index: var(--z-index-modal);
|
|
189
|
+
display: flex;
|
|
190
|
+
align-items: center;
|
|
191
|
+
justify-content: center;
|
|
192
|
+
background: color-mix(in oklch, var(--color-overlay) 60%, transparent);
|
|
193
|
+
opacity: 0;
|
|
194
|
+
pointer-events: none;
|
|
195
|
+
transition: opacity 150ms ease-out;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.page-loading-overlay[data-visible] {
|
|
199
|
+
opacity: 1;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
a[data-zd-nav-pending],
|
|
203
|
+
button[data-zd-nav-pending] {
|
|
204
|
+
color: var(--color-accent);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.page-loading-spinner {
|
|
208
|
+
width: 48px;
|
|
209
|
+
height: 48px;
|
|
210
|
+
border: 5px solid var(--color-fg, #fff);
|
|
211
|
+
border-bottom-color: transparent;
|
|
212
|
+
border-radius: 50%;
|
|
213
|
+
display: inline-block;
|
|
214
|
+
box-sizing: border-box;
|
|
215
|
+
animation: page-loading-spin 1s linear infinite;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
@media (min-width: 1024px) {
|
|
219
|
+
.page-loading-spinner {
|
|
220
|
+
width: 64px;
|
|
221
|
+
height: 64px;
|
|
222
|
+
border-width: 6px;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
@keyframes page-loading-spin {
|
|
227
|
+
0% { transform: rotate(0deg); }
|
|
228
|
+
100% { transform: rotate(360deg); }
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
@media (prefers-reduced-motion: reduce) {
|
|
232
|
+
.page-loading-spinner {
|
|
233
|
+
animation: none;
|
|
234
|
+
border-bottom-color: var(--color-fg, #fff);
|
|
235
|
+
opacity: 0.5;
|
|
236
|
+
}
|
|
237
|
+
}`,
|
|
238
|
+
},
|
|
239
|
+
],
|
|
240
|
+
});
|
package/dist/features/index.js
CHANGED
|
@@ -19,6 +19,7 @@ import { versioningFeature } from "./versioning.js";
|
|
|
19
19
|
import { tauriFeature } from "./tauri.js";
|
|
20
20
|
import { tauriDevFeature } from "./tauri-dev.js";
|
|
21
21
|
import { imageEnlargeFeature } from "./image-enlarge.js";
|
|
22
|
+
import { dynamicPageTransitionFeature } from "./dynamic-page-transition.js";
|
|
22
23
|
import { tagGovernanceFeature } from "./tag-governance.js";
|
|
23
24
|
import { footerTaglistFeature } from "./footer-taglist.js";
|
|
24
25
|
import { bodyFootUtilFeature } from "./body-foot-util.js";
|
|
@@ -43,6 +44,7 @@ export const featureModules = {
|
|
|
43
44
|
tauri: tauriFeature,
|
|
44
45
|
tauriDev: tauriDevFeature,
|
|
45
46
|
imageEnlarge: imageEnlargeFeature,
|
|
47
|
+
dynamicPageTransition: dynamicPageTransitionFeature,
|
|
46
48
|
// skillSymlinker — handled in scaffold.ts
|
|
47
49
|
// claudeSkills — handled in scaffold.ts (copies zudo-doc-* skills from monorepo)
|
|
48
50
|
tagGovernance: tagGovernanceFeature,
|
package/dist/scaffold.js
CHANGED
|
@@ -434,20 +434,24 @@ function generatePackageJson(choices) {
|
|
|
434
434
|
// surface — VNode/VNodeArray/VNodeObject are now exported from
|
|
435
435
|
// "@takazudo/zfb" (#972) — plus removal of the no-op linkValidation.allowExternal
|
|
436
436
|
// knob (#925); both are non-breaking for a fresh scaffold. A fresh scaffold
|
|
437
|
-
// sets
|
|
437
|
+
// sets codeHighlight.themeLight/themeDark for dual-theme syntect output. No
|
|
438
438
|
// consumer-facing / CLI breaking change. next.52: adds the
|
|
439
439
|
// `ClientRouter({ preserveHtmlAttrs })` option (zfb#1104) — consumers can
|
|
440
440
|
// declare runtime `<html>` attribute names to preserve across SPA swaps so
|
|
441
441
|
// e.g. `data-sidebar-hidden` / `data-theme` survive (zudolab/zudo-doc#2200).
|
|
442
|
-
// Additive, non-breaking for a fresh scaffold. next.53
|
|
442
|
+
// Additive, non-breaking for a fresh scaffold. next.53:
|
|
443
443
|
// zfb-content GFM-autolink fix — terminate the autolink path at CJK
|
|
444
444
|
// boundaries (zfb#1105). Content-rendering bug fix, additive; relevant for
|
|
445
445
|
// CJK (e.g. Japanese) docs. No consumer-facing / CLI breaking change.
|
|
446
|
-
|
|
447
|
-
|
|
446
|
+
// next.54 (current pin): bug-fix + perf release — cross-OS CSS hash
|
|
447
|
+
// stability, multi-valued response headers (e.g. multiple Set-Cookie),
|
|
448
|
+
// supplementary-plane CJK reading-time, plus CLI/server/runtime hardening
|
|
449
|
+
// and render perf passes. No consumer-facing / CLI breaking change.
|
|
450
|
+
"@takazudo/zfb": "0.1.0-next.54",
|
|
451
|
+
"@takazudo/zfb-runtime": "0.1.0-next.54",
|
|
448
452
|
// zfb-adapter-cloudflare — required for any route with `prerender = false`.
|
|
449
453
|
// Pinned in lockstep with @takazudo/zfb.
|
|
450
|
-
"@takazudo/zfb-adapter-cloudflare": "0.1.0-next.
|
|
454
|
+
"@takazudo/zfb-adapter-cloudflare": "0.1.0-next.54",
|
|
451
455
|
// @takazudo/zudo-doc — published from this monorepo via
|
|
452
456
|
// .github/workflows/publish-zudo-doc.yml. The pin here is bumped in
|
|
453
457
|
// lockstep by scripts/release-create-zudo-doc.sh whenever zudo-doc's
|
|
@@ -459,7 +463,7 @@ function generatePackageJson(choices) {
|
|
|
459
463
|
// ties this pin to packages/zudo-doc's version, so the lockstep release
|
|
460
464
|
// bumps both together; do not cut a create-zudo-doc release until the
|
|
461
465
|
// matching @takazudo/zudo-doc version (with content.css) is on npm.
|
|
462
|
-
"@takazudo/zudo-doc": "^0.2.
|
|
466
|
+
"@takazudo/zudo-doc": "^0.2.16",
|
|
463
467
|
// zod — used by the generated zfb.config.ts. zfb-config-gen emits
|
|
464
468
|
// `import { z } from "zod"` for the content-collection schema +
|
|
465
469
|
// `z.toJSONSchema(...)` conversion. Without this dep, the consumer
|
|
@@ -513,7 +517,7 @@ function generatePackageJson(choices) {
|
|
|
513
517
|
// @takazudo/zudo-doc/integrations/doc-history which in turn imports
|
|
514
518
|
// @takazudo/zudo-doc-history-server/git-history. Without this dep the
|
|
515
519
|
// plugin host fails at init with ERR_MODULE_NOT_FOUND — W8A (#1739).
|
|
516
|
-
deps["@takazudo/zudo-doc-history-server"] = "^0.2.
|
|
520
|
+
deps["@takazudo/zudo-doc-history-server"] = "^0.2.16";
|
|
517
521
|
// W7A (#1736): doc-history-plugin.mjs spawns `tsx -e <inline-script>` to
|
|
518
522
|
// run the v2 runtime in a TS-aware Node subprocess; without tsx the
|
|
519
523
|
// plugin's preBuild step exits with ENOENT before zfb finishes config
|
package/dist/settings-gen.js
CHANGED
|
@@ -192,6 +192,12 @@ export function generateSettingsFile(choices) {
|
|
|
192
192
|
else {
|
|
193
193
|
lines.push(` imageEnlarge: false as boolean,`);
|
|
194
194
|
}
|
|
195
|
+
if (choices.features.includes("dynamicPageTransition")) {
|
|
196
|
+
lines.push(` dynamicPageTransition: true as boolean,`);
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
lines.push(` dynamicPageTransition: false as boolean,`);
|
|
200
|
+
}
|
|
195
201
|
lines.push(` htmlPreview: undefined as HtmlPreviewConfig | undefined,`);
|
|
196
202
|
if (choices.features.includes("versioning")) {
|
|
197
203
|
lines.push(` versions: [] satisfies VersionConfig[] as VersionConfig[] | false,`);
|
|
@@ -251,6 +257,13 @@ export function generateSettingsFile(choices) {
|
|
|
251
257
|
}
|
|
252
258
|
lines.push(` headerNav: [`);
|
|
253
259
|
lines.push(` { label: "Getting Started", path: "/docs/getting-started", categoryMatch: "getting-started" },`);
|
|
260
|
+
// The "claude" categoryMatch is load-bearing beyond the header link: getCategoryOrder()
|
|
261
|
+
// derives the satellite-grouping prefixes from headerNav, so without this entry
|
|
262
|
+
// groupSatelliteNodes() never nests claude-md/claude-skills/... under the "claude"
|
|
263
|
+
// overview node and they spread out as separate top-level cards on the index sitemap.
|
|
264
|
+
if (choices.features.includes("claudeResources")) {
|
|
265
|
+
lines.push(` { label: "Claude", path: "/docs/claude", categoryMatch: "claude" },`);
|
|
266
|
+
}
|
|
254
267
|
if (choices.features.includes("changelog")) {
|
|
255
268
|
lines.push(` { label: "Changelog", path: "/docs/changelog", categoryMatch: "changelog" },`);
|
|
256
269
|
}
|
package/dist/zfb-config-gen.js
CHANGED
|
@@ -190,6 +190,14 @@ export function generateZfbConfig(choices) {
|
|
|
190
190
|
lines.push(` },`);
|
|
191
191
|
lines.push(` base: settings.base,`);
|
|
192
192
|
lines.push(` trailingSlash: settings.trailingSlash,`);
|
|
193
|
+
// codeHighlight — dual-theme syntect output so CSS can resolve tokens via
|
|
194
|
+
// light-dark(var(--shiki-light), var(--shiki-dark)) and code blocks follow
|
|
195
|
+
// the active light/dark toggle with no client JS. Theme names are syntect
|
|
196
|
+
// built-ins shipped with zfb (not Shiki names). Matches the host config.
|
|
197
|
+
lines.push(` codeHighlight: {`);
|
|
198
|
+
lines.push(` themeLight: "base16-ocean.light",`);
|
|
199
|
+
lines.push(` themeDark: "base16-ocean.dark",`);
|
|
200
|
+
lines.push(` },`);
|
|
193
201
|
// markdown.features block — mirrors the zfb next.13 opt-in model.
|
|
194
202
|
//
|
|
195
203
|
// Value-shape rule (empirically verified against the next.13 Rust loader):
|
package/package.json
CHANGED
|
@@ -33,10 +33,8 @@ import { Island } from "@takazudo/zfb";
|
|
|
33
33
|
import { settings } from "@/config/settings";
|
|
34
34
|
|
|
35
35
|
import AiChatModal from "@/components/ai-chat-modal";
|
|
36
|
-
import ClientRouterBootstrap from "@/components/client-router-bootstrap";
|
|
37
36
|
import ImageEnlarge, { ImageEnlargeSsrFallback } from "@/components/image-enlarge";
|
|
38
37
|
import MermaidEnlarge, { MermaidEnlargeSsrFallback } from "@/components/mermaid-enlarge";
|
|
39
|
-
import { PageLoadingOverlay } from "@takazudo/zudo-doc/page-loading";
|
|
40
38
|
// @slot:body-end-islands:imports
|
|
41
39
|
|
|
42
40
|
// Set explicit `displayName` on each default-exported island so zfb's
|
|
@@ -47,8 +45,6 @@ import { PageLoadingOverlay } from "@takazudo/zudo-doc/page-loading";
|
|
|
47
45
|
// function names by default, but the explicit assignment is a
|
|
48
46
|
// belt-and-braces guard for production minification regressions.
|
|
49
47
|
(AiChatModal as { displayName?: string }).displayName = "AiChatModal";
|
|
50
|
-
(ClientRouterBootstrap as { displayName?: string }).displayName =
|
|
51
|
-
"ClientRouterBootstrap";
|
|
52
48
|
(ImageEnlarge as { displayName?: string }).displayName = "ImageEnlarge";
|
|
53
49
|
(MermaidEnlarge as { displayName?: string }).displayName = "MermaidEnlarge";
|
|
54
50
|
// @slot:body-end-islands:display-names
|
|
@@ -104,16 +100,6 @@ export function BodyEndIslands({
|
|
|
104
100
|
basePath,
|
|
105
101
|
aiChatBodyLabel = DEFAULT_AI_CHAT_BODY_LABEL,
|
|
106
102
|
}: BodyEndIslandsProps): JSX.Element {
|
|
107
|
-
// Hydrates first (when="load") so the SPA-router click intercept is
|
|
108
|
-
// registered as soon as the islands runtime mounts the marker. The
|
|
109
|
-
// component renders nothing visually — the island bundle's top-level
|
|
110
|
-
// `import "@takazudo/zfb-runtime/client-router"` is what actually
|
|
111
|
-
// wires up the router (zudolab/zudo-doc#1524 W7A fix).
|
|
112
|
-
const clientRouterBootstrap = Island({
|
|
113
|
-
when: "load",
|
|
114
|
-
children: <ClientRouterBootstrap />,
|
|
115
|
-
}) as unknown as VNode;
|
|
116
|
-
|
|
117
103
|
// Gated on `settings.aiAssistant` (zudolab/zudo-doc#2058): when the AI
|
|
118
104
|
// assistant feature is off, neither the AiChatModal island marker nor the
|
|
119
105
|
// sr-only "AI Assistant" landmark heading should reach the SSG output —
|
|
@@ -177,11 +163,6 @@ export function BodyEndIslands({
|
|
|
177
163
|
|
|
178
164
|
return (
|
|
179
165
|
<>
|
|
180
|
-
{/* Pure SSR — no Island wrap. The component emits its overlay div,
|
|
181
|
-
inline styles, and a small inline script that self-wires
|
|
182
|
-
zfb:before-preparation / zfb:after-swap listeners at runtime. */}
|
|
183
|
-
<PageLoadingOverlay />
|
|
184
|
-
{clientRouterBootstrap}
|
|
185
166
|
{aiAssistant}
|
|
186
167
|
{imageEnlarge}
|
|
187
168
|
{mermaidEnlarge}
|
|
@@ -296,6 +296,10 @@ body {
|
|
|
296
296
|
button {
|
|
297
297
|
cursor: pointer;
|
|
298
298
|
}
|
|
299
|
+
:focus-visible {
|
|
300
|
+
outline: 2px solid var(--color-accent);
|
|
301
|
+
outline-offset: 2px;
|
|
302
|
+
}
|
|
299
303
|
}
|
|
300
304
|
|
|
301
305
|
/* ========================================
|
|
@@ -558,104 +562,6 @@ pre[class^="syntect-"] .line .highlighted-word {
|
|
|
558
562
|
}
|
|
559
563
|
}
|
|
560
564
|
|
|
561
|
-
/* Root cross-fade for same-document View Transitions (Strategy B).
|
|
562
|
-
*
|
|
563
|
-
* @view-transition { navigation: auto; } (Strategy A, cross-document) is
|
|
564
|
-
* deleted — Strategy B uses <ClientRouter /> which drives same-document
|
|
565
|
-
* transitions via document.startViewTransition on each navigation.
|
|
566
|
-
*
|
|
567
|
-
* Chrome extraction via view-transition-name (Strategy B+, zudolab/zudo-doc#1558):
|
|
568
|
-
* <header>, <aside id="desktop-sidebar">, <footer>, and the desktop-sidebar-toggle
|
|
569
|
-
* button each carry a data-zfb-transition-persist attribute whose value is keyed
|
|
570
|
-
* by locale and nav-section. The four attribute selectors below assign a stable
|
|
571
|
-
* view-transition-name based on the attribute value prefix, extracting those
|
|
572
|
-
* elements from the root snapshot into their own named layers:
|
|
573
|
-
* - header-{lang} → zfb-header
|
|
574
|
-
* - sidebar-{locale}-… → zfb-sidebar
|
|
575
|
-
* - footer-{lang} → zfb-footer
|
|
576
|
-
* - desktop-sidebar-toggle → zfb-sidebar-toggle
|
|
577
|
-
*
|
|
578
|
-
* Once extracted, the root cross-fade animates only non-chrome content (main,
|
|
579
|
-
* article, TOC, etc.). The twelve ::view-transition-{old,new,group}(<name>)
|
|
580
|
-
* rules disable animation for all four chrome layers. The group pseudo must be
|
|
581
|
-
* neutralised too — even when old/new are static the group container can still
|
|
582
|
-
* produce a geometry-morph animation when snapshot size/position differs.
|
|
583
|
-
*
|
|
584
|
-
* Entry/exit cross-fade (zudolab/zudo-doc#2072): animation: none is only
|
|
585
|
-
* correct when the chrome element exists on BOTH pages of a navigation.
|
|
586
|
-
* When it exists on one side only (docs page with sidebar → top page
|
|
587
|
-
* without), the named group holds a single snapshot — frozen at full
|
|
588
|
-
* opacity for the whole transition, then dropped abruptly at finish. The
|
|
589
|
-
* :only-child rules below detect that one-sided case and cross-fade the
|
|
590
|
-
* lone snapshot in sync with the root content fade (spec-standard
|
|
591
|
-
* entry/exit pattern; :only-child outranks the animation: none rules via
|
|
592
|
-
* the extra pseudo-class specificity). */
|
|
593
|
-
|
|
594
|
-
/* Chrome extraction — assign view-transition-name from data-zfb-transition-persist */
|
|
595
|
-
[data-zfb-transition-persist^="header-"] { view-transition-name: zfb-header; }
|
|
596
|
-
[data-zfb-transition-persist^="sidebar-"] { view-transition-name: zfb-sidebar; }
|
|
597
|
-
[data-zfb-transition-persist^="footer-"] { view-transition-name: zfb-footer; }
|
|
598
|
-
[data-zfb-transition-persist="desktop-sidebar-toggle"] { view-transition-name: zfb-sidebar-toggle; }
|
|
599
|
-
|
|
600
|
-
/* Disable animation for all four chrome layers (old, new, and group) */
|
|
601
|
-
::view-transition-old(zfb-header),
|
|
602
|
-
::view-transition-new(zfb-header),
|
|
603
|
-
::view-transition-group(zfb-header),
|
|
604
|
-
::view-transition-old(zfb-sidebar),
|
|
605
|
-
::view-transition-new(zfb-sidebar),
|
|
606
|
-
::view-transition-group(zfb-sidebar),
|
|
607
|
-
::view-transition-old(zfb-footer),
|
|
608
|
-
::view-transition-new(zfb-footer),
|
|
609
|
-
::view-transition-group(zfb-footer),
|
|
610
|
-
::view-transition-old(zfb-sidebar-toggle),
|
|
611
|
-
::view-transition-new(zfb-sidebar-toggle),
|
|
612
|
-
::view-transition-group(zfb-sidebar-toggle) { animation: none; }
|
|
613
|
-
|
|
614
|
-
/* Entry/exit: chrome element present on one side only (#2072).
|
|
615
|
-
* Exit — lone old snapshot fades out with the content cross-fade. */
|
|
616
|
-
::view-transition-old(zfb-header):only-child,
|
|
617
|
-
::view-transition-old(zfb-sidebar):only-child,
|
|
618
|
-
::view-transition-old(zfb-footer):only-child,
|
|
619
|
-
::view-transition-old(zfb-sidebar-toggle):only-child {
|
|
620
|
-
animation: 150ms ease-in both contentFadeOut;
|
|
621
|
-
}
|
|
622
|
-
/* Entry — lone new snapshot fades in with the content cross-fade. */
|
|
623
|
-
::view-transition-new(zfb-header):only-child,
|
|
624
|
-
::view-transition-new(zfb-sidebar):only-child,
|
|
625
|
-
::view-transition-new(zfb-footer):only-child,
|
|
626
|
-
::view-transition-new(zfb-sidebar-toggle):only-child {
|
|
627
|
-
animation: 300ms ease-out both contentFadeIn;
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
/* Root cross-fade rules — animate only the non-chrome snapshot */
|
|
631
|
-
::view-transition-old(root) {
|
|
632
|
-
animation: 150ms ease-in both contentFadeOut;
|
|
633
|
-
}
|
|
634
|
-
::view-transition-new(root) {
|
|
635
|
-
animation: 300ms ease-out both contentFadeIn;
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
/* Reduced motion: collapse all view-transition animation to an instant
|
|
639
|
-
* swap (zudolab/zudo-doc#2086). With every snapshot animation at none the
|
|
640
|
-
* transition settles immediately — no cross-fade, no translateY slide.
|
|
641
|
-
* Covers the root cross-fade and the #2072 :only-child entry/exit rules;
|
|
642
|
-
* the both-sides chrome layers are already animation: none above. Equal
|
|
643
|
-
* specificity per selector, but later in source order, so these win. */
|
|
644
|
-
@media (prefers-reduced-motion: reduce) {
|
|
645
|
-
::view-transition-old(root),
|
|
646
|
-
::view-transition-new(root),
|
|
647
|
-
::view-transition-old(zfb-header):only-child,
|
|
648
|
-
::view-transition-old(zfb-sidebar):only-child,
|
|
649
|
-
::view-transition-old(zfb-footer):only-child,
|
|
650
|
-
::view-transition-old(zfb-sidebar-toggle):only-child,
|
|
651
|
-
::view-transition-new(zfb-header):only-child,
|
|
652
|
-
::view-transition-new(zfb-sidebar):only-child,
|
|
653
|
-
::view-transition-new(zfb-footer):only-child,
|
|
654
|
-
::view-transition-new(zfb-sidebar-toggle):only-child {
|
|
655
|
-
animation: none;
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
|
|
659
565
|
/* Version-switcher responsive visibility (moved out of the component to
|
|
660
566
|
* avoid <style>-inside-<div> HTML5 content-model violation; required when
|
|
661
567
|
* the i18nVersion feature ships a <VersionSwitcher> wrapped in
|
|
File without changes
|