@takazudo/zudo-doc 5.24.2 → 5.25.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 (52) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/asset-components/asset-card.js +2 -2
  3. package/dist/asset-components/asset-code.js +1 -1
  4. package/dist/asset-index-page/body.d.ts +80 -0
  5. package/dist/asset-index-page/body.js +116 -0
  6. package/dist/asset-index-page/index.d.ts +3 -9
  7. package/dist/asset-index-page/index.js +18 -149
  8. package/dist/asset-index-page/tree.d.ts +24 -0
  9. package/dist/asset-index-page/tree.js +43 -0
  10. package/dist/asset-page/body.d.ts +98 -0
  11. package/dist/asset-page/body.js +107 -0
  12. package/dist/asset-page/components.d.ts +94 -0
  13. package/dist/asset-page/components.js +175 -0
  14. package/dist/asset-page/index.d.ts +3 -78
  15. package/dist/asset-page/index.js +30 -240
  16. package/dist/asset-page/shared.d.ts +15 -0
  17. package/dist/asset-page/shared.js +28 -0
  18. package/dist/breadcrumb/breadcrumb.js +1 -1
  19. package/dist/compiled.css +15 -40
  20. package/dist/config/component-tokens.js +3 -1
  21. package/dist/config.d.ts +10 -0
  22. package/dist/config.js +1 -0
  23. package/dist/doc-pager/index.js +2 -2
  24. package/dist/footer/footer.js +3 -3
  25. package/dist/header-with-defaults/index.d.ts +20 -0
  26. package/dist/header-with-defaults/index.js +9 -2
  27. package/dist/home-page/index.js +48 -21
  28. package/dist/i18n-defaults/index.js +3 -0
  29. package/dist/mdx-components/index.js +1 -1
  30. package/dist/nav-indexing/category-nav.js +1 -1
  31. package/dist/nav-indexing/category-tree-nav.js +1 -1
  32. package/dist/nav-indexing/doc-card-grid.js +2 -2
  33. package/dist/nav-indexing/docs-sitemap.js +2 -2
  34. package/dist/nav-indexing/nav-card-grid.d.ts +3 -2
  35. package/dist/nav-indexing/nav-card-grid.js +3 -3
  36. package/dist/nav-indexing/note-tray-index-parts/card-list.js +1 -1
  37. package/dist/nav-indexing/note-tray-index-parts/index-list.js +1 -1
  38. package/dist/nav-indexing/note-tray-index-parts/timeline.js +1 -1
  39. package/dist/nav-indexing/site-tree-nav-demo.js +2 -2
  40. package/dist/nav-indexing/versions-page-content.js +2 -2
  41. package/dist/plugins/routes.js +2 -13
  42. package/dist/safelist.css +1 -1
  43. package/dist/settings.d.ts +2 -0
  44. package/dist/site-tree-nav-island/index.js +3 -3
  45. package/dist/theme-packs/foundry/pack.css +4 -9
  46. package/eject/asset-components/asset-card.tsx +2 -2
  47. package/eject/asset-components/asset-code.tsx +1 -1
  48. package/eject/breadcrumb/breadcrumb.tsx +1 -1
  49. package/eject/doc-pager/index.tsx +2 -2
  50. package/eject/footer/footer.tsx +4 -4
  51. package/eject/site-tree-nav-island/index.tsx +4 -4
  52. package/package.json +37 -9
@@ -1,203 +1,36 @@
1
1
  import { Fragment, jsx, jsxs } from "preact/jsx-runtime";
2
- import { BodyFootUtilArea } from "../body-foot-util/index.js";
3
2
  import { deriveBodyEndIslands, deriveDateFormats } from "../chrome/derive.js";
4
3
  import { deriveComposeMetaTitle } from "../chrome/derive.js";
5
4
  import { derivePrimaryChromeSlots } from "../chrome/primary-slots.js";
6
5
  import { assertChromeContext } from "../chrome/assert-chrome-context.js";
7
6
  import { DocLayoutWithDefaults } from "../doclayout/index.js";
8
- import { formatDate } from "../format-date/index.js";
9
7
  import { buildGitHubSourceUrl } from "../github-helpers/index.js";
10
8
  import { createHeadWithDefaults } from "../head-with-defaults/index.js";
11
9
  import { assetRawHref, assetViewerHref } from "../asset-path/index.js";
12
- import { ChevronLeft, ChevronRight } from "../icons/index.js";
13
- import { formatAssetBytes } from "../asset-components/index.js";
14
10
  import { resolveThemePackSsrSlug } from "../theme/theme-pack-provider.js";
15
- import { ASSET_DETAILS_PREPAINT_SCRIPT, ASSET_PAGE_SCRIPT } from "./script.js";
11
+ import { AssetPageBody, renderAssetDetailsPrepaintScript, resolveAssetPageLabels } from "./body.js";
16
12
  import {
17
13
  ASSET_DETAILS_HIDDEN_ATTR,
18
- ASSET_DETAILS_PREPAINT_SCRIPT as ASSET_DETAILS_PREPAINT_SCRIPT2,
14
+ ASSET_DETAILS_PREPAINT_SCRIPT,
19
15
  ASSET_DETAILS_STORAGE_KEY,
20
- ASSET_PAGE_SCRIPT as ASSET_PAGE_SCRIPT2
16
+ ASSET_PAGE_SCRIPT
21
17
  } from "./script.js";
22
- function formatDuration(seconds) {
23
- const rounded = Math.round(seconds);
24
- const minutes = Math.floor(rounded / 60);
25
- const remainder = (rounded % 60).toString().padStart(2, "0");
26
- return `${minutes}:${remainder}`;
27
- }
28
- function kindLabel(asset) {
29
- if (asset.language) return asset.language;
30
- return asset.mime.split("/").at(-1)?.toUpperCase() ?? asset.kind.toUpperCase();
31
- }
32
- function facetLabel(asset, linesLabel) {
33
- if (asset.lines !== void 0) return linesLabel ?? `${asset.lines} lines`;
34
- if (asset.kind === "video" && asset.durationSec !== void 0) {
35
- return formatDuration(asset.durationSec);
36
- }
37
- if (asset.width !== void 0 && asset.height !== void 0) return `${asset.width} \xD7 ${asset.height}`;
38
- if (asset.durationSec !== void 0) return formatDuration(asset.durationSec);
39
- return null;
40
- }
41
- function AssetEyebrow({ asset, badge }) {
42
- return /* @__PURE__ */ jsxs("div", { class: "mb-vsp-xs flex flex-wrap items-center gap-hsp-xs text-micro tracking-wide uppercase", children: [
43
- /* @__PURE__ */ jsx("span", { class: "rounded-full border border-muted px-hsp-sm py-vsp-3xs text-fg", children: badge }),
44
- /* @__PURE__ */ jsx("span", { class: "rounded-full border border-muted px-hsp-sm py-vsp-3xs text-muted", children: kindLabel(asset) })
45
- ] });
46
- }
47
- function AssetHeader({ asset, locale, badge, updatedLabel, linesLabel, fullPattern }) {
48
- const facet = facetLabel(asset, linesLabel);
49
- return /* @__PURE__ */ jsxs("header", { children: [
50
- /* @__PURE__ */ jsx(AssetEyebrow, { asset, badge }),
51
- /* @__PURE__ */ jsx("h1", { class: "mb-vsp-xs border-b border-fg pb-vsp-xs font-mono text-heading font-bold leading-tight break-words", children: asset.name }),
52
- /* @__PURE__ */ jsxs("div", { "data-doc-metainfo": true, class: "mb-vsp-md flex flex-wrap items-center gap-x-hsp-md gap-y-vsp-2xs text-caption text-fg", children: [
53
- asset.dir && /* @__PURE__ */ jsx("span", { children: asset.dir }),
54
- facet && /* @__PURE__ */ jsx("span", { children: facet }),
55
- /* @__PURE__ */ jsx("span", { children: formatAssetBytes(asset.bytes) }),
56
- asset.updatedDate && /* @__PURE__ */ jsxs("span", { children: [
57
- updatedLabel,
58
- " ",
59
- formatDate(asset.updatedDate, locale, fullPattern)
60
- ] }),
61
- asset.author && /* @__PURE__ */ jsx("span", { children: asset.author })
62
- ] }),
63
- asset.description && /* @__PURE__ */ jsx("p", { class: "mb-vsp-lg text-title text-muted", "data-doc-description": true, children: asset.description })
64
- ] });
65
- }
66
- function actionClass(primary = false) {
67
- return `${primary ? "border-accent bg-accent text-bg" : "border-muted bg-surface text-fg"} inline-flex items-center justify-center rounded border px-hsp-md py-vsp-2xs text-caption font-medium hover:border-accent focus-visible:border-accent`;
68
- }
69
- function AssetActions({ rawUrl, downloadLabel, openRawLabel, copyLabel, wrapLabel, code = false, bottom = false }) {
70
- return /* @__PURE__ */ jsxs("div", { class: `${bottom ? "mt-vsp-lg border-t border-muted pt-vsp-md" : "mb-vsp-md"} flex flex-wrap gap-hsp-sm`, "data-zd-asset-actions": true, children: [
71
- /* @__PURE__ */ jsx("a", { download: true, href: rawUrl, class: `${actionClass(true)} hover:underline focus-visible:underline`, children: downloadLabel }),
72
- /* @__PURE__ */ jsx("a", { href: rawUrl, "data-zfb-reload": true, target: "_blank", rel: "noopener", class: `${actionClass()} hover:underline focus-visible:underline`, children: openRawLabel }),
73
- code && /* @__PURE__ */ jsx("button", { type: "button", disabled: true, "data-zd-asset-action": "copy", class: actionClass(), children: copyLabel }),
74
- code && /* @__PURE__ */ jsx("button", { type: "button", disabled: true, "data-zd-asset-action": "wrap", class: actionClass(), children: wrapLabel })
75
- ] });
76
- }
77
- function AssetCodeBody({ asset, copyLabel, wrapLabel, truncatedLabel, linesLabel }) {
78
- const highlightedCode = asset.html?.match(/^<pre\b[^>]*>\s*(<code\b[\s\S]*<\/code>)\s*<\/pre>$/)?.[1] ?? "";
79
- return /* @__PURE__ */ jsxs("section", { children: [
80
- /* @__PURE__ */ jsxs("div", { class: "zd-asset-filebar flex flex-wrap items-center justify-between gap-hsp-sm border border-muted bg-surface px-hsp-md py-vsp-2xs text-caption", children: [
81
- /* @__PURE__ */ jsxs("span", { class: "font-mono", children: [
82
- asset.name,
83
- " \xB7 ",
84
- linesLabel ?? `${asset.lines ?? 0} lines`,
85
- " \xB7 ",
86
- formatAssetBytes(asset.bytes),
87
- " \xB7 ",
88
- kindLabel(asset)
89
- ] }),
90
- /* @__PURE__ */ jsxs("span", { class: "flex gap-hsp-sm", children: [
91
- /* @__PURE__ */ jsx("button", { type: "button", disabled: true, "data-zd-asset-action": "copy", class: "text-fg hover:text-accent focus-visible:text-accent", children: copyLabel }),
92
- /* @__PURE__ */ jsx("button", { type: "button", disabled: true, "data-zd-asset-action": "wrap", class: "text-fg hover:text-accent focus-visible:text-accent", children: wrapLabel })
93
- ] })
94
- ] }),
95
- /* @__PURE__ */ jsx("pre", { class: "hi-root zd-asset-code", "data-lang": asset.language ?? "text", dangerouslySetInnerHTML: { __html: highlightedCode } }),
96
- asset.truncated && /* @__PURE__ */ jsx("p", { class: "mt-vsp-xs text-caption text-muted", children: truncatedLabel })
97
- ] });
98
- }
99
- function EnlargeIcon() {
100
- return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { fill: "none", stroke: "currentColor", "stroke-linecap": "round", "stroke-linejoin": "round", "stroke-width": "2", d: "M8 3H3v5m13-5h5v5M8 21H3v-5m13 5h5v-5M3 8l6-6m12 6-6-6M3 16l6 6m12-6-6 6" }) });
101
- }
102
- function AssetImageStage({ asset, rawUrl, labels }) {
103
- return /* @__PURE__ */ jsxs("section", { children: [
104
- /* @__PURE__ */ jsxs("div", { class: "mb-vsp-xs flex flex-wrap gap-hsp-sm text-caption", children: [
105
- /* @__PURE__ */ jsxs("div", { class: "flex rounded border border-muted", children: [
106
- /* @__PURE__ */ jsx("button", { type: "button", "aria-pressed": "true", "data-zd-asset-action": "fit", class: "px-hsp-sm py-vsp-3xs", children: labels.fit }),
107
- /* @__PURE__ */ jsx("button", { type: "button", "aria-pressed": "false", "data-zd-asset-action": "1to1", class: "border-l border-muted px-hsp-sm py-vsp-3xs", children: labels.actualSize })
108
- ] }),
109
- /* @__PURE__ */ jsxs("div", { class: "flex rounded border border-muted", children: [
110
- /* @__PURE__ */ jsx("button", { type: "button", "aria-pressed": "true", "data-zd-asset-action": "checker", class: "px-hsp-sm py-vsp-3xs", children: labels.checker }),
111
- /* @__PURE__ */ jsx("button", { type: "button", "aria-pressed": "false", "data-zd-asset-action": "dark", class: "border-l border-muted px-hsp-sm py-vsp-3xs", children: labels.dark })
112
- ] })
113
- ] }),
114
- /* @__PURE__ */ jsxs("figure", { class: "zd-enlargeable zd-asset-stage is-checker flex min-h-[20rem] items-center justify-center overflow-auto rounded border border-muted bg-surface p-hsp-lg", children: [
115
- /* @__PURE__ */ jsx("img", { src: rawUrl, alt: asset.description ?? asset.name, width: asset.width, height: asset.height }),
116
- /* @__PURE__ */ jsx("button", { type: "button", class: "zd-enlarge-btn", hidden: true, "aria-label": labels.enlarge, children: /* @__PURE__ */ jsx(EnlargeIcon, {}) })
117
- ] })
118
- ] });
119
- }
120
- function AssetVideoStage({ asset, rawUrl }) {
121
- return /* @__PURE__ */ jsx("div", { class: "zd-asset-stage flex items-center justify-center rounded border border-muted bg-surface p-hsp-lg", children: /* @__PURE__ */ jsx("video", { controls: true, preload: "metadata", src: rawUrl, width: asset.width, height: asset.height, class: "max-w-full" }) });
122
- }
123
- function AssetPdfStage({ asset, rawUrl, children }) {
124
- return /* @__PURE__ */ jsxs("section", { children: [
125
- /* @__PURE__ */ jsx("iframe", { title: asset.name, src: `${rawUrl}#view=FitH`, class: "zd-asset-pdf h-[70vh] w-full rounded border border-muted" }),
126
- /* @__PURE__ */ jsx("div", { class: "mt-vsp-sm", children })
127
- ] });
128
- }
129
- function AssetDownloadPanel({ asset, rawUrl, noPreview, downloadLabel, copyLabel }) {
130
- return /* @__PURE__ */ jsxs("section", { class: "rounded border border-dashed border-muted p-hsp-xl text-center", children: [
131
- /* @__PURE__ */ jsx("div", { "aria-hidden": "true", class: "mb-vsp-xs text-heading", children: "\u2193" }),
132
- /* @__PURE__ */ jsx("h2", { class: "font-mono text-title font-bold", children: asset.name }),
133
- /* @__PURE__ */ jsxs("p", { class: "mb-vsp-sm text-caption text-muted", children: [
134
- kindLabel(asset),
135
- " \xB7 ",
136
- formatAssetBytes(asset.bytes)
137
- ] }),
138
- /* @__PURE__ */ jsx("p", { class: "mb-vsp-md text-small text-muted", children: noPreview }),
139
- /* @__PURE__ */ jsxs("div", { class: "flex flex-wrap justify-center gap-hsp-sm", children: [
140
- /* @__PURE__ */ jsx("a", { download: true, href: rawUrl, class: `${actionClass(true)} hover:underline focus-visible:underline`, children: downloadLabel }),
141
- /* @__PURE__ */ jsx("button", { type: "button", "data-zd-asset-action": "copy-url", "data-zd-copy-url": rawUrl, class: actionClass(), children: copyLabel })
142
- ] })
143
- ] });
144
- }
145
- function AssetLinkedFrom({ asset, label }) {
146
- if (asset.linkedFrom.length === 0) return null;
147
- return /* @__PURE__ */ jsxs("section", { class: "mt-vsp-lg", children: [
148
- /* @__PURE__ */ jsx("h2", { class: "mb-vsp-xs text-title font-bold", children: label }),
149
- /* @__PURE__ */ jsx("ul", { class: "space-y-vsp-sm", children: asset.linkedFrom.map((link) => /* @__PURE__ */ jsxs("li", { class: "border-l border-muted pl-hsp-md", children: [
150
- /* @__PURE__ */ jsx("p", { class: "text-caption text-muted", children: link.crumb }),
151
- /* @__PURE__ */ jsx("a", { href: link.href, class: "text-accent hover:underline focus-visible:underline", children: link.title }),
152
- /* @__PURE__ */ jsx("p", { class: "text-caption italic text-muted", children: link.context })
153
- ] })) })
154
- ] });
155
- }
156
- function AssetDetails({ asset, labels }) {
157
- const rows = [[labels.type, asset.mime], [labels.size, formatAssetBytes(asset.bytes)], [labels.path, asset.path]];
158
- if (asset.width !== void 0 && asset.height !== void 0) rows.splice(1, 0, [labels.dimensions, `${asset.width} \xD7 ${asset.height}`]);
159
- if (asset.updatedDate) rows.push([labels.updated, asset.updatedDate]);
160
- return /* @__PURE__ */ jsxs("section", { children: [
161
- /* @__PURE__ */ jsx("h2", { class: "mb-vsp-xs text-title font-bold", children: labels.heading }),
162
- /* @__PURE__ */ jsx("dl", { "data-zd-asset-details-list": true, class: "grid grid-cols-[auto_1fr] gap-x-hsp-md gap-y-vsp-2xs text-caption", children: rows.map(([term, value]) => /* @__PURE__ */ jsxs(Fragment, { children: [
163
- /* @__PURE__ */ jsx("dt", { class: "font-medium text-muted", children: term }),
164
- /* @__PURE__ */ jsx("dd", { class: "min-w-0 break-words text-fg", children: value })
165
- ] })) })
166
- ] });
167
- }
168
- const ASSET_DETAILS_RAIL_ID = "zd-asset-details-rail";
169
- function AssetDetailsToggle({ labels }) {
170
- return /* @__PURE__ */ jsxs(
171
- "button",
172
- {
173
- type: "button",
174
- disabled: true,
175
- "data-zd-asset-details-toggle": true,
176
- "data-zd-label-collapse": labels.collapse,
177
- "data-zd-label-expand": labels.expand,
178
- "aria-controls": ASSET_DETAILS_RAIL_ID,
179
- "aria-expanded": "true",
180
- "aria-label": labels.collapse,
181
- class: "zd-asset-details-toggle hidden lg:flex fixed bottom-vsp-xl z-sidebar items-center justify-center w-[1.5rem] h-[3rem] bg-surface border border-muted border-r-0 rounded-l-DEFAULT text-muted cursor-pointer transition-colors duration-200 ease-in-out hover:text-fg disabled:cursor-default disabled:opacity-50",
182
- children: [
183
- /* @__PURE__ */ jsx("span", { "data-zd-asset-details-chevron": "collapse", children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-icon-sm w-icon-sm" }) }),
184
- /* @__PURE__ */ jsx("span", { "data-zd-asset-details-chevron": "expand", children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-icon-sm w-icon-sm" }) })
185
- ]
186
- }
187
- );
188
- }
189
- function AssetBodyLayout({ stage, details, linked, toggleLabels }) {
190
- return /* @__PURE__ */ jsxs(Fragment, { children: [
191
- /* @__PURE__ */ jsx(AssetDetailsToggle, { labels: toggleLabels }),
192
- /* @__PURE__ */ jsxs("div", { class: "zd-asset-media-grid", children: [
193
- /* @__PURE__ */ jsx("div", { class: "min-w-0", children: stage }),
194
- /* @__PURE__ */ jsxs("div", { id: ASSET_DETAILS_RAIL_ID, "data-zd-asset-details": true, class: "zd-asset-media-rail", children: [
195
- /* @__PURE__ */ jsx("div", { class: "rounded border border-muted p-hsp-lg", children: details }),
196
- linked
197
- ] })
198
- ] })
199
- ] });
200
- }
18
+ import {
19
+ AssetActions,
20
+ AssetCodeBody,
21
+ AssetDetails,
22
+ AssetDownloadPanel,
23
+ AssetEyebrow,
24
+ AssetHeader,
25
+ AssetImageStage,
26
+ AssetLinkedFrom,
27
+ AssetPdfStage,
28
+ AssetVideoStage
29
+ } from "./components.js";
30
+ import {
31
+ AssetPageBody as AssetPageBody2,
32
+ resolveAssetPageLabels as resolveAssetPageLabels2
33
+ } from "./body.js";
201
34
  function createAssetPageView(ctx) {
202
35
  assertChromeContext(ctx, "createAssetPageView");
203
36
  const settings = ctx.settings;
@@ -214,10 +47,6 @@ function createAssetPageView(ctx) {
214
47
  const dir = ctx.assetManifest?.dir ?? settings.assetViewerDir;
215
48
  const viewerUrl = assetViewerHref({ base: settings.base, routePrefix, path: asset.path, locale: localeSegment });
216
49
  const rawUrl = assetRawHref({ base: settings.base, dir, path: asset.path });
217
- const linesLabel = t("asset.lines", locale).replace(
218
- "{count}",
219
- String(asset.lines ?? 0)
220
- );
221
50
  const dirSegments = asset.dir.split("/").filter(Boolean);
222
51
  const indexUrl = ctx.withBase(`/${localeSegment ? `${localeSegment}/` : ""}${routePrefix}/`);
223
52
  const homeUrl = ctx.withBase(`/${localeSegment ? `${localeSegment}/` : ""}`);
@@ -228,62 +57,21 @@ function createAssetPageView(ctx) {
228
57
  ...dirSegments.map((label) => ({ label })),
229
58
  { label: asset.name }
230
59
  ];
231
- const linked = /* @__PURE__ */ jsx(AssetLinkedFrom, { asset, label: t("asset.linkedFrom", locale) });
232
- const detailsLabels = {
233
- heading: t("asset.details", locale),
234
- type: t("asset.type", locale),
235
- size: t("asset.size", locale),
236
- path: t("asset.path", locale),
237
- dimensions: t("asset.dimensions", locale),
238
- updated: t("doc.updated", locale)
239
- };
240
- const imageStageLabels = {
241
- fit: t("asset.fit", locale),
242
- actualSize: t("asset.actualSize", locale),
243
- checker: t("asset.checker", locale),
244
- dark: t("asset.dark", locale),
245
- enlarge: t("asset.enlarge", locale)
246
- };
247
- const details = /* @__PURE__ */ jsx(AssetDetails, { asset, labels: detailsLabels });
248
- const downloadPanel = /* @__PURE__ */ jsx(AssetDownloadPanel, { asset, rawUrl, noPreview: t("asset.noPreview", locale), downloadLabel: t("asset.download", locale), copyLabel: t("asset.copy", locale) });
249
- let stage;
250
- const isMedia = asset.previewable && asset.sniffOk && ["image", "video", "pdf"].includes(asset.kind);
251
- if (!asset.previewable || !asset.sniffOk) stage = downloadPanel;
252
- else if (asset.kind === "image") stage = /* @__PURE__ */ jsx(AssetImageStage, { asset, rawUrl, labels: imageStageLabels });
253
- else if (asset.kind === "video") stage = /* @__PURE__ */ jsx(AssetVideoStage, { asset, rawUrl });
254
- else if (asset.kind === "pdf") stage = /* @__PURE__ */ jsx(AssetPdfStage, { asset, rawUrl, children: downloadPanel });
255
- else stage = /* @__PURE__ */ jsx(AssetCodeBody, { asset, copyLabel: t("asset.copy", locale), wrapLabel: t("asset.wrap", locale), truncatedLabel: t("asset.truncated", locale), linesLabel });
256
- const detailsToggleLabels = {
257
- collapse: t("asset.detailsCollapse", locale),
258
- expand: t("asset.detailsExpand", locale)
259
- };
260
- const body = /* @__PURE__ */ jsx(AssetBodyLayout, { stage, details, linked, toggleLabels: detailsToggleLabels });
261
- const showSource = settings.bodyFootUtilArea !== false && settings.bodyFootUtilArea.viewSourceLink !== false;
262
- const railPrepaint = /* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: ASSET_DETAILS_PREPAINT_SCRIPT } });
60
+ const labels = resolveAssetPageLabels(t, locale);
61
+ const showViewSource = settings.bodyFootUtilArea !== false && settings.bodyFootUtilArea.viewSourceLink !== false;
62
+ const viewSourceUrl = buildGitHubSourceUrl(settings.githubUrl, `public/${dir}`, asset.path);
63
+ const fullPattern = dateFormatsFor(locale).full;
263
64
  return /* @__PURE__ */ jsx(DocLayoutWithDefaults, { title: composeMetaTitle(asset.name), head: /* @__PURE__ */ jsxs(Fragment, { children: [
264
- railPrepaint,
65
+ renderAssetDetailsPrepaintScript(),
265
66
  /* @__PURE__ */ jsx(HeadWithDefaults, { title: asset.name, description: asset.description, canonical: ctx.absoluteUrl(viewerUrl) })
266
- ] }), lang: locale, dataThemePack, noindex: settings.noindex, hideSidebar: true, hideToc: true, sidebarOverride: false, contentWide: true, breadcrumbOverride: /* @__PURE__ */ jsx(BreadcrumbWithDefaults, { items: breadcrumbItems }), headerOverride: /* @__PURE__ */ jsx(HeaderWithDefaults, { lang: locale, currentPath: viewerUrl, hideSidebarToggle: true }), footerOverride: /* @__PURE__ */ jsx(FooterWithDefaults, { lang: locale }), bodyEndComponents: /* @__PURE__ */ jsx(BodyEndIslands, { basePath: settings.base ?? "/", forceImageEnlarge: asset.kind === "image" && asset.previewable && asset.sniffOk }), enableClientRouter: settings.dynamicPageTransition, children: /* @__PURE__ */ jsxs("div", { class: "zd-asset-page", "data-zd-asset-page": true, children: [
267
- backLink && /* @__PURE__ */ jsx("p", { class: "mb-vsp-xs text-caption", children: /* @__PURE__ */ jsxs("a", { href: backLink.href, class: "text-muted hover:text-accent focus-visible:text-accent hover:underline focus-visible:underline", children: [
268
- "\u2190 ",
269
- t("asset.backTo", locale),
270
- " ",
271
- backLink.title
272
- ] }) }),
273
- /* @__PURE__ */ jsx(AssetHeader, { asset, locale, badge: t("asset.badge", locale), updatedLabel: t("doc.updated", locale), linesLabel, fullPattern: dateFormatsFor(locale).full }),
274
- /* @__PURE__ */ jsx(AssetActions, { rawUrl, downloadLabel: t("asset.download", locale), openRawLabel: t("asset.openRaw", locale), copyLabel: t("asset.copy", locale), wrapLabel: t("asset.wrap", locale), code: !isMedia && asset.previewable && asset.sniffOk }),
275
- body,
276
- /* @__PURE__ */ jsx(AssetActions, { rawUrl, downloadLabel: t("asset.download", locale), openRawLabel: t("asset.openRaw", locale), copyLabel: t("asset.copy", locale), wrapLabel: t("asset.wrap", locale), bottom: true }),
277
- showSource && /* @__PURE__ */ jsx(BodyFootUtilArea, { sourceUrl: buildGitHubSourceUrl(settings.githubUrl, `public/${dir}`, asset.path), viewSourceLabel: t("doc.viewSource", locale), docHistoryIsland: null }),
278
- /* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: ASSET_PAGE_SCRIPT } })
279
- ] }) });
67
+ ] }), lang: locale, dataThemePack, noindex: settings.noindex, hideSidebar: true, hideToc: true, sidebarOverride: false, contentWide: true, breadcrumbOverride: /* @__PURE__ */ jsx(BreadcrumbWithDefaults, { items: breadcrumbItems }), headerOverride: /* @__PURE__ */ jsx(HeaderWithDefaults, { lang: locale, currentPath: viewerUrl, hideSidebarToggle: true }), footerOverride: /* @__PURE__ */ jsx(FooterWithDefaults, { lang: locale }), bodyEndComponents: /* @__PURE__ */ jsx(BodyEndIslands, { basePath: settings.base ?? "/", forceImageEnlarge: asset.kind === "image" && asset.previewable && asset.sniffOk }), enableClientRouter: settings.dynamicPageTransition, children: /* @__PURE__ */ jsx(AssetPageBody, { entry: asset, locale, rawUrl, labels, fullPattern, backLink, viewSourceUrl, showViewSource }) });
280
68
  };
281
69
  }
282
70
  export {
283
71
  ASSET_DETAILS_HIDDEN_ATTR,
284
- ASSET_DETAILS_PREPAINT_SCRIPT2 as ASSET_DETAILS_PREPAINT_SCRIPT,
72
+ ASSET_DETAILS_PREPAINT_SCRIPT,
285
73
  ASSET_DETAILS_STORAGE_KEY,
286
- ASSET_PAGE_SCRIPT2 as ASSET_PAGE_SCRIPT,
74
+ ASSET_PAGE_SCRIPT,
287
75
  AssetActions,
288
76
  AssetCodeBody,
289
77
  AssetDetails,
@@ -292,7 +80,9 @@ export {
292
80
  AssetHeader,
293
81
  AssetImageStage,
294
82
  AssetLinkedFrom,
83
+ AssetPageBody2 as AssetPageBody,
295
84
  AssetPdfStage,
296
85
  AssetVideoStage,
297
- createAssetPageView
86
+ createAssetPageView,
87
+ resolveAssetPageLabels2 as resolveAssetPageLabels
298
88
  };
@@ -0,0 +1,15 @@
1
+ import type { AssetIndexEntry } from "../route-context-payload/types.js";
2
+ /** Formats a duration in seconds as `M:SS`. */
3
+ export declare function formatDuration(seconds: number): string;
4
+ /** Short kind badge for an asset: its language when known, else the MIME subtype or kind, uppercased. */
5
+ export declare function kindLabel(asset: AssetIndexEntry): string;
6
+ /**
7
+ * Secondary metadata facet shown next to the kind badge: line count, video
8
+ * duration, or image dimensions — whichever applies to the asset's kind.
9
+ * `linesLabel`, when given, is used verbatim in place of the default
10
+ * `"${lines} lines"` fallback (callers resolve the localized/templated text
11
+ * themselves).
12
+ */
13
+ export declare function facetLabel(asset: AssetIndexEntry, linesLabel?: string): string | null;
14
+ /** Tailwind classes for an asset action button/link, primary (filled) or secondary (outlined). */
15
+ export declare function actionClass(primary?: boolean): string;
@@ -0,0 +1,28 @@
1
+ function formatDuration(seconds) {
2
+ const rounded = Math.round(seconds);
3
+ const minutes = Math.floor(rounded / 60);
4
+ const remainder = (rounded % 60).toString().padStart(2, "0");
5
+ return `${minutes}:${remainder}`;
6
+ }
7
+ function kindLabel(asset) {
8
+ if (asset.language) return asset.language;
9
+ return asset.mime.split("/").at(-1)?.toUpperCase() ?? asset.kind.toUpperCase();
10
+ }
11
+ function facetLabel(asset, linesLabel) {
12
+ if (asset.lines !== void 0) return linesLabel ?? `${asset.lines} lines`;
13
+ if (asset.kind === "video" && asset.durationSec !== void 0) {
14
+ return formatDuration(asset.durationSec);
15
+ }
16
+ if (asset.width !== void 0 && asset.height !== void 0) return `${asset.width} \xD7 ${asset.height}`;
17
+ if (asset.durationSec !== void 0) return formatDuration(asset.durationSec);
18
+ return null;
19
+ }
20
+ function actionClass(primary = false) {
21
+ return `${primary ? "border-accent bg-accent text-bg" : "border-muted bg-surface text-fg"} inline-flex items-center justify-center rounded border px-hsp-md py-vsp-2xs text-caption font-medium hover:border-accent focus-visible:border-accent`;
22
+ }
23
+ export {
24
+ actionClass,
25
+ facetLabel,
26
+ formatDuration,
27
+ kindLabel
28
+ };
@@ -81,7 +81,7 @@ function Breadcrumb(props) {
81
81
  "a",
82
82
  {
83
83
  href: item.href,
84
- class: "text-muted underline hover:text-fg flex items-center gap-x-hsp-2xs",
84
+ class: "text-muted hover:text-accent hover:underline focus-visible:text-accent focus-visible:underline flex items-center gap-x-hsp-2xs",
85
85
  children: [
86
86
  i === 0 && /* @__PURE__ */ jsx(HomeIcon, {}),
87
87
  /* @__PURE__ */ jsx(SmartLabel, { label: item.label })
package/dist/compiled.css CHANGED
@@ -972,6 +972,9 @@
972
972
  .gap-x-hsp-sm {
973
973
  column-gap: var(--spacing-hsp-sm);
974
974
  }
975
+ .gap-x-hsp-xl {
976
+ column-gap: var(--spacing-hsp-xl);
977
+ }
975
978
  .gap-x-hsp-xs {
976
979
  column-gap: var(--spacing-hsp-xs);
977
980
  }
@@ -1462,6 +1465,9 @@
1462
1465
  .text-accent {
1463
1466
  color: var(--color-accent);
1464
1467
  }
1468
+ .text-accent-hover {
1469
+ color: var(--color-accent-hover);
1470
+ }
1465
1471
  .text-bg {
1466
1472
  color: var(--color-bg);
1467
1473
  }
@@ -1658,13 +1664,6 @@
1658
1664
  }
1659
1665
  }
1660
1666
  }
1661
- .group-hover\:text-accent-hover {
1662
- &:is(:where(.group):hover *) {
1663
- @media (hover: hover) {
1664
- color: var(--color-accent-hover);
1665
- }
1666
- }
1667
- }
1668
1667
  .group-hover\:text-bg {
1669
1668
  &:is(:where(.group):hover *) {
1670
1669
  @media (hover: hover) {
@@ -1686,23 +1685,11 @@
1686
1685
  }
1687
1686
  }
1688
1687
  }
1689
- .group-hover\:decoration-accent {
1690
- &:is(:where(.group):hover *) {
1691
- @media (hover: hover) {
1692
- text-decoration-color: var(--color-accent);
1693
- }
1694
- }
1695
- }
1696
1688
  .group-focus-visible\:text-accent {
1697
1689
  &:is(:where(.group):focus-visible *) {
1698
1690
  color: var(--color-accent);
1699
1691
  }
1700
1692
  }
1701
- .group-focus-visible\:text-accent-hover {
1702
- &:is(:where(.group):focus-visible *) {
1703
- color: var(--color-accent-hover);
1704
- }
1705
- }
1706
1693
  .group-focus-visible\:text-fg {
1707
1694
  &:is(:where(.group):focus-visible *) {
1708
1695
  color: var(--color-fg);
@@ -1713,11 +1700,6 @@
1713
1700
  text-decoration-line: underline;
1714
1701
  }
1715
1702
  }
1716
- .group-focus-visible\:decoration-accent {
1717
- &:is(:where(.group):focus-visible *) {
1718
- text-decoration-color: var(--color-accent);
1719
- }
1720
- }
1721
1703
  .peer-hover\:border-accent {
1722
1704
  &:is(:where(.peer):hover ~ *) {
1723
1705
  @media (hover: hover) {
@@ -1902,13 +1884,6 @@
1902
1884
  }
1903
1885
  }
1904
1886
  }
1905
- .hover\:decoration-accent {
1906
- &:hover {
1907
- @media (hover: hover) {
1908
- text-decoration-color: var(--color-accent);
1909
- }
1910
- }
1911
- }
1912
1887
  .focus\:border-accent {
1913
1888
  &:focus {
1914
1889
  border-color: var(--color-accent);
@@ -1953,11 +1928,6 @@
1953
1928
  text-decoration-line: underline;
1954
1929
  }
1955
1930
  }
1956
- .focus-visible\:decoration-accent {
1957
- &:focus-visible {
1958
- text-decoration-color: var(--color-accent);
1959
- }
1960
- }
1961
1931
  .focus-visible\:outline-2 {
1962
1932
  &:focus-visible {
1963
1933
  outline-style: var(--tw-outline-style);
@@ -2252,14 +2222,19 @@
2252
2222
  pointer-events: auto;
2253
2223
  }
2254
2224
  }
2255
- .\[\&_a\]\:text-accent {
2225
+ .\[\&_a\]\:underline {
2256
2226
  & a {
2227
+ text-decoration-line: underline;
2228
+ }
2229
+ }
2230
+ .\[\&_a\:focus-visible\]\:text-accent {
2231
+ & a:focus-visible {
2257
2232
  color: var(--color-accent);
2258
2233
  }
2259
2234
  }
2260
- .\[\&_a\]\:underline {
2261
- & a {
2262
- text-decoration-line: underline;
2235
+ .\[\&_a\:hover\]\:text-accent {
2236
+ & a:hover {
2237
+ color: var(--color-accent);
2263
2238
  }
2264
2239
  }
2265
2240
  .\[\&_li\]\:mb-0 {
@@ -126,6 +126,8 @@ const COMPONENT_TOKENS = [
126
126
  // utility (0,1,0) and only matches the styled anchor — not block or hash-link
127
127
  // variants. Default `underline` is a CSS keyword literal: no text-decoration
128
128
  // token exists in the scale (documented exception to the prefer-var-() rule).
129
+ // Prose links only (Link Color Rule, rule 1 — src/CLAUDE.md): chrome links are
130
+ // `text-fg`, so the underlined hero/footer-copyright rows (rule 2) never match.
129
131
  {
130
132
  cssVar: "--zdc-doc-link-decoration",
131
133
  selector: "a.text-accent.underline",
@@ -134,7 +136,7 @@ const COMPONENT_TOKENS = [
134
136
  component: "content-link",
135
137
  surface: "content",
136
138
  category: "typography",
137
- description: "Text decoration of styled content links (the `text-accent underline` path in ContentLink; excludes block and hash-link variants). Defaults to `underline` (byte-identical to the current utility). Set to `none` to remove underlines from content links."
139
+ description: "Text decoration of prose links only \u2014 the `text-accent underline` path in ContentLink (`a.text-accent.underline`); excludes block and hash-link variants and never matches chrome links, including the underlined home hero and footer copyright rows (those are `text-fg`). Defaults to `underline` (byte-identical to the current utility). Set to `none` to remove underlines from prose links."
138
140
  },
139
141
  // ── Admonition block ([data-admonition], content-admonition.tsx, #2460) ───
140
142
  // The base `[data-admonition]` rule in content.css has:
package/dist/config.d.ts CHANGED
@@ -155,6 +155,16 @@ export interface ZudoDocConfig {
155
155
  * @default []
156
156
  */
157
157
  siteTreeNavIgnore?: string[];
158
+ /**
159
+ * Top-level category slugs moved OUT of the package-owned home-page category
160
+ * grid on `/` and locale homes and rendered instead as one row of small
161
+ * links directly under the grid, in this list's order. Home page only — the
162
+ * `<SiteTreeNav />` MDX tag is unchanged. Slugs absent from the tree are
163
+ * skipped, `siteTreeNavIgnore` wins over this list, and a category with no
164
+ * page of its own stays in the grid.
165
+ * @default []
166
+ */
167
+ siteTreeNavSecondary?: string[];
158
168
  /**
159
169
  * Minify production HTML output from `zfb build`.
160
170
  * @default true
package/dist/config.js CHANGED
@@ -47,6 +47,7 @@ const DEFAULT_SETTINGS = {
47
47
  trailingSlash: false,
48
48
  home: { wide: false, introMarkdown: "", sitemapHeading: "" },
49
49
  siteTreeNavIgnore: [],
50
+ siteTreeNavSecondary: [],
50
51
  minifyHtml: true,
51
52
  docsDir: "src/content/docs",
52
53
  entryDocSlug: "getting-started",
@@ -19,7 +19,7 @@ function createDocPager(ctx) {
19
19
  /* @__PURE__ */ jsx(ChevronLeft, { className: "h-[1.125rem] w-[1.125rem]" }),
20
20
  /* @__PURE__ */ jsx("span", { class: "no-underline", children: t("nav.previous", locale) })
21
21
  ] }),
22
- /* @__PURE__ */ jsx("p", { class: "text-small font-semibold underline group-hover:text-accent", children: prev.label }),
22
+ /* @__PURE__ */ jsx("p", { class: "text-small font-semibold text-fg group-hover:text-accent group-hover:underline group-focus-visible:text-accent group-focus-visible:underline", children: prev.label }),
23
23
  prev.date && /* @__PURE__ */ jsx("p", { class: "text-caption text-muted", children: formatDate(prev.date, locale, dateFormatsFor(locale).full) })
24
24
  ]
25
25
  }
@@ -34,7 +34,7 @@ function createDocPager(ctx) {
34
34
  /* @__PURE__ */ jsx("span", { class: "no-underline", children: t("nav.next", locale) }),
35
35
  /* @__PURE__ */ jsx(ChevronRight, { className: "h-[1.125rem] w-[1.125rem]" })
36
36
  ] }),
37
- /* @__PURE__ */ jsx("p", { class: "text-small font-semibold underline group-hover:text-accent", children: next.label }),
37
+ /* @__PURE__ */ jsx("p", { class: "text-small font-semibold text-fg group-hover:text-accent group-hover:underline group-focus-visible:text-accent group-focus-visible:underline", children: next.label }),
38
38
  next.date && /* @__PURE__ */ jsx("p", { class: "text-caption text-muted", children: formatDate(next.date, locale, dateFormatsFor(locale).full) })
39
39
  ]
40
40
  }
@@ -6,7 +6,7 @@ function Footer(props) {
6
6
  const persistKey = props.persistKey;
7
7
  const hasColumns = linkColumns.length > 0 || tagColumns.length > 0;
8
8
  const hasCopyright = copyright.length > 0;
9
- const copyrightClass = hasColumns ? "text-center text-caption text-muted [&_a]:text-accent [&_a]:underline mt-vsp-lg border-t border-muted pt-vsp-md" : "text-center text-caption text-muted [&_a]:text-accent [&_a]:underline";
9
+ const copyrightClass = hasColumns ? "text-center text-caption text-muted [&_a]:underline [&_a:hover]:text-accent [&_a:focus-visible]:text-accent mt-vsp-lg border-t border-muted pt-vsp-md" : "text-center text-caption text-muted [&_a]:underline [&_a:hover]:text-accent [&_a:focus-visible]:text-accent";
10
10
  return /* @__PURE__ */ jsx(
11
11
  "footer",
12
12
  {
@@ -25,7 +25,7 @@ function Footer(props) {
25
25
  "a",
26
26
  {
27
27
  href: item.href,
28
- class: "text-caption text-muted hover:text-accent hover:underline focus-visible:underline",
28
+ class: "text-caption text-muted hover:text-accent hover:underline focus-visible:text-accent focus-visible:underline",
29
29
  ...item.isExternal ? {
30
30
  target: "_blank",
31
31
  rel: "noopener noreferrer"
@@ -47,7 +47,7 @@ function Footer(props) {
47
47
  "a",
48
48
  {
49
49
  href,
50
- class: "text-caption text-muted hover:text-accent hover:underline focus-visible:underline",
50
+ class: "text-caption text-muted hover:text-accent hover:underline focus-visible:text-accent focus-visible:underline",
51
51
  children: [
52
52
  "#",
53
53
  tag,
@@ -3,6 +3,7 @@
3
3
  import type { JSX } from "preact";
4
4
  import type { ChromeContext } from "../factory-context/index.js";
5
5
  import type { Settings } from "../settings.js";
6
+ import type { SidebarNavNode } from "../sidebar/types.js";
6
7
  export interface HeaderWithDefaultsProps {
7
8
  /** Active locale; defaults to the configured defaultLocale. */
8
9
  lang?: string;
@@ -16,6 +17,25 @@ export interface HeaderWithDefaultsProps {
16
17
  navSection?: string;
17
18
  /** Omit the mobile sidebar toggle and its drawer landmark. */
18
19
  hideSidebarToggle?: boolean;
20
+ /**
21
+ * Override the mobile-drawer sidebar tree passed to `SidebarToggle`,
22
+ * resolved per render against that render's `lang`/`navSection`/
23
+ * `currentVersion`.
24
+ *
25
+ * - An array is passed to `SidebarToggle` as-is — no default build, no
26
+ * version-href remapping.
27
+ * - A callback receives the resolved `lang`/`navSection`/`currentVersion`
28
+ * plus a lazy `buildDefault()` that reproduces today's default tree
29
+ * (`buildSidebarNodes(lang, navSection, currentVersion)`) only when
30
+ * called.
31
+ * - Omitted: behavior is unchanged from before this prop existed.
32
+ */
33
+ sidebarNodes?: SidebarNavNode[] | ((args: {
34
+ lang: string;
35
+ navSection: string | undefined;
36
+ currentVersion: string | undefined;
37
+ buildDefault: () => SidebarNavNode[];
38
+ }) => SidebarNavNode[]);
19
39
  }
20
40
  /** Version config entry subset the factory reads. */
21
41
  export interface HeaderVersionEntry {
@@ -39,14 +39,21 @@ function createHeaderWithDefaults(ctx) {
39
39
  currentVersion,
40
40
  currentSlug,
41
41
  navSection,
42
- hideSidebarToggle = false
42
+ hideSidebarToggle = false,
43
+ sidebarNodes: sidebarNodesProp
43
44
  } = props;
44
45
  const lang = langProp;
45
46
  const rootMenuItems = buildRootMenuItems(lang, currentVersion);
46
47
  const backToMenuLabel = t("nav.backToMenu", lang);
47
48
  const localeLinks = buildLocaleLinksForNav(currentPath, lang, locales.length);
48
49
  const themeDefaultMode = getThemeDefaultMode();
49
- const sidebarNodes = buildSidebarNodes(lang, navSection, currentVersion);
50
+ const buildDefaultSidebarNodes = () => buildSidebarNodes(lang, navSection, currentVersion);
51
+ const sidebarNodes = sidebarNodesProp === void 0 ? buildDefaultSidebarNodes() : typeof sidebarNodesProp === "function" ? sidebarNodesProp({
52
+ lang,
53
+ navSection,
54
+ currentVersion,
55
+ buildDefault: buildDefaultSidebarNodes
56
+ }) : sidebarNodesProp;
50
57
  const sidebarToggle = hideSidebarToggle ? false : Island({
51
58
  when: "visible",
52
59
  children: /* @__PURE__ */ jsx(