@takazudo/zudo-doc 4.3.0 → 4.4.1

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 (144) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/bin/zudo-doc.mjs +54 -13
  3. package/dist/auto-logo/index.d.ts +18 -0
  4. package/dist/auto-logo/index.js +33 -0
  5. package/dist/auto-logo/shapes.d.ts +32 -0
  6. package/dist/auto-logo/shapes.js +183 -0
  7. package/dist/auto-logo/standalone.d.ts +7 -0
  8. package/dist/auto-logo/standalone.js +35 -0
  9. package/dist/config.d.ts +8 -0
  10. package/dist/config.js +1 -0
  11. package/dist/content.css +4 -3
  12. package/dist/doc-content-header/index.js +1 -1
  13. package/dist/doc-history/index.js +1 -1
  14. package/dist/doc-page-shell/index.js +1 -1
  15. package/dist/doc-pager/index.js +2 -2
  16. package/dist/eject-logo/config-parse.d.ts +25 -0
  17. package/dist/eject-logo/config-parse.js +68 -0
  18. package/dist/eject-logo/config-rewriter.d.ts +22 -0
  19. package/dist/eject-logo/config-rewriter.js +68 -0
  20. package/dist/eject-logo/eject.d.ts +18 -0
  21. package/dist/eject-logo/eject.js +66 -0
  22. package/dist/eject-logo/index.d.ts +4 -0
  23. package/dist/eject-logo/index.js +16 -0
  24. package/dist/eject-logo/site-name.d.ts +21 -0
  25. package/dist/eject-logo/site-name.js +31 -0
  26. package/dist/header/header.js +14 -6
  27. package/dist/header/nav-class-tokens.d.ts +12 -0
  28. package/dist/header/nav-class-tokens.js +80 -0
  29. package/dist/header/nav-overflow-script.js +33 -16
  30. package/dist/home-page/index.d.ts +34 -5
  31. package/dist/home-page/index.js +95 -38
  32. package/dist/home-page/prepare-home-data.d.ts +9 -0
  33. package/dist/home-page/prepare-home-data.js +10 -3
  34. package/dist/i18n-defaults/index.js +3 -0
  35. package/dist/metainfo/doc-metainfo.js +35 -22
  36. package/dist/routes/index.js +2 -1
  37. package/dist/routes/locale-index.js +2 -1
  38. package/dist/safelist.css +1 -1
  39. package/dist/settings.d.ts +7 -0
  40. package/dist/sidebar-tree-island/index.js +3 -3
  41. package/dist/theme-cli/config-scanner.js +47 -0
  42. package/dist/theme-packs/academia/fonts/CourierPrime-400-latin.woff2 +0 -0
  43. package/dist/theme-packs/academia/fonts/CourierPrime-700-latin.woff2 +0 -0
  44. package/dist/theme-packs/academia/fonts/OFL.txt +94 -0
  45. package/dist/theme-packs/academia/fonts/SourceSerif4-italic-latin.woff2 +0 -0
  46. package/dist/theme-packs/academia/fonts/SourceSerif4-latin.woff2 +0 -0
  47. package/dist/theme-packs/academia/meta.json +37 -0
  48. package/dist/theme-packs/academia/pack.css +505 -0
  49. package/dist/theme-packs/bauhaus/fonts/ArchivoBlack-latin.woff2 +0 -0
  50. package/dist/theme-packs/bauhaus/fonts/Jost-latin.woff2 +0 -0
  51. package/dist/theme-packs/bauhaus/fonts/OFL.txt +95 -0
  52. package/dist/theme-packs/bauhaus/fonts/SpaceMono-400-latin.woff2 +0 -0
  53. package/dist/theme-packs/bauhaus/fonts/SpaceMono-700-latin.woff2 +0 -0
  54. package/dist/theme-packs/bauhaus/meta.json +38 -0
  55. package/dist/theme-packs/bauhaus/pack.css +614 -0
  56. package/dist/theme-packs/blueprint/fonts/IBMPlexMono-400-latin-ext.woff2 +0 -0
  57. package/dist/theme-packs/blueprint/fonts/IBMPlexMono-400-latin.woff2 +0 -0
  58. package/dist/theme-packs/blueprint/fonts/IBMPlexMono-500-latin-ext.woff2 +0 -0
  59. package/dist/theme-packs/blueprint/fonts/IBMPlexMono-500-latin.woff2 +0 -0
  60. package/dist/theme-packs/blueprint/fonts/Inter-latin-ext.woff2 +0 -0
  61. package/dist/theme-packs/blueprint/fonts/Inter-latin.woff2 +0 -0
  62. package/dist/theme-packs/blueprint/fonts/JosefinSans-latin.woff2 +0 -0
  63. package/dist/theme-packs/blueprint/fonts/OFL.txt +95 -0
  64. package/dist/theme-packs/blueprint/meta.json +38 -0
  65. package/dist/theme-packs/blueprint/pack.css +577 -0
  66. package/dist/theme-packs/botanica/fonts/CormorantGaramond-latin.woff2 +0 -0
  67. package/dist/theme-packs/botanica/fonts/CourierPrime-400-latin-ext.woff2 +0 -0
  68. package/dist/theme-packs/botanica/fonts/CourierPrime-400-latin.woff2 +0 -0
  69. package/dist/theme-packs/botanica/fonts/CourierPrime-700-latin-ext.woff2 +0 -0
  70. package/dist/theme-packs/botanica/fonts/CourierPrime-700-latin.woff2 +0 -0
  71. package/dist/theme-packs/botanica/fonts/CrimsonPro-latin-italic.woff2 +0 -0
  72. package/dist/theme-packs/botanica/fonts/CrimsonPro-latin.woff2 +0 -0
  73. package/dist/theme-packs/botanica/fonts/OFL.txt +95 -0
  74. package/dist/theme-packs/botanica/meta.json +38 -0
  75. package/dist/theme-packs/botanica/pack.css +546 -0
  76. package/dist/theme-packs/brutalist/meta.json +1 -1
  77. package/dist/theme-packs/brutalist/pack.css +10 -9
  78. package/dist/theme-packs/eink/fonts/CourierPrime-400-latin-ext.woff2 +0 -0
  79. package/dist/theme-packs/eink/fonts/CourierPrime-400-latin.woff2 +0 -0
  80. package/dist/theme-packs/eink/fonts/CourierPrime-700-latin-ext.woff2 +0 -0
  81. package/dist/theme-packs/eink/fonts/CourierPrime-700-latin.woff2 +0 -0
  82. package/dist/theme-packs/eink/fonts/EBGaramond-latin-italic.woff2 +0 -0
  83. package/dist/theme-packs/eink/fonts/EBGaramond-latin.woff2 +0 -0
  84. package/dist/theme-packs/eink/fonts/OFL.txt +94 -0
  85. package/dist/theme-packs/eink/meta.json +37 -0
  86. package/dist/theme-packs/eink/pack.css +490 -0
  87. package/dist/theme-packs/foundry/meta.json +1 -1
  88. package/dist/theme-packs/foundry/pack.css +2 -2
  89. package/dist/theme-packs/hearth/meta.json +1 -1
  90. package/dist/theme-packs/hearth/pack.css +1 -1
  91. package/dist/theme-packs/nocturne/pack.css +4 -0
  92. package/dist/theme-packs/observatory/meta.json +1 -1
  93. package/dist/theme-packs/observatory/pack.css +4 -1
  94. package/dist/theme-packs/onyx/pack.css +6 -0
  95. package/dist/theme-packs/phosphor/pack.css +4 -0
  96. package/dist/theme-packs/riso/fonts/ArchivoBlack-latin.woff2 +0 -0
  97. package/dist/theme-packs/riso/fonts/OFL.txt +95 -0
  98. package/dist/theme-packs/riso/fonts/SpaceGrotesk-latin.woff2 +0 -0
  99. package/dist/theme-packs/riso/fonts/SpaceMono-400-italic-latin.woff2 +0 -0
  100. package/dist/theme-packs/riso/fonts/SpaceMono-400-latin.woff2 +0 -0
  101. package/dist/theme-packs/riso/fonts/SpaceMono-700-latin.woff2 +0 -0
  102. package/dist/theme-packs/riso/meta.json +38 -0
  103. package/dist/theme-packs/riso/pack.css +602 -0
  104. package/dist/theme-packs/sakura/fonts/MPlus1Code-400-latin.woff2 +0 -0
  105. package/dist/theme-packs/sakura/fonts/MPlus1Code-500-latin.woff2 +0 -0
  106. package/dist/theme-packs/sakura/fonts/Nunito-latin.woff2 +0 -0
  107. package/dist/theme-packs/sakura/fonts/OFL.txt +95 -0
  108. package/dist/theme-packs/sakura/fonts/ShipporiMincho-600-latin.woff2 +0 -0
  109. package/dist/theme-packs/sakura/fonts/ShipporiMincho-700-latin.woff2 +0 -0
  110. package/dist/theme-packs/sakura/meta.json +38 -0
  111. package/dist/theme-packs/sakura/pack.css +473 -0
  112. package/dist/theme-packs/scandi/fonts/Manrope-latin.woff2 +0 -0
  113. package/dist/theme-packs/scandi/fonts/OFL.txt +94 -0
  114. package/dist/theme-packs/scandi/fonts/SplineSansMono-latin.woff2 +0 -0
  115. package/dist/theme-packs/scandi/meta.json +37 -0
  116. package/dist/theme-packs/scandi/pack.css +370 -0
  117. package/dist/theme-packs/sumi/meta.json +1 -1
  118. package/dist/theme-packs/sumi/pack.css +4 -1
  119. package/dist/theme-packs/swissgrid/pack.css +4 -0
  120. package/dist/theme-packs/tidepool/fonts/JetBrainsMono-latin-ext.woff2 +0 -0
  121. package/dist/theme-packs/tidepool/fonts/JetBrainsMono-latin.woff2 +0 -0
  122. package/dist/theme-packs/tidepool/fonts/OFL.txt +94 -0
  123. package/dist/theme-packs/tidepool/fonts/SpaceGrotesk-latin.woff2 +0 -0
  124. package/dist/theme-packs/tidepool/meta.json +37 -0
  125. package/dist/theme-packs/tidepool/pack.css +425 -0
  126. package/dist/theme-packs/timberline/fonts/Fraunces-italic-latin.woff2 +0 -0
  127. package/dist/theme-packs/timberline/fonts/Fraunces-latin.woff2 +0 -0
  128. package/dist/theme-packs/timberline/fonts/JetBrainsMono-latin.woff2 +0 -0
  129. package/dist/theme-packs/timberline/fonts/OFL.txt +95 -0
  130. package/dist/theme-packs/timberline/fonts/SourceSans3-italic-latin.woff2 +0 -0
  131. package/dist/theme-packs/timberline/fonts/SourceSans3-latin.woff2 +0 -0
  132. package/dist/theme-packs/timberline/meta.json +38 -0
  133. package/dist/theme-packs/timberline/pack.css +509 -0
  134. package/dist/theme-packs/washi/meta.json +1 -1
  135. package/dist/theme-packs/washi/pack.css +6 -26
  136. package/eject/doc-history/index.tsx +1 -1
  137. package/eject/doc-pager/index.tsx +2 -2
  138. package/eject/header/header.tsx +16 -8
  139. package/eject/header/nav-class-tokens.ts +115 -0
  140. package/eject/header/nav-overflow-script.ts +49 -16
  141. package/eject/sidebar-tree-island/index.tsx +4 -4
  142. package/package.json +17 -9
  143. package/routes-src/index.tsx +2 -1
  144. package/routes-src/locale-index.tsx +2 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,39 @@ All notable changes to `@takazudo/zudo-doc` are documented in this file.
4
4
 
5
5
  The format is based on Keep a Changelog, and release notes are generated from the changelog MDX pages.
6
6
 
7
+ ## [4.4.1] - 2026-07-22
8
+
9
+ ### Bug Fixes
10
+
11
+ - create-zudo-doc: removed the unused `@takazudo/zfb-adapter-cloudflare` dependency from the generated scaffold — fresh projects no longer install an adapter they never reference (eabda6fc2)
12
+
13
+ ### Other Changes
14
+
15
+ - Bumped first-party `^4.0.0` dependency floors to `^4.4.0`: the `@takazudo/zudo-doc-history-server` peer floor in `@takazudo/zudo-doc`, and the target-manifest test fixture's `@takazudo/zudo-doc` pin (84d592cea)
16
+
17
+ ## [4.4.0] - 2026-07-22
18
+
19
+ ### Features
20
+
21
+ - Theme Batch 5 adds 10 new theme packs — academia, bauhaus, blueprint, botanica, eink, riso, sakura, scandi, tidepool, and timberline — bringing the catalog to 31 packs, each with full admonition/code/sidebar coverage and a rendered WCAG contrast audit (29bb3497f, 12665cdf1)
22
+ - A new `logo` setting renders a generated "auto" home-hero logo by default, alongside a `zudo-doc eject logo` CLI subcommand for taking full ownership of the logo markup (a2ba5188a, 3cedbee04)
23
+ - `scripts/theme-a11y-audit.ts` adds a rendered per-pack contrast audit tool, now wired into the nightly exam and documented for theme authors (4c969cd4a, 8ed913f9d)
24
+ - The top-page Tags section now renders tag chips, and the home hero gained a `heroLink` prop with an inline extras row (55be502c2, fc865e6be)
25
+ - Header, sidebar, overflow-dropdown, and doc-pager non-active nav elements now consistently turn accent color on hover/focus, with a shared class-token source of truth for the header nav (fafecc154, 274a8641f)
26
+
27
+ ### Bug Fixes
28
+
29
+ - Resolved 10 rendered-a11y audit findings across 5 existing theme packs, plus follow-up contrast fixes for blueprint/sakura/tidepool and five additional deep-review findings (b694708dc, 2c2dcc858, 12f7964f9)
30
+ - Theme-CLI config scanning now correctly decodes `\uXXXX`, `\u{...}`, and `\xXX` escapes (2e58e3dc0)
31
+ - The default title-rule border is reset only where a theme pack owns its own title styling, keeping custom pack titles from double-bordering (9043b9439)
32
+ - Doc-history anchors now point at the actual generated heading slugs (9e3e62655)
33
+
34
+ ### Other Changes
35
+
36
+ - Re-baselined the route-injection parity test's normalized-HTML sha256 snapshots twice this cycle, attributing every changed byte to its originating merged PR (5e46831d4, 4fd642c3b)
37
+ - Bumped the `@takazudo/zfb` family to `0.1.0-next.90`
38
+ - Expanded English and Japanese documentation for the logo/eject-logo feature, the Theme Gallery (31 packs), and the hover-accent interactive color rule
39
+
7
40
  ## [4.3.0] - 2026-07-19
8
41
 
9
42
  ### Features
package/bin/zudo-doc.mjs CHANGED
@@ -3,21 +3,28 @@
3
3
  //
4
4
  // Package bin: `zudo-doc eject <component>` swizzle CLI, plus
5
5
  // `zudo-doc theme list|apply <slug>` (issue #2824; ADR
6
- // docs/adr/theme-packs.md).
6
+ // docs/adr/theme-packs.md), plus `zudo-doc eject logo` (issue #3050; epic
7
+ // #3047).
7
8
  //
8
- // Self-contained ESM — runs on plain `node` with NO `tsx` requirement. Both
9
- // the eject and theme-cli logic are imported from the package's COMPILED
10
- // `../dist/eject/index.js` / `../dist/theme-cli/index.js`, and the only
11
- // runtime deps are `minimist` + `picocolors` (declared deps of this package,
12
- // so they are present transitively in any consumer's node_modules). This is
13
- // the key difference from the tsx-runner pattern used by `bin/tags-audit.mjs`:
14
- // tags-audit must load the *project's* TypeScript config files at runtime (hence
15
- // tsx), whereas eject/theme-cli only copy files / rewrite text — no TS eval
16
- // needed so both work in a default generated project that never installed
17
- // tsx (#2367).
9
+ // Self-contained ESM — runs on plain `node` with NO `tsx` requirement. The
10
+ // eject, theme-cli, and eject-logo logic are imported from the package's
11
+ // COMPILED `../dist/eject/index.js` / `../dist/theme-cli/index.js` /
12
+ // `../dist/eject-logo/index.js`, and the only runtime deps are `minimist` +
13
+ // `picocolors` (declared deps of this package, so they are present
14
+ // transitively in any consumer's node_modules). This is the key difference
15
+ // from the tsx-runner pattern used by `bin/tags-audit.mjs`: tags-audit must
16
+ // load the *project's* TypeScript config files at runtime (hence tsx),
17
+ // whereas eject/theme-cli/eject-logo only copy files / rewrite text no TS
18
+ // eval needed — so all three work in a default generated project that never
19
+ // installed tsx (#2367).
20
+ //
21
+ // `eject logo` is special-cased below, BEFORE the EJECTABLE lookup — it is
22
+ // NOT a source-copy swizzle (EJECTABLE's contract), it renders a fresh SVG
23
+ // and rewrites a config field, so it does not belong in that map.
18
24
  //
19
25
  // Usage:
20
26
  // zudo-doc eject <component> # eject a component's TS source into the project
27
+ // zudo-doc eject logo # render public/img/logo.svg + rewrite the logo field
21
28
  // zudo-doc theme list # list installed theme packs + the active one
22
29
  // zudo-doc theme apply <slug> # rewrite zfb.config.ts's themePack field
23
30
  // zudo-doc --help # show help
@@ -25,10 +32,12 @@
25
32
  import minimist from "minimist";
26
33
  import pc from "picocolors";
27
34
  import { eject, EJECTABLE } from "../dist/eject/index.js";
35
+ import { ejectLogo } from "../dist/eject-logo/index.js";
28
36
  import { applyThemePack, formatThemeList, listThemePacks } from "../dist/theme-cli/index.js";
29
37
 
30
38
  const argv = minimist(process.argv.slice(2), {
31
- boolean: ["help"],
39
+ string: ["seed"],
40
+ boolean: ["help", "force"],
32
41
  alias: { h: "help" },
33
42
  });
34
43
 
@@ -40,6 +49,9 @@ ${pc.bold("Usage:")} zudo-doc <subcommand> [options]
40
49
  ${pc.bold("Subcommands:")}
41
50
  eject <component> Copy a component's TS source into your project and
42
51
  rewrite imports so it resolves locally.
52
+ eject logo [--seed <name>] [--force]
53
+ Render public/img/logo.svg and rewrite zfb.config.ts's
54
+ logo field to point at it.
43
55
  theme list List the installed theme packs and which one is active.
44
56
  theme apply <slug> Rewrite zfb.config.ts's themePack field to <slug>.
45
57
 
@@ -47,7 +59,9 @@ ${pc.bold("Ejectable components:")}
47
59
  ${validNames}
48
60
 
49
61
  ${pc.bold("Options:")}
50
- -h, --help Show this help message
62
+ -h, --help Show this help message
63
+ --seed <name> (eject logo) Seed for the generated logo, overriding siteName
64
+ --force (eject logo) Overwrite an existing public/img/logo.svg
51
65
 
52
66
  ${pc.bold("Examples:")}
53
67
  ${pc.dim("# Eject the header component")}
@@ -56,6 +70,9 @@ ${pc.bold("Examples:")}
56
70
  ${pc.dim("# Eject the theme-toggle component")}
57
71
  zudo-doc eject theme-toggle
58
72
 
73
+ ${pc.dim("# Eject the logo as a standalone SVG")}
74
+ zudo-doc eject logo
75
+
59
76
  ${pc.dim("# List installed theme packs")}
60
77
  zudo-doc theme list
61
78
 
@@ -77,6 +94,29 @@ async function runEject(componentArg) {
77
94
  await eject(componentArg, { cwd: process.cwd() });
78
95
  }
79
96
 
97
+ async function runEjectLogo() {
98
+ const hasSeedFlag = Object.prototype.hasOwnProperty.call(argv, "seed");
99
+ if (hasSeedFlag && argv.seed.length === 0) {
100
+ console.error(
101
+ pc.red(`Missing value for --seed.`) +
102
+ `\nUsage: zudo-doc eject logo [--seed <name>] [--force]`,
103
+ );
104
+ process.exit(1);
105
+ }
106
+
107
+ const result = await ejectLogo({
108
+ cwd: process.cwd(),
109
+ seed: hasSeedFlag ? argv.seed : undefined,
110
+ force: argv.force,
111
+ });
112
+
113
+ if (!result.ok) {
114
+ console.error(pc.red(result.message));
115
+ process.exit(1);
116
+ }
117
+ console.log(pc.green(result.message));
118
+ }
119
+
80
120
  async function runThemeList() {
81
121
  const result = await listThemePacks({ cwd: process.cwd() });
82
122
  console.log(formatThemeList(result));
@@ -120,6 +160,7 @@ async function main() {
120
160
 
121
161
  const [subcommand, ...rest] = argv._;
122
162
 
163
+ if (subcommand === "eject" && rest[0] === "logo") return runEjectLogo();
123
164
  if (subcommand === "eject") return runEject(rest[0]);
124
165
  if (subcommand === "theme") return runTheme(rest);
125
166
 
@@ -0,0 +1,18 @@
1
+ /** @jsxRuntime automatic */
2
+ /** @jsxImportSource preact */
3
+ import type { JSX } from "preact";
4
+ import { pickGlyphName } from "./shapes.js";
5
+ export { pickGlyphName };
6
+ export { renderAutoLogoStandaloneSvg } from "./standalone.js";
7
+ /** Props for {@link AutoLogo}. */
8
+ export interface AutoLogoProps {
9
+ /** Deterministic seed — pass `settings.siteName`. */
10
+ seed: string;
11
+ /** Class list for the root `<svg>` (sizing/color, e.g. `w-[320px] text-fg`). */
12
+ class?: string;
13
+ }
14
+ /**
15
+ * Generated "decorated plate" logo — plate + frame + corner rays + disc +
16
+ * seeded line-art glyph. Server-rendered, deterministic per seed.
17
+ */
18
+ export declare function AutoLogo({ seed, class: className }: AutoLogoProps): JSX.Element;
@@ -0,0 +1,33 @@
1
+ import { jsx, jsxs } from "preact/jsx-runtime";
2
+ import { h } from "preact";
3
+ import {
4
+ W,
5
+ H,
6
+ PLATE_SHAPE,
7
+ INNER_FRAME_SHAPE,
8
+ RAY_SHAPES,
9
+ DISC_SHAPE,
10
+ GLYPH_SHAPES,
11
+ GLYPH_TRANSFORM,
12
+ pickGlyphName
13
+ } from "./shapes.js";
14
+ import { renderAutoLogoStandaloneSvg } from "./standalone.js";
15
+ function renderShape({ el, attrs }, key) {
16
+ return h(el, key !== void 0 ? { ...attrs, key } : attrs);
17
+ }
18
+ function AutoLogo({ seed, class: className }) {
19
+ const glyphName = pickGlyphName(seed);
20
+ const glyphShapes = GLYPH_SHAPES[glyphName];
21
+ return /* @__PURE__ */ jsxs("svg", { viewBox: `0 0 ${W} ${H}`, fill: "currentColor", class: className, "data-auto-logo": glyphName, "aria-hidden": "true", children: [
22
+ renderShape(PLATE_SHAPE),
23
+ renderShape(INNER_FRAME_SHAPE),
24
+ RAY_SHAPES.map((ray, i) => renderShape(ray, i)),
25
+ renderShape(DISC_SHAPE),
26
+ /* @__PURE__ */ jsx("g", { transform: GLYPH_TRANSFORM, children: glyphShapes.map((shape, i) => renderShape(shape, i)) })
27
+ ] });
28
+ }
29
+ export {
30
+ AutoLogo,
31
+ pickGlyphName,
32
+ renderAutoLogoStandaloneSvg
33
+ };
@@ -0,0 +1,32 @@
1
+ /** One SVG primitive as plain data: element name + its attributes. */
2
+ export interface ShapePrimitive {
3
+ el: "path" | "line" | "circle" | "rect";
4
+ attrs: Record<string, string | number>;
5
+ }
6
+ export declare const W = 200;
7
+ export declare const H = 105;
8
+ export declare const INSET = 6;
9
+ export declare const CX: number;
10
+ export declare const CY: number;
11
+ export declare const DISC_R = 34;
12
+ /** Full-bleed plate rect. */
13
+ export declare const PLATE_SHAPE: ShapePrimitive;
14
+ /** Thin inner frame stroke, knocked out against the page background. */
15
+ export declare const INNER_FRAME_SHAPE: ShapePrimitive;
16
+ /** 4 diagonal corner rays, each stopping just short of the disc edge. */
17
+ export declare const RAY_SHAPES: ShapePrimitive[];
18
+ /** Centered disc, knocked out against the page background. */
19
+ export declare const DISC_SHAPE: ShapePrimitive;
20
+ /** Scale + translate that centers a glyph's 100×100 box onto the disc
21
+ * (slight overscan reads better). */
22
+ export declare const GLYPH_SCALE: number;
23
+ export declare const GLYPH_TRANSFORM = "translate(64.3, 16.799999999999997) scale(0.7140000000000001)";
24
+ /** Line-art glyphs drawn in a 100×100 box centered at (50,50), as plain
25
+ * shape data. The original JSX wrapped each glyph (and some repeated
26
+ * groups) in a `<g>` that carried no attributes of its own — only React
27
+ * `key`s for list rendering — so flattening into a single primitive array
28
+ * per glyph loses no visual information. */
29
+ export declare const GLYPH_SHAPES: Record<string, ShapePrimitive[]>;
30
+ export declare const GLYPH_NAMES: string[];
31
+ /** Deterministic glyph name for a seed. */
32
+ export declare function pickGlyphName(seed: string): string;
@@ -0,0 +1,183 @@
1
+ const W = 200;
2
+ const H = 105;
3
+ const INSET = 6;
4
+ const CX = W / 2;
5
+ const CY = H / 2;
6
+ const DISC_R = 34;
7
+ const KO = "var(--color-bg)";
8
+ const PLATE_SHAPE = {
9
+ el: "rect",
10
+ attrs: { x: 0, y: 0, width: W, height: H, fill: "currentColor" }
11
+ };
12
+ const INNER_FRAME_SHAPE = {
13
+ el: "rect",
14
+ attrs: {
15
+ x: INSET,
16
+ y: INSET,
17
+ width: W - INSET * 2,
18
+ height: H - INSET * 2,
19
+ fill: "none",
20
+ stroke: KO,
21
+ "stroke-width": 1.6
22
+ }
23
+ };
24
+ const RAY_SHAPES = [
25
+ [INSET, INSET],
26
+ [W - INSET, INSET],
27
+ [INSET, H - INSET],
28
+ [W - INSET, H - INSET]
29
+ ].map(([x, y]) => {
30
+ const dx = CX - x;
31
+ const dy = CY - y;
32
+ const len = Math.hypot(dx, dy);
33
+ const stop = (len - DISC_R - 7) / len;
34
+ return {
35
+ el: "line",
36
+ attrs: { x1: x, y1: y, x2: x + dx * stop, y2: y + dy * stop, stroke: KO, "stroke-width": 1.4 }
37
+ };
38
+ });
39
+ const DISC_SHAPE = {
40
+ el: "circle",
41
+ attrs: { cx: CX, cy: CY, r: DISC_R, fill: KO }
42
+ };
43
+ const GLYPH_SCALE = DISC_R * 2 / 100 * 1.05;
44
+ const GLYPH_TRANSFORM = `translate(${CX - 50 * GLYPH_SCALE}, ${CY - 50 * GLYPH_SCALE}) scale(${GLYPH_SCALE})`;
45
+ const GLYPH_SHAPES = {
46
+ book: [
47
+ {
48
+ el: "path",
49
+ attrs: {
50
+ d: "M50 30 C42 24, 30 23, 22 26 L22 68 C30 65, 42 66, 50 72 C58 66, 70 65, 78 68 L78 26 C70 23, 58 24, 50 30 Z",
51
+ fill: "none",
52
+ stroke: "currentColor",
53
+ "stroke-width": 6,
54
+ "stroke-linejoin": "round"
55
+ }
56
+ },
57
+ { el: "line", attrs: { x1: 50, y1: 30, x2: 50, y2: 72, stroke: "currentColor", "stroke-width": 5 } },
58
+ ...[0, 1, 2].flatMap(
59
+ (i) => [
60
+ {
61
+ el: "line",
62
+ attrs: {
63
+ x1: 29,
64
+ y1: 38 + i * 10,
65
+ x2: 43,
66
+ y2: 40 + i * 10,
67
+ stroke: "currentColor",
68
+ "stroke-width": 4,
69
+ "stroke-linecap": "round"
70
+ }
71
+ },
72
+ {
73
+ el: "line",
74
+ attrs: {
75
+ x1: 57,
76
+ y1: 40 + i * 10,
77
+ x2: 71,
78
+ y2: 38 + i * 10,
79
+ stroke: "currentColor",
80
+ "stroke-width": 4,
81
+ "stroke-linecap": "round"
82
+ }
83
+ }
84
+ ]
85
+ )
86
+ ],
87
+ doc: [
88
+ {
89
+ el: "path",
90
+ attrs: { d: "M32 22 L60 22 L70 32 L70 78 L32 78 Z", fill: "none", stroke: "currentColor", "stroke-width": 6, "stroke-linejoin": "round" }
91
+ },
92
+ {
93
+ el: "path",
94
+ attrs: { d: "M60 22 L60 32 L70 32", fill: "none", stroke: "currentColor", "stroke-width": 5, "stroke-linejoin": "round" }
95
+ },
96
+ ...[0, 1, 2].map(
97
+ (i) => ({
98
+ el: "line",
99
+ attrs: { x1: 40, y1: 44 + i * 11, x2: 62, y2: 44 + i * 11, stroke: "currentColor", "stroke-width": 4, "stroke-linecap": "round" }
100
+ })
101
+ )
102
+ ],
103
+ bookmark: [
104
+ {
105
+ el: "path",
106
+ attrs: { d: "M34 22 L66 22 L66 78 L50 64 L34 78 Z", fill: "none", stroke: "currentColor", "stroke-width": 6, "stroke-linejoin": "round" }
107
+ },
108
+ { el: "line", attrs: { x1: 42, y1: 36, x2: 58, y2: 36, stroke: "currentColor", "stroke-width": 4, "stroke-linecap": "round" } }
109
+ ],
110
+ compass: [
111
+ { el: "circle", attrs: { cx: 50, cy: 50, r: 26, fill: "none", stroke: "currentColor", "stroke-width": 6 } },
112
+ { el: "path", attrs: { d: "M60 40 L54 56 L40 60 L46 44 Z", fill: "currentColor" } }
113
+ ],
114
+ terminal: [
115
+ { el: "rect", attrs: { x: 24, y: 30, width: 52, height: 40, rx: 5, fill: "none", stroke: "currentColor", "stroke-width": 6 } },
116
+ {
117
+ el: "path",
118
+ attrs: { d: "M33 42 L41 50 L33 58", fill: "none", stroke: "currentColor", "stroke-width": 5, "stroke-linecap": "round", "stroke-linejoin": "round" }
119
+ },
120
+ { el: "line", attrs: { x1: 47, y1: 58, x2: 60, y2: 58, stroke: "currentColor", "stroke-width": 5, "stroke-linecap": "round" } }
121
+ ],
122
+ bulb: [
123
+ {
124
+ el: "path",
125
+ attrs: {
126
+ d: "M50 22 C37 22, 30 32, 30 41 C30 49, 35 53, 39 58 L39 64 L61 64 L61 58 C65 53, 70 49, 70 41 C70 32, 63 22, 50 22 Z",
127
+ fill: "none",
128
+ stroke: "currentColor",
129
+ "stroke-width": 6,
130
+ "stroke-linejoin": "round"
131
+ }
132
+ },
133
+ { el: "line", attrs: { x1: 42, y1: 71, x2: 58, y2: 71, stroke: "currentColor", "stroke-width": 5, "stroke-linecap": "round" } },
134
+ { el: "line", attrs: { x1: 45, y1: 78, x2: 55, y2: 78, stroke: "currentColor", "stroke-width": 5, "stroke-linecap": "round" } }
135
+ ],
136
+ chat: [
137
+ {
138
+ el: "path",
139
+ attrs: { d: "M26 28 L74 28 L74 62 L48 62 L36 74 L36 62 L26 62 Z", fill: "none", stroke: "currentColor", "stroke-width": 6, "stroke-linejoin": "round" }
140
+ },
141
+ ...[0, 1, 2].map((i) => ({ el: "circle", attrs: { cx: 38 + i * 12, cy: 45, r: 3, fill: "currentColor" } }))
142
+ ],
143
+ layers: [
144
+ { el: "path", attrs: { d: "M50 22 L78 36 L50 50 L22 36 Z", fill: "none", stroke: "currentColor", "stroke-width": 6, "stroke-linejoin": "round" } },
145
+ {
146
+ el: "path",
147
+ attrs: { d: "M25 47 L50 60 L75 47", fill: "none", stroke: "currentColor", "stroke-width": 6, "stroke-linecap": "round", "stroke-linejoin": "round" }
148
+ },
149
+ {
150
+ el: "path",
151
+ attrs: { d: "M25 58 L50 71 L75 58", fill: "none", stroke: "currentColor", "stroke-width": 6, "stroke-linecap": "round", "stroke-linejoin": "round" }
152
+ }
153
+ ]
154
+ };
155
+ const GLYPH_NAMES = Object.keys(GLYPH_SHAPES);
156
+ function hashString(str) {
157
+ let h = 2166136261 >>> 0;
158
+ for (let i = 0; i < str.length; i++) {
159
+ h ^= str.charCodeAt(i);
160
+ h = Math.imul(h, 16777619);
161
+ }
162
+ return h >>> 0;
163
+ }
164
+ function pickGlyphName(seed) {
165
+ return GLYPH_NAMES[hashString(seed) % GLYPH_NAMES.length];
166
+ }
167
+ export {
168
+ CX,
169
+ CY,
170
+ DISC_R,
171
+ DISC_SHAPE,
172
+ GLYPH_NAMES,
173
+ GLYPH_SCALE,
174
+ GLYPH_SHAPES,
175
+ GLYPH_TRANSFORM,
176
+ H,
177
+ INNER_FRAME_SHAPE,
178
+ INSET,
179
+ PLATE_SHAPE,
180
+ RAY_SHAPES,
181
+ W,
182
+ pickGlyphName
183
+ };
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Render the deterministic AutoLogo design as a standalone, mask-friendly
3
+ * SVG string. Same seed always produces byte-identical output; glyph
4
+ * selection reuses {@link pickGlyphName} so an ejected file shows the same
5
+ * glyph as `logo: "auto"` mode.
6
+ */
7
+ export declare function renderAutoLogoStandaloneSvg(seed: string): string;
@@ -0,0 +1,35 @@
1
+ import {
2
+ W,
3
+ H,
4
+ PLATE_SHAPE,
5
+ INNER_FRAME_SHAPE,
6
+ RAY_SHAPES,
7
+ DISC_SHAPE,
8
+ GLYPH_SHAPES,
9
+ GLYPH_TRANSFORM,
10
+ pickGlyphName
11
+ } from "./shapes.js";
12
+ const MASK_ID = "auto-logo-mask";
13
+ const MASK_COLORS = {
14
+ currentColor: "#fff",
15
+ "var(--color-bg)": "#000"
16
+ };
17
+ function escapeAttr(value) {
18
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
19
+ }
20
+ function renderShape(shape) {
21
+ const attrs = Object.entries(shape.attrs).map(([key, value]) => {
22
+ const resolved = typeof value === "string" && value in MASK_COLORS ? MASK_COLORS[value] : value;
23
+ return `${key}="${escapeAttr(String(resolved))}"`;
24
+ }).join(" ");
25
+ return `<${shape.el} ${attrs} />`;
26
+ }
27
+ function renderAutoLogoStandaloneSvg(seed) {
28
+ const glyphName = pickGlyphName(seed);
29
+ const glyphShapes = GLYPH_SHAPES[glyphName];
30
+ const maskContent = renderShape(PLATE_SHAPE) + renderShape(INNER_FRAME_SHAPE) + RAY_SHAPES.map(renderShape).join("") + renderShape(DISC_SHAPE) + `<g transform="${escapeAttr(GLYPH_TRANSFORM)}">${glyphShapes.map(renderShape).join("")}</g>`;
31
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" data-auto-logo="${glyphName}"><mask id="${MASK_ID}" mask-type="luminance" maskUnits="userSpaceOnUse" maskContentUnits="userSpaceOnUse" x="0" y="0" width="${W}" height="${H}">` + maskContent + `</mask><rect x="0" y="0" width="${W}" height="${H}" fill="#000" mask="url(#${MASK_ID})" /></svg>`;
32
+ }
33
+ export {
34
+ renderAutoLogoStandaloneSvg
35
+ };
package/dist/config.d.ts CHANGED
@@ -92,6 +92,14 @@ export interface ZudoDocConfig {
92
92
  * @default ""
93
93
  */
94
94
  siteDescription?: string;
95
+ /**
96
+ * Home-hero logo. `"auto"` renders a generated deterministic SVG seeded by
97
+ * `siteName` (no asset needed); a path string (e.g. `"/img/logo.svg"`)
98
+ * renders that asset as a theme-adaptive CSS mask; `false` hides the logo
99
+ * block entirely.
100
+ * @default "auto"
101
+ */
102
+ logo?: string | false;
95
103
  /**
96
104
  * Public URL sub-path prefix mounted in front of every absolute asset URL
97
105
  * (e.g. `"/pj/my-site/"`). `"/"` = root-mounted.
package/dist/config.js CHANGED
@@ -13,6 +13,7 @@ const DEFAULT_SETTINGS = {
13
13
  },
14
14
  siteName: "Docs",
15
15
  siteDescription: "",
16
+ logo: "auto",
16
17
  base: "/",
17
18
  trailingSlash: false,
18
19
  home: { wide: false },
package/dist/content.css CHANGED
@@ -144,12 +144,13 @@
144
144
  /* ── Links ── */
145
145
 
146
146
  /* Link reset inside site-nav (component can't detect ancestor context) */
147
- .zd-content [data-site-nav] a {
147
+ /* Reset must stay specificity-0 (:where()) so island utility classes like hover:text-accent control color */
148
+ :where(.zd-content [data-site-nav] a) {
148
149
  color: inherit;
149
150
  text-decoration: none;
150
151
  }
151
- .zd-content [data-site-nav] a:hover,
152
- .zd-content [data-site-nav] a:focus-visible {
152
+ :where(.zd-content [data-site-nav] a):hover,
153
+ :where(.zd-content [data-site-nav] a):focus-visible {
153
154
  text-decoration: underline;
154
155
  }
155
156
 
@@ -19,7 +19,7 @@ function createDocContentHeader(ctx) {
19
19
  version
20
20
  }) {
21
21
  return /* @__PURE__ */ jsxs(Fragment, { children: [
22
- /* @__PURE__ */ jsx("h1", { class: "text-heading font-bold mb-vsp-xs", children: entry.data.title }),
22
+ /* @__PURE__ */ jsx("h1", { class: "text-heading font-bold border-b border-fg pb-vsp-xs mb-vsp-xs", children: entry.data.title }),
23
23
  docContentHeaderExtras?.({
24
24
  entry,
25
25
  slug,
@@ -339,7 +339,7 @@ function DocHistory({ slug, locale, basePath = "/" }) {
339
339
  {
340
340
  type: "button",
341
341
  onClick: handleOpen,
342
- className: "doc-history-trigger flex items-center gap-hsp-xs px-hsp-md py-vsp-xs rounded-lg bg-surface border border-muted text-muted hover:text-fg hover:border-fg transition-colors",
342
+ className: "doc-history-trigger flex items-center gap-hsp-xs px-hsp-md py-vsp-xs rounded-lg bg-surface border border-muted text-muted hover:text-accent hover:border-accent focus-visible:text-accent focus-visible:border-accent transition-colors",
343
343
  "aria-label": "View document history",
344
344
  children: [
345
345
  /* @__PURE__ */ jsx(History, { className: "h-icon-md w-icon-md" }),
@@ -133,7 +133,7 @@ function createDocPageShell(ctx) {
133
133
  Fragment (not <div>) so children become direct children of
134
134
  <article class="zd-content">, picking up the flow-space rule. */
135
135
  /* @__PURE__ */ jsxs(Fragment, { children: [
136
- /* @__PURE__ */ jsx("h1", { class: "text-heading font-bold mb-vsp-xs", children: autoIndexLabel }),
136
+ /* @__PURE__ */ jsx("h1", { class: "text-heading font-bold border-b border-fg pb-vsp-xs mb-vsp-xs", children: autoIndexLabel }),
137
137
  metainfoSlot,
138
138
  description && /* @__PURE__ */ jsx("p", { class: "mb-vsp-lg text-title text-muted", "data-doc-description": true, children: description }),
139
139
  /* @__PURE__ */ jsx(NavCardGrid, { children: autoIndexChildren ?? [] })
@@ -12,7 +12,7 @@ function createDocPager(ctx) {
12
12
  href: prev.href,
13
13
  class: "group border border-muted rounded-lg p-hsp-lg hover:border-accent",
14
14
  children: [
15
- /* @__PURE__ */ jsxs("div", { class: "flex items-center gap-hsp-xs text-caption text-muted mb-vsp-2xs", children: [
15
+ /* @__PURE__ */ jsxs("div", { class: "flex items-center gap-hsp-xs text-caption text-muted mb-vsp-2xs group-hover:text-accent group-focus-visible:text-accent", children: [
16
16
  /* @__PURE__ */ jsx(ChevronLeft, { className: "h-[1.125rem] w-[1.125rem]" }),
17
17
  /* @__PURE__ */ jsx("span", { class: "no-underline", children: t("nav.previous", locale) })
18
18
  ] }),
@@ -26,7 +26,7 @@ function createDocPager(ctx) {
26
26
  href: next.href,
27
27
  class: "group border border-muted rounded-lg p-hsp-lg hover:border-accent text-right",
28
28
  children: [
29
- /* @__PURE__ */ jsxs("div", { class: "flex items-center justify-end gap-hsp-xs text-caption text-muted mb-vsp-2xs", children: [
29
+ /* @__PURE__ */ jsxs("div", { class: "flex items-center justify-end gap-hsp-xs text-caption text-muted mb-vsp-2xs group-hover:text-accent group-focus-visible:text-accent", children: [
30
30
  /* @__PURE__ */ jsx("span", { class: "no-underline", children: t("nav.next", locale) }),
31
31
  /* @__PURE__ */ jsx(ChevronRight, { className: "h-[1.125rem] w-[1.125rem]" })
32
32
  ] }),
@@ -0,0 +1,25 @@
1
+ import { type TopLevelMember } from "../theme-cli/config-scanner.js";
2
+ export interface ParsedZudoDocConfig {
3
+ ok: true;
4
+ members: TopLevelMember[];
5
+ /** Index of the zudoDoc({ ... }) call's opening `{`. */
6
+ braceOpenIdx: number;
7
+ /** Index of the matching closing `}`. */
8
+ braceCloseIdx: number;
9
+ }
10
+ export interface ParsedZudoDocConfigRefusal {
11
+ ok: false;
12
+ /** Human-readable WHY, with no field-specific hint appended — callers own
13
+ * wording their own remediation (a read-only lookup vs. a write refusal
14
+ * need different follow-up text). */
15
+ reason: string;
16
+ }
17
+ export type ParseZudoDocConfigResult = ParsedZudoDocConfig | ParsedZudoDocConfigRefusal;
18
+ /**
19
+ * Locate and validate the project's `zfb.config.ts` canonical shape — a
20
+ * single `zudoDoc({ ... })` call whose sole argument is a literal,
21
+ * non-spread object — and return its top-level members. Mirrors
22
+ * `theme-cli/config-rewriter.ts#applyThemePackToConfigSource`'s validation
23
+ * exactly, generalized to any caller field.
24
+ */
25
+ export declare function parseZudoDocConfigMembers(source: string): ParseZudoDocConfigResult;
@@ -0,0 +1,68 @@
1
+ import {
2
+ ConfigSyntaxError,
3
+ findMatchingBrace,
4
+ findZudoDocCallParens,
5
+ skipWhitespaceAndComments,
6
+ splitTopLevelMembers
7
+ } from "../theme-cli/config-scanner.js";
8
+ function parseZudoDocConfigMembers(source) {
9
+ let calls;
10
+ try {
11
+ calls = findZudoDocCallParens(source);
12
+ } catch (err) {
13
+ return { ok: false, reason: `could not parse zfb.config.ts (${err.message}).` };
14
+ }
15
+ if (calls.length === 0) {
16
+ return {
17
+ ok: false,
18
+ reason: "no zudoDoc(...) call found in zfb.config.ts. This CLI only reads/rewrites the canonical generated shape: export default defineConfig(zudoDoc({ ... }))."
19
+ };
20
+ }
21
+ if (calls.length > 1) {
22
+ return {
23
+ ok: false,
24
+ reason: `found ${calls.length} zudoDoc(...) call sites in zfb.config.ts \u2014 cannot safely determine which one to use.`
25
+ };
26
+ }
27
+ const openParenIdx = calls[0];
28
+ let braceOpenIdx;
29
+ let braceCloseIdx;
30
+ let afterBrace;
31
+ try {
32
+ braceOpenIdx = skipWhitespaceAndComments(source, openParenIdx + 1);
33
+ if (source[braceOpenIdx] !== "{") {
34
+ return {
35
+ ok: false,
36
+ reason: "zudoDoc(...) is not called with a plain object literal (found a computed or non-literal argument instead of zudoDoc({ ... })). This CLI only reads/rewrites the canonical generated shape."
37
+ };
38
+ }
39
+ braceCloseIdx = findMatchingBrace(source, braceOpenIdx);
40
+ afterBrace = skipWhitespaceAndComments(source, braceCloseIdx + 1);
41
+ } catch (err) {
42
+ return { ok: false, reason: `could not parse zfb.config.ts (${err.message}).` };
43
+ }
44
+ const afterOptionalComma = source[afterBrace] === "," ? skipWhitespaceAndComments(source, afterBrace + 1) : afterBrace;
45
+ if (source[afterOptionalComma] !== ")") {
46
+ return {
47
+ ok: false,
48
+ reason: "zudoDoc(...) is called with more than one argument \u2014 not the canonical single-object-literal shape."
49
+ };
50
+ }
51
+ let members;
52
+ try {
53
+ members = splitTopLevelMembers(source, braceOpenIdx + 1, braceCloseIdx);
54
+ } catch (err) {
55
+ return { ok: false, reason: `could not parse zfb.config.ts (${err.message}).` };
56
+ }
57
+ const spread = members.find((m) => m.isSpread);
58
+ if (spread) {
59
+ return {
60
+ ok: false,
61
+ reason: "zudoDoc({ ...spread }) uses a spread argument \u2014 not the canonical generated shape. This CLI only reads/rewrites a literal, hand-editable field list."
62
+ };
63
+ }
64
+ return { ok: true, members, braceOpenIdx, braceCloseIdx };
65
+ }
66
+ export {
67
+ parseZudoDocConfigMembers
68
+ };