@takazudo/zudo-doc 5.19.0 → 5.20.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 (76) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/asset-page/index.d.ts +3 -1
  3. package/dist/asset-page/index.js +5 -4
  4. package/dist/breadcrumb/breadcrumb.js +21 -14
  5. package/dist/chrome/derive.d.ts +23 -1
  6. package/dist/chrome/derive.js +17 -2
  7. package/dist/chrome-bindings.d.ts +6 -1
  8. package/dist/compiled.css +23 -0
  9. package/dist/config.d.ts +13 -1
  10. package/dist/config.js +1 -0
  11. package/dist/date-format-resolve/index.d.ts +10 -0
  12. package/dist/date-format-resolve/index.js +22 -0
  13. package/dist/doc-content-header/index.js +4 -2
  14. package/dist/doc-history/index.d.ts +17 -1
  15. package/dist/doc-history/index.js +19 -13
  16. package/dist/doc-history-area/index.d.ts +3 -1
  17. package/dist/doc-history-area/index.js +7 -2
  18. package/dist/doc-metainfo-area/index.js +4 -2
  19. package/dist/doc-pager/index.js +4 -2
  20. package/dist/docs-schema/index.d.ts +1 -1
  21. package/dist/eject/index.js +1 -1
  22. package/dist/factory-context/index.d.ts +3 -6
  23. package/dist/format-date/index.d.ts +15 -6
  24. package/dist/format-date/index.js +79 -15
  25. package/dist/header-with-defaults/index.js +5 -1
  26. package/dist/home-page/index.js +9 -7
  27. package/dist/nav-indexing/note-tray-index-parts/card-list.js +18 -5
  28. package/dist/nav-indexing/note-tray-index-parts/date-line.d.ts +3 -1
  29. package/dist/nav-indexing/note-tray-index-parts/date-line.js +4 -3
  30. package/dist/nav-indexing/note-tray-index-parts/index-list.js +9 -1
  31. package/dist/nav-indexing/note-tray-index-parts/timeline.js +2 -2
  32. package/dist/nav-indexing/note-tray-index.d.ts +9 -0
  33. package/dist/note-tray-index/index.d.ts +9 -0
  34. package/dist/note-tray-index/index.js +3 -1
  35. package/dist/note-tray-model/index.d.ts +13 -3
  36. package/dist/note-tray-model/index.js +9 -2
  37. package/dist/plugins/internal/claude-resources/generate.js +3 -2
  38. package/dist/plugins/internal/codex-resources/overview.js +3 -2
  39. package/dist/plugins/internal/resource-docs-shared/fs.d.ts +1 -1
  40. package/dist/plugins/internal/resource-docs-shared/fs.js +3 -2
  41. package/dist/routes/_context.d.ts +1 -4
  42. package/dist/safelist.css +1 -1
  43. package/dist/settings.d.ts +37 -0
  44. package/dist/sidebar-toggle-island/index.d.ts +16 -1
  45. package/dist/sidebar-toggle-island/index.js +6 -2
  46. package/dist/sidebar-tree-island/index.d.ts +15 -1
  47. package/dist/sidebar-tree-island/index.js +19 -8
  48. package/dist/sidebar-with-defaults/index.js +5 -2
  49. package/dist/site-tree-nav/index.d.ts +13 -0
  50. package/dist/site-tree-nav/index.js +6 -2
  51. package/dist/site-tree-nav-island/index.d.ts +9 -1
  52. package/dist/site-tree-nav-island/index.js +19 -3
  53. package/dist/theme-no-reset.css +379 -0
  54. package/dist/theme-packs/academia/pack.css +12 -2
  55. package/dist/theme-packs/blueprint/pack.css +7 -1
  56. package/dist/theme-packs/broadsheet/pack.css +7 -1
  57. package/dist/theme-packs/brutalist/pack.css +21 -3
  58. package/dist/theme-packs/drift/pack.css +5 -1
  59. package/dist/theme-packs/foundry/pack.css +13 -2
  60. package/dist/theme-packs/hearth/pack.css +7 -1
  61. package/dist/theme-packs/ledger/pack.css +12 -2
  62. package/dist/theme-packs/observatory/pack.css +5 -1
  63. package/dist/theme-packs/riso/pack.css +9 -6
  64. package/dist/theme-packs/sakura/pack.css +6 -2
  65. package/dist/theme-packs/sumi/pack.css +7 -1
  66. package/dist/theme-packs/swissgrid/pack.css +7 -1
  67. package/dist/theme-packs/tidepool/pack.css +8 -1
  68. package/dist/theme-packs/washi/pack.css +5 -2
  69. package/dist/theme.css +60 -0
  70. package/eject/breadcrumb/breadcrumb.tsx +8 -3
  71. package/eject/doc-history/index.tsx +41 -16
  72. package/eject/doc-pager/index.tsx +12 -2
  73. package/eject/sidebar-toggle-island/index.tsx +19 -0
  74. package/eject/sidebar-tree-island/index.tsx +40 -4
  75. package/eject/site-tree-nav-island/index.tsx +31 -5
  76. package/package.json +10 -9
@@ -0,0 +1,379 @@
1
+ /* ============================================================================
2
+ * @takazudo/zudo-doc — default design tokens + project-agnostic base rules
3
+ *
4
+ * Shipped to consumers as `@takazudo/zudo-doc/theme.css` (copied to
5
+ * `dist/theme.css` by scripts/copy-theme-css.mjs on build). Single source of
6
+ * truth for the default `@theme` token block (colors, spacing, icon sizes,
7
+ * elevation, typography, radius, breakpoints, z-index) plus the small set of
8
+ * project-agnostic base rules (scroll-margin, selection color, focus ring,
9
+ * search / find-in-page highlight, version-switcher visibility) every
10
+ * zudo-doc project needs. See zudolab/zudo-doc#2655.
11
+ *
12
+ * ── @import order (load-bearing) ────────────────────────────────────────────
13
+ * The consumer's global.css MUST import this file:
14
+ * 1. AFTER `@layer zd-preflight, zd-flow;` and the two Tailwind imports
15
+ * (`tailwindcss/preflight` layer(zd-preflight), `tailwindcss/utilities`).
16
+ * Those three lines stay project-side — path-relative `@source`/`@layer`
17
+ * resolution requires them to live in the consumer's own CSS file (see
18
+ * packages/create-zudo-doc/templates/base/src/styles/global.css).
19
+ * 2. BEFORE `@takazudo/zudo-doc/safelist.css`, `content.css`,
20
+ * `page-loading.css`, and `features.css` — all four consume the
21
+ * `@theme` tokens defined here (colors, spacing, typography, z-index),
22
+ * and `content.css`'s `@layer zd-flow` rule depends on the cascade
23
+ * layer order already being established by the time it loads.
24
+ * 3. BEFORE the project's own token-override `@theme { … }` block — Tailwind
25
+ * v4 `@theme` resolution is last-declaration-wins per custom property,
26
+ * so a later project block can redefine any default declared here.
27
+ *
28
+ * ── Consumer contract ────────────────────────────────────────────────────────
29
+ * The consumer must inject the `--zd-*` ramp/role custom properties this file
30
+ * aliases into `--color-*` tokens (`ColorSchemeProvider`, see
31
+ * `@takazudo/zudo-doc/theme`). Without them every `--color-*` token below
32
+ * resolves to nothing (the tight-token guardrail below wipes Tailwind's
33
+ * default palette; only `--zd-*`-backed aliases repopulate it).
34
+ *
35
+ * Namespace contract: zudo-doc reserves the 23 bare `--color-*` alias names
36
+ * declared in the first block plus the `--color-zd-*` prefix. Every other
37
+ * `--color-*` name, such as `--color-acme-*`, belongs to the embedder. A
38
+ * second `@theme` block that defines any reserved bare name in the same
39
+ * document is unsupported: Tailwind merges every `@theme` block reached from
40
+ * the entry, last declaration wins, and no error is reported. Embedders must
41
+ * namespace their own tokens (`--color-<theirs>-*`), import this file before
42
+ * their own `@theme` block so this reset cannot reach their tokens, or import
43
+ * `@takazudo/zudo-doc/theme-no-reset.css` (#4055) when import order is outside
44
+ * their control. Consume zudo-doc colors through `--color-zd-*` / `bg-zd-*`.
45
+ *
46
+ * Deliberately NOT shipped here: `--color-page-loading-overlay`. That scrim
47
+ * token stays feature-injected by a project's `dynamicPageTransition` wiring
48
+ * (its default depends on `--color-overlay`, which IS defined below) — do
49
+ * not add it to this base block.
50
+ *
51
+ * ── Z-index defaults ─────────────────────────────────────────────────────────
52
+ * The 13 `--z-index-*` entries below are the package DEFAULT tiers (mirrors
53
+ * `defaultZIndexTiers` from `@takazudo/zudo-doc/z-index-defaults`, #2654).
54
+ * A project only needs its own `src/config/z-index-tokens.ts` +
55
+ * `gen:z-index`/`check:z-index` codegen wiring if it OVERRIDES a tier — its
56
+ * own `@theme` block (declared after this file's import) simply redefines
57
+ * the specific `--z-index-<name>` it wants to change.
58
+ * ============================================================================ */
59
+
60
+ @theme {
61
+ /* ========================================
62
+ * Tight-token color reset
63
+ *
64
+ * Wipes all Tailwind default color tokens (`--color-*`) so only
65
+ * project-defined palette and semantic tokens remain. This enforces
66
+ * zudo-doc's tight-token policy: "NEVER use Tailwind default colors"
67
+ * (documented per-project in src/CLAUDE.md's Color Rules). Semantic color
68
+ * tokens (defined below) are added back after this reset.
69
+ *
70
+ * The upstream split-import fix (zfb#159 / 9e37551, shipped in f68a9ba)
71
+ * eliminated the original leak cause (zfb no longer prepends the full
72
+ * @import "tailwindcss" bundle). The reset is retained as an explicit
73
+ * design guardrail — it prevents accidental default palette bleed from any
74
+ * future upstream change and keeps the color surface intentional.
75
+ *
76
+ * NOT reset: `--spacing` (the base spacing scale, default 0.25rem).
77
+ * Tailwind spacing utilities compute as calc(var(--spacing) * N); wiping
78
+ * `--spacing` collapses them to zero (observed in Wave 4 Sub-7 #1403).
79
+ * This guardrail resets ONLY the color namespace — never widen it.
80
+ * ======================================== */
81
+ /* theme-no-reset.css: the --color-*: initial guardrail is intentionally omitted — see theme.css and zudolab/zudo-doc#4051 */
82
+
83
+ /* ========================================
84
+ * Colors — Three-tier token system
85
+ * ======================================== */
86
+
87
+ /* ── Base ── */
88
+ --color-bg: var(--zd-bg);
89
+ --color-fg: var(--zd-fg);
90
+ --color-sel-bg: var(--zd-selection-bg);
91
+ --color-sel-fg: var(--zd-selection-fg);
92
+
93
+ /* ── Semantic aliases ── */
94
+ --color-surface: var(--zd-surface);
95
+ --color-muted: var(--zd-muted);
96
+ --color-accent: var(--zd-accent);
97
+ --color-accent-hover: var(--zd-accent-hover);
98
+ --color-code-bg: var(--zd-code-bg);
99
+ --color-code-fg: var(--zd-code-fg);
100
+ --color-success: var(--zd-success);
101
+ --color-danger: var(--zd-danger);
102
+ --color-warning: var(--zd-warning);
103
+ --color-info: var(--zd-info);
104
+ /* Overlay is intentionally theme-independent (always dark, not scheme-driven) */
105
+ --color-overlay: #000;
106
+ --color-image-overlay-bg: var(--zd-image-overlay-bg);
107
+ --color-image-overlay-fg: var(--zd-image-overlay-fg);
108
+ --color-chat-user-bg: var(--zd-chat-user-bg);
109
+ --color-chat-user-text: var(--zd-chat-user-text);
110
+ --color-chat-assistant-bg: var(--zd-chat-assistant-bg);
111
+ --color-chat-assistant-text: var(--zd-chat-assistant-text);
112
+ --color-matched-keyword-bg: var(--zd-matched-keyword-bg);
113
+ --color-matched-keyword-fg: var(--zd-matched-keyword-fg);
114
+
115
+ /* ========================================
116
+ * Spacing — hsp (horizontal) + vsp (vertical)
117
+ * ======================================== */
118
+ --spacing-0: 0;
119
+ --spacing-px: 1px;
120
+
121
+ /* Horizontal spacing (7 steps) */
122
+ --spacing-hsp-2xs: 0.125rem; /* 2px — tight inline */
123
+ --spacing-hsp-xs: 0.375rem; /* 6px — compact inline */
124
+ --spacing-hsp-sm: 0.5rem; /* 8px — small padding */
125
+ --spacing-hsp-md: 0.75rem; /* 12px — default gaps */
126
+ --spacing-hsp-lg: 1rem; /* 16px — standard padding */
127
+ --spacing-hsp-xl: 1.5rem; /* 24px — generous padding */
128
+ --spacing-hsp-2xl: 2rem; /* 32px — large padding */
129
+
130
+ /* Vertical spacing (8 steps) */
131
+ --spacing-vsp-3xs: 0.25rem; /* 4px — hairline gap */
132
+ --spacing-vsp-2xs: 0.4375rem; /* 7px — tight gap */
133
+ --spacing-vsp-xs: 0.875rem; /* 14px — small gap */
134
+ --spacing-vsp-sm: 1.25rem; /* 20px — compact gap */
135
+ --spacing-vsp-md: 1.5rem; /* 24px — standard gap */
136
+ --spacing-vsp-lg: 1.75rem; /* 28px — section gap */
137
+ --spacing-vsp-xl: 2.5rem; /* 40px — large section gap */
138
+ --spacing-vsp-2xl: 3.5rem; /* 56px — page-level gap */
139
+
140
+ /* ========================================
141
+ * Element sizes — semantic icon dimensions
142
+ * ======================================== */
143
+ --spacing-icon-xs: 0.75rem; /* 12px — inline meta icons, chevrons */
144
+ --spacing-icon-sm: 1rem; /* 16px — standard UI icons */
145
+ --spacing-icon-md: 1.25rem; /* 20px — medium emphasis icons */
146
+ --spacing-icon-lg: 1.5rem; /* 24px — large / mobile icons */
147
+
148
+ /* Image overlay button chrome */
149
+ --spacing-image-overlay-inset: 0.5rem; /* 8px — overlay button corner inset + internal padding */
150
+
151
+ /* ========================================
152
+ * Elevation — the tight `@import "tailwindcss/utilities"` (no default
153
+ * @theme) drops Tailwind's default shadow scale, so `shadow-*` utilities
154
+ * generate nothing. Re-add the one elevation the components use: header
155
+ * and version-switcher dropdown panels apply `shadow-lg`. Value is
156
+ * Tailwind v4's default shadow-lg; the black is intentionally
157
+ * theme-independent (a drop shadow is absence of light, not a themed color).
158
+ * ======================================== */
159
+ --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
160
+
161
+ /* ========================================
162
+ * Typography
163
+ * ======================================== */
164
+
165
+ /* ── Tier 2: Semantic font-size tokens (reference Tier 1 scale only) ── */
166
+ --text-micro: var(--text-scale-2xs); /* 12px — compact panel labels, color swatch annotations */
167
+ --text-caption: var(--text-scale-xs); /* labels, timestamps */
168
+ --text-small: var(--text-scale-sm); /* secondary text, nav items */
169
+ --text-body: var(--text-scale-md); /* paragraphs, default */
170
+ --text-title: var(--text-scale-lg); /* card titles, section labels */
171
+ --text-heading: var(--text-scale-xl); /* page headings */
172
+ --text-display: var(--text-scale-2xl); /* hero text */
173
+
174
+ /* Font families */
175
+ --font-sans: system-ui, sans-serif;
176
+ --font-mono: ui-monospace, monospace;
177
+
178
+ /* Font weights (4 steps) */
179
+ --font-weight-normal: 400;
180
+ --font-weight-medium: 500;
181
+ --font-weight-semibold: 600;
182
+ --font-weight-bold: 700;
183
+
184
+ /* Line heights (4 steps) */
185
+ --leading-tight: 1.25;
186
+ --leading-snug: 1.375;
187
+ --leading-normal: 1.5;
188
+ --leading-relaxed: 1.625;
189
+
190
+ /* Letter spacing / tracking (4 steps) */
191
+ --tracking-tight: -0.025em; /* headings, display text */
192
+ --tracking-normal: normal; /* browser default — resets any inherited tracking */
193
+ --tracking-wide: 0.05em; /* labels, UI text */
194
+ --tracking-wider: 0.1em; /* small caps, decorative */
195
+
196
+ /* ========================================
197
+ * Border radius (3 steps)
198
+ * ======================================== */
199
+ --radius-DEFAULT: 0.25rem; /* 4px — rounded */
200
+ --radius-lg: 0.5rem; /* 8px — rounded-lg */
201
+ --radius-full: 9999px; /* rounded-full */
202
+
203
+ /* ========================================
204
+ * Breakpoints (3 steps)
205
+ * ======================================== */
206
+ --breakpoint-sm: 640px;
207
+ --breakpoint-lg: 1024px;
208
+ --breakpoint-xl: 1280px;
209
+ }
210
+
211
+ /* ==========================================================================
212
+ * Tailwind-facing zudo-doc namespace aliases
213
+ *
214
+ * zudolab/zudo-doc#4051 needs a collision-free surface for code outside
215
+ * zudo-doc. An embedder's own components can use `bg-zd-*` / `var(--color-zd-*)`
216
+ * without touching the bare tier consumed by the package chrome.
217
+ *
218
+ * This block is `static` because Tailwind v4 emits a plain `@theme` token into
219
+ * `:root` only when something in its own scan consumes it. zudo-doc never
220
+ * scans the code that consumes these aliases, so the variables must always be
221
+ * emitted. `static` guarantees the variables; `bg-zd-*` utilities are still
222
+ * generated only when the consumer's own Tailwind scan sees those classes.
223
+ *
224
+ * Keep this block after the reset: `--color-*: initial` clears every
225
+ * `--color-*` declared before it in the merged theme, regardless of the file
226
+ * that declared it. The package chrome deliberately continues consuming the
227
+ * bare tier; do not migrate call sites ad hoc.
228
+ *
229
+ * The raw tier is prefix-first (`--zd-*`) outside `@theme`; this semantic tier
230
+ * is category-first (`--color-zd-*`) inside `@theme`. A third ordering that
231
+ * puts the namespace before the category is disconnected from both tiers and
232
+ * must never be introduced.
233
+ * ========================================================================== */
234
+ @theme static {
235
+ --color-zd-bg: var(--zd-bg);
236
+ --color-zd-fg: var(--zd-fg);
237
+ --color-zd-sel-bg: var(--zd-selection-bg);
238
+ --color-zd-sel-fg: var(--zd-selection-fg);
239
+ --color-zd-surface: var(--zd-surface);
240
+ --color-zd-muted: var(--zd-muted);
241
+ --color-zd-accent: var(--zd-accent);
242
+ --color-zd-accent-hover: var(--zd-accent-hover);
243
+ --color-zd-code-bg: var(--zd-code-bg);
244
+ --color-zd-code-fg: var(--zd-code-fg);
245
+ --color-zd-success: var(--zd-success);
246
+ --color-zd-danger: var(--zd-danger);
247
+ --color-zd-warning: var(--zd-warning);
248
+ --color-zd-info: var(--zd-info);
249
+ --color-zd-overlay: #000;
250
+ --color-zd-image-overlay-bg: var(--zd-image-overlay-bg);
251
+ --color-zd-image-overlay-fg: var(--zd-image-overlay-fg);
252
+ --color-zd-chat-user-bg: var(--zd-chat-user-bg);
253
+ --color-zd-chat-user-text: var(--zd-chat-user-text);
254
+ --color-zd-chat-assistant-bg: var(--zd-chat-assistant-bg);
255
+ --color-zd-chat-assistant-text: var(--zd-chat-assistant-text);
256
+ --color-zd-matched-keyword-bg: var(--zd-matched-keyword-bg);
257
+ --color-zd-matched-keyword-fg: var(--zd-matched-keyword-fg);
258
+ }
259
+
260
+ /* ========================================
261
+ * Z-index tiers — semantic, single-namespace default scale.
262
+ *
263
+ * The 13 tokens below are the package DEFAULTS (mirrors defaultZIndexTiers
264
+ * from @takazudo/zudo-doc/z-index-defaults, #2654). Tailwind v4 reads each
265
+ * --z-index-<name> theme key and generates a `z-<name>` utility, so e.g.
266
+ * --z-index-toolbar: 20 produces `.z-toolbar { z-index: 20 }`; raw CSS can
267
+ * also reference `z-index: var(--z-index-<name>)`.
268
+ *
269
+ * A project only needs its own src/config/z-index-tokens.ts +
270
+ * gen:z-index/check:z-index codegen if it OVERRIDES a tier — the
271
+ * GENERATED:Z_INDEX block that used to live in every project's global.css is
272
+ * now opt-in customization only, not a mandatory per-project artifact.
273
+ * ======================================== */
274
+ @theme inline {
275
+ --z-index-content: 0;
276
+ --z-index-local-1: 1;
277
+ --z-index-local-2: 2;
278
+ --z-index-local-3: 3;
279
+ --z-index-sidebar: 10;
280
+ --z-index-toolbar: 20;
281
+ --z-index-dropdown: 30;
282
+ --z-index-popover: 40;
283
+ --z-index-modal-backdrop: 50;
284
+ --z-index-modal: 60;
285
+ --z-index-toast: 70;
286
+ --z-index-tooltip: 80;
287
+ --z-index-drag: 90;
288
+ }
289
+
290
+ :root {
291
+ /* Default responsive range; sidebar-resizer.ts allows 192–448px for explicit resizing. */
292
+ --zd-sidebar-w: clamp(14rem, 20vw, 22rem);
293
+
294
+ /* ── Tier 1: Abstract font-size scale (raw values — NOT for direct component use) ── */
295
+ /* Used only as a source of truth for Tier 2 semantic tokens above. */
296
+ --text-scale-2xs: 0.75rem; /* 12px */
297
+ --text-scale-xs: 0.875rem; /* 14px */
298
+ --text-scale-sm: 1rem; /* 16px */
299
+ --text-scale-md: 1.2rem; /* 19.2px */
300
+ --text-scale-lg: 1.4rem; /* 22.4px */
301
+ --text-scale-xl: 3rem; /* 48px */
302
+ --text-scale-2xl: 3.75rem; /* 60px */
303
+
304
+ --default-transition-duration: 150ms; /* consumed by image-enlarge and hash-link opacity fade */
305
+ --zd-transition-slow: 200ms; /* sidebar transform/visibility and content-band max-width */
306
+ --zd-transition-slower: 300ms; /* view-transition fade-in (contentFadeIn animation) */
307
+ --zd-header-h: 80px; /* sticky header height; consumed by scroll-margin-top on [id] anchors */
308
+ }
309
+
310
+ /* ========================================
311
+ * Base styles
312
+ * ======================================== */
313
+
314
+ [id] {
315
+ scroll-margin-top: var(--zd-header-h);
316
+ }
317
+
318
+ body {
319
+ background-color: var(--color-bg);
320
+ color: var(--color-fg);
321
+ }
322
+
323
+ ::selection {
324
+ background-color: var(--color-sel-bg);
325
+ color: var(--color-sel-fg);
326
+ }
327
+
328
+ @layer base {
329
+ a {
330
+ text-underline-offset: 4px;
331
+ }
332
+ button {
333
+ cursor: pointer;
334
+ }
335
+ :focus-visible {
336
+ outline: 2px solid var(--color-accent);
337
+ outline-offset: 2px;
338
+ }
339
+ }
340
+
341
+ /* ── Search highlight ── */
342
+ [data-search-results] mark {
343
+ background-color: var(--color-matched-keyword-bg);
344
+ color: var(--color-matched-keyword-fg);
345
+ padding: 0;
346
+ border-radius: 2px;
347
+ }
348
+
349
+ /* Version-switcher responsive visibility (was an inline <style> child of
350
+ * the data-version-switcher <div>; moved to global.css to avoid the
351
+ * <style>-inside-<div> HTML5 content-model violation flagged by
352
+ * html-validate — see zudolab/zudo-doc#1505).
353
+ *
354
+ * Pins onto the `.hidden` baseline that Tailwind always generates so
355
+ * consumers wrapping <VersionSwitcher> in `<div class="hidden lg:block">`
356
+ * still get the desktop override even when the content scanner did not
357
+ * generate `.lg:block`. Mobile (<64rem) falls through to `.hidden`'s
358
+ * `display: none`. Inert when no version-switcher is on the page. */
359
+ @media (min-width: 64rem) {
360
+ .hidden:has(> [data-version-switcher]) {
361
+ display: block;
362
+ }
363
+ }
364
+
365
+ /* ── Find-in-page highlight ── */
366
+ /* Deliberate ephemeral-vs-persistent split: find-in-page uses color-mix(warning)
367
+ * because it is a transient browser-controlled overlay that appears and disappears
368
+ * without page navigation. Search results use --color-matched-keyword-bg/fg because
369
+ * they are persistent semantic highlights tied to the current query state.
370
+ * Do NOT unify these two highlight strategies — the visual distinction is intentional. */
371
+ .find-match {
372
+ background-color: color-mix(in oklch, var(--color-warning) 40%, transparent);
373
+ border-radius: 2px;
374
+ }
375
+ .find-match-active {
376
+ background-color: color-mix(in oklch, var(--color-warning) 70%, transparent);
377
+ border-radius: 2px;
378
+ outline: 2px solid color-mix(in oklch, var(--color-warning) 90%, transparent);
379
+ }
@@ -395,7 +395,13 @@ html[data-theme-pack="academia"] [data-nav-item] {
395
395
  font-size: 1rem;
396
396
  letter-spacing: 0.08em;
397
397
  }
398
- html[data-theme-pack="academia"] [data-nav-item]:hover {
398
+ /* Exclude the current page: it carries the base `bg-fg text-bg` inverted
399
+ fill — recoloring its text to the accent would sit bright-on-bright.
400
+ Two shapes exist (header.tsx): a plain `a[data-nav-item]`, and a dropdown
401
+ whose data-nav-item sits on the wrapper div while aria-current + the
402
+ colors live on its child anchor — target the anchor in both. */
403
+ html[data-theme-pack="academia"] a[data-nav-item]:not([aria-current="page"]):hover,
404
+ html[data-theme-pack="academia"] [data-nav-item-dropdown] > a:not([aria-current="page"]):hover {
399
405
  color: var(--zd-accent);
400
406
  text-decoration: none;
401
407
  }
@@ -411,7 +417,11 @@ html[data-theme-pack="academia"] aside[data-zd-mobile-sidebar] {
411
417
  background-color: var(--zd-bg);
412
418
  border-right-color: color-mix(in srgb, var(--zd-fg) 25%, transparent);
413
419
  }
414
- html[data-theme-pack="academia"] #desktop-sidebar a:hover {
420
+ /* Exclude the current page: root/branch current nodes carry
421
+ aria-current="page" without data-nav-active and keep the base
422
+ inverted `bg-fg text-bg` fill — recoloring their text to the
423
+ accent on hover would sit accent-on-fg (~1:1). */
424
+ html[data-theme-pack="academia"] #desktop-sidebar a:not([aria-current="page"]):hover {
415
425
  color: var(--zd-accent);
416
426
  text-decoration: none;
417
427
  }
@@ -297,7 +297,13 @@ html[data-theme-pack="blueprint"] [data-nav-item] {
297
297
  letter-spacing: 0.11em;
298
298
  border-radius: 0;
299
299
  }
300
- html[data-theme-pack="blueprint"] [data-nav-item]:hover {
300
+ /* Exclude the current page: it carries the base `bg-fg text-bg` inverted
301
+ fill — recoloring its text to fg would sit fg-on-fg. Two shapes exist
302
+ (header.tsx): a plain `a[data-nav-item]`, and a dropdown whose
303
+ data-nav-item sits on the wrapper div while aria-current + the colors
304
+ live on its child anchor — target the anchor in both. */
305
+ html[data-theme-pack="blueprint"] a[data-nav-item]:not([aria-current="page"]):hover,
306
+ html[data-theme-pack="blueprint"] [data-nav-item-dropdown] > a:not([aria-current="page"]):hover {
301
307
  color: var(--zd-fg);
302
308
  text-decoration: underline dashed;
303
309
  text-underline-offset: 4px;
@@ -212,7 +212,13 @@ html[data-theme-pack="broadsheet"] [data-nav-item] {
212
212
  letter-spacing: 0.1em;
213
213
  font-weight: 600;
214
214
  }
215
- html[data-theme-pack="broadsheet"] [data-nav-item]:hover {
215
+ /* Exclude the current page: it carries the base `bg-fg text-bg` inverted
216
+ fill — recoloring its text to the accent would sit bright-on-bright.
217
+ Two shapes exist (header.tsx): a plain `a[data-nav-item]`, and a dropdown
218
+ whose data-nav-item sits on the wrapper div while aria-current + the
219
+ colors live on its child anchor — target the anchor in both. */
220
+ html[data-theme-pack="broadsheet"] a[data-nav-item]:not([aria-current="page"]):hover,
221
+ html[data-theme-pack="broadsheet"] [data-nav-item-dropdown] > a:not([aria-current="page"]):hover {
216
222
  color: var(--zd-accent);
217
223
  text-decoration-thickness: 2px;
218
224
  }
@@ -386,13 +386,31 @@ html[data-theme-pack="brutalist"] .zd-content blockquote a {
386
386
  text-underline-offset: 3px;
387
387
  font-weight: 500;
388
388
  }
389
+ /* Card-style prose links (e.g. the note-tray IndexList rank/label/
390
+ description spans) put their own color on group-hover: utility
391
+ classes (group-hover:text-fg / group-hover:text-accent), which beats
392
+ the anchor's inherited `color` — so the anchor-level color below
393
+ never reached that text. Measured 2.63:1 (rgb(245 245 245) on
394
+ rgb(255 106 31), dark) / 4.06:1 (rgb(0 0 0) on rgb(194 65 12), light)
395
+ against the prior var(--zd-accent)/var(--zd-bg) pair, because the
396
+ rendered ink was actually --zd-fg (the descendant spans' own
397
+ resting/hover color), not --zd-bg. Darken the background (same hue,
398
+ not the shared --zd-accent token) and force white ink on every
399
+ descendant so the fix reaches those spans too. */
389
400
  html[data-theme-pack="brutalist"] .zd-content p a:hover,
390
401
  html[data-theme-pack="brutalist"] .zd-content li a:not([aria-label]):hover,
391
402
  html[data-theme-pack="brutalist"] .zd-content td a:hover,
392
403
  html[data-theme-pack="brutalist"] .zd-content blockquote a:hover {
393
- background-color: var(--zd-accent);
394
- color: var(--zd-bg);
395
- text-decoration-color: var(--zd-bg);
404
+ background-color: color-mix(in srgb, var(--zd-accent) 70%, black);
405
+ color: #ffffff;
406
+ text-decoration-color: #ffffff;
407
+ }
408
+ html[data-theme-pack="brutalist"] .zd-content p a:hover *,
409
+ html[data-theme-pack="brutalist"] .zd-content li a:not([aria-label]):hover *,
410
+ html[data-theme-pack="brutalist"] .zd-content td a:hover *,
411
+ html[data-theme-pack="brutalist"] .zd-content blockquote a:hover * {
412
+ color: #ffffff;
413
+ text-decoration-color: #ffffff;
396
414
  }
397
415
 
398
416
  /* prose furniture */
@@ -288,7 +288,11 @@ html[data-theme-pack="drift"] #desktop-sidebar a {
288
288
  html[data-theme-pack="drift"] #desktop-sidebar a[aria-current="page"]:not([data-nav-active]) {
289
289
  border-radius: 0;
290
290
  }
291
- html[data-theme-pack="drift"] #desktop-sidebar a:hover {
291
+ /* Exclude the active item: aria-current="page" carries the base
292
+ `bg-fg text-bg` inverted fill (or the glow pill below); an
293
+ unconditional hover background would replace that fill while the
294
+ inverted ink survives, sitting bg-on-bg. */
295
+ html[data-theme-pack="drift"] #desktop-sidebar a:not([aria-current="page"]):hover {
292
296
  text-decoration: none;
293
297
  background: color-mix(in oklch, var(--zd-accent) 10%, transparent);
294
298
  }
@@ -127,13 +127,24 @@ html[data-theme-pack="foundry"] .zd-content code:not(pre code) {
127
127
  html[data-theme-pack="foundry"] [data-nav-item] {
128
128
  border-radius: 6px;
129
129
  }
130
- html[data-theme-pack="foundry"] [data-nav-item]:hover {
130
+ /* Exclude the current page: it carries the base `bg-fg text-bg` inverted
131
+ fill — the hover wash would replace that fill (bg-on-bg, ~1:1) since
132
+ `text-bg` ink survives underneath it. Two shapes exist (header.tsx): a
133
+ plain `a[data-nav-item]`, and a dropdown whose data-nav-item sits on the
134
+ wrapper div while aria-current + the colors live on its child anchor —
135
+ target the anchor in both. */
136
+ html[data-theme-pack="foundry"] a[data-nav-item]:not([aria-current="page"]):hover,
137
+ html[data-theme-pack="foundry"] [data-nav-item-dropdown] > a:not([aria-current="page"]):hover {
131
138
  text-decoration: none;
132
139
  background: color-mix(in srgb, var(--zd-muted) 8%, transparent);
133
140
  }
134
141
 
135
142
  /* sidebar: gray hover wash; active item = subtle fill + Primer-blue bar */
136
- html[data-theme-pack="foundry"] #desktop-sidebar a:hover {
143
+ /* Exclude the current page: root/branch current nodes carry
144
+ aria-current="page" without data-nav-active and keep the base
145
+ inverted `bg-fg text-bg` fill — the hover wash would replace that
146
+ fill (bg-on-bg, ~1:1) since `text-bg` ink survives underneath it. */
147
+ html[data-theme-pack="foundry"] #desktop-sidebar a:not([aria-current="page"]):hover {
137
148
  text-decoration: none;
138
149
  background: color-mix(in srgb, var(--zd-muted) 8%, transparent);
139
150
  }
@@ -197,7 +197,13 @@ html[data-theme-pack="hearth"] [data-header-logo] {
197
197
  font-weight: 620;
198
198
  color: var(--zd-accent);
199
199
  }
200
- html[data-theme-pack="hearth"] [data-nav-item]:hover {
200
+ /* Exclude the current page: it carries the base `bg-fg text-bg` inverted
201
+ fill — recoloring its text to the accent would sit bright-on-bright.
202
+ Two shapes exist (header.tsx): a plain `a[data-nav-item]`, and a dropdown
203
+ whose data-nav-item sits on the wrapper div while aria-current + the
204
+ colors live on its child anchor — target the anchor in both. */
205
+ html[data-theme-pack="hearth"] a[data-nav-item]:not([aria-current="page"]):hover,
206
+ html[data-theme-pack="hearth"] [data-nav-item-dropdown] > a:not([aria-current="page"]):hover {
201
207
  color: var(--zd-accent);
202
208
  text-decoration-color: color-mix(in oklch, var(--zd-accent) 50%, transparent);
203
209
  text-underline-offset: 4px;
@@ -214,7 +214,13 @@ html[data-theme-pack="ledger"] [data-nav-item] {
214
214
  font-variant-caps: all-small-caps;
215
215
  letter-spacing: 0.09em;
216
216
  }
217
- html[data-theme-pack="ledger"] [data-nav-item]:hover {
217
+ /* Exclude the current page: it carries the base `bg-fg text-bg` inverted
218
+ fill — recoloring its text to the accent would sit bright-on-bright.
219
+ Two shapes exist (header.tsx): a plain `a[data-nav-item]`, and a dropdown
220
+ whose data-nav-item sits on the wrapper div while aria-current + the
221
+ colors live on its child anchor — target the anchor in both. */
222
+ html[data-theme-pack="ledger"] a[data-nav-item]:not([aria-current="page"]):hover,
223
+ html[data-theme-pack="ledger"] [data-nav-item-dropdown] > a:not([aria-current="page"]):hover {
218
224
  color: var(--zd-accent);
219
225
  text-decoration: none;
220
226
  }
@@ -225,7 +231,11 @@ html[data-theme-pack="ledger"] [data-nav-item]:hover {
225
231
  html[data-theme-pack="ledger"] #desktop-sidebar {
226
232
  border-right-color: color-mix(in srgb, var(--zd-fg) 22%, transparent);
227
233
  }
228
- html[data-theme-pack="ledger"] #desktop-sidebar a:hover {
234
+ /* Exclude the current page: root/branch current nodes carry
235
+ aria-current="page" without data-nav-active and keep the base
236
+ inverted `bg-fg text-bg` fill — recoloring their text to the
237
+ accent on hover would sit accent-on-fg (~1:1). */
238
+ html[data-theme-pack="ledger"] #desktop-sidebar a:not([aria-current="page"]):hover {
229
239
  color: var(--zd-accent);
230
240
  text-decoration: none;
231
241
  }
@@ -340,7 +340,11 @@ html[data-theme-pack="observatory"] #desktop-sidebar {
340
340
  html[data-theme-pack="observatory"] #desktop-sidebar a {
341
341
  border-radius: 0;
342
342
  }
343
- html[data-theme-pack="observatory"] #desktop-sidebar a:not([data-nav-active]):hover {
343
+ /* :not([data-nav-active]) was incomplete: an active root/category node
344
+ carries aria-current="page" without data-nav-active, so hover still
345
+ repainted it and collapsed the gold-plate contrast. Guard the actual
346
+ active marker, aria-current="page", instead. */
347
+ html[data-theme-pack="observatory"] #desktop-sidebar a:not([aria-current="page"]):hover {
344
348
  color: var(--zd-accent);
345
349
  }
346
350
  /* active item: the gold indicator token fills it; square plate */
@@ -265,12 +265,15 @@ html[data-theme-pack="riso"] [data-nav-item]:hover {
265
265
  html[data-theme-pack="riso"] #desktop-sidebar {
266
266
  border-right: 2px solid light-dark(rgb(13 92 148 / 0.35), rgb(88 184 232 / 0.35));
267
267
  }
268
- /* :not([data-nav-active]) is load-bearing, not cosmetic: the bare ID
269
- selector would out-rank the active chip below and repaint its blue
270
- fill with the pale wash on hover — leaving paper-colored text on a
271
- paper-colored wash (invisible). Narrow, single-hook :not — not a
272
- broad recolor chain. */
273
- html[data-theme-pack="riso"] #desktop-sidebar a:hover:not([data-nav-active]) {
268
+ /* The exclusion is load-bearing, not cosmetic: the bare ID selector
269
+ would out-rank the active chip below and repaint its blue fill with
270
+ the pale wash on hover — leaving paper-colored text on a paper-
271
+ colored wash (invisible). Narrow, single-hook :not — not a broad
272
+ recolor chain. :not([data-nav-active]) was incomplete: an active
273
+ root/category node carries aria-current="page" without
274
+ data-nav-active, so hover still repainted it. Guard
275
+ aria-current="page" instead. */
276
+ html[data-theme-pack="riso"] #desktop-sidebar a:hover:not([aria-current="page"]) {
274
277
  text-decoration: none;
275
278
  background: light-dark(#e6e7df, #1a3343);
276
279
  }
@@ -256,8 +256,12 @@ html[data-theme-pack="sakura"] #desktop-sidebar a[aria-current="page"]:not([data
256
256
  html[data-theme-pack="sakura"] aside[data-zd-mobile-sidebar] a[aria-current="page"]:not([data-nav-active]) {
257
257
  border-radius: 0;
258
258
  }
259
- html[data-theme-pack="sakura"] #desktop-sidebar a:not([data-nav-active]):hover,
260
- html[data-theme-pack="sakura"] aside[data-zd-mobile-sidebar] a:not([data-nav-active]):hover {
259
+ /* :not([data-nav-active]) was incomplete: an active root/category node
260
+ carries aria-current="page" without data-nav-active, so hover could
261
+ still repaint it. Guard aria-current="page" instead (latent hole —
262
+ fixed here for consistency with the other packs). */
263
+ html[data-theme-pack="sakura"] #desktop-sidebar a:not([aria-current="page"]):hover,
264
+ html[data-theme-pack="sakura"] aside[data-zd-mobile-sidebar] a:not([aria-current="page"]):hover {
261
265
  text-decoration: none;
262
266
  color: var(--zd-accent);
263
267
  background: color-mix(in srgb, var(--zd-accent) 10%, transparent);
@@ -265,7 +265,13 @@ html[data-theme-pack="sumi"] [data-header-logo]::after {
265
265
  background: light-dark(#c2242f, #d9333f);
266
266
  margin-left: 0.45em;
267
267
  }
268
- html[data-theme-pack="sumi"] [data-nav-item]:hover {
268
+ /* Exclude the current page: it carries the base `bg-fg text-bg` inverted
269
+ fill — recoloring its text to the accent would sit fg-on-fg. Two DOM
270
+ shapes exist (header.tsx): a plain `a[data-nav-item]`, and a dropdown
271
+ whose data-nav-item sits on the wrapper div while aria-current + the
272
+ colors live on its child anchor — target the anchor in both. */
273
+ html[data-theme-pack="sumi"] a[data-nav-item]:not([aria-current="page"]):hover,
274
+ html[data-theme-pack="sumi"] [data-nav-item-dropdown] > a:not([aria-current="page"]):hover {
269
275
  color: var(--zd-accent);
270
276
  text-decoration-thickness: 2px;
271
277
  text-underline-offset: 3px;
@@ -223,7 +223,13 @@ html[data-theme-pack="swissgrid"] [data-nav-item] {
223
223
  font-weight: 700;
224
224
  letter-spacing: 0.1em;
225
225
  }
226
- html[data-theme-pack="swissgrid"] [data-nav-item]:hover {
226
+ /* Exclude the current page: it carries the base `bg-fg text-bg` inverted
227
+ fill — recoloring its text would sit fg-on-fg. Two DOM shapes exist
228
+ (header.tsx): a plain `a[data-nav-item]`, and a dropdown whose
229
+ data-nav-item sits on the wrapper div while aria-current + the colors
230
+ live on its child anchor — target the anchor in both. */
231
+ html[data-theme-pack="swissgrid"] a[data-nav-item]:not([aria-current="page"]):hover,
232
+ html[data-theme-pack="swissgrid"] [data-nav-item-dropdown] > a:not([aria-current="page"]):hover {
227
233
  color: var(--zd-fg);
228
234
  text-decoration-line: underline;
229
235
  text-decoration-color: #ff0000;