create-zudo-doc 0.2.4 → 0.2.5
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/constants.js +1 -1
- package/dist/features/doc-history.js +57 -1
- package/dist/preset.d.ts +10 -0
- package/dist/preset.js +33 -0
- package/dist/prompts.d.ts +3 -1
- package/dist/prompts.js +1 -0
- package/dist/scaffold.js +7 -5
- package/dist/settings-gen.js +30 -0
- package/package.json +1 -1
- package/templates/base/pages/_mdx-components.ts +6 -2
- package/templates/base/pages/lib/_doc-page-shell.tsx +3 -1
- package/templates/base/pages/lib/_head-with-defaults.tsx +23 -4
- package/templates/base/src/config/settings-types.ts +17 -0
- package/templates/base/src/styles/global.css +75 -4
package/dist/constants.js
CHANGED
|
@@ -7,5 +7,61 @@
|
|
|
7
7
|
*/
|
|
8
8
|
export const docHistoryFeature = () => ({
|
|
9
9
|
name: "docHistory",
|
|
10
|
-
injections: [
|
|
10
|
+
injections: [
|
|
11
|
+
{
|
|
12
|
+
// Diff-viewer CSS — the DocHistory island's side-by-side diff markup
|
|
13
|
+
// (.diff-row / .diff-line-*) is styled only here; without this block
|
|
14
|
+
// scaffolded projects render the diff viewer unstyled (#2081). Values
|
|
15
|
+
// mirror the showcase src/styles/global.css (per-line separators at
|
|
16
|
+
// the 15% muted mix, #2077).
|
|
17
|
+
file: "src/styles/global.css",
|
|
18
|
+
anchor: "/* @slot:global-css:feature-styles */",
|
|
19
|
+
content: `/* ========================================
|
|
20
|
+
* Doc History Diff Viewer (side-by-side)
|
|
21
|
+
* ======================================== */
|
|
22
|
+
|
|
23
|
+
.diff-row {
|
|
24
|
+
border-bottom: 1px solid color-mix(in oklch, var(--color-muted) 15%, transparent);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.diff-line-num {
|
|
28
|
+
font-family: var(--font-mono, ui-monospace, monospace);
|
|
29
|
+
font-size: var(--text-caption);
|
|
30
|
+
line-height: 1.5;
|
|
31
|
+
padding: 0 var(--spacing-hsp-xs);
|
|
32
|
+
text-align: right;
|
|
33
|
+
color: var(--color-muted);
|
|
34
|
+
user-select: none;
|
|
35
|
+
vertical-align: top;
|
|
36
|
+
border-right: 1px solid color-mix(in oklch, var(--color-muted) 15%, transparent);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.diff-line-content {
|
|
40
|
+
font-family: var(--font-mono, ui-monospace, monospace);
|
|
41
|
+
font-size: var(--text-caption);
|
|
42
|
+
line-height: 1.5;
|
|
43
|
+
padding: 0 var(--spacing-hsp-sm);
|
|
44
|
+
white-space: pre-wrap;
|
|
45
|
+
word-break: break-all;
|
|
46
|
+
vertical-align: top;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* Left column right border to separate the two sides */
|
|
50
|
+
.diff-row td:nth-child(2) {
|
|
51
|
+
border-right: 2px solid var(--color-muted);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.diff-line-added {
|
|
55
|
+
background-color: color-mix(in oklch, var(--color-success) 15%, transparent);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.diff-line-removed {
|
|
59
|
+
background-color: color-mix(in oklch, var(--color-danger) 15%, transparent);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.diff-line-empty {
|
|
63
|
+
background-color: color-mix(in oklch, var(--color-muted) 8%, transparent);
|
|
64
|
+
}`,
|
|
65
|
+
},
|
|
66
|
+
],
|
|
11
67
|
});
|
package/dist/preset.d.ts
CHANGED
|
@@ -17,6 +17,15 @@ export interface PresetHeaderRightTriggerItem {
|
|
|
17
17
|
trigger: PresetHeaderRightTriggerName;
|
|
18
18
|
}
|
|
19
19
|
export type PresetHeaderRightItem = PresetHeaderRightComponentItem | PresetHeaderRightTriggerItem;
|
|
20
|
+
export interface PresetMetaTagsConfig {
|
|
21
|
+
description?: boolean;
|
|
22
|
+
keywords?: string | false;
|
|
23
|
+
ogImage?: string | false;
|
|
24
|
+
ogSiteName?: boolean;
|
|
25
|
+
twitterCard?: "summary" | "summary_large_image" | false;
|
|
26
|
+
twitterSite?: string;
|
|
27
|
+
twitterCreator?: string;
|
|
28
|
+
}
|
|
20
29
|
export interface PresetJson {
|
|
21
30
|
projectName?: string;
|
|
22
31
|
defaultLang?: string;
|
|
@@ -31,6 +40,7 @@ export interface PresetJson {
|
|
|
31
40
|
cjkFriendly?: boolean;
|
|
32
41
|
packageManager?: "pnpm" | "npm" | "yarn" | "bun";
|
|
33
42
|
headerRightItems?: PresetHeaderRightItem[];
|
|
43
|
+
metaTags?: PresetMetaTagsConfig;
|
|
34
44
|
}
|
|
35
45
|
export declare function loadPreset(pathOrStdin: string): PartialChoices;
|
|
36
46
|
export declare function validatePreset(json: unknown): string | null;
|
package/dist/preset.js
CHANGED
|
@@ -112,6 +112,36 @@ export function validatePreset(json) {
|
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
|
+
if (p.metaTags !== undefined) {
|
|
116
|
+
if (typeof p.metaTags !== "object" || p.metaTags === null || Array.isArray(p.metaTags)) {
|
|
117
|
+
return `"metaTags" must be an object in preset`;
|
|
118
|
+
}
|
|
119
|
+
const mt = p.metaTags;
|
|
120
|
+
if (mt.description !== undefined && typeof mt.description !== "boolean") {
|
|
121
|
+
return `"metaTags.description" must be a boolean`;
|
|
122
|
+
}
|
|
123
|
+
if (mt.keywords !== undefined && mt.keywords !== false && typeof mt.keywords !== "string") {
|
|
124
|
+
return `"metaTags.keywords" must be a string or false`;
|
|
125
|
+
}
|
|
126
|
+
if (mt.ogImage !== undefined && mt.ogImage !== false && typeof mt.ogImage !== "string") {
|
|
127
|
+
return `"metaTags.ogImage" must be a string or false`;
|
|
128
|
+
}
|
|
129
|
+
if (mt.ogSiteName !== undefined && typeof mt.ogSiteName !== "boolean") {
|
|
130
|
+
return `"metaTags.ogSiteName" must be a boolean`;
|
|
131
|
+
}
|
|
132
|
+
if (mt.twitterCard !== undefined &&
|
|
133
|
+
mt.twitterCard !== false &&
|
|
134
|
+
mt.twitterCard !== "summary" &&
|
|
135
|
+
mt.twitterCard !== "summary_large_image") {
|
|
136
|
+
return `"metaTags.twitterCard" must be "summary", "summary_large_image", or false`;
|
|
137
|
+
}
|
|
138
|
+
if (mt.twitterSite !== undefined && typeof mt.twitterSite !== "string") {
|
|
139
|
+
return `"metaTags.twitterSite" must be a string`;
|
|
140
|
+
}
|
|
141
|
+
if (mt.twitterCreator !== undefined && typeof mt.twitterCreator !== "string") {
|
|
142
|
+
return `"metaTags.twitterCreator" must be a string`;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
115
145
|
// Cross-field validation
|
|
116
146
|
if (p.colorSchemeMode === "single" && (p.lightScheme || p.darkScheme)) {
|
|
117
147
|
return `lightScheme/darkScheme are only valid with colorSchemeMode "light-dark"`;
|
|
@@ -150,6 +180,9 @@ export function presetToChoices(json) {
|
|
|
150
180
|
if (json.headerRightItems !== undefined) {
|
|
151
181
|
choices.headerRightItems = json.headerRightItems;
|
|
152
182
|
}
|
|
183
|
+
if (json.metaTags !== undefined) {
|
|
184
|
+
choices.metaTags = json.metaTags;
|
|
185
|
+
}
|
|
153
186
|
if (json.features) {
|
|
154
187
|
// Warn about unrecognized feature names
|
|
155
188
|
for (const name of json.features) {
|
package/dist/prompts.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PresetHeaderRightItem } from "./preset.js";
|
|
1
|
+
import type { PresetHeaderRightItem, PresetMetaTagsConfig } from "./preset.js";
|
|
2
2
|
export interface UserChoices {
|
|
3
3
|
projectName: string;
|
|
4
4
|
defaultLang: string;
|
|
@@ -14,6 +14,7 @@ export interface UserChoices {
|
|
|
14
14
|
cjkFriendly?: boolean;
|
|
15
15
|
packageManager: "pnpm" | "npm" | "yarn" | "bun";
|
|
16
16
|
headerRightItems?: PresetHeaderRightItem[];
|
|
17
|
+
metaTags?: PresetMetaTagsConfig;
|
|
17
18
|
}
|
|
18
19
|
export interface PartialChoices {
|
|
19
20
|
projectName?: string;
|
|
@@ -30,5 +31,6 @@ export interface PartialChoices {
|
|
|
30
31
|
cjkFriendly?: boolean;
|
|
31
32
|
packageManager?: "pnpm" | "npm" | "yarn" | "bun";
|
|
32
33
|
headerRightItems?: PresetHeaderRightItem[];
|
|
34
|
+
metaTags?: PresetMetaTagsConfig;
|
|
33
35
|
}
|
|
34
36
|
export declare function runPrompts(prefilled?: PartialChoices): Promise<UserChoices>;
|
package/dist/prompts.js
CHANGED
package/dist/scaffold.js
CHANGED
|
@@ -283,16 +283,18 @@ function generatePackageJson(choices) {
|
|
|
283
283
|
// (Takazudo/zudo-front-builder#1030) — and the data-file skip warning
|
|
284
284
|
// (e.g. for `_category_.json`) now respects the collection's
|
|
285
285
|
// include/exclude globs (#1032). No consumer-facing breaking change.
|
|
286
|
-
|
|
287
|
-
|
|
286
|
+
// next.42/next.43: release-tooling + formatter-glob fixes only (npm-publish
|
|
287
|
+
// idempotency, gitignored-artifact excludes). No consumer-facing change.
|
|
288
|
+
"@takazudo/zfb": "0.1.0-next.43",
|
|
289
|
+
"@takazudo/zfb-runtime": "0.1.0-next.43",
|
|
288
290
|
// zfb-adapter-cloudflare — required for any route with `prerender = false`.
|
|
289
291
|
// Pinned in lockstep with @takazudo/zfb.
|
|
290
|
-
"@takazudo/zfb-adapter-cloudflare": "0.1.0-next.
|
|
292
|
+
"@takazudo/zfb-adapter-cloudflare": "0.1.0-next.43",
|
|
291
293
|
// @takazudo/zudo-doc — published from this monorepo via
|
|
292
294
|
// .github/workflows/publish-zudo-doc.yml. The pin here is bumped in
|
|
293
295
|
// lockstep by scripts/release-create-zudo-doc.sh whenever zudo-doc's
|
|
294
296
|
// version moves, so a fresh scaffold pulls the version we just published.
|
|
295
|
-
"@takazudo/zudo-doc": "^0.2.
|
|
297
|
+
"@takazudo/zudo-doc": "^0.2.5",
|
|
296
298
|
// zod — used by the generated zfb.config.ts. zfb-config-gen emits
|
|
297
299
|
// `import { z } from "zod"` for the content-collection schema +
|
|
298
300
|
// `z.toJSONSchema(...)` conversion. Without this dep, the consumer
|
|
@@ -346,7 +348,7 @@ function generatePackageJson(choices) {
|
|
|
346
348
|
// @takazudo/zudo-doc/integrations/doc-history which in turn imports
|
|
347
349
|
// @takazudo/zudo-doc-history-server/git-history. Without this dep the
|
|
348
350
|
// plugin host fails at init with ERR_MODULE_NOT_FOUND — W8A (#1739).
|
|
349
|
-
deps["@takazudo/zudo-doc-history-server"] = "^0.2.
|
|
351
|
+
deps["@takazudo/zudo-doc-history-server"] = "^0.2.5";
|
|
350
352
|
// W7A (#1736): doc-history-plugin.mjs spawns `tsx -e <inline-script>` to
|
|
351
353
|
// run the v2 runtime in a TS-aware Node subprocess; without tsx the
|
|
352
354
|
// plugin's preBuild step exits with ENOENT before zfb finishes config
|
package/dist/settings-gen.js
CHANGED
|
@@ -16,6 +16,7 @@ export function generateSettingsFile(choices) {
|
|
|
16
16
|
lines.push(` TagPlacement,`);
|
|
17
17
|
lines.push(` TagGovernanceMode,`);
|
|
18
18
|
lines.push(` TagVocabularyEntry,`);
|
|
19
|
+
lines.push(` MetaTagsConfig,`);
|
|
19
20
|
lines.push(`} from "./settings-types";`);
|
|
20
21
|
lines.push(`import type {`);
|
|
21
22
|
lines.push(` HeaderNavItem,`);
|
|
@@ -29,6 +30,7 @@ export function generateSettingsFile(choices) {
|
|
|
29
30
|
lines.push(` BodyFootUtilAreaConfig,`);
|
|
30
31
|
lines.push(` TagPlacement,`);
|
|
31
32
|
lines.push(` TagGovernanceMode,`);
|
|
33
|
+
lines.push(` MetaTagsConfig,`);
|
|
32
34
|
lines.push(`} from "./settings-types";`);
|
|
33
35
|
lines.push(``);
|
|
34
36
|
lines.push(`export const settings = {`);
|
|
@@ -59,6 +61,34 @@ export function generateSettingsFile(choices) {
|
|
|
59
61
|
lines.push(` githubUrl: false as string | false,`);
|
|
60
62
|
}
|
|
61
63
|
lines.push(` siteUrl: "" as string,`);
|
|
64
|
+
lines.push(` metaTags: {`);
|
|
65
|
+
if (choices.metaTags) {
|
|
66
|
+
const mt = choices.metaTags;
|
|
67
|
+
lines.push(` description: ${mt.description !== undefined ? mt.description : true},`);
|
|
68
|
+
lines.push(` keywords: ${mt.keywords !== undefined ? JSON.stringify(mt.keywords) : false},`);
|
|
69
|
+
lines.push(` ogImage: ${mt.ogImage !== undefined ? JSON.stringify(mt.ogImage) : false},`);
|
|
70
|
+
lines.push(` ogSiteName: ${mt.ogSiteName !== undefined ? mt.ogSiteName : true},`);
|
|
71
|
+
if (mt.twitterCard) {
|
|
72
|
+
lines.push(` twitterCard: ${JSON.stringify(mt.twitterCard)},`);
|
|
73
|
+
if (mt.twitterSite) {
|
|
74
|
+
lines.push(` twitterSite: ${JSON.stringify(mt.twitterSite)},`);
|
|
75
|
+
}
|
|
76
|
+
if (mt.twitterCreator) {
|
|
77
|
+
lines.push(` twitterCreator: ${JSON.stringify(mt.twitterCreator)},`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
lines.push(` twitterCard: false,`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
lines.push(` description: true,`);
|
|
86
|
+
lines.push(` keywords: false,`);
|
|
87
|
+
lines.push(` ogImage: false,`);
|
|
88
|
+
lines.push(` ogSiteName: true,`);
|
|
89
|
+
lines.push(` twitterCard: false,`);
|
|
90
|
+
}
|
|
91
|
+
lines.push(` } satisfies MetaTagsConfig as MetaTagsConfig,`);
|
|
62
92
|
lines.push(` docsDir: "src/content/docs",`);
|
|
63
93
|
lines.push(` defaultLocale: ${JSON.stringify(choices.defaultLang ?? "en")} as const,`);
|
|
64
94
|
if (choices.features.includes("i18n")) {
|
package/package.json
CHANGED
|
@@ -40,10 +40,11 @@
|
|
|
40
40
|
import type { ComponentChildren } from "preact";
|
|
41
41
|
// @slot:mdx-components:enlarge-imports
|
|
42
42
|
import { htmlOverrides } from "@takazudo/zudo-doc/content";
|
|
43
|
-
import { HtmlPreviewWrapper } from "@takazudo/zudo-doc/html-preview-wrapper";
|
|
43
|
+
import { HtmlPreviewWrapper, type HtmlPreviewWrapperProps } from "@takazudo/zudo-doc/html-preview-wrapper";
|
|
44
44
|
import { Tabs } from "@takazudo/zudo-doc/code-syntax";
|
|
45
45
|
import { TabItem } from "@takazudo/zudo-doc/tab-item";
|
|
46
46
|
import { defaultLocale, type Locale } from "@/config/i18n";
|
|
47
|
+
import { settings } from "@/config/settings";
|
|
47
48
|
import { withBase } from "@/utils/base";
|
|
48
49
|
import { CategoryNavWrapper } from "./lib/_category-nav";
|
|
49
50
|
import { CategoryTreeNavWrapper } from "./lib/_category-tree-nav";
|
|
@@ -120,6 +121,9 @@ function IslandWrapper(props: {
|
|
|
120
121
|
}
|
|
121
122
|
|
|
122
123
|
// @slot:mdx-components:enlarge-defs
|
|
124
|
+
const HtmlPreviewWithGlobalConfig = (props: HtmlPreviewWrapperProps) =>
|
|
125
|
+
HtmlPreviewWrapper({ globalConfig: settings.htmlPreview ?? null, ...props });
|
|
126
|
+
|
|
123
127
|
/**
|
|
124
128
|
* Build a locale-aware MDX components map for the given locale.
|
|
125
129
|
*
|
|
@@ -164,7 +168,7 @@ export function createMdxComponents(lang: Locale | string = defaultLocale) {
|
|
|
164
168
|
// site. withBase() is generic — any configured base value works.
|
|
165
169
|
img: ContentImg,
|
|
166
170
|
// @slot:mdx-components:enlarge-p-entry
|
|
167
|
-
HtmlPreview:
|
|
171
|
+
HtmlPreview: HtmlPreviewWithGlobalConfig,
|
|
168
172
|
// Admonitions — real typed Preact components (src/components/content/
|
|
169
173
|
// content-admonition.tsx) emitting the `.admonition` / `data-admonition`
|
|
170
174
|
// structure the design-system CSS targets. The `directives` map in
|
|
@@ -176,7 +176,9 @@ export function DocPageShell(props: DocPageShellProps): JSX.Element {
|
|
|
176
176
|
return (
|
|
177
177
|
<DocLayoutWithDefaults
|
|
178
178
|
title={composeMetaTitle(title)}
|
|
179
|
-
|
|
179
|
+
// Plain <meta name="description"> is emitted by DocLayout from this prop —
|
|
180
|
+
// gate it here alongside the og:description gate inside HeadWithDefaults (#2078)
|
|
181
|
+
description={settings.metaTags.description ? description : undefined}
|
|
180
182
|
head={<HeadWithDefaults title={title} description={description} canonical={canonical} />}
|
|
181
183
|
lang={locale}
|
|
182
184
|
noindex={settings.noindex}
|
|
@@ -63,6 +63,10 @@ export interface HeadWithDefaultsProps {
|
|
|
63
63
|
* (the legacy Astro layout produced both shapes; the zfb host has to
|
|
64
64
|
* compose them itself).
|
|
65
65
|
*
|
|
66
|
+
* og:title is always emitted — it is the unconditional DocHead contract
|
|
67
|
+
* (OgTags always emits og:title regardless of settings). All other tags
|
|
68
|
+
* are gated by settings.metaTags.
|
|
69
|
+
*
|
|
66
70
|
* Pure SSR — no state, no client-only imports. Intended for use as:
|
|
67
71
|
* head={<HeadWithDefaults title={title} description={description} canonical={canonical} />}
|
|
68
72
|
* on every DocLayoutWithDefaults call site in the host pages.
|
|
@@ -72,6 +76,8 @@ export function HeadWithDefaults({
|
|
|
72
76
|
description,
|
|
73
77
|
canonical,
|
|
74
78
|
}: HeadWithDefaultsProps): JSX.Element {
|
|
79
|
+
const { metaTags } = settings;
|
|
80
|
+
|
|
75
81
|
// og:image / twitter:image must be absolute URLs — crawlers silently drop
|
|
76
82
|
// relative og:image values. absoluteUrl joins siteUrl (no trailing slash) +
|
|
77
83
|
// the base-prefixed asset path, and returns undefined when siteUrl is empty
|
|
@@ -80,7 +86,10 @@ export function HeadWithDefaults({
|
|
|
80
86
|
// TwitterCard already gate their image emission on the prop being defined;
|
|
81
87
|
// the og:image:* companion tags below are gated explicitly because they
|
|
82
88
|
// would dangle without the parent og:image.
|
|
83
|
-
const ogImageUrl =
|
|
89
|
+
const ogImageUrl =
|
|
90
|
+
metaTags.ogImage !== false
|
|
91
|
+
? absoluteUrl(withBase(metaTags.ogImage))
|
|
92
|
+
: undefined;
|
|
84
93
|
|
|
85
94
|
// Resolve the palette CSS body once per page render (the v2 component
|
|
86
95
|
// is pure SSR — no caching needed).
|
|
@@ -93,12 +102,15 @@ export function HeadWithDefaults({
|
|
|
93
102
|
<>
|
|
94
103
|
<OgTags
|
|
95
104
|
title={composeMetaTitle(title)}
|
|
96
|
-
description={description}
|
|
105
|
+
description={metaTags.description ? description : undefined}
|
|
97
106
|
ogType="website"
|
|
98
107
|
ogUrl={canonical}
|
|
99
108
|
ogImage={ogImageUrl}
|
|
100
|
-
ogSiteName={settings.siteName}
|
|
109
|
+
ogSiteName={metaTags.ogSiteName ? settings.siteName : undefined}
|
|
101
110
|
/>
|
|
111
|
+
{metaTags.keywords !== false && metaTags.keywords.length > 0 && (
|
|
112
|
+
<meta name="keywords" content={metaTags.keywords} />
|
|
113
|
+
)}
|
|
102
114
|
{/* og:image:width / og:image:height / og:image:alt — not in OgTags API;
|
|
103
115
|
emitted here directly to avoid expanding the shared HeadProps surface.
|
|
104
116
|
Standard 1200×630 social preview dimensions. Gated on ogImageUrl so
|
|
@@ -110,7 +122,14 @@ export function HeadWithDefaults({
|
|
|
110
122
|
<meta property="og:image:alt" content={composeMetaTitle(title)} />
|
|
111
123
|
</>
|
|
112
124
|
)}
|
|
113
|
-
|
|
125
|
+
{metaTags.twitterCard !== false && (
|
|
126
|
+
<TwitterCard
|
|
127
|
+
card={metaTags.twitterCard}
|
|
128
|
+
image={ogImageUrl}
|
|
129
|
+
site={metaTags.twitterSite}
|
|
130
|
+
creator={metaTags.twitterCreator}
|
|
131
|
+
/>
|
|
132
|
+
)}
|
|
114
133
|
<ColorSchemeProvider cssText={cssText} colorMode={colorMode} />
|
|
115
134
|
{/* Pre-paint inline script: restore persisted sidebar width to
|
|
116
135
|
--zd-sidebar-w on :root before first paint, so a reload after
|
|
@@ -160,3 +160,20 @@ export interface VersionConfig {
|
|
|
160
160
|
/** Banner text shown on versioned pages (e.g., "unmaintained", "unreleased") */
|
|
161
161
|
banner?: "unmaintained" | "unreleased" | false;
|
|
162
162
|
}
|
|
163
|
+
|
|
164
|
+
export interface MetaTagsConfig {
|
|
165
|
+
/** Emit <meta name="description">. Default true. */
|
|
166
|
+
description: boolean;
|
|
167
|
+
/** Emit <meta name="keywords"> with a comma-separated string. false = omit. Default false. */
|
|
168
|
+
keywords: string | false;
|
|
169
|
+
/** og:image (and twitter:image) path. false = omit. Default false. Showcase: '/img/ogp.png'. */
|
|
170
|
+
ogImage: string | false;
|
|
171
|
+
/** Emit og:site_name. Default true (preserves current og:site_name). */
|
|
172
|
+
ogSiteName: boolean;
|
|
173
|
+
/** TwitterCard type. false = omit entire twitter:card block. Default false. Showcase: 'summary_large_image'. */
|
|
174
|
+
twitterCard: "summary" | "summary_large_image" | false;
|
|
175
|
+
/** twitter:site handle (e.g. '@yourbrand'). Optional. */
|
|
176
|
+
twitterSite?: string;
|
|
177
|
+
/** twitter:creator handle. Optional. */
|
|
178
|
+
twitterCreator?: string;
|
|
179
|
+
}
|
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
/* Cascade-layer order for the flow-space fix (zudolab/zudo-doc#2082 / #2109):
|
|
2
|
+
* zd-preflight — Tailwind's preflight reset (incl. `* { margin: 0 }`)
|
|
3
|
+
* zd-flow — the .zd-content flow-space margin-top rule (below)
|
|
4
|
+
* (unlayered) — `tailwindcss/utilities` (mt-* etc.) — highest priority
|
|
5
|
+
* Unlayered always beats any layer, so `mt-*` utilities win over the flow
|
|
6
|
+
* rule; the flow rule (zd-flow) sits above zd-preflight so it still beats the
|
|
7
|
+
* preflight `margin: 0` reset and the .zd-content vertical rhythm is preserved.
|
|
8
|
+
* Preflight is the de-facto lowest-priority origin already, so moving it into
|
|
9
|
+
* the lowest layer is observably inert for every other author rule (which stay
|
|
10
|
+
* unlayered and continue to outrank it). */
|
|
11
|
+
@layer zd-preflight, zd-flow;
|
|
12
|
+
@import "tailwindcss/preflight" layer(zd-preflight);
|
|
2
13
|
@import "tailwindcss/utilities";
|
|
3
14
|
|
|
4
15
|
/* ========================================
|
|
@@ -263,8 +274,21 @@ body {
|
|
|
263
274
|
|
|
264
275
|
/* ── Flow spacing (vertical rhythm) ── */
|
|
265
276
|
|
|
266
|
-
|
|
267
|
-
|
|
277
|
+
/* In the `zd-flow` cascade layer (declared at the top of this file) so the
|
|
278
|
+
* unlayered `tailwindcss/utilities` import wins over it: unlayered declarations
|
|
279
|
+
* always beat layered ones regardless of specificity or source order. Both this
|
|
280
|
+
* rule and a `mt-*` utility have specificity (0,1,0); without the layer this
|
|
281
|
+
* rule comes later in source order and silently kills every `mt-*` on a
|
|
282
|
+
* `.zd-content` direct child. zd-flow sits ABOVE zd-preflight so this rule still
|
|
283
|
+
* beats preflight's `* { margin: 0 }` reset and the .zd-content rhythm is
|
|
284
|
+
* preserved for plain content blocks. The layer is scoped to ONLY this flow rule
|
|
285
|
+
* — every other `.zd-content` author rule (links, code, lists, headings, the
|
|
286
|
+
* `:first-child` reset, etc.) stays unlayered so utilities do NOT flip those.
|
|
287
|
+
* See zudolab/zudo-doc#2082 / #2109. */
|
|
288
|
+
@layer zd-flow {
|
|
289
|
+
.zd-content > :where(* + *) {
|
|
290
|
+
margin-top: var(--flow-space, var(--spacing-vsp-md));
|
|
291
|
+
}
|
|
268
292
|
}
|
|
269
293
|
|
|
270
294
|
/* ── Headings ── */
|
|
@@ -938,7 +962,17 @@ pre[class^="syntect-"] .line .highlighted-word {
|
|
|
938
962
|
* article, TOC, etc.). The twelve ::view-transition-{old,new,group}(<name>)
|
|
939
963
|
* rules disable animation for all four chrome layers. The group pseudo must be
|
|
940
964
|
* neutralised too — even when old/new are static the group container can still
|
|
941
|
-
* produce a geometry-morph animation when snapshot size/position differs.
|
|
965
|
+
* produce a geometry-morph animation when snapshot size/position differs.
|
|
966
|
+
*
|
|
967
|
+
* Entry/exit cross-fade (zudolab/zudo-doc#2072): animation: none is only
|
|
968
|
+
* correct when the chrome element exists on BOTH pages of a navigation.
|
|
969
|
+
* When it exists on one side only (docs page with sidebar → top page
|
|
970
|
+
* without), the named group holds a single snapshot — frozen at full
|
|
971
|
+
* opacity for the whole transition, then dropped abruptly at finish. The
|
|
972
|
+
* :only-child rules below detect that one-sided case and cross-fade the
|
|
973
|
+
* lone snapshot in sync with the root content fade (spec-standard
|
|
974
|
+
* entry/exit pattern; :only-child outranks the animation: none rules via
|
|
975
|
+
* the extra pseudo-class specificity). */
|
|
942
976
|
|
|
943
977
|
/* Chrome extraction — assign view-transition-name from data-zfb-transition-persist */
|
|
944
978
|
[data-zfb-transition-persist^="header-"] { view-transition-name: zfb-header; }
|
|
@@ -960,6 +994,22 @@ pre[class^="syntect-"] .line .highlighted-word {
|
|
|
960
994
|
::view-transition-new(zfb-sidebar-toggle),
|
|
961
995
|
::view-transition-group(zfb-sidebar-toggle) { animation: none; }
|
|
962
996
|
|
|
997
|
+
/* Entry/exit: chrome element present on one side only (#2072).
|
|
998
|
+
* Exit — lone old snapshot fades out with the content cross-fade. */
|
|
999
|
+
::view-transition-old(zfb-header):only-child,
|
|
1000
|
+
::view-transition-old(zfb-sidebar):only-child,
|
|
1001
|
+
::view-transition-old(zfb-footer):only-child,
|
|
1002
|
+
::view-transition-old(zfb-sidebar-toggle):only-child {
|
|
1003
|
+
animation: 150ms ease-in both contentFadeOut;
|
|
1004
|
+
}
|
|
1005
|
+
/* Entry — lone new snapshot fades in with the content cross-fade. */
|
|
1006
|
+
::view-transition-new(zfb-header):only-child,
|
|
1007
|
+
::view-transition-new(zfb-sidebar):only-child,
|
|
1008
|
+
::view-transition-new(zfb-footer):only-child,
|
|
1009
|
+
::view-transition-new(zfb-sidebar-toggle):only-child {
|
|
1010
|
+
animation: 300ms ease-out both contentFadeIn;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
963
1013
|
/* Root cross-fade rules — animate only the non-chrome snapshot */
|
|
964
1014
|
::view-transition-old(root) {
|
|
965
1015
|
animation: 150ms ease-in both contentFadeOut;
|
|
@@ -968,6 +1018,27 @@ pre[class^="syntect-"] .line .highlighted-word {
|
|
|
968
1018
|
animation: 300ms ease-out both contentFadeIn;
|
|
969
1019
|
}
|
|
970
1020
|
|
|
1021
|
+
/* Reduced motion: collapse all view-transition animation to an instant
|
|
1022
|
+
* swap (zudolab/zudo-doc#2086). With every snapshot animation at none the
|
|
1023
|
+
* transition settles immediately — no cross-fade, no translateY slide.
|
|
1024
|
+
* Covers the root cross-fade and the #2072 :only-child entry/exit rules;
|
|
1025
|
+
* the both-sides chrome layers are already animation: none above. Equal
|
|
1026
|
+
* specificity per selector, but later in source order, so these win. */
|
|
1027
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1028
|
+
::view-transition-old(root),
|
|
1029
|
+
::view-transition-new(root),
|
|
1030
|
+
::view-transition-old(zfb-header):only-child,
|
|
1031
|
+
::view-transition-old(zfb-sidebar):only-child,
|
|
1032
|
+
::view-transition-old(zfb-footer):only-child,
|
|
1033
|
+
::view-transition-old(zfb-sidebar-toggle):only-child,
|
|
1034
|
+
::view-transition-new(zfb-header):only-child,
|
|
1035
|
+
::view-transition-new(zfb-sidebar):only-child,
|
|
1036
|
+
::view-transition-new(zfb-footer):only-child,
|
|
1037
|
+
::view-transition-new(zfb-sidebar-toggle):only-child {
|
|
1038
|
+
animation: none;
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
|
|
971
1042
|
/* Version-switcher responsive visibility (moved out of the component to
|
|
972
1043
|
* avoid <style>-inside-<div> HTML5 content-model violation; required when
|
|
973
1044
|
* the i18nVersion feature ships a <VersionSwitcher> wrapped in
|