@takazudo/zudo-doc 4.3.0 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/CHANGELOG.md +23 -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 +16 -8
  143. package/routes-src/index.tsx +2 -1
  144. package/routes-src/locale-index.tsx +2 -1
@@ -0,0 +1,22 @@
1
+ export declare const EJECTED_LOGO_VALUE = "/img/logo.svg";
2
+ export interface ApplyLogoFieldToSourceOk {
3
+ ok: true;
4
+ /** The full rewritten file contents. Identical to the input when
5
+ * `changed` is false (idempotent re-run of an already-ejected logo). */
6
+ source: string;
7
+ changed: boolean;
8
+ mode: "replaced" | "inserted-after-site-name" | "inserted-first";
9
+ }
10
+ export interface ApplyLogoFieldToSourceRefusal {
11
+ ok: false;
12
+ /** Human-readable WHY, ending with the manual one-liner to add by hand.
13
+ * The input source is never touched when this is returned. */
14
+ reason: string;
15
+ }
16
+ export type ApplyLogoFieldToSourceResult = ApplyLogoFieldToSourceOk | ApplyLogoFieldToSourceRefusal;
17
+ /**
18
+ * Apply `logo: "/img/logo.svg"` to a `zfb.config.ts` source string. Pure and
19
+ * filesystem-free — callers own reading/writing the actual file (see
20
+ * `eject.ts`).
21
+ */
22
+ export declare function applyLogoFieldToConfigSource(source: string): ApplyLogoFieldToSourceResult;
@@ -0,0 +1,68 @@
1
+ import { isPlainStringLiteralSpan } from "../theme-cli/config-scanner.js";
2
+ import { parseZudoDocConfigMembers } from "./config-parse.js";
3
+ const EJECTED_LOGO_VALUE = "/img/logo.svg";
4
+ function manualHint() {
5
+ return `Add the field manually inside your zudoDoc({ ... }) call: logo: ${JSON.stringify(EJECTED_LOGO_VALUE)},`;
6
+ }
7
+ function refuse(reason) {
8
+ return { ok: false, reason: `${reason} ${manualHint()}` };
9
+ }
10
+ function isPlainFalseLiteralSpan(source, start, end) {
11
+ return source.slice(start, end).trim() === "false";
12
+ }
13
+ function detectIndent(source, members) {
14
+ const ref = members[0];
15
+ const refPos = ref ? ref.keyStart ?? ref.memberStart : null;
16
+ if (refPos !== null && refPos !== void 0) {
17
+ const newlineIdx = source.lastIndexOf("\n", refPos);
18
+ const lineStart = newlineIdx === -1 ? 0 : newlineIdx + 1;
19
+ const line = source.slice(lineStart, refPos);
20
+ if (/^[ \t]*$/.test(line) && line.length > 0) return line;
21
+ }
22
+ return " ";
23
+ }
24
+ function applyLogoFieldToConfigSource(source) {
25
+ const parsed = parseZudoDocConfigMembers(source);
26
+ if (!parsed.ok) return refuse(parsed.reason);
27
+ const { members, braceOpenIdx } = parsed;
28
+ const newValueLiteral = JSON.stringify(EJECTED_LOGO_VALUE);
29
+ const logoMembers = members.filter((m) => m.key === "logo");
30
+ if (logoMembers.length > 1) {
31
+ return refuse(
32
+ 'zfb.config.ts declares "logo" more than once inside zudoDoc({ ... }). This CLI only rewrites a single canonical field; remove the duplicate and re-run.'
33
+ );
34
+ }
35
+ const logoMember = logoMembers[0];
36
+ if (logoMember) {
37
+ if (logoMember.valueStart === null || logoMember.valueEnd === null) {
38
+ return refuse('the existing "logo" field in zfb.config.ts is not a simple `key: value` property this CLI understands.');
39
+ }
40
+ const isReplaceableLiteral = isPlainStringLiteralSpan(source, logoMember.valueStart, logoMember.valueEnd) || isPlainFalseLiteralSpan(source, logoMember.valueStart, logoMember.valueEnd);
41
+ if (!isReplaceableLiteral) {
42
+ return refuse(
43
+ 'the existing "logo" field\'s value is not a plain string literal or `false` \u2014 cannot safely rewrite it without risking corruption.'
44
+ );
45
+ }
46
+ const newSource2 = source.slice(0, logoMember.valueStart) + newValueLiteral + source.slice(logoMember.valueEnd);
47
+ return { ok: true, source: newSource2, changed: newSource2 !== source, mode: "replaced" };
48
+ }
49
+ const siteNameMember = members.find((m) => m.key === "siteName");
50
+ const indent = detectIndent(source, members);
51
+ if (siteNameMember) {
52
+ const insertAt2 = siteNameMember.commaIndex !== null ? siteNameMember.commaIndex + 1 : siteNameMember.memberEnd;
53
+ const prefix = siteNameMember.commaIndex !== null ? "" : ",";
54
+ const insertText2 = `${prefix}
55
+ ${indent}logo: ${newValueLiteral},`;
56
+ const newSource2 = source.slice(0, insertAt2) + insertText2 + source.slice(insertAt2);
57
+ return { ok: true, source: newSource2, changed: true, mode: "inserted-after-site-name" };
58
+ }
59
+ const insertAt = braceOpenIdx + 1;
60
+ const insertText = `
61
+ ${indent}logo: ${newValueLiteral},`;
62
+ const newSource = source.slice(0, insertAt) + insertText + source.slice(insertAt);
63
+ return { ok: true, source: newSource, changed: true, mode: "inserted-first" };
64
+ }
65
+ export {
66
+ EJECTED_LOGO_VALUE,
67
+ applyLogoFieldToConfigSource
68
+ };
@@ -0,0 +1,18 @@
1
+ export interface EjectLogoOptions {
2
+ cwd?: string;
3
+ /** Explicit seed override (already validated non-empty by the CLI arg
4
+ * parser in bin/zudo-doc.mjs). When omitted, the seed is resolved from
5
+ * zfb.config.ts's siteName field. */
6
+ seed?: string;
7
+ force?: boolean;
8
+ }
9
+ export interface EjectLogoResult {
10
+ ok: boolean;
11
+ /** Human-readable outcome message (success confirmation, or a refusal
12
+ * explaining WHY). */
13
+ message: string;
14
+ /** True when the SVG was written but the config rewrite was refused — a
15
+ * nonzero-exit partial success, not a total failure. */
16
+ partial?: boolean;
17
+ }
18
+ export declare function ejectLogo(options?: EjectLogoOptions): Promise<EjectLogoResult>;
@@ -0,0 +1,66 @@
1
+ import fs from "fs-extra";
2
+ import path from "node:path";
3
+ import { renderAutoLogoStandaloneSvg } from "../auto-logo/standalone.js";
4
+ import { applyLogoFieldToConfigSource } from "./config-rewriter.js";
5
+ import { resolveSiteNameFromConfigSource } from "./site-name.js";
6
+ const ZFB_CONFIG_FILENAME = "zfb.config.ts";
7
+ const LOGO_RELATIVE_PATH = path.join("public", "img", "logo.svg");
8
+ const DEFAULT_SEED = "Docs";
9
+ async function ejectLogo(options = {}) {
10
+ const cwd = options.cwd ?? process.cwd();
11
+ const force = options.force ?? false;
12
+ const configPath = path.join(cwd, ZFB_CONFIG_FILENAME);
13
+ if (!await fs.pathExists(configPath)) {
14
+ return {
15
+ ok: false,
16
+ message: `zfb.config.ts not found at ${configPath}.
17
+ Run this command from your project root.`
18
+ };
19
+ }
20
+ const source = await fs.readFile(configPath, "utf8");
21
+ let seed;
22
+ if (options.seed) {
23
+ seed = options.seed;
24
+ } else {
25
+ const resolution = resolveSiteNameFromConfigSource(source);
26
+ if (resolution.kind === "unresolvable") {
27
+ return {
28
+ ok: false,
29
+ message: `Could not determine a logo seed from zfb.config.ts: ${resolution.reason}
30
+ Pass --seed <name> to specify the logo seed explicitly.`
31
+ };
32
+ }
33
+ seed = resolution.kind === "literal" ? resolution.value : DEFAULT_SEED;
34
+ }
35
+ const rewriteResult = applyLogoFieldToConfigSource(source);
36
+ const svgPath = path.join(cwd, LOGO_RELATIVE_PATH);
37
+ if (!force && await fs.pathExists(svgPath)) {
38
+ return {
39
+ ok: false,
40
+ message: `${LOGO_RELATIVE_PATH} already exists.
41
+ Pass --force to overwrite it.`
42
+ };
43
+ }
44
+ const svg = renderAutoLogoStandaloneSvg(seed);
45
+ await fs.ensureDir(path.dirname(svgPath));
46
+ await fs.writeFile(svgPath, svg, "utf8");
47
+ if (!rewriteResult.ok) {
48
+ return {
49
+ ok: false,
50
+ partial: true,
51
+ message: `Wrote ${LOGO_RELATIVE_PATH}, but could not update zfb.config.ts: ${rewriteResult.reason}`
52
+ };
53
+ }
54
+ if (rewriteResult.changed) {
55
+ await fs.writeFile(configPath, rewriteResult.source, "utf8");
56
+ }
57
+ const verb = rewriteResult.changed ? "Ejected logo" : "Ejected logo (field already set)";
58
+ return {
59
+ ok: true,
60
+ message: `${verb} \u2192 ${LOGO_RELATIVE_PATH}
61
+ Updated zfb.config.ts's logo field.`
62
+ };
63
+ }
64
+ export {
65
+ ejectLogo
66
+ };
@@ -0,0 +1,4 @@
1
+ export { ejectLogo, type EjectLogoOptions, type EjectLogoResult } from "./eject.js";
2
+ export { applyLogoFieldToConfigSource, EJECTED_LOGO_VALUE, type ApplyLogoFieldToSourceOk, type ApplyLogoFieldToSourceRefusal, type ApplyLogoFieldToSourceResult, } from "./config-rewriter.js";
3
+ export { resolveSiteNameFromConfigSource, type SiteNameResolution } from "./site-name.js";
4
+ export { parseZudoDocConfigMembers, type ParsedZudoDocConfig, type ParsedZudoDocConfigRefusal, type ParseZudoDocConfigResult, } from "./config-parse.js";
@@ -0,0 +1,16 @@
1
+ import { ejectLogo } from "./eject.js";
2
+ import {
3
+ applyLogoFieldToConfigSource,
4
+ EJECTED_LOGO_VALUE
5
+ } from "./config-rewriter.js";
6
+ import { resolveSiteNameFromConfigSource } from "./site-name.js";
7
+ import {
8
+ parseZudoDocConfigMembers
9
+ } from "./config-parse.js";
10
+ export {
11
+ EJECTED_LOGO_VALUE,
12
+ applyLogoFieldToConfigSource,
13
+ ejectLogo,
14
+ parseZudoDocConfigMembers,
15
+ resolveSiteNameFromConfigSource
16
+ };
@@ -0,0 +1,21 @@
1
+ export type SiteNameResolution = {
2
+ kind: "literal";
3
+ value: string;
4
+ } | {
5
+ kind: "absent";
6
+ } | {
7
+ kind: "unresolvable";
8
+ reason: string;
9
+ };
10
+ /**
11
+ * Resolve `siteName` from a `zfb.config.ts` source string.
12
+ *
13
+ * - Literal `siteName: "..."` → `{ kind: "literal", value }`.
14
+ * - Canonical literal config with no `siteName` field → `{ kind: "absent" }`
15
+ * (the runtime default `"Docs"` genuinely applies — this IS a confirmed
16
+ * reading, the caller decides the default value).
17
+ * - Anything else (no/duplicate zudoDoc call, spread argument, computed or
18
+ * duplicate `siteName`, non-string value) → `{ kind: "unresolvable",
19
+ * reason }`; the caller must require `--seed`.
20
+ */
21
+ export declare function resolveSiteNameFromConfigSource(source: string): SiteNameResolution;
@@ -0,0 +1,31 @@
1
+ import { isPlainStringLiteralSpan, unquoteStringLiteralSpan } from "../theme-cli/config-scanner.js";
2
+ import { parseZudoDocConfigMembers } from "./config-parse.js";
3
+ function resolveSiteNameFromConfigSource(source) {
4
+ const parsed = parseZudoDocConfigMembers(source);
5
+ if (!parsed.ok) return { kind: "unresolvable", reason: parsed.reason };
6
+ const siteNameMembers = parsed.members.filter((m) => m.key === "siteName");
7
+ if (siteNameMembers.length > 1) {
8
+ return {
9
+ kind: "unresolvable",
10
+ reason: 'zfb.config.ts declares "siteName" more than once inside zudoDoc({ ... }) \u2014 cannot safely determine which one the runtime reads.'
11
+ };
12
+ }
13
+ const member = siteNameMembers[0];
14
+ if (!member) return { kind: "absent" };
15
+ if (member.valueStart === null || member.valueEnd === null) {
16
+ return {
17
+ kind: "unresolvable",
18
+ reason: 'the "siteName" field in zfb.config.ts is not a simple `key: value` property this CLI understands.'
19
+ };
20
+ }
21
+ if (!isPlainStringLiteralSpan(source, member.valueStart, member.valueEnd)) {
22
+ return {
23
+ kind: "unresolvable",
24
+ reason: `the "siteName" field's value in zfb.config.ts is not a plain string literal.`
25
+ };
26
+ }
27
+ return { kind: "literal", value: unquoteStringLiteralSpan(source, member.valueStart, member.valueEnd) };
28
+ }
29
+ export {
30
+ resolveSiteNameFromConfigSource
31
+ };
@@ -6,6 +6,14 @@ import {
6
6
  pathForMatch
7
7
  } from "./nav-active.js";
8
8
  import { NAV_OVERFLOW_SCRIPT } from "./nav-overflow-script.js";
9
+ import {
10
+ NAV_CHEVRON_ACTIVE,
11
+ NAV_CHEVRON_INACTIVE,
12
+ NAV_CHILD_ACTIVE,
13
+ NAV_CHILD_INACTIVE,
14
+ NAV_TOP_ACTIVE,
15
+ NAV_TOP_INACTIVE
16
+ } from "./nav-class-tokens.js";
9
17
  import { LANGUAGE_SWITCHER_INIT_SCRIPT } from "../i18n-version/language-switcher.js";
10
18
  import { VERSION_SWITCHER_REWIRE_SCRIPT } from "../i18n-version/version-switcher.js";
11
19
  import { GitHub as GitHubIcon } from "../icons/index.js";
@@ -81,7 +89,7 @@ function Header(props) {
81
89
  "button",
82
90
  {
83
91
  type: "button",
84
- class: "px-hsp-md py-vsp-2xs text-small font-medium text-muted hover:underline cursor-pointer",
92
+ class: "px-hsp-md py-vsp-2xs text-small font-medium text-muted hover:text-accent hover:underline focus-visible:text-accent focus-visible:underline cursor-pointer",
85
93
  "data-nav-more-toggle": true,
86
94
  "aria-expanded": "false",
87
95
  children: "\xB7\xB7\xB7"
@@ -177,7 +185,7 @@ function renderNavItem(item, activeNavPath, activeCategory, lang, currentVersion
177
185
  "aria-expanded": "false",
178
186
  class: [
179
187
  "flex items-center gap-x-hsp-xs px-hsp-md py-vsp-2xs text-small font-medium transition-colors",
180
- isActive ? "bg-fg text-bg" : "text-muted hover:underline focus:underline"
188
+ isActive ? NAV_TOP_ACTIVE.join(" ") : NAV_TOP_INACTIVE.join(" ")
181
189
  ].join(" "),
182
190
  children: [
183
191
  label,
@@ -186,7 +194,7 @@ function renderNavItem(item, activeNavPath, activeCategory, lang, currentVersion
186
194
  {
187
195
  class: [
188
196
  "h-[0.5rem] w-[0.5rem] shrink-0",
189
- isActive ? "text-bg" : "text-muted"
197
+ isActive ? NAV_CHEVRON_ACTIVE.join(" ") : NAV_CHEVRON_INACTIVE.join(" ")
190
198
  ].join(" "),
191
199
  fill: "none",
192
200
  viewBox: "0 0 24 24",
@@ -216,8 +224,8 @@ function renderNavItem(item, activeNavPath, activeCategory, lang, currentVersion
216
224
  href: childHref,
217
225
  "data-active": childActive ? "" : void 0,
218
226
  class: [
219
- "block px-hsp-md py-vsp-2xs text-small hover:bg-accent/10 hover:underline",
220
- childActive ? "font-bold text-accent" : "text-fg"
227
+ "block px-hsp-md py-vsp-2xs text-small hover:bg-accent/10 hover:underline focus-visible:underline",
228
+ childActive ? NAV_CHILD_ACTIVE.join(" ") : NAV_CHILD_INACTIVE.join(" ")
221
229
  ].join(" "),
222
230
  children: childLabel
223
231
  }
@@ -235,7 +243,7 @@ function renderNavItem(item, activeNavPath, activeCategory, lang, currentVersion
235
243
  "data-nav-item": true,
236
244
  class: [
237
245
  "px-hsp-md py-vsp-2xs text-small font-medium transition-colors shrink-0",
238
- isActive ? "bg-fg text-bg" : "text-muted hover:underline focus:underline"
246
+ isActive ? NAV_TOP_ACTIVE.join(" ") : NAV_TOP_INACTIVE.join(" ")
239
247
  ].join(" "),
240
248
  children: label
241
249
  }
@@ -0,0 +1,12 @@
1
+ export declare const NAV_TOP_ACTIVE: readonly string[];
2
+ export declare const NAV_TOP_INACTIVE: readonly string[];
3
+ export declare const NAV_CHEVRON_ACTIVE: readonly string[];
4
+ export declare const NAV_CHEVRON_INACTIVE: readonly string[];
5
+ export declare const NAV_CHILD_ACTIVE: readonly string[];
6
+ export declare const NAV_CHILD_INACTIVE: readonly string[];
7
+ export declare const NAV_MENU_PARENT: readonly string[];
8
+ export declare const NAV_MENU_PARENT_ACTIVE_SUFFIX: readonly string[];
9
+ export declare const NAV_MENU_PLAIN: readonly string[];
10
+ export declare const NAV_MENU_PLAIN_ACTIVE_SUFFIX: readonly string[];
11
+ export declare const NAV_MENU_CHILD_ACTIVE: readonly string[];
12
+ export declare const NAV_MENU_CHILD_INACTIVE: readonly string[];
@@ -0,0 +1,80 @@
1
+ const NAV_TOP_ACTIVE = ["bg-fg", "text-bg"];
2
+ const NAV_TOP_INACTIVE = [
3
+ "text-muted",
4
+ "hover:text-accent",
5
+ "hover:underline",
6
+ "focus:underline",
7
+ "focus:text-accent"
8
+ ];
9
+ const NAV_CHEVRON_ACTIVE = ["text-bg"];
10
+ const NAV_CHEVRON_INACTIVE = ["text-muted"];
11
+ const NAV_CHILD_ACTIVE = ["font-bold", "text-accent"];
12
+ const NAV_CHILD_INACTIVE = [
13
+ "text-fg",
14
+ "hover:text-accent",
15
+ "focus-visible:text-accent"
16
+ ];
17
+ const MENU_ROW_BASE = [
18
+ "block",
19
+ "px-hsp-md",
20
+ "py-vsp-2xs",
21
+ "text-small"
22
+ ];
23
+ const MENU_CHILD_BASE = [
24
+ "block",
25
+ "pl-hsp-xl",
26
+ "pr-hsp-md",
27
+ "py-vsp-2xs",
28
+ "text-small"
29
+ ];
30
+ const MENU_ROW_RESTING = [
31
+ "hover:bg-accent/10",
32
+ "hover:underline",
33
+ "focus-visible:underline",
34
+ "focus-visible:text-accent",
35
+ "text-fg",
36
+ "hover:text-accent"
37
+ ];
38
+ const NAV_MENU_PARENT = [
39
+ ...MENU_ROW_BASE,
40
+ "font-bold",
41
+ ...MENU_ROW_RESTING
42
+ ];
43
+ const NAV_MENU_PARENT_ACTIVE_SUFFIX = ["text-accent"];
44
+ const NAV_MENU_PLAIN = [
45
+ ...MENU_ROW_BASE,
46
+ ...MENU_ROW_RESTING
47
+ ];
48
+ const NAV_MENU_PLAIN_ACTIVE_SUFFIX = [
49
+ ...NAV_CHILD_ACTIVE
50
+ ];
51
+ const NAV_MENU_CHILD_ACTIVE = [
52
+ ...MENU_CHILD_BASE,
53
+ ...NAV_CHILD_ACTIVE,
54
+ "hover:bg-accent/10",
55
+ "hover:underline",
56
+ "focus-visible:underline"
57
+ ];
58
+ const NAV_MENU_CHILD_INACTIVE = [
59
+ ...MENU_CHILD_BASE,
60
+ "text-fg",
61
+ "hover:bg-accent/10",
62
+ "hover:text-accent",
63
+ "hover:underline",
64
+ "focus-visible:underline",
65
+ "focus-visible:text-accent"
66
+ ];
67
+ export {
68
+ NAV_CHEVRON_ACTIVE,
69
+ NAV_CHEVRON_INACTIVE,
70
+ NAV_CHILD_ACTIVE,
71
+ NAV_CHILD_INACTIVE,
72
+ NAV_MENU_CHILD_ACTIVE,
73
+ NAV_MENU_CHILD_INACTIVE,
74
+ NAV_MENU_PARENT,
75
+ NAV_MENU_PARENT_ACTIVE_SUFFIX,
76
+ NAV_MENU_PLAIN,
77
+ NAV_MENU_PLAIN_ACTIVE_SUFFIX,
78
+ NAV_TOP_ACTIVE,
79
+ NAV_TOP_INACTIVE
80
+ };
@@ -1,4 +1,21 @@
1
1
  import { AFTER_NAVIGATE_EVENT } from "../transitions/page-events.js";
2
+ import {
3
+ NAV_CHEVRON_ACTIVE,
4
+ NAV_CHEVRON_INACTIVE,
5
+ NAV_CHILD_ACTIVE,
6
+ NAV_CHILD_INACTIVE,
7
+ NAV_MENU_CHILD_ACTIVE,
8
+ NAV_MENU_CHILD_INACTIVE,
9
+ NAV_MENU_PARENT,
10
+ NAV_MENU_PARENT_ACTIVE_SUFFIX,
11
+ NAV_MENU_PLAIN,
12
+ NAV_MENU_PLAIN_ACTIVE_SUFFIX,
13
+ NAV_TOP_ACTIVE,
14
+ NAV_TOP_INACTIVE
15
+ } from "./nav-class-tokens.js";
16
+ const clsArgs = (tokens) => tokens.map((token) => JSON.stringify(token)).join(", ");
17
+ const clsLiteral = (tokens) => JSON.stringify(tokens.join(" "));
18
+ const clsAppend = (tokens) => JSON.stringify(" " + tokens.join(" "));
2
19
  const NAV_OVERFLOW_SCRIPT = `(function () {
3
20
  var cleanupNavOverflow = null;
4
21
 
@@ -56,12 +73,12 @@ const NAV_OVERFLOW_SCRIPT = `(function () {
56
73
  function setTopActive(a, active) {
57
74
  if (!a) return;
58
75
  if (active) {
59
- a.classList.add("bg-fg", "text-bg");
60
- a.classList.remove("text-muted", "hover:underline", "focus:underline");
76
+ a.classList.add(${clsArgs(NAV_TOP_ACTIVE)});
77
+ a.classList.remove(${clsArgs(NAV_TOP_INACTIVE)});
61
78
  a.setAttribute("aria-current", "page");
62
79
  } else {
63
- a.classList.remove("bg-fg", "text-bg");
64
- a.classList.add("text-muted", "hover:underline", "focus:underline");
80
+ a.classList.remove(${clsArgs(NAV_TOP_ACTIVE)});
81
+ a.classList.add(${clsArgs(NAV_TOP_INACTIVE)});
65
82
  a.removeAttribute("aria-current");
66
83
  }
67
84
  }
@@ -79,19 +96,19 @@ const NAV_OVERFLOW_SCRIPT = `(function () {
79
96
  if (childActive) {
80
97
  anyChild = true;
81
98
  c.setAttribute("data-active", "");
82
- c.classList.add("font-bold", "text-accent");
83
- c.classList.remove("text-fg");
99
+ c.classList.add(${clsArgs(NAV_CHILD_ACTIVE)});
100
+ c.classList.remove(${clsArgs(NAV_CHILD_INACTIVE)});
84
101
  } else {
85
102
  c.removeAttribute("data-active");
86
- c.classList.remove("font-bold", "text-accent");
87
- c.classList.add("text-fg");
103
+ c.classList.remove(${clsArgs(NAV_CHILD_ACTIVE)});
104
+ c.classList.add(${clsArgs(NAV_CHILD_INACTIVE)});
88
105
  }
89
106
  });
90
107
  topActive = parentMatch || anyChild;
91
108
  var svg = topA ? topA.querySelector("svg") : null;
92
109
  if (svg) {
93
- if (topActive) { svg.classList.add("text-bg"); svg.classList.remove("text-muted"); }
94
- else { svg.classList.add("text-muted"); svg.classList.remove("text-bg"); }
110
+ if (topActive) { svg.classList.add(${clsArgs(NAV_CHEVRON_ACTIVE)}); svg.classList.remove(${clsArgs(NAV_CHEVRON_INACTIVE)}); }
111
+ else { svg.classList.add(${clsArgs(NAV_CHEVRON_INACTIVE)}); svg.classList.remove(${clsArgs(NAV_CHEVRON_ACTIVE)}); }
95
112
  }
96
113
  } else {
97
114
  topActive = activePath !== "" && navPathname(topA) === activePath;
@@ -173,9 +190,9 @@ const NAV_OVERFLOW_SCRIPT = `(function () {
173
190
  a.href = parentLink.href;
174
191
  var parentText = parentLink.textContent ? parentLink.textContent.trim().replace(/\\s+/g, " ") : "";
175
192
  a.textContent = parentText;
176
- a.className = "block px-hsp-md py-vsp-2xs text-small font-bold hover:bg-accent/10 hover:underline focus-visible:underline text-fg";
193
+ a.className = ${clsLiteral(NAV_MENU_PARENT)};
177
194
  if (parentLink.getAttribute("aria-current") === "page") {
178
- a.className += " text-accent";
195
+ a.className += ${clsAppend(NAV_MENU_PARENT_ACTIVE_SUFFIX)};
179
196
  }
180
197
  li.appendChild(a);
181
198
  moreMenu.appendChild(li);
@@ -187,8 +204,8 @@ const NAV_OVERFLOW_SCRIPT = `(function () {
187
204
  a.textContent = child.textContent ? child.textContent.trim() : "";
188
205
  var isChildActive = child.hasAttribute("data-active");
189
206
  a.className = isChildActive
190
- ? "block pl-hsp-xl pr-hsp-md py-vsp-2xs text-small font-bold text-accent hover:bg-accent/10 hover:underline focus-visible:underline"
191
- : "block pl-hsp-xl pr-hsp-md py-vsp-2xs text-small text-muted hover:bg-accent/10 hover:text-fg hover:underline focus-visible:underline";
207
+ ? ${clsLiteral(NAV_MENU_CHILD_ACTIVE)}
208
+ : ${clsLiteral(NAV_MENU_CHILD_INACTIVE)};
192
209
  li.appendChild(a);
193
210
  moreMenu.appendChild(li);
194
211
  });
@@ -198,9 +215,9 @@ const NAV_OVERFLOW_SCRIPT = `(function () {
198
215
  var a2 = document.createElement("a");
199
216
  a2.href = anchor.href;
200
217
  a2.textContent = anchor.textContent ? anchor.textContent.trim() : "";
201
- a2.className = "block px-hsp-md py-vsp-2xs text-small hover:bg-accent/10 hover:underline focus-visible:underline text-fg";
218
+ a2.className = ${clsLiteral(NAV_MENU_PLAIN)};
202
219
  if (anchor.getAttribute("aria-current") === "page") {
203
- a2.className += " font-bold text-accent";
220
+ a2.className += ${clsAppend(NAV_MENU_PLAIN_ACTIVE_SUFFIX)};
204
221
  }
205
222
  li2.appendChild(a2);
206
223
  moreMenu.appendChild(li2);
@@ -1,6 +1,7 @@
1
1
  /** @jsxRuntime automatic */
2
2
  /** @jsxImportSource preact */
3
3
  import type { ComponentChildren, JSX } from "preact";
4
+ import type { TagItem } from "../nav-indexing/types.js";
4
5
  import type { DocNavNode } from "../doc-page-props/index.js";
5
6
  import type { ChromeContext } from "../factory-context/index.js";
6
7
  import type { Settings } from "../settings.js";
@@ -12,9 +13,10 @@ export interface HomePageViewProps {
12
13
  * `hostBindings.homeExtras({ locale })` call when `extras` is absent. */
13
14
  locale: string;
14
15
  /**
15
- * Extra content rendered after the links row, inside the hero text column.
16
- * A VALUE (already-rendered children) the host-PAGE path. Takes
17
- * precedence over `ctx.hostBindings.homeExtras` when both are present.
16
+ * Extra content rendered INLINE at the end of the links row, `/`-separated
17
+ * from whatever precedes it (the primary link and/or the GitHub link). A
18
+ * VALUE (already-rendered children) the host-PAGE path. Takes precedence
19
+ * over `ctx.hostBindings.homeExtras` when both are present.
18
20
  *
19
21
  * This value-vs-renderer asymmetry (prop = value, `hostBindings.homeExtras`
20
22
  * = renderer) is INTENTIONAL, not an oversight to "unify": a host page
@@ -24,6 +26,17 @@ export interface HomePageViewProps {
24
26
  * locale })`.
25
27
  */
26
28
  extras?: ComponentChildren;
29
+ /**
30
+ * Overrides the default first hero link, which is otherwise
31
+ * `settings.headerNav[0]` + `t("nav.overview", locale)`. `path` is
32
+ * locale-relative (the view prefixes it with the locale-URL prefix and
33
+ * resolves it through `withBase`, same as the default); `labelKey`
34
+ * resolves through `t(labelKey, locale)`.
35
+ */
36
+ heroLink?: {
37
+ path: string;
38
+ labelKey: string;
39
+ };
27
40
  /** Prepared nav tree for the `SiteTreeNav` grid (already grouped via
28
41
  * `groupSatelliteNodes` by the caller — the two routes group their own
29
42
  * locale-specific tree before handing it here). */
@@ -32,9 +45,25 @@ export interface HomePageViewProps {
32
45
  categoryOrder: string[];
33
46
  /** Root-category slugs that should start collapsed in `SiteTreeNav`. */
34
47
  initiallyCollapsedCategorySlugs?: string[];
35
- /** Unique tag count for the current locale — gates the "all tags" section
36
- * together with `settings.docTags`. */
48
+ /** Unique tag count for the current locale — gates the "Tags" section
49
+ * together with `settings.docTags`. Part of the documented public
50
+ * `@takazudo/zudo-doc/home-page` props shape (kept for back-compat). */
37
51
  tagCount: number;
52
+ /**
53
+ * Alphabetically-sorted tag list (tag + doc count + pre-resolved href) for
54
+ * the current locale. When provided (and non-empty) the section renders the
55
+ * tag chips (first {@link HomePageViewProps.tagLimit}) plus a "see all tags"
56
+ * nav. When omitted — legacy callers that pass only `tagCount` — the section
57
+ * falls back to the pre-#3027 single "All Tags" link, so existing
58
+ * `@takazudo/zudo-doc/home-page` consumers are not broken.
59
+ */
60
+ tags?: TagItem[];
61
+ /**
62
+ * Max number of tag chips rendered in the home "Tags" section before a "…"
63
+ * overflow indicator is shown. The full set stays reachable via the
64
+ * "see all tags" link. @default 30
65
+ */
66
+ tagLimit?: number;
38
67
  /**
39
68
  * Opt the home page into the **wide** content layout (full-viewport
40
69
  * category grid instead of the standard capped reading column). Threaded to