create-zudo-doc 0.2.21 → 1.0.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 (92) hide show
  1. package/dist/compose.d.ts +6 -3
  2. package/dist/compose.js +6 -4
  3. package/dist/features/image-enlarge.d.ts +22 -12
  4. package/dist/features/image-enlarge.js +23 -191
  5. package/dist/scaffold.d.ts +11 -0
  6. package/dist/scaffold.js +51 -24
  7. package/dist/settings-gen.js +4 -0
  8. package/dist/zfb-config-gen.d.ts +16 -10
  9. package/dist/zfb-config-gen.js +34 -239
  10. package/package.json +1 -1
  11. package/templates/base/pages/_mdx-components.ts +64 -185
  12. package/templates/base/pages/index.tsx +1 -1
  13. package/templates/base/pages/lib/_body-end-islands.tsx +6 -13
  14. package/templates/base/pages/lib/_category-nav.tsx +30 -115
  15. package/templates/base/pages/lib/_category-tree-nav.tsx +38 -65
  16. package/templates/base/pages/lib/_compose-meta-title.ts +2 -6
  17. package/templates/base/pages/lib/_doc-body-end.tsx +3 -35
  18. package/templates/base/pages/lib/_doc-content-header.tsx +10 -111
  19. package/templates/base/pages/lib/_doc-history-area.tsx +19 -211
  20. package/templates/base/pages/lib/_doc-metainfo-area.tsx +10 -113
  21. package/templates/base/pages/lib/_doc-page-renderer.tsx +22 -183
  22. package/templates/base/pages/lib/_doc-page-shell.tsx +17 -251
  23. package/templates/base/pages/lib/_doc-pager.tsx +4 -74
  24. package/templates/base/pages/lib/_doc-route-entries.ts +43 -177
  25. package/templates/base/pages/lib/_doc-route-paths.ts +16 -101
  26. package/templates/base/pages/lib/_doc-tags-area.tsx +14 -77
  27. package/templates/base/pages/lib/_extract-headings.ts +21 -295
  28. package/templates/base/pages/lib/_footer-with-defaults.tsx +37 -225
  29. package/templates/base/pages/lib/_frontmatter-preview-data.ts +5 -31
  30. package/templates/base/pages/lib/_head-with-defaults.tsx +13 -138
  31. package/templates/base/pages/lib/_header-with-defaults.tsx +24 -324
  32. package/templates/base/pages/lib/_inline-version-switcher.tsx +16 -78
  33. package/templates/base/pages/lib/_math-block.tsx +4 -63
  34. package/templates/base/pages/lib/_nav-data-prep.ts +32 -51
  35. package/templates/base/pages/lib/_nav-source-cache.ts +12 -57
  36. package/templates/base/pages/lib/_nav-source-docs.ts +33 -233
  37. package/templates/base/pages/lib/_search-widget-script.ts +2 -470
  38. package/templates/base/pages/lib/_search-widget.tsx +9 -195
  39. package/templates/base/pages/lib/_sidebar-prepaint.tsx +6 -59
  40. package/templates/base/pages/lib/_sidebar-with-defaults.tsx +16 -118
  41. package/templates/base/pages/lib/_site-tree-nav.tsx +29 -80
  42. package/templates/base/pages/lib/doc-page-props.ts +26 -44
  43. package/templates/base/pages/lib/locale-merge.ts +32 -145
  44. package/templates/base/pages/lib/route-enumerators.ts +52 -286
  45. package/templates/base/src/components/ai-chat-modal.tsx +9 -8
  46. package/templates/base/src/components/content/code-group.tsx +3 -76
  47. package/templates/base/src/components/content/content-admonition.tsx +4 -56
  48. package/templates/base/src/components/doc-history.tsx +9 -8
  49. package/templates/base/src/components/image-enlarge.tsx +11 -8
  50. package/templates/base/src/components/sidebar-toggle.tsx +6 -170
  51. package/templates/base/src/components/sidebar-tree.tsx +6 -548
  52. package/templates/base/src/config/color-scheme-utils.ts +34 -158
  53. package/templates/base/src/config/i18n.ts +9 -0
  54. package/templates/base/src/config/settings-types.ts +34 -172
  55. package/templates/base/src/config/z-index-tokens.ts +5 -4
  56. package/templates/base/src/styles/global.css +40 -587
  57. package/templates/base/src/utils/base.ts +1 -1
  58. package/templates/base/src/utils/docs.ts +47 -16
  59. package/templates/base/src/utils/github.ts +12 -9
  60. package/templates/base/src/utils/nav-scope.ts +13 -42
  61. package/templates/base/src/utils/sidebar.ts +18 -86
  62. package/templates/base/src/utils/slug.ts +10 -53
  63. package/templates/base/src/utils/smart-break.tsx +12 -120
  64. package/templates/base/src/utils/tags.ts +25 -68
  65. package/templates/features/bodyFootUtil/files/src/utils/github.ts +12 -9
  66. package/templates/features/designTokenPanel/files/src/lib/design-token-panel-bootstrap.ts +13 -39
  67. package/templates/features/docHistory/files/src/components/doc-history.tsx +8 -636
  68. package/templates/features/docHistory/files/src/types/doc-history.ts +7 -23
  69. package/templates/features/docTags/files/pages/lib/_tag-pages.tsx +35 -201
  70. package/templates/features/i18n/files/pages/[locale]/index.tsx +1 -1
  71. package/templates/features/imageEnlarge/files/src/components/image-enlarge.tsx +8 -269
  72. package/templates/features/sidebarToggle/files/src/components/desktop-sidebar-toggle.tsx +6 -99
  73. package/templates/features/tagGovernance/files/scripts/tags-audit.ts +67 -515
  74. package/templates/features/versioning/files/pages/lib/_versions-page.tsx +21 -73
  75. package/templates/base/pages/404.tsx +0 -61
  76. package/templates/base/pages/robots.txt.tsx +0 -29
  77. package/templates/base/pages/sitemap.xml.tsx +0 -59
  78. package/templates/base/plugins/connect-adapter.mjs +0 -169
  79. package/templates/base/plugins/copy-public-plugin.mjs +0 -58
  80. package/templates/base/plugins/search-index-plugin.mjs +0 -66
  81. package/templates/base/scripts/gen-z-index.mjs +0 -157
  82. package/templates/base/src/components/mermaid-enlarge.tsx +0 -490
  83. package/templates/base/src/components/site-tree-nav.tsx +0 -220
  84. package/templates/features/claudeResources/files/plugins/claude-resources-plugin.mjs +0 -47
  85. package/templates/features/docHistory/files/plugins/doc-history-plugin.mjs +0 -111
  86. package/templates/features/docTags/files/pages/[locale]/docs/tags/[tag].tsx +0 -59
  87. package/templates/features/docTags/files/pages/[locale]/docs/tags/index.tsx +0 -39
  88. package/templates/features/docTags/files/pages/docs/tags/[tag].tsx +0 -43
  89. package/templates/features/docTags/files/pages/docs/tags/index.tsx +0 -20
  90. package/templates/features/llmsTxt/files/plugins/llms-txt-plugin.mjs +0 -93
  91. package/templates/features/versioning/files/pages/[locale]/docs/versions.tsx +0 -48
  92. package/templates/features/versioning/files/pages/docs/versions.tsx +0 -20
@@ -42,6 +42,11 @@
42
42
  above (line 11); the safelist import above generates the component utilities
43
43
  its headings/links/etc. depend on. */
44
44
  @import "@takazudo/zudo-doc/content.css";
45
+ /* Non-island feature CSS (code block buttons, syntect light-dark token rule,
46
+ .zd-html-preview-code, KaTeX, sidebar toggle geometry, view-transition chrome,
47
+ .ai-chat-md, .zd-enlarge*, .zd-mermaid*).
48
+ Shipped by @takazudo/zudo-doc as single source of truth. See zudolab/zudo-doc#2331. */
49
+ @import "@takazudo/zudo-doc/features.css";
45
50
  /* @slot:global-css:imports */
46
51
 
47
52
  /* ========================================
@@ -84,6 +89,27 @@
84
89
  * ======================================== */
85
90
 
86
91
  @theme {
92
+ /* ========================================
93
+ * Project tight-token color reset
94
+ *
95
+ * Wipes all Tailwind default color tokens (`--color-*`) so only
96
+ * project-defined palette and semantic tokens remain. This enforces the
97
+ * tight-token policy: "NEVER use Tailwind default colors" (per Color Rules
98
+ * in src/CLAUDE.md). Project color tokens (defined below) are added back
99
+ * after this reset.
100
+ *
101
+ * The upstream split-import fix (zfb#159 / 9e37551, shipped in f68a9ba)
102
+ * eliminated the original leak cause (zfb no longer prepends the full
103
+ * @import "tailwindcss" bundle). The reset is retained as an explicit
104
+ * design guardrail — it prevents accidental default palette bleed from any
105
+ * future upstream change and keeps the project's color surface intentional.
106
+ *
107
+ * NOT reset: `--spacing` (the base spacing scale, default 0.25rem).
108
+ * Tailwind spacing utilities compute as calc(var(--spacing) * N); wiping
109
+ * `--spacing` collapses them to zero (observed in Wave 4 Sub-7 #1403).
110
+ * ======================================== */
111
+ --color-*: initial;
112
+
87
113
  /* ========================================
88
114
  * Colors — Three-tier token system
89
115
  * ======================================== */
@@ -125,13 +151,19 @@
125
151
  --color-info: var(--zd-info);
126
152
  /* Overlay is intentionally theme-independent (always dark, not scheme-driven) */
127
153
  --color-overlay: #000;
154
+ /* The page-loading overlay scrim token is feature-gated: the dynamicPageTransition
155
+ feature injects it into @slot:global-css:theme-tokens when enabled, so it is
156
+ intentionally absent from this base @theme block (the host showcase global.css
157
+ carries it inline only because the host is fully featured). Do not re-add it here. */
128
158
  --color-image-overlay-bg: var(--zd-image-overlay-bg);
129
159
  --color-image-overlay-fg: var(--zd-image-overlay-fg);
130
- --color-mermaid-node-bg: var(--zd-mermaid-node-bg);
131
- --color-mermaid-text: var(--zd-mermaid-text);
132
- --color-mermaid-line: var(--zd-mermaid-line);
133
- --color-mermaid-label-bg: var(--zd-mermaid-label-bg);
134
- --color-mermaid-note-bg: var(--zd-mermaid-note-bg);
160
+ /* --color-mermaid-* aliases removed: mermaid-init-script.ts reads --zd-mermaid-* directly;
161
+ these @theme registrations were unused (no Tailwind utility references). --zd-mermaid-*
162
+ injection in color-scheme-utils.ts is preserved and remains the live source. */
163
+ --color-chat-user-bg: var(--zd-chat-user-bg);
164
+ --color-chat-user-text: var(--zd-chat-user-text);
165
+ --color-chat-assistant-bg: var(--zd-chat-assistant-bg);
166
+ --color-chat-assistant-text: var(--zd-chat-assistant-text);
135
167
  --color-matched-keyword-bg: var(--zd-matched-keyword-bg);
136
168
  --color-matched-keyword-fg: var(--zd-matched-keyword-fg);
137
169
  /* @slot:global-css:theme-tokens */
@@ -191,7 +223,7 @@
191
223
  --text-caption: var(--text-scale-xs); /* labels, timestamps */
192
224
  --text-small: var(--text-scale-sm); /* secondary text, nav items */
193
225
  --text-body: var(--text-scale-md); /* paragraphs, default */
194
- --text-title: var(--text-scale-lg); /* card titles, section labels */
226
+ --text-title: var(--text-scale-lg); /* card titles, section labels */
195
227
  --text-heading: var(--text-scale-xl); /* page headings */
196
228
  --text-display: var(--text-scale-2xl); /* hero text */
197
229
 
@@ -258,7 +290,7 @@
258
290
  /* GENERATED:Z_INDEX_END */
259
291
 
260
292
  :root {
261
- /* Default responsive range; sidebar-resizer.ts allows 192–448px for explicit resizing */
293
+ /* Default responsive range; sidebar-resizer.ts allows 192–448px for explicit resizing. */
262
294
  --zd-sidebar-w: clamp(14rem, 20vw, 22rem);
263
295
 
264
296
  /* ── Tier 1: Abstract font-size scale (raw values — NOT for direct component use) ── */
@@ -271,7 +303,7 @@
271
303
  --text-scale-xl: 3rem; /* 48px */
272
304
  --text-scale-2xl: 3.75rem; /* 60px */
273
305
 
274
- --default-transition-duration: 150ms; /* consumed by hash-link opacity fade */
306
+ --default-transition-duration: 150ms; /* consumed by image-enlarge and hash-link opacity fade */
275
307
  --zd-transition-slow: 200ms; /* sidebar transform/visibility and content-band max-width */
276
308
  --zd-transition-slower: 300ms; /* view-transition fade-in (contentFadeIn animation) */
277
309
  --zd-header-h: 80px; /* sticky header height; consumed by scroll-margin-top on [id] anchors */
@@ -308,234 +340,6 @@ body {
308
340
  }
309
341
  }
310
342
 
311
- /* ========================================
312
- * Code block buttons (copy + word wrap)
313
- * ======================================== */
314
-
315
- .code-block-wrapper {
316
- position: relative;
317
- }
318
-
319
- pre[class^="syntect-"].word-wrap {
320
- overflow-x: hidden;
321
- }
322
-
323
- pre[class^="syntect-"].word-wrap code {
324
- white-space: pre-wrap;
325
- overflow-wrap: anywhere;
326
- }
327
-
328
- .code-block-sr-announce {
329
- position: absolute;
330
- width: 1px;
331
- height: 1px;
332
- overflow: hidden;
333
- clip: rect(0, 0, 0, 0);
334
- white-space: nowrap;
335
- }
336
-
337
- .code-buttons {
338
- position: absolute;
339
- top: var(--spacing-hsp-sm);
340
- right: var(--spacing-hsp-sm);
341
- display: flex;
342
- gap: var(--spacing-hsp-2xs);
343
- opacity: 0;
344
- transition: opacity 0.15s;
345
- z-index: var(--z-index-local-1);
346
- }
347
-
348
- .code-block-wrapper:hover .code-buttons,
349
- .code-block-wrapper:focus-within .code-buttons {
350
- opacity: 1;
351
- }
352
-
353
- .code-btn {
354
- display: flex;
355
- align-items: center;
356
- justify-content: center;
357
- width: var(--spacing-hsp-2xl);
358
- height: var(--spacing-hsp-2xl);
359
- padding: 0;
360
- border: 1px solid var(--color-muted);
361
- border-radius: var(--radius-lg);
362
- background-color: var(--color-surface);
363
- color: var(--color-fg);
364
- cursor: pointer;
365
- transition:
366
- color 0.15s,
367
- border-color 0.15s,
368
- background-color 0.15s;
369
- }
370
-
371
- .code-btn:hover {
372
- color: var(--color-fg);
373
- border-color: var(--color-fg);
374
- }
375
-
376
- .code-btn .code-icon {
377
- width: var(--spacing-hsp-lg);
378
- height: var(--spacing-hsp-lg);
379
- }
380
-
381
- /* Copy: icon swap */
382
- .code-btn-copy .code-icon-check {
383
- display: none;
384
- }
385
- .code-btn-copy .code-icon-copy {
386
- display: block;
387
- }
388
- .code-btn-copy.copied .code-icon-check {
389
- display: block;
390
- color: var(--color-success);
391
- }
392
- .code-btn-copy.copied .code-icon-copy {
393
- display: none;
394
- }
395
-
396
- /* Word wrap active state */
397
- .code-btn-wrap.active {
398
- color: var(--color-accent);
399
- border-color: var(--color-accent);
400
- }
401
-
402
- @media (scripting: none) {
403
- .code-buttons {
404
- display: none;
405
- }
406
- }
407
-
408
- /* ========================================
409
- * Code-block syntax-highlight token color rule
410
- * ======================================== */
411
-
412
- /* Build-time: zfb's Rust pipeline runs SyntectPlugin and emits
413
- * <pre class="syntect-..."> with inline style="color:#hex" on every token
414
- * span. The inline hex is the desired token color — this rule must NOT
415
- * override it (so: NO !important on `color`).
416
- *
417
- * Runtime (design-token-tweak panel, when enabled): Shiki re-highlights on
418
- * demand and emits spans with inline style="--shiki-light:#X;--shiki-dark:#Y"
419
- * — CSS custom properties only, no `color` declaration — depending on this
420
- * rule to resolve light-dark(var(--shiki-light), var(--shiki-dark)) to the
421
- * active theme. Shiki spans have no inline `color`, so the rule applies
422
- * without needing !important. */
423
- [data-theme] .shiki,
424
- [data-theme] .shiki span,
425
- [data-theme] pre[class^="syntect-"],
426
- [data-theme] pre[class^="syntect-"] span {
427
- color: light-dark(var(--shiki-light), var(--shiki-dark));
428
- background-color: light-dark(var(--shiki-light-bg), var(--shiki-dark-bg));
429
- font-style: light-dark(
430
- var(--shiki-light-font-style, inherit),
431
- var(--shiki-dark-font-style, inherit)
432
- );
433
- }
434
-
435
- /* HtmlPreview component — Shiki code blocks
436
- * Token colors inherit from the global [data-theme] .shiki rule above.
437
- * These rules only adjust layout and background for the preview context. */
438
- .zd-html-preview-code pre.shiki {
439
- margin: 0;
440
- padding: 0.75rem;
441
- font-size: 0.875rem;
442
- line-height: 1.6;
443
- overflow-x: auto;
444
- }
445
-
446
- .zd-html-preview-code pre.shiki code {
447
- font-family: var(--font-mono);
448
- white-space: pre;
449
- }
450
-
451
- /* ========================================
452
- * Code block line highlighting
453
- * ======================================== */
454
-
455
- /* Line highlighting via {1,3-5} meta syntax.
456
- * zfb emits data-line-highlight="true" on the <span class="line"> element
457
- * (verified empirically at next.13). The .highlighted class form is kept for
458
- * forward-compat in case a future zfb version emits both. */
459
- pre[class^="syntect-"] .line.highlighted,
460
- pre[class^="syntect-"] [data-line-highlight="true"] {
461
- background-color: color-mix(in srgb, var(--color-accent) 15%, transparent);
462
- }
463
-
464
- /* Word highlighting via /word/ meta syntax */
465
- pre[class^="syntect-"] .line .highlighted-word {
466
- background-color: color-mix(in srgb, var(--color-accent) 25%, transparent);
467
- border-bottom: 1px solid var(--color-accent);
468
- }
469
-
470
- /* ========================================
471
- * Code block titles
472
- * ======================================== */
473
-
474
- .code-block-container {
475
- margin-top: 1em;
476
- margin-bottom: 1em;
477
- }
478
-
479
- .code-block-title {
480
- font-family: var(--font-mono);
481
- font-size: var(--text-small);
482
- color: var(--color-muted);
483
- background-color: var(--color-code-bg);
484
- border: 1px solid var(--color-muted);
485
- border-bottom: none;
486
- padding: var(--spacing-vsp-2xs) var(--spacing-hsp-lg);
487
- }
488
-
489
- .code-block-container pre[class^="syntect-"] {
490
- margin-top: 0 !important;
491
- border-top-left-radius: 0;
492
- border-top-right-radius: 0;
493
- }
494
-
495
- /* ========================================
496
- * CodeGroup raw-code panels (codeTabs Option A)
497
- *
498
- * zfb's :::code-group emits <CodeGroup tabs={[...]}> with one
499
- * <pre data-lang="…">{RAW text}</pre> child per fence. These <pre> elements
500
- * are NOT syntect-highlighted (the Rust pipeline doesn't highlight inside
501
- * code-group fences), so they get no `.syntect-*` class. Apply explicit
502
- * code-block visual treatment via tokens to match the syntect-highlighted
503
- * code blocks surrounding them.
504
- * ======================================== */
505
-
506
- .code-group-panel pre[data-lang] {
507
- font-family: var(--font-mono);
508
- font-size: var(--text-small);
509
- line-height: var(--leading-relaxed);
510
- background-color: var(--color-code-bg);
511
- color: var(--color-code-fg);
512
- padding: var(--spacing-vsp-xs) var(--spacing-hsp-lg);
513
- overflow-x: auto;
514
- border-radius: 0 0 var(--radius-DEFAULT) var(--radius-DEFAULT);
515
- margin: 0;
516
- }
517
-
518
- /* KaTeX math equations */
519
- .katex {
520
- font-size: 1.1em;
521
- }
522
-
523
- .katex-display {
524
- margin: var(--spacing-vsp-md) 0;
525
- overflow-x: auto;
526
- overflow-y: hidden;
527
- padding: var(--spacing-vsp-xs) 0;
528
- }
529
-
530
- .katex-display > .katex {
531
- white-space: nowrap;
532
- }
533
-
534
- /* Make KaTeX respect our color scheme */
535
- .katex .base {
536
- color: var(--zd-fg);
537
- }
538
-
539
343
  /* ── Search highlight ── */
540
344
  [data-search-results] mark {
541
345
  background-color: var(--color-matched-keyword-bg);
@@ -544,30 +348,6 @@ pre[class^="syntect-"] .line .highlighted-word {
544
348
  border-radius: 2px;
545
349
  }
546
350
 
547
- /* ========================================
548
- * Page transition animations
549
- * ======================================== */
550
-
551
- @keyframes contentFadeIn {
552
- from {
553
- opacity: 0;
554
- transform: translateY(-0.75rem);
555
- }
556
- to {
557
- opacity: 1;
558
- transform: translateY(0);
559
- }
560
- }
561
-
562
- @keyframes contentFadeOut {
563
- from {
564
- opacity: 1;
565
- }
566
- to {
567
- opacity: 0;
568
- }
569
- }
570
-
571
351
  /* Version-switcher responsive visibility (moved out of the component to
572
352
  * avoid <style>-inside-<div> HTML5 content-model violation; required when
573
353
  * the i18nVersion feature ships a <VersionSwitcher> wrapped in
@@ -579,61 +359,6 @@ pre[class^="syntect-"] .line .highlighted-word {
579
359
  }
580
360
  }
581
361
 
582
- /* ========================================
583
- * Desktop sidebar toggle (W7A: unconditional — feature gates at runtime)
584
- * ======================================== */
585
-
586
- @media (min-width: 1024px) {
587
- #desktop-sidebar {
588
- transition:
589
- transform var(--zd-transition-slow) ease-in-out,
590
- visibility var(--zd-transition-slow);
591
- }
592
-
593
- html[data-sidebar-hidden] #desktop-sidebar {
594
- transform: translateX(calc(-1 * var(--zd-sidebar-w)));
595
- visibility: hidden;
596
- }
597
-
598
- .zd-sidebar-content-wrapper {
599
- transition: margin-left var(--zd-transition-slow) ease-in-out;
600
- }
601
-
602
- html[data-sidebar-hidden] .zd-sidebar-content-wrapper {
603
- margin-left: 0;
604
- }
605
-
606
- /* When hidden via the toggle, narrow the content band to the
607
- * hide_sidebar frontmatter width so it centers (the flex parent already
608
- * applies justify-content: center) instead of leaving a dead gap where
609
- * the sidebar was. 80rem must match the `max-w-[80rem]` hide_sidebar
610
- * branch in doc-layout.tsx. These rules are unlayered, so they win over
611
- * the Tailwind `max-w-[clamp(...)]` utility (utilities layer). (#2002) */
612
- .zd-doc-content-band {
613
- transition: max-width var(--zd-transition-slow) ease-in-out;
614
- }
615
-
616
- html[data-sidebar-hidden] .zd-doc-content-band {
617
- max-width: 80rem;
618
- }
619
- }
620
-
621
- /* Sidebar toggle button — left position uses CSS variable, needs global rule */
622
- @media (min-width: 1024px) {
623
- .zd-desktop-sidebar-toggle {
624
- left: var(--zd-sidebar-w);
625
- }
626
-
627
- html[data-sidebar-hidden] .zd-desktop-sidebar-toggle {
628
- left: 0;
629
- }
630
- }
631
-
632
- /* The SPA-swap flash guard (`data-sidebar-no-transition`) was removed once
633
- * doc-layout adopted <ClientRouter preserveHtmlAttrs={["data-sidebar-hidden",
634
- * ...]} /> (zfb-runtime >= 0.1.0-next.52): the attribute now survives the swap
635
- * before paint, so no sidebar geometry transition fires mid-swap. */
636
-
637
362
  /* ── Find-in-page highlight (W7A: unconditional — Tauri feature gates at runtime) ── */
638
363
  .find-match {
639
364
  background-color: color-mix(in oklch, var(--color-warning) 40%, transparent);
@@ -645,276 +370,4 @@ pre[class^="syntect-"] .line .highlighted-word {
645
370
  outline: 2px solid color-mix(in oklch, var(--color-warning) 90%, transparent);
646
371
  }
647
372
 
648
- /* ========================================
649
- * Image enlarge (.zd-enlargeable) — W7A: unconditional, runtime-gated
650
- * ======================================== */
651
-
652
- .zd-enlargeable {
653
- position: relative;
654
- display: block;
655
- margin-inline: 0;
656
- margin-block-end: 0;
657
- border: 1px solid transparent;
658
- transition: border-color var(--default-transition-duration);
659
- }
660
-
661
- /* Eligible images (expand-btn visible): the entire image becomes the click target.
662
- * Show a subtle border as the interactivity affordance, accent on hover. */
663
- .zd-enlargeable:has(.zd-enlarge-btn:not([hidden])) {
664
- border-color: var(--color-muted);
665
- cursor: pointer;
666
- }
667
-
668
- .zd-enlargeable:has(.zd-enlarge-btn:not([hidden])):hover {
669
- border-color: var(--color-accent);
670
- }
671
-
672
- .zd-enlargeable img {
673
- display: block;
674
- max-width: 100%;
675
- height: auto;
676
- }
677
-
678
- .zd-enlarge-btn {
679
- position: absolute;
680
- top: var(--spacing-image-overlay-inset);
681
- right: var(--spacing-image-overlay-inset);
682
- display: flex;
683
- align-items: center;
684
- justify-content: center;
685
- padding: var(--spacing-image-overlay-inset);
686
- border: none;
687
- background: transparent;
688
- cursor: pointer;
689
- z-index: var(--z-index-local-1);
690
- transition: opacity var(--default-transition-duration);
691
- }
692
-
693
- .zd-enlarge-btn::before {
694
- content: "";
695
- position: absolute;
696
- inset: 0;
697
- background: color-mix(in oklch, var(--color-image-overlay-bg) 80%, transparent);
698
- z-index: var(--z-index-local-1);
699
- }
700
-
701
- .zd-enlarge-btn > svg {
702
- position: relative;
703
- z-index: var(--z-index-local-2);
704
- width: var(--spacing-icon-sm);
705
- height: var(--spacing-icon-sm);
706
- color: var(--color-image-overlay-fg);
707
- fill: currentColor;
708
- }
709
-
710
- .zd-enlarge-btn:hover {
711
- opacity: 0.8;
712
- }
713
-
714
- .zd-enlarge-btn[hidden] {
715
- display: none !important;
716
- }
717
-
718
- dialog.zd-enlarge-dialog::backdrop {
719
- background: color-mix(in oklch, var(--color-overlay) 80%, transparent);
720
- }
721
-
722
- /* Close button is positioned to the VIEWPORT corner (not the dialog) so it
723
- * stays visually distinct against any image, regardless of the dialog's size.
724
- * This relies on the dialog being transform-free (see DIALOG_STYLE in
725
- * image-enlarge.tsx) — a transform on the dialog would re-anchor this fixed
726
- * button to the dialog corner. */
727
- .zd-enlarge-dialog-close {
728
- position: fixed;
729
- top: 4px;
730
- right: 4px;
731
- display: flex;
732
- align-items: center;
733
- justify-content: center;
734
- padding: var(--spacing-image-overlay-inset);
735
- border: none;
736
- background: transparent;
737
- cursor: pointer;
738
- z-index: var(--z-index-local-1);
739
- transition: opacity var(--default-transition-duration);
740
- }
741
-
742
- .zd-enlarge-dialog-close:hover {
743
- opacity: 0.8;
744
- }
745
-
746
- .zd-enlarge-dialog-close::before {
747
- content: "";
748
- position: absolute;
749
- inset: 0;
750
- background: color-mix(in oklch, var(--color-image-overlay-bg) 80%, transparent);
751
- z-index: var(--z-index-local-1);
752
- }
753
-
754
- .zd-enlarge-dialog-close > svg {
755
- position: relative;
756
- z-index: var(--z-index-local-2);
757
- width: var(--spacing-icon-lg);
758
- height: var(--spacing-icon-lg);
759
- color: var(--color-image-overlay-fg);
760
- fill: currentColor;
761
- }
762
-
763
- /* Wide viewport: ~2× larger close button to read clearly against any image. */
764
- @media (min-width: 1024px) {
765
- .zd-enlarge-dialog-close {
766
- top: 10px;
767
- right: 10px;
768
- }
769
-
770
- .zd-enlarge-dialog-close > svg {
771
- width: calc(var(--spacing-icon-lg) * 2);
772
- height: calc(var(--spacing-icon-lg) * 2);
773
- }
774
- }
775
-
776
- /* ========================================
777
- * Mermaid enlarge (.zd-mermaid-enlargeable) — issue #2176 / #2178
778
- *
779
- * Unlike images (SSR-wrapped in figure.zd-enlargeable), mermaid diagrams render
780
- * client-side, so the mermaid-enlarge island injects the `.zd-enlarge-btn` into
781
- * each rendered `.mermaid` container and adds `.zd-mermaid-enlargeable` (which
782
- * makes the container position:relative so the button anchors to it). The
783
- * button + close-button visuals are SHARED with image-enlarge above
784
- * (`.zd-enlarge-btn`, `.zd-enlarge-dialog-close`).
785
- * ======================================== */
786
-
787
- .zd-mermaid-enlargeable {
788
- position: relative;
789
- border: 1px solid transparent;
790
- border-radius: var(--radius-DEFAULT);
791
- transition: border-color var(--default-transition-duration);
792
- }
793
-
794
- /* The diagram is always enlargeable once the button is injected — show a subtle
795
- * border affordance, accent on hover (mirrors the image-enlarge affordance). */
796
- .zd-mermaid-enlargeable:has(.zd-enlarge-btn) {
797
- cursor: pointer;
798
- border-color: var(--color-muted);
799
- }
800
-
801
- .zd-mermaid-enlargeable:has(.zd-enlarge-btn):hover {
802
- border-color: var(--color-accent);
803
- }
804
-
805
- /* Zoom/pan dialog. The `<dialog>` stays transform-free (see DIALOG_STYLE in
806
- * mermaid-enlarge.tsx); the transform lives on .zd-mermaid-transform. */
807
- dialog.zd-mermaid-dialog::backdrop {
808
- background: color-mix(in oklch, var(--color-overlay) 80%, transparent);
809
- }
810
-
811
- /* Pan viewport — fills the dialog and clips the (possibly zoomed) diagram.
812
- * touch-action:none so pointer-drag pans on touch without the browser
813
- * intercepting the gesture for scrolling. */
814
- .zd-mermaid-viewport {
815
- width: 100%;
816
- height: 100%;
817
- overflow: hidden;
818
- display: flex;
819
- align-items: center;
820
- justify-content: center;
821
- touch-action: none;
822
- }
823
-
824
- .zd-mermaid-viewport[data-pan-active] {
825
- cursor: grab;
826
- /* In pan mode a drag should pan, not select diagram text. Scoped to
827
- * [data-pan-active] so text stays selectable when pan mode is off. */
828
- -webkit-user-select: none;
829
- user-select: none;
830
- }
831
-
832
- .zd-mermaid-viewport[data-pan-active]:active {
833
- cursor: grabbing;
834
- }
835
-
836
- .zd-mermaid-transform {
837
- width: 100%;
838
- height: 100%;
839
- display: flex;
840
- align-items: center;
841
- justify-content: center;
842
- will-change: transform;
843
- }
844
-
845
- /* SVG sizing — fit the cloned mermaid svg inside the dialog like
846
- * object-fit:contain. The svg fills the viewport box (100% x 100%) and its
847
- * viewBox content is letterboxed/centered by the default
848
- * preserveAspectRatio="xMidYMid meet". A TALL diagram (e.g. a state diagram) no
849
- * longer gets forced to full dialog width with `height:auto` driving the height
850
- * far past the viewport via its aspect ratio — it now shrinks to fit the height
851
- * instead. max-width/max-height !important neutralize the inline px sizing
852
- * mermaid bakes onto the svg (without it the diagram renders tiny). Scoped to
853
- * .zd-mermaid-transform (the cloned-diagram wrapper) so it does NOT hit the
854
- * toolbar / close-button icon svgs that also live inside the dialog. */
855
- .zd-mermaid-transform svg {
856
- max-width: 100% !important;
857
- max-height: 100% !important;
858
- width: 100%;
859
- height: 100%;
860
- }
861
-
862
- /* Edge labels (e.g. "Yes"/"No") render inside a tightly-sized <foreignObject>;
863
- * when the cloned diagram is upscaled to fill the dialog, sub-pixel overflow
864
- * makes the browser clip the last glyph at the foreignObject's right edge.
865
- * Letting the foreignObject overflow render keeps the full label visible. */
866
- .zd-mermaid-transform svg foreignObject {
867
- overflow: visible;
868
- }
869
-
870
- /* Toolbar — bottom-right pill of zoom/pan controls (2× sized buttons). */
871
- .zd-mermaid-toolbar {
872
- position: absolute;
873
- bottom: var(--spacing-image-overlay-inset);
874
- right: var(--spacing-image-overlay-inset);
875
- display: flex;
876
- gap: var(--spacing-hsp-xs);
877
- padding: var(--spacing-hsp-xs);
878
- border: 1px solid var(--color-muted);
879
- border-radius: var(--radius-DEFAULT);
880
- background: color-mix(in oklch, var(--color-surface) 92%, transparent);
881
- z-index: var(--z-index-local-2);
882
- }
883
-
884
- .zd-mermaid-tool-btn {
885
- display: flex;
886
- align-items: center;
887
- justify-content: center;
888
- width: calc(var(--spacing-icon-lg) * 2);
889
- height: calc(var(--spacing-icon-lg) * 2);
890
- padding: var(--spacing-vsp-2xs);
891
- border: none;
892
- border-radius: var(--radius-DEFAULT);
893
- background: transparent;
894
- color: var(--color-fg);
895
- cursor: pointer;
896
- transition:
897
- background-color var(--default-transition-duration),
898
- opacity var(--default-transition-duration);
899
- }
900
-
901
- .zd-mermaid-tool-btn:hover:not([disabled]) {
902
- background: color-mix(in oklch, var(--color-muted) 25%, transparent);
903
- }
904
-
905
- .zd-mermaid-tool-btn[aria-pressed="true"] {
906
- background: color-mix(in oklch, var(--color-accent) 30%, transparent);
907
- color: var(--color-accent);
908
- }
909
-
910
- .zd-mermaid-tool-btn[disabled] {
911
- opacity: 0.4;
912
- cursor: default;
913
- }
914
-
915
- .zd-mermaid-tool-btn > svg {
916
- width: calc(var(--spacing-icon-md) * 2);
917
- height: calc(var(--spacing-icon-md) * 2);
918
- }
919
-
920
373
  /* @slot:global-css:feature-styles */
@@ -156,7 +156,7 @@ export function isDefaultLocaleOnlyPath(path: string): boolean {
156
156
  }
157
157
 
158
158
  /** Build a versioned docs URL for the given slug, version, and lang. */
159
- export function versionedDocsUrl(slug: string, versionSlug: string, lang: Locale = defaultLocale): string {
159
+ export function versionedDocsUrl(slug: string, versionSlug: string, lang: Locale | string = defaultLocale): string {
160
160
  const path = lang === defaultLocale
161
161
  ? `/v/${versionSlug}/docs/${slug}`
162
162
  : `/v/${versionSlug}/${lang}/docs/${slug}`;