@takazudo/zudo-doc 2.0.0 → 2.1.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.
@@ -0,0 +1,262 @@
1
+ const COMPONENT_TOKENS = [
2
+ {
3
+ cssVar: "--zdc-doc-title-font",
4
+ selector: "h1.text-heading",
5
+ property: "font-family",
6
+ default: "inherit",
7
+ component: "doc-title",
8
+ surface: "content",
9
+ category: "typography",
10
+ description: "Font family of the doc-page title <h1> (and the other page-title h1s that share `text-heading`). Defaults to `inherit` so the title keeps the host's brand heading font; redefine in :root to rebrand."
11
+ },
12
+ {
13
+ cssVar: "--zdc-doc-title-weight",
14
+ selector: "h1.text-heading",
15
+ property: "font-weight",
16
+ default: "var(--font-weight-bold)",
17
+ component: "doc-title",
18
+ surface: "content",
19
+ category: "typography",
20
+ description: "Font weight of the doc-page title <h1> (and the other page-title h1s that share `text-heading`). Defaults to the `--font-weight-bold` token (matching the original `font-bold` utility)."
21
+ },
22
+ // `--tracking-normal: normal` (S2 scale token) — letter-spacing chains to the
23
+ // global scale so a consumer can override the entire scale in one place.
24
+ // Byte-identical because `normal` is the browser default for letter-spacing.
25
+ {
26
+ cssVar: "--zdc-doc-title-tracking",
27
+ selector: "h1.text-heading",
28
+ property: "letter-spacing",
29
+ default: "var(--tracking-normal)",
30
+ component: "doc-title",
31
+ surface: "content",
32
+ category: "typography",
33
+ description: "Letter spacing of the doc-page title <h1> (and the other page-title h1s that share `text-heading`). Defaults to `var(--tracking-normal)` (resolves to `normal` \u2014 byte-identical to the browser default); redefine in :root or tighten via `--tracking-tight`."
34
+ },
35
+ // ── Content heading h2 (HeadingH2 component, selector census: #2449) ───────
36
+ // `h2.text-title` matches every h2 with the `text-title` size role in the
37
+ // package: HeadingH2 content override (primary target), plus tag-section
38
+ // headings on index/locale-index pages, versions-page section headers, and
39
+ // modal headings (ai-chat, color-tweak-export). All share identical
40
+ // `text-title font-bold` styling — a semantically-consistent superset
41
+ // analogous to `h1.text-heading`. Byte-identical defaults produce zero
42
+ // computed change; a `:root` override rebrands all consistently.
43
+ {
44
+ cssVar: "--zdc-doc-h2-font",
45
+ selector: "h2.text-title",
46
+ property: "font-family",
47
+ default: "inherit",
48
+ component: "heading-h2",
49
+ surface: "content",
50
+ category: "typography",
51
+ description: "Font family of content h2 headings (HeadingH2 component and other h2s sharing `text-title`). Defaults to `inherit` so the heading keeps the host's brand font; redefine in :root to rebrand."
52
+ },
53
+ {
54
+ cssVar: "--zdc-doc-h2-weight",
55
+ selector: "h2.text-title",
56
+ property: "font-weight",
57
+ default: "var(--font-weight-bold)",
58
+ component: "heading-h2",
59
+ surface: "content",
60
+ category: "typography",
61
+ description: "Font weight of content h2 headings (HeadingH2 component and other h2s sharing `text-title`). Defaults to the `--font-weight-bold` token (matching the original `font-bold` utility)."
62
+ },
63
+ {
64
+ cssVar: "--zdc-doc-h2-tracking",
65
+ selector: "h2.text-title",
66
+ property: "letter-spacing",
67
+ default: "var(--tracking-normal)",
68
+ component: "heading-h2",
69
+ surface: "content",
70
+ category: "typography",
71
+ description: "Letter spacing of content h2 headings (HeadingH2 component and other h2s sharing `text-title`). Defaults to `var(--tracking-normal)` (byte-identical to the browser default); redefine in :root to tighten or widen."
72
+ },
73
+ // ── Content heading h3 (HeadingH3 component, selector census: #2449) ───────
74
+ // `h3.text-body.font-bold` is unique to the HeadingH3 component — no other
75
+ // h3 in the package emits both `text-body` and `font-bold`.
76
+ {
77
+ cssVar: "--zdc-doc-h3-weight",
78
+ selector: "h3.text-body.font-bold",
79
+ property: "font-weight",
80
+ default: "var(--font-weight-bold)",
81
+ component: "heading-h3",
82
+ surface: "content",
83
+ category: "typography",
84
+ description: "Font weight of content h3 headings (HeadingH3 component). Defaults to the `--font-weight-bold` token (matching the original `font-bold` utility)."
85
+ },
86
+ // ── Content heading h4 (HeadingH4 component, selector census: #2449) ───────
87
+ // `h4.text-body.font-semibold` is unique to the HeadingH4 component — no
88
+ // other h4 in the package emits both `text-body` and `font-semibold`.
89
+ {
90
+ cssVar: "--zdc-doc-h4-weight",
91
+ selector: "h4.text-body.font-semibold",
92
+ property: "font-weight",
93
+ default: "var(--font-weight-semibold)",
94
+ component: "heading-h4",
95
+ surface: "content",
96
+ category: "typography",
97
+ description: "Font weight of content h4 headings (HeadingH4 component). Defaults to the `--font-weight-semibold` token (matching the original `font-semibold` utility)."
98
+ },
99
+ // ── Content prose font (.zd-content, #2460) ────────────────────────────────
100
+ // Sets font-family on the content root so every element inside inherits it by
101
+ // default. The headings whose `--zdc-doc-*-font` default to `inherit` will
102
+ // inherit this value — safe because the current computed heading family already
103
+ // resolves to `var(--font-sans)` (Tailwind preflight sets it on `html`).
104
+ // Byte-identical: `.zd-content` currently carries no explicit font-family, so
105
+ // setting it to `var(--font-sans)` produces the same computed value.
106
+ {
107
+ cssVar: "--zdc-doc-prose-font",
108
+ selector: ".zd-content",
109
+ property: "font-family",
110
+ default: "var(--font-sans)",
111
+ component: "doc-prose",
112
+ surface: "content",
113
+ category: "typography",
114
+ description: "Font family of the prose content area (.zd-content). Defaults to `var(--font-sans)` (the inherited font \u2014 byte-identical); redefine in :root to use a distinct body font for doc content. Headings with `--zdc-doc-*-font: inherit` inherit this value."
115
+ },
116
+ // ── Content link (ContentLink component, styled path, #2460) ─────────────
117
+ // ContentLink returns early (unstyled) for `block` and `hash-link` classes;
118
+ // the styled path emits `text-accent underline hover:text-accent-hover`.
119
+ // Selector `a.text-accent.underline` (specificity 0,2,1) beats the `.underline`
120
+ // utility (0,1,0) and only matches the styled anchor — not block or hash-link
121
+ // variants. Default `underline` is a CSS keyword literal: no text-decoration
122
+ // token exists in the scale (documented exception to the prefer-var-() rule).
123
+ {
124
+ cssVar: "--zdc-doc-link-decoration",
125
+ selector: "a.text-accent.underline",
126
+ property: "text-decoration",
127
+ default: "underline",
128
+ component: "content-link",
129
+ surface: "content",
130
+ category: "typography",
131
+ 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."
132
+ },
133
+ // ── Admonition block ([data-admonition], content-admonition.tsx, #2460) ───
134
+ // The base `[data-admonition]` rule in content.css has:
135
+ // border-left: 4px solid var(--color-muted);
136
+ // border-radius: 0 var(--radius-DEFAULT) var(--radius-DEFAULT) 0;
137
+ // Both properties are tokenized below. The token rules land in the BEGIN/END
138
+ // block (after the base rule in source order), so at equal specificity the
139
+ // token defaults override the originals — byte-identical because the defaults
140
+ // ARE the original values.
141
+ // Exception: `4px` is a bare literal — no border-width scale token exists.
142
+ {
143
+ cssVar: "--zdc-admonition-radius",
144
+ selector: "[data-admonition]",
145
+ property: "border-radius",
146
+ default: "0 var(--radius-DEFAULT) var(--radius-DEFAULT) 0",
147
+ component: "admonition",
148
+ surface: "content",
149
+ category: "shape",
150
+ description: "Border radius of the admonition/callout block. Defaults to the current value (0 on the left edges, `--radius-DEFAULT` on the right \u2014 preserving the flush-left accent look); redefine to round all corners or square them off."
151
+ },
152
+ {
153
+ cssVar: "--zdc-admonition-border-width",
154
+ selector: "[data-admonition]",
155
+ property: "border-left-width",
156
+ default: "4px",
157
+ component: "admonition",
158
+ surface: "content",
159
+ category: "shape",
160
+ description: "Width of the admonition left accent border. Defaults to `4px` (byte-identical to the current `border-left: 4px solid` rule). No border-width scale token exists \u2014 bare literal is the documented exception."
161
+ },
162
+ // ── Card radius (nav-indexing card grids, chrome, #2461) ──────────────────
163
+ // Selector census: `a.group.block` also appears in search-widget-script
164
+ // (search result link: `class="group block px-hsp-lg py-vsp-sm ..."`).
165
+ // Adding `.rounded` (present on all three card anchors — doc-card-grid,
166
+ // nav-card-grid, category-nav — and absent from the search result link)
167
+ // makes the selector unique to cards. `a.group.block.rounded` specificity
168
+ // 0,3,1 beats `.rounded` utility (0,1,0); unlayered beats layered (@layer
169
+ // utilities). No JSX change needed — cards already emit `rounded`.
170
+ // `--zdc-surface-radius` is a consumer meta-knob: NOT a registry entry
171
+ // (no single backing selector); it lives only in this default chain so a
172
+ // consumer can set `--zdc-surface-radius` in :root to round all surfaces.
173
+ {
174
+ cssVar: "--zdc-card-radius",
175
+ selector: "a.group.block.rounded",
176
+ property: "border-radius",
177
+ default: "var(--zdc-surface-radius, var(--radius-DEFAULT))",
178
+ component: "card-grid",
179
+ surface: "chrome",
180
+ category: "shape",
181
+ description: "Border radius of the nav/doc card anchor (doc-card-grid, nav-card-grid, category-nav). Defaults to `var(--zdc-surface-radius, var(--radius-DEFAULT))` \u2014 chains through the meta-knob so setting `--zdc-surface-radius` in :root rounds all surfaces at once."
182
+ },
183
+ // ── Content band max-width (.zd-doc-content-band, chrome, #2461) ──────────
184
+ // Two widths exist in doc-layout.tsx:
185
+ // • showSidebar=true: max-w-[clamp(50rem,75vw,90rem)] → tokenized here
186
+ // • showSidebar=false: max-w-[80rem] → replaced by `data-zd-nosidebar`
187
+ // attr on the band div + `.zd-doc-content-band[data-zd-nosidebar]` rule
188
+ // in features.css (unlayered, specificity 0,2,0 > token rule 0,1,0).
189
+ // Token rule is unlayered and beats the Tailwind utility (layered). The
190
+ // `max-w-[clamp(50rem,75vw,90rem)]` JSX class is removed from the sidebar
191
+ // branch; default value is the transcribed literal — byte-identical.
192
+ // JS-toggle (html[data-sidebar-hidden]) remains covered by the existing
193
+ // `html[data-sidebar-hidden] .zd-doc-content-band` rule (specificity 0,2,1).
194
+ // Exception: `clamp(50rem,75vw,90rem)` is a bare CSS function expression —
195
+ // no equivalent design token exists (documented exception to the prefer-var()).
196
+ {
197
+ cssVar: "--zdc-content-max-width",
198
+ selector: ".zd-doc-content-band",
199
+ property: "max-width",
200
+ default: "clamp(50rem,75vw,90rem)",
201
+ component: "doc-content-band",
202
+ surface: "chrome",
203
+ category: "layout",
204
+ description: "Max width of the doc reading column (sidebar-present case). Defaults to `clamp(50rem,75vw,90rem)` (byte-identical to the current utility). The hide_sidebar 80rem variant is handled by `.zd-doc-content-band[data-zd-nosidebar]` in features.css; the JS-toggle variant by `html[data-sidebar-hidden] .zd-doc-content-band`."
205
+ },
206
+ // ── TOC width (nav[data-zd-toc], chrome, #2461) ───────────────────────────
207
+ // `nav[aria-label="Table of contents"]` has inner quotes so the parseTokens
208
+ // regex (which matches double-quoted strings) cannot handle it as a bare
209
+ // double-quoted field. `data-zd-toc` is added to the nav element in
210
+ // toc.tsx instead, making the selector parser-safe and an explicit anchor.
211
+ // Specificity 0,1,1 (element + attribute) beats `w-[280px]` (0,1,0);
212
+ // unlayered beats layered (@layer utilities).
213
+ // Exception: `280px` is a bare pixel literal — no fixed-width token exists
214
+ // (documented exception to the prefer-var-() rule).
215
+ {
216
+ cssVar: "--zdc-toc-width",
217
+ selector: "nav[data-zd-toc]",
218
+ property: "width",
219
+ default: "280px",
220
+ component: "toc",
221
+ surface: "chrome",
222
+ category: "layout",
223
+ description: "Width of the desktop Table of Contents right rail. Defaults to `280px` (byte-identical to the current `w-[280px]` utility in toc.tsx). No fixed-width design token exists \u2014 bare literal is the documented exception."
224
+ },
225
+ // ── Sidebar nav active item (a[data-nav-active], chrome, #2462) ───────────
226
+ // Targets non-root leaf active links in the SSR sidebar-tree island.
227
+ // `data-nav-active=""` is emitted on the `<a>` when `isActive && !isRoot`
228
+ // (sidebar-tree-island/index.tsx LeafNode). Root-level and category active
229
+ // items retain their unconditional `font-semibold` class (not conditional
230
+ // on active state) — targeting those with a single weight default would
231
+ // break byte-identity. Non-root leaf active is the canonical "current page"
232
+ // link: the most brand-identifying nav detail (#2462).
233
+ //
234
+ // Selector `a[data-nav-active]` specificity 0,1,1 beats the Tailwind
235
+ // utilities `bg-fg` (0,1,0) and `font-medium` (0,1,0); unlayered beats
236
+ // layered (@layer utilities). Defaults match the current classes exactly:
237
+ // `bg-fg` → background-color: var(--color-fg) (byte-identical)
238
+ // `font-medium`→ font-weight: var(--font-weight-medium) (byte-identical)
239
+ {
240
+ cssVar: "--zdc-nav-active-indicator-color",
241
+ selector: "a[data-nav-active]",
242
+ property: "background-color",
243
+ default: "var(--color-fg)",
244
+ component: "nav-active",
245
+ surface: "chrome",
246
+ category: "typography",
247
+ description: "Background color of the active sidebar nav link (non-root leaf, the current page). Defaults to `var(--color-fg)` (byte-identical to the current `bg-fg` utility \u2014 the inversion fill that marks the active item). Redefine in :root to use an accent stripe or custom fill instead."
248
+ },
249
+ {
250
+ cssVar: "--zdc-nav-active-weight",
251
+ selector: "a[data-nav-active]",
252
+ property: "font-weight",
253
+ default: "var(--font-weight-medium)",
254
+ component: "nav-active",
255
+ surface: "chrome",
256
+ category: "typography",
257
+ description: "Font weight of the active sidebar nav link (non-root leaf, the current page). Defaults to `var(--font-weight-medium)` (byte-identical to the current `font-medium` utility). Redefine in :root to use semibold or bold for stronger active emphasis."
258
+ }
259
+ ];
260
+ export {
261
+ COMPONENT_TOKENS
262
+ };
package/dist/content.css CHANGED
@@ -462,3 +462,59 @@
462
462
  .zd-content .mermaid foreignObject {
463
463
  overflow: visible;
464
464
  }
465
+
466
+ /* ============================================================================
467
+ * Component tokens (`--zdc-*`) — epic zudolab/zudo-doc#2446
468
+ *
469
+ * Token-backed rules on EXISTING, component-anchored selectors. Each rule
470
+ * targets the classes the component already emits (NO JSX/class change) with
471
+ * specificity that beats the consumer's UNLAYERED Tailwind utilities, and is
472
+ * itself kept UNLAYERED (NOT in `@layer zd-flow`). Defaults chain to existing
473
+ * tokens / `inherit` (never literals) so the rendered default is byte-identical
474
+ * to the pre-token output — the #2425 route-injection byte-hash gate stays
475
+ * green — and consumer `@theme` overrides keep flowing through.
476
+ *
477
+ * Single source of truth: `src/config/component-tokens.ts`. Wave 2 (#2448) adds
478
+ * the `gen-component-tokens` generator that owns the BEGIN/END block below
479
+ * (mirroring `gen-z-index`); until then the block is maintained by hand to
480
+ * match what the generator will emit. Consumers rebrand a knob by redefining
481
+ * its `--zdc-*` custom property in `:root` — no specificity fight, reaches
482
+ * every route.
483
+ * ========================================================================== */
484
+
485
+ /* BEGIN --zdc-* component tokens (generated by gen-component-tokens; do not edit by hand) */
486
+
487
+ h1.text-heading {
488
+ font-family: var(--zdc-doc-title-font, inherit);
489
+ font-weight: var(--zdc-doc-title-weight, var(--font-weight-bold));
490
+ letter-spacing: var(--zdc-doc-title-tracking, var(--tracking-normal));
491
+ }
492
+
493
+ h2.text-title {
494
+ font-family: var(--zdc-doc-h2-font, inherit);
495
+ font-weight: var(--zdc-doc-h2-weight, var(--font-weight-bold));
496
+ letter-spacing: var(--zdc-doc-h2-tracking, var(--tracking-normal));
497
+ }
498
+
499
+ h3.text-body.font-bold {
500
+ font-weight: var(--zdc-doc-h3-weight, var(--font-weight-bold));
501
+ }
502
+
503
+ h4.text-body.font-semibold {
504
+ font-weight: var(--zdc-doc-h4-weight, var(--font-weight-semibold));
505
+ }
506
+
507
+ .zd-content {
508
+ font-family: var(--zdc-doc-prose-font, var(--font-sans));
509
+ }
510
+
511
+ a.text-accent.underline {
512
+ text-decoration: var(--zdc-doc-link-decoration, underline);
513
+ }
514
+
515
+ [data-admonition] {
516
+ border-radius: var(--zdc-admonition-radius, 0 var(--radius-DEFAULT) var(--radius-DEFAULT) 0);
517
+ border-left-width: var(--zdc-admonition-border-width, 4px);
518
+ }
519
+
520
+ /* END --zdc-* component tokens */
@@ -1,7 +1,9 @@
1
1
  import { Fragment, jsx, jsxs } from "preact/jsx-runtime";
2
2
  import { SidebarResizerInit } from "../sidebar-resizer/index.js";
3
3
  import { deriveBodyEndIslands } from "../chrome/derive.js";
4
+ import { assertChromeContext } from "../chrome/assert-chrome-context.js";
4
5
  function createDocBodyEnd(ctx) {
6
+ assertChromeContext(ctx, "createDocBodyEnd");
5
7
  const settings = ctx.settings;
6
8
  const BodyEndIslands = deriveBodyEndIslands(ctx);
7
9
  function DocBodyEnd() {
@@ -2,7 +2,9 @@ import { Fragment, jsx, jsxs } from "preact/jsx-runtime";
2
2
  import { FrontmatterPreview } from "../metainfo/index.js";
3
3
  import { createDocMetainfoArea } from "../doc-metainfo-area/index.js";
4
4
  import { createDocTagsArea } from "../doc-tags-area/index.js";
5
+ import { assertChromeContext } from "../chrome/assert-chrome-context.js";
5
6
  function createDocContentHeader(ctx) {
7
+ assertChromeContext(ctx, "createDocContentHeader");
6
8
  const t = ctx.t;
7
9
  const buildFrontmatterPreviewEntries = ctx.hostBindings.buildFrontmatterPreviewEntries ?? (() => []);
8
10
  const frontmatterRenderers = ctx.hostBindings.frontmatterRenderers ?? {};
@@ -4,7 +4,9 @@ import { BodyFootUtilArea } from "../body-foot-util/index.js";
4
4
  import { toHistorySlug } from "../slug/index.js";
5
5
  import { buildGitHubSourceUrl as buildGitHubSourceUrlBase } from "../github-helpers/index.js";
6
6
  import { deriveDocHistorySlot } from "../chrome/derive.js";
7
+ import { assertChromeContext } from "../chrome/assert-chrome-context.js";
7
8
  function createDocHistoryArea(ctx) {
9
+ assertChromeContext(ctx, "createDocHistoryArea");
8
10
  const settings = ctx.settings;
9
11
  const defaultLocale = ctx.defaultLocale;
10
12
  const docHistoryMeta = ctx.hostBindings.docHistoryMeta ?? {};
@@ -1,6 +1,7 @@
1
1
  import { jsx } from "preact/jsx-runtime";
2
2
  import { DocMetainfo } from "../metainfo/index.js";
3
3
  import { toHistorySlug } from "../slug/index.js";
4
+ import { assertChromeContext } from "../chrome/assert-chrome-context.js";
4
5
  const LOCALE_TO_BCP47 = {
5
6
  en: "en-US",
6
7
  ja: "ja-JP",
@@ -16,6 +17,7 @@ function formatDate(isoDate, locale) {
16
17
  });
17
18
  }
18
19
  function createDocMetainfoArea(ctx) {
20
+ assertChromeContext(ctx, "createDocMetainfoArea");
19
21
  const settings = ctx.settings;
20
22
  const defaultLocale = ctx.defaultLocale;
21
23
  const docHistoryMeta = ctx.hostBindings.docHistoryMeta ?? {};
@@ -4,7 +4,9 @@ import { createDocContentHeader } from "../doc-content-header/index.js";
4
4
  import { createDocMetainfoArea } from "../doc-metainfo-area/index.js";
5
5
  import { createDocHistoryArea } from "../doc-history-area/index.js";
6
6
  import { deriveMdxComponents, deriveInlineVersionSwitcher } from "../chrome/derive.js";
7
+ import { assertChromeContext } from "../chrome/assert-chrome-context.js";
7
8
  function createRenderDocPage(ctx) {
9
+ assertChromeContext(ctx, "createRenderDocPage");
8
10
  const docsUrl = ctx.docsUrl;
9
11
  const versionedDocsUrl = ctx.versionedDocsUrl;
10
12
  const absoluteUrl = ctx.absoluteUrl;
@@ -12,7 +12,9 @@ import { createFooterWithDefaults } from "../footer-with-defaults/index.js";
12
12
  import { createDocBodyEnd } from "../doc-body-end/index.js";
13
13
  import { createDocPager } from "../doc-pager/index.js";
14
14
  import { deriveComposeMetaTitle } from "../chrome/derive.js";
15
+ import { assertChromeContext } from "../chrome/assert-chrome-context.js";
15
16
  function createDocPageShell(ctx) {
17
+ assertChromeContext(ctx, "createDocPageShell");
16
18
  const settings = ctx.settings;
17
19
  const composeMetaTitle = deriveComposeMetaTitle(ctx);
18
20
  const HeadWithDefaults = createHeadWithDefaults(ctx);
@@ -1,6 +1,8 @@
1
1
  import { jsx, jsxs } from "preact/jsx-runtime";
2
2
  import { ChevronLeft, ChevronRight } from "../icons/index.js";
3
+ import { assertChromeContext } from "../chrome/assert-chrome-context.js";
3
4
  function createDocPager(ctx) {
5
+ assertChromeContext(ctx, "createDocPager");
4
6
  const t = ctx.t;
5
7
  function DocPager({ prev, next, locale }) {
6
8
  return /* @__PURE__ */ jsxs("nav", { class: "mt-vsp-2xl grid grid-cols-2 gap-hsp-xl", children: [
@@ -1,7 +1,9 @@
1
1
  import { jsx } from "preact/jsx-runtime";
2
2
  import { DocTags } from "../metainfo/index.js";
3
3
  import { resolvePageTags } from "../tag-helpers/index.js";
4
+ import { assertChromeContext } from "../chrome/assert-chrome-context.js";
4
5
  function createDocTagsArea(ctx) {
6
+ assertChromeContext(ctx, "createDocTagsArea");
5
7
  const settings = ctx.settings;
6
8
  const defaultLocale = ctx.defaultLocale;
7
9
  const withBase = ctx.withBase;
@@ -70,7 +70,8 @@ function DocLayout(props) {
70
70
  /* @__PURE__ */ jsx("div", { class: "flex min-h-[calc(100vh-3.5rem)] justify-center", children: /* @__PURE__ */ jsxs(
71
71
  "div",
72
72
  {
73
- class: `zd-doc-content-band flex w-full gap-[clamp(1.5rem,3vw,4rem)] ${showSidebar ? "max-w-[clamp(50rem,75vw,90rem)]" : "max-w-[80rem]"}`,
73
+ class: "zd-doc-content-band flex w-full gap-[clamp(1.5rem,3vw,4rem)]",
74
+ ...!showSidebar ? { "data-zd-nosidebar": "" } : {},
74
75
  children: [
75
76
  /* @__PURE__ */ jsxs("main", { class: "flex-1 min-w-0 px-hsp-xl py-vsp-xl lg:px-hsp-2xl lg:py-vsp-2xl", children: [
76
77
  breadcrumb,
package/dist/features.css CHANGED
@@ -309,12 +309,13 @@ pre[class^="syntect-"] .line .highlighted-word {
309
309
  margin-left: 0;
310
310
  }
311
311
 
312
- /* When hidden via the toggle, narrow the content band to the
313
- * hide_sidebar frontmatter width so it centers (the flex parent already
312
+ /* When the sidebar is hidden via the JS toggle, narrow the content band
313
+ * to the hide_sidebar width so it centers (the flex parent already
314
314
  * applies justify-content: center) instead of leaving a dead gap where
315
- * the sidebar was. 80rem must match the `max-w-[80rem]` hide_sidebar
316
- * branch in doc-layout.tsx. These rules are unlayered, so they win over
317
- * the Tailwind `max-w-[clamp(...)]` utility (utilities layer). (#2002) */
315
+ * the sidebar was. The 80rem value mirrors the static hide_sidebar band
316
+ * width (see `.zd-doc-content-band[data-zd-nosidebar]` below and in the
317
+ * --zdc-content-max-width chrome token). These rules are unlayered, so
318
+ * they win over the Tailwind utilities layer. (#2002) */
318
319
  .zd-doc-content-band {
319
320
  transition: max-width var(--zd-transition-slow) ease-in-out;
320
321
  }
@@ -324,6 +325,16 @@ pre[class^="syntect-"] .line .highlighted-word {
324
325
  }
325
326
  }
326
327
 
328
+ /* hide_sidebar band width — applies when doc-layout.tsx sets showSidebar=false
329
+ * (hide_sidebar frontmatter). The `max-w-[80rem]` JSX utility was replaced by a
330
+ * data-zd-nosidebar attr + this rule when --zdc-content-max-width was added (#2461).
331
+ * Specificity 0,2,0 (class + attr) beats the generated token rule (0,1,0) so
332
+ * hide_sidebar pages keep their narrower 80rem reading column. All-screen (not
333
+ * inside @media) because the band element exists at every breakpoint. */
334
+ .zd-doc-content-band[data-zd-nosidebar] {
335
+ max-width: 80rem;
336
+ }
337
+
327
338
  /* Sidebar toggle button — left position uses CSS variable, needs global rule */
328
339
  @media (min-width: 1024px) {
329
340
  .zd-desktop-sidebar-toggle {
@@ -880,3 +891,41 @@ dialog.zd-mermaid-dialog::backdrop {
880
891
  .diff-line-empty {
881
892
  background-color: color-mix(in oklch, var(--color-muted) 8%, transparent);
882
893
  }
894
+
895
+ /* ============================================================================
896
+ * Chrome component tokens (`--zdc-*`) — epic zudolab/zudo-doc#2446
897
+ *
898
+ * The CHROME-surface counterpart of the content-surface `--zdc-*` block in
899
+ * `src/content.css`. Token-backed rules on EXISTING, component-anchored
900
+ * selectors for the app shell (header/footer/sidebar/toc/cards). Each rule
901
+ * targets the classes the component already emits (NO JSX/class change) with
902
+ * specificity that beats the consumer's UNLAYERED Tailwind utilities, and is
903
+ * itself kept UNLAYERED (mirroring the content block) so a consumer rebrands a
904
+ * knob by redefining its `--zdc-*` custom property in `:root`.
905
+ *
906
+ * Single source of truth: `src/config/component-tokens.ts` (tokens with
907
+ * `surface: "chrome"`). The `gen-component-tokens` generator owns the BEGIN/END
908
+ * block below — never hand-edit between the markers. Seeded empty: no chrome
909
+ * tokens exist yet (S1, #2458 added only the routing mechanism).
910
+ * ========================================================================== */
911
+
912
+ /* BEGIN --zdc-* chrome component tokens (generated by gen-component-tokens; do not edit by hand) */
913
+
914
+ a.group.block.rounded {
915
+ border-radius: var(--zdc-card-radius, var(--zdc-surface-radius, var(--radius-DEFAULT)));
916
+ }
917
+
918
+ .zd-doc-content-band {
919
+ max-width: var(--zdc-content-max-width, clamp(50rem,75vw,90rem));
920
+ }
921
+
922
+ nav[data-zd-toc] {
923
+ width: var(--zdc-toc-width, 280px);
924
+ }
925
+
926
+ a[data-nav-active] {
927
+ background-color: var(--zdc-nav-active-indicator-color, var(--color-fg));
928
+ font-weight: var(--zdc-nav-active-weight, var(--font-weight-medium));
929
+ }
930
+
931
+ /* END --zdc-* chrome component tokens */
@@ -1,6 +1,8 @@
1
1
  import { jsx } from "preact/jsx-runtime";
2
2
  import { Footer } from "../footer/index.js";
3
+ import { assertChromeContext } from "../chrome/assert-chrome-context.js";
3
4
  function createFooterWithDefaults(ctx) {
5
+ assertChromeContext(ctx, "createFooterWithDefaults");
4
6
  const settings = ctx.settings;
5
7
  const defaultLocale = ctx.defaultLocale;
6
8
  const tagVocabulary = ctx.hostBindings.tagVocabulary ?? [];
@@ -79,3 +79,42 @@ export interface HeadProps {
79
79
  crossorigin?: "anonymous" | "use-credentials";
80
80
  }>;
81
81
  }
82
+ /** Stylesheet link descriptor for {@link SiteHeadConfig}. */
83
+ export interface HeadStylesheet {
84
+ href: string;
85
+ crossorigin?: "anonymous" | "use-credentials";
86
+ /** CSS media attribute applied to the `<link media>` attribute. */
87
+ media?: string;
88
+ /**
89
+ * When `true`, loads the stylesheet non-render-blocking via the
90
+ * `media="print" + onload="this.media='all'"` pattern, plus a
91
+ * `<noscript><link rel="stylesheet" href>` fallback.
92
+ * Plain (absent or `false`) emits a normal `<link rel="stylesheet">`.
93
+ */
94
+ async?: boolean;
95
+ }
96
+ /** Preconnect hint descriptor for {@link SiteHeadConfig}. */
97
+ export interface HeadPreconnect {
98
+ href: string;
99
+ crossorigin?: "anonymous" | "use-credentials";
100
+ }
101
+ /** Preload hint descriptor for {@link SiteHeadConfig}. */
102
+ export interface HeadPreload {
103
+ href: string;
104
+ as: string;
105
+ type?: string;
106
+ crossorigin?: "anonymous" | "use-credentials";
107
+ }
108
+ /** Meta tag descriptor for {@link SiteHeadConfig}. */
109
+ export interface HeadMeta {
110
+ name?: string;
111
+ property?: string;
112
+ content: string;
113
+ }
114
+ /** Alternate link descriptor for {@link SiteHeadConfig}. */
115
+ export interface HeadAlternateLink {
116
+ rel: string;
117
+ href: string;
118
+ type?: string;
119
+ title?: string;
120
+ }