jekyll-theme-zer0 1.29.0 → 1.31.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 (163) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +886 -2
  3. data/_data/README.md +2 -0
  4. data/_data/ai.yml +5 -3
  5. data/_data/ai_pricing.yml +36 -0
  6. data/_data/backlog.yml +196 -22
  7. data/_data/consumers.yml +147 -5
  8. data/_data/features.yml +147 -21
  9. data/_data/feedback_types.yml +17 -12
  10. data/_data/landing.yml +5 -2
  11. data/_data/navigation/quickstart.yml +4 -0
  12. data/_data/site_builder.yml +874 -0
  13. data/_data/theme-manifest.yml +132 -122
  14. data/_data/ui-text.yml +19 -0
  15. data/_includes/README.md +11 -1
  16. data/_includes/analytics/posthog.html +30 -11
  17. data/_includes/components/README.md +1 -1
  18. data/_includes/components/admin-links.html +2 -2
  19. data/_includes/components/admin-tabs.html +2 -2
  20. data/_includes/components/ai-chat.html +14 -11
  21. data/_includes/components/analytics-dashboard.html +8 -8
  22. data/_includes/components/author-bio.html +1 -1
  23. data/_includes/components/author-card.html +10 -2
  24. data/_includes/components/author-eeat.html +4 -4
  25. data/_includes/components/background-customizer.html +8 -8
  26. data/_includes/components/background-image.html +120 -0
  27. data/_includes/components/background-settings.html +28 -15
  28. data/_includes/components/collection-manager.html +5 -5
  29. data/_includes/components/component-showcase.html +13 -13
  30. data/_includes/components/config-editor.html +12 -12
  31. data/_includes/components/config-viewer.html +8 -8
  32. data/_includes/components/cookie-consent.html +41 -41
  33. data/_includes/components/cta-button.html +7 -2
  34. data/_includes/components/dev-shortcuts.html +7 -7
  35. data/_includes/components/env-dashboard.html +8 -8
  36. data/_includes/components/env-switcher.html +9 -9
  37. data/_includes/components/feature-card.html +2 -2
  38. data/_includes/components/halfmoon.html +2 -2
  39. data/_includes/components/info-section.html +36 -36
  40. data/_includes/components/js-cdn.html +15 -15
  41. data/_includes/components/language-toggle.html +4 -4
  42. data/_includes/components/mermaid.html +79 -435
  43. data/_includes/components/nanobar.html +5 -5
  44. data/_includes/components/nav-editor.html +2 -2
  45. data/_includes/components/nav-export.html +2 -2
  46. data/_includes/components/nav-overview.html +2 -2
  47. data/_includes/components/page-feedback.html +45 -30
  48. data/_includes/components/page-views-init.html +1 -1
  49. data/_includes/components/post-card.html +22 -22
  50. data/_includes/components/post-type-badge.html +2 -2
  51. data/_includes/components/powered-by.html +2 -2
  52. data/_includes/components/preview-image.html +28 -3
  53. data/_includes/components/quick-index.html +2 -2
  54. data/_includes/components/search-modal.html +27 -2
  55. data/_includes/components/searchbar.html +2 -2
  56. data/_includes/components/svg-background.html +2 -2
  57. data/_includes/components/theme-customizer.html +2 -2
  58. data/_includes/components/theme-info.html +6 -6
  59. data/_includes/components/theme-preview-gallery.html +23 -23
  60. data/_includes/content/giscus.html +2 -2
  61. data/_includes/content/intro.html +15 -8
  62. data/_includes/content/jsonld-faq.html +2 -2
  63. data/_includes/content/jsonld-software.html +2 -2
  64. data/_includes/content/seo.html +12 -4
  65. data/_includes/content/sitemap.html +247 -290
  66. data/_includes/content/toc.html +183 -183
  67. data/_includes/core/branding.html +13 -20
  68. data/_includes/core/console-capture.html +32 -74
  69. data/_includes/core/favicon.html +49 -7
  70. data/_includes/core/footer-fabs.html +17 -3
  71. data/_includes/core/footer.html +31 -18
  72. data/_includes/core/head.html +128 -92
  73. data/_includes/core/header.html +85 -54
  74. data/_includes/core/tokens-inline.html +16 -1
  75. data/_includes/docs/bootstrap-docs.html +8 -8
  76. data/_includes/landing/landing-install-cards.html +2 -2
  77. data/_includes/landing/landing-quick-links.html +1 -1
  78. data/_includes/navigation/admin-nav.html +2 -2
  79. data/_includes/navigation/nav-tree.html +8 -8
  80. data/_includes/navigation/navbar.html +21 -18
  81. data/_includes/navigation/section-sidebar.html +16 -16
  82. data/_includes/navigation/sidebar-config.html +36 -2
  83. data/_includes/navigation/sidebar-left.html +15 -15
  84. data/_includes/navigation/sidebar-right.html +6 -6
  85. data/_includes/obsidian/full-graph.html +2 -2
  86. data/_includes/setup/claude-session.html +72 -0
  87. data/_includes/setup/prereq-checklist.html +90 -0
  88. data/_includes/setup/wizard.html +910 -291
  89. data/_includes/stats/stats-categories.html +8 -8
  90. data/_includes/stats/stats-header.html +14 -14
  91. data/_includes/stats/stats-metrics.html +12 -12
  92. data/_includes/stats/stats-no-data.html +12 -12
  93. data/_includes/stats/stats-overview.html +6 -6
  94. data/_includes/stats/stats-tags.html +8 -8
  95. data/_layouts/404.html +38 -24
  96. data/_layouts/admin.html +22 -22
  97. data/_layouts/article.html +54 -37
  98. data/_layouts/author.html +20 -20
  99. data/_layouts/authors.html +2 -2
  100. data/_layouts/book-abc.html +12 -12
  101. data/_layouts/book-story.html +15 -15
  102. data/_layouts/book.html +12 -12
  103. data/_layouts/collection.html +33 -33
  104. data/_layouts/cookbook.html +12 -12
  105. data/_layouts/default.html +27 -24
  106. data/_layouts/home.html +36 -24
  107. data/_layouts/index.html +10 -10
  108. data/_layouts/landing.html +17 -17
  109. data/_layouts/news.html +44 -44
  110. data/_layouts/note.html +40 -39
  111. data/_layouts/notebook.html +36 -35
  112. data/_layouts/recipe.html +24 -24
  113. data/_layouts/root.html +95 -55
  114. data/_layouts/section.html +23 -23
  115. data/_layouts/setup.html +3 -3
  116. data/_layouts/sitemap-collection.html +49 -49
  117. data/_layouts/stats.html +40 -40
  118. data/_layouts/tag.html +12 -12
  119. data/_layouts/welcome.html +21 -21
  120. data/_sass/components/_cookie-banner.scss +48 -0
  121. data/_sass/components/_mermaid.scss +375 -0
  122. data/_sass/components/_setup-wizard.scss +588 -41
  123. data/_sass/components/_ui-enhancements.scss +11 -0
  124. data/_sass/core/_navbar.scss +147 -100
  125. data/_sass/core/_sidebar-extras.scss +2 -14
  126. data/_sass/core/code-copy.scss +27 -8
  127. data/_sass/layouts/_navbar-extras.scss +14 -4
  128. data/assets/css/extension-points-demo.css +27 -0
  129. data/assets/css/main.scss +1 -0
  130. data/assets/data/wiki-index.json +19 -0
  131. data/assets/js/ai-chat.js +47 -5
  132. data/assets/js/auto-hide-nav.js +53 -13
  133. data/assets/js/code-copy.js +72 -1
  134. data/assets/js/extension-points-demo.js +48 -0
  135. data/assets/js/fleet-feedback-capture.js +124 -0
  136. data/assets/js/fleet-feedback.js +853 -0
  137. data/assets/js/mermaid-diagrams.js +1373 -0
  138. data/assets/js/modules/navigation/config.js +9 -6
  139. data/assets/js/modules/navigation/scroll-spy.js +315 -80
  140. data/assets/js/modules/theme/appearance.js +8 -2
  141. data/assets/js/obsidian-local-graph.js +27 -5
  142. data/assets/js/obsidian-wiki-links.js +8 -3
  143. data/assets/js/page-feedback.js +125 -192
  144. data/assets/js/search-modal.js +26 -0
  145. data/assets/js/setup-wizard.js +2119 -363
  146. data/assets/js/site-builder.js +1834 -0
  147. data/assets/js/ui-enhancements.js +11 -3
  148. data/scripts/README.md +15 -0
  149. data/scripts/ai/README.md +38 -0
  150. data/scripts/ai/api_call.rb +124 -0
  151. data/scripts/ai/usage.rb +314 -0
  152. data/scripts/ai/usage_report.rb +225 -0
  153. data/scripts/ci/test_visual_evidence_autogen.py +403 -0
  154. data/scripts/ci/visual_evidence_autogen.py +1070 -0
  155. data/scripts/content-review.rb +20 -1
  156. data/scripts/features/install-preview-generator +1 -1
  157. data/scripts/lib/README.md +12 -0
  158. data/scripts/lib/preview_generator.py +2 -2
  159. data/scripts/test/integration/mermaid +22 -8
  160. data/scripts/test/lib/run_tests.sh +1 -0
  161. data/scripts/test/lib/test_visual_evidence_autogen.sh +24 -0
  162. data/scripts/translate.rb +23 -1
  163. metadata +21 -2
@@ -53,6 +53,17 @@ img {
53
53
  }
54
54
  }
55
55
 
56
+ // Article hero (_layouts/article.html, figure.featured-hero) — the LCP image.
57
+ // Reserve its box before it loads so the text below never shifts (issue #485).
58
+ // A fixed ratio in CSS rather than width/height attributes: preview assets are
59
+ // 3:2 (1024x683) generated, but some are portrait or external, and a declared
60
+ // intrinsic size that disagrees with the file would itself cause the shift.
61
+ // object-fit: cover (inline on the <img>) crops, never distorts; the inline
62
+ // max-height: 500px still caps the box on wide viewports.
63
+ .featured-hero img {
64
+ aspect-ratio: 3 / 2;
65
+ }
66
+
56
67
  // Hero content animations
57
68
  .bg-primary .container-xl {
58
69
  animation: fadeInUp 0.8s ease-out;
@@ -6,10 +6,12 @@
6
6
  // Breakpoints:
7
7
  // - Desktop >= 992px (inline nav; label density via @container bd-nav)
8
8
  // - Mobile < 992px (offcanvas)
9
- // Nav label tiers (center track / bd-nav container width):
9
+ // Nav label tiers (center track / bd-nav container width) — TWO, never truncating:
10
10
  // - >= 51rem (~816px): icon + full label
11
- // - 41-50.99rem: label only (icons dropped so the labels still fit)
12
- // - < 41rem (~656px): icon-only (tooltips on lg–xl viewports)
11
+ // - < 51rem: icon-only (tooltips on lg–xl viewports)
12
+ // Icons are present in BOTH tiers. The old bare-label middle tier and the
13
+ // `text-overflow: ellipsis` safety net were removed in #405 — a truncated
14
+ // label ("Quicksta…") is worse than an icon with a tooltip.
13
15
  // ==============================================================================
14
16
 
15
17
  // -----------------------------------------------------------------------------
@@ -92,6 +94,20 @@
92
94
  }
93
95
  }
94
96
 
97
+ // The below-lg menu toggle carries a visible "Menu" label beside its glyph, so
98
+ // it cannot be confused with the sidebar hamburger sitting next to it (#405).
99
+ .navbar-toggler.navbar-toggler-labeled {
100
+ display: inline-flex;
101
+ align-items: center;
102
+ gap: 0.375rem;
103
+ width: auto;
104
+
105
+ .navbar-toggler-text {
106
+ font-size: 0.9375rem;
107
+ line-height: 1;
108
+ }
109
+ }
110
+
95
111
  // Shared icon-only nav styling (applied via @container bd-nav below)
96
112
  @mixin navbar-nav-icon-only {
97
113
  #bdNavbar .nav-link .nav-link-text {
@@ -137,21 +153,16 @@
137
153
  overflow: visible;
138
154
  }
139
155
 
156
+ // No transform on :hover. A translate moves the element out from under a
157
+ // pointer parked on the edge it vacates, hover exits, the element returns —
158
+ // an infinite flicker. Nav items are thin enough for a 1px lift to do it.
140
159
  #bdNavbar .nav-link:hover {
141
160
  background-color: var(--bs-tertiary-bg);
142
- transform: translateY(-1px);
143
161
  }
144
162
 
145
163
  .nav-hover-dropdown .dropdown-toggle-split {
146
164
  padding: var(--zer0-space-1) var(--zer0-space-2);
147
165
 
148
- &::after {
149
- transition: transform 0.2s ease-in-out;
150
- }
151
-
152
- &:hover::after {
153
- transform: translateY(2px);
154
- }
155
166
  }
156
167
  }
157
168
 
@@ -181,12 +192,18 @@
181
192
  &:hover {
182
193
  color: var(--bs-primary);
183
194
  background-color: var(--bs-tertiary-bg);
184
- transform: translateY(-1px);
185
195
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
196
+ // These are `.btn`s, and _ui-enhancements.scss lifts every button by 2px
197
+ // on hover. Dropping the navbar's own translateY(-1px) is not enough —
198
+ // the inherited lift flickers the same way. `transform: none` is the
199
+ // repo's existing corrective pattern (see _post-navigation.scss).
200
+ transform: none;
186
201
  }
187
202
 
203
+ // A pressed cue is safe: :active only holds while the button is held down,
204
+ // so the pointer cannot leave and re-enter in a loop.
188
205
  &:active {
189
- transform: translateY(0);
206
+ transform: translateY(1px);
190
207
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
191
208
  }
192
209
 
@@ -197,11 +214,6 @@
197
214
 
198
215
  i {
199
216
  font-size: 1.15rem;
200
- transition: transform 0.15s ease-in-out;
201
- }
202
-
203
- &:hover i {
204
- transform: scale(1.1);
205
217
  }
206
218
  }
207
219
  }
@@ -400,16 +412,29 @@
400
412
  transform: rotate(180deg);
401
413
  }
402
414
 
403
- // Label density degrades in three tiers, measured on the center track
404
- // (the `bd-nav` container), so items fit instead of ellipsizing:
415
+ // Label density has TWO tiers, measured on the center track (the `bd-nav`
416
+ // container), so items either fit whole or drop to icons — never truncate:
405
417
  // >= 51rem (816px) icon + label — the roomy default
406
- // 41-50.99rem label only — icons dropped to buy ~20px/item
407
- // < 41rem (656px) icon only — labels no longer fit at all
408
- // Each boundary is the MEASURED width the tier below it needs for the
409
- // theme's seven demo items (815px with icons, 647px without), so a tier only
410
- // engages once its own content fits. Items keep min-width:0 + ellipsis as the
411
- // last-resort safety net for consumers with more or longer entries.
412
- @container bd-nav (min-width: 41rem) {
418
+ // < 51rem icon only — icon + label no longer fits
419
+ // 51rem is the MEASURED width the icon+label row needs for the theme's seven
420
+ // demo items (815px), so the upper tier engages exactly when its own content
421
+ // fits and the lower one takes over otherwise.
422
+ //
423
+ // There used to be a middle 41-50.99rem tier that dropped the ICONS to buy
424
+ // ~20px per item, plus `text-overflow: ellipsis` as a last-resort safety net.
425
+ // Both are gone (#405): the ellipsis produced "Quicksta…", and a clipped
426
+ // label is worse than an icon with a tooltip — the icon is at least honest
427
+ // about being an abbreviation. Icons now survive at every tier, and the
428
+ // icon-only cutoff moved 41rem -> 51rem to take over the band the middle
429
+ // tier used to hold. That is a deliberate behaviour change at 656-816px of
430
+ // center track: those widths used to show bare labels and now show icons,
431
+ // with `.nav-tooltip` (992-1199px) carrying the name on hover.
432
+ //
433
+ // Consumers with materially more (or longer) items than the theme's seven
434
+ // demo entries can exceed 51rem of content; with shrink and ellipsis both
435
+ // removed, the row overflows rather than truncates. A container query
436
+ // cannot measure content, so raise the boundary below if that happens.
437
+ @container bd-nav (min-width: 51rem) {
413
438
  #bdNavbar .navbar-nav {
414
439
  gap: 0.125rem;
415
440
  }
@@ -418,87 +443,105 @@
418
443
  padding: 0.375rem;
419
444
  }
420
445
 
446
+ // No `overflow: hidden` / `text-overflow: ellipsis` here: this tier only
447
+ // engages once the whole icon+label row fits, so a clipped label would be
448
+ // a bug rather than a fallback. Labels stay whole or the row goes
449
+ // icon-only (#405).
421
450
  #bdNavbar .nav-link .nav-link-text {
422
451
  display: inline !important;
423
452
  margin-left: 0;
424
453
  min-width: 0;
425
- overflow: hidden;
426
- text-overflow: ellipsis;
427
454
  white-space: nowrap;
428
455
  transition: opacity 0.15s ease-in-out;
429
456
  }
430
457
 
458
+ // Items do NOT shrink in this tier. Shrinking is what produced the
459
+ // clipped label this tier removes: the tier only engages once the full
460
+ // icon+label row fits, so an item that shrinks here is being cut for no
461
+ // reason.
431
462
  #bdNavbar .nav-item,
432
463
  #bdNavbar .nav-hover-dropdown {
433
- flex: 0 1 auto;
464
+ flex: 0 0 auto;
434
465
  min-width: 0;
435
466
  }
436
467
 
437
468
  #bdNavbar .nav-item > .nav-link:not(.dropdown-toggle-split),
438
469
  #bdNavbar .nav-hover-dropdown > .nav-link:first-child {
439
- flex: 1 1 auto;
470
+ flex: 0 0 auto;
440
471
  min-width: 0;
441
472
  max-width: 100%;
442
- overflow: hidden;
473
+ overflow: visible;
443
474
  gap: 0.25rem;
444
475
  }
445
476
 
446
477
  #bdNavbar .nav-link i {
447
478
  margin-right: 0;
448
479
  font-size: 1rem;
449
- transition: transform 0.15s ease-in-out;
450
480
  }
451
481
 
452
- #bdNavbar .nav-link:hover {
482
+ // Hover lives on the ROW, not on each control. With the chevron overlaying
483
+ // the link's padding box (below), a per-control background would light
484
+ // only half the row as the pointer crossed it.
485
+ #bdNavbar .nav-item:not(.nav-hover-dropdown) > .nav-link:hover,
486
+ #bdNavbar .nav-hover-dropdown:hover > .nav-link:first-child {
453
487
  background-color: var(--bs-tertiary-bg);
454
488
  border-radius: 0.375rem;
489
+ }
455
490
 
456
- i {
457
- transform: scale(1.1);
458
- }
491
+ // Chevron merged into the parent link's box (#405). It stays a real
492
+ // <button> — keyboard reachable, `aria-expanded`/`aria-haspopup` intact —
493
+ // and is only repositioned: absolutely placed over the right end of the
494
+ // link's padding, which the link reserves with extra padding-right. There
495
+ // is no longer a gap between the two, so the dead zone between label and
496
+ // chevron is gone and the whole row is one hover target.
497
+ // Every rule below is scoped with #bdNavbar deliberately: the tier's own
498
+ // `#bdNavbar .nav-link { padding: 0.375rem }` above carries an id, so a
499
+ // class-only `padding-right` here would lose to it and the chevron would
500
+ // have no reserved space to sit in.
501
+ #bdNavbar .nav-hover-dropdown {
502
+ position: relative;
459
503
  }
460
504
 
461
- .nav-hover-dropdown > .nav-link {
462
- border-top-right-radius: 0;
463
- border-bottom-right-radius: 0;
505
+ #bdNavbar .nav-hover-dropdown > .nav-link:first-child {
506
+ border-radius: 0.375rem;
507
+ padding-right: 1.25rem;
464
508
  }
465
509
 
466
- .nav-hover-dropdown > .dropdown-toggle-split {
467
- border-top-left-radius: 0;
468
- border-bottom-left-radius: 0;
510
+ #bdNavbar .nav-hover-dropdown > .dropdown-toggle-split {
511
+ position: absolute;
512
+ top: 0;
513
+ right: 0;
514
+ bottom: 0;
515
+ z-index: 1;
516
+ display: flex;
517
+ align-items: center;
518
+ justify-content: center;
519
+ width: 1.25rem;
469
520
  min-width: 1.25rem;
470
521
  min-height: 0;
471
- width: 1.25rem;
472
- padding-left: 0;
473
- padding-right: 0;
474
- margin-left: 0 !important;
475
- }
476
- }
477
-
478
- // Middle tier — the labels fit but the icons no longer do. Dropping the
479
- // decorative icon (the label is the accessible name) and tightening the
480
- // chevron buys back roughly 20px per item, which is the difference between
481
- // "Notebooks" and "N…" at 992-1200px viewports.
482
- @container bd-nav (min-width: 41rem) and (max-width: 50.99rem) {
483
- #bdNavbar .nav-link i {
484
- display: none;
485
- }
486
-
487
- #bdNavbar .nav-item > .nav-link:not(.dropdown-toggle-split),
488
- #bdNavbar .nav-hover-dropdown > .nav-link:first-child {
489
- gap: 0;
522
+ margin: 0 !important;
523
+ padding: 0;
524
+ background-color: transparent;
525
+ border: 0;
526
+ border-radius: 0 0.375rem 0.375rem 0;
490
527
  }
491
528
 
492
- // Scoped with #bdNavbar to outrank the shared base rule at the top of this
493
- // file, which sets the chevron to 1.5rem.
494
- #bdNavbar .nav-hover-dropdown > .dropdown-toggle-split {
495
- min-width: 1rem;
496
- width: 1rem;
529
+ // The overlay must not paint its own hover background — the row already
530
+ // does. Without this the chevron reads as a separate control again, which
531
+ // is the whole defect. `!important` because the shared base block at the
532
+ // top of this file sets the chevron's own hover background with equal
533
+ // specificity and later source order.
534
+ #bdNavbar .nav-hover-dropdown > .dropdown-toggle-split:hover,
535
+ #bdNavbar .nav-hover-dropdown > .dropdown-toggle-split:focus-visible,
536
+ #bdNavbar .nav-hover-dropdown > .dropdown-toggle-split[aria-expanded="true"] {
537
+ background-color: transparent !important;
497
538
  }
498
539
  }
499
540
 
500
- // Icon-only when the center track is too narrow even for bare labels
501
- @container bd-nav (max-width: 40.99rem) {
541
+ // Icon-only when the center track cannot fit the icon+label row. The
542
+ // boundary is the complement of the tier above so the two tiers tile the
543
+ // whole range with no gap.
544
+ @container bd-nav (max-width: 50.99rem) {
502
545
  @include navbar-nav-icon-only;
503
546
  }
504
547
  }
@@ -681,7 +724,6 @@
681
724
 
682
725
  &:hover {
683
726
  background-color: var(--bs-tertiary-bg);
684
- transform: rotate(90deg);
685
727
  }
686
728
 
687
729
  &:focus-visible {
@@ -768,20 +810,10 @@
768
810
  i {
769
811
  font-size: 1rem;
770
812
  width: 1.5rem;
771
- transition: transform 0.15s ease-in-out;
772
813
  }
773
814
 
774
815
  &:hover {
775
816
  background-color: var(--bs-tertiary-bg);
776
-
777
- // translateX widens overflow; desktop menus sit inside a clipped nav track
778
- @media (max-width: 991.98px) {
779
- transform: translateX(4px);
780
- }
781
-
782
- i {
783
- transform: scale(1.1);
784
- }
785
817
  }
786
818
 
787
819
  &:focus-visible {
@@ -1119,8 +1151,7 @@
1119
1151
  align-items: center;
1120
1152
  }
1121
1153
 
1122
- #navbar .site-title-text,
1123
- #navbar .site-subtitle-text {
1154
+ #navbar .site-title-text {
1124
1155
  display: inline-block;
1125
1156
  }
1126
1157
 
@@ -1157,9 +1188,28 @@
1157
1188
 
1158
1189
  // Desktop (lg+): 3-column grid — [brand/title | main nav | utilities] so nav cannot paint over Search/Settings
1159
1190
  @media (min-width: 992px) {
1191
+ // Optical centering (#405) WITHOUT breaking the container queries.
1192
+ //
1193
+ // The obvious spelling — `1fr auto 1fr` — is a trap here. Track 2 must stay
1194
+ // a `1fr` (a DEFINITE width): `.bd-navbar-nav-viewport` inside it is
1195
+ // `width: 100%` + `container-type: inline-size`, and that percentage only
1196
+ // resolves against a definite track. Make track 2 `auto` and it becomes
1197
+ // content-sized, size containment hands `bd-nav` a degenerate width, and
1198
+ // EVERY `@container bd-nav` label tier stops firing — the bar collapses to
1199
+ // icon-only at all widths, which is the opposite of what #405 is for.
1200
+ //
1201
+ // So the nav keeps the elastic middle track and the two side tracks are
1202
+ // equalised instead, via a shared floor. Whenever the brand and the
1203
+ // utility cluster both fit inside it (the common case), the side tracks
1204
+ // are the same width and the menubar is centred in the bar, not merely
1205
+ // centred in its own track. The floor is deliberately small so it never
1206
+ // steals the width the label tier needs at 1280px.
1160
1207
  #navbar .navbar-main {
1161
1208
  display: grid !important;
1162
- grid-template-columns: minmax(0, auto) minmax(0, 1fr) auto;
1209
+ grid-template-columns:
1210
+ minmax(var(--zer0-navbar-side-min, 9rem), auto)
1211
+ minmax(0, 1fr)
1212
+ minmax(var(--zer0-navbar-side-min, 9rem), auto);
1163
1213
  align-items: center;
1164
1214
  column-gap: 0.75rem;
1165
1215
  row-gap: 0.25rem;
@@ -1228,8 +1278,7 @@
1228
1278
  display: none;
1229
1279
  }
1230
1280
 
1231
- #navbar .site-title,
1232
- #navbar .site-subtitle {
1281
+ #navbar .site-title {
1233
1282
  flex-shrink: 1;
1234
1283
  min-width: 0;
1235
1284
  }
@@ -1247,15 +1296,6 @@
1247
1296
  max-width: none;
1248
1297
  }
1249
1298
 
1250
- #navbar .site-subtitle .site-subtitle-text {
1251
- overflow: hidden;
1252
- text-overflow: ellipsis;
1253
- white-space: nowrap;
1254
- display: inline-block;
1255
- vertical-align: bottom;
1256
- max-width: none;
1257
- }
1258
-
1259
1299
  // Progressive brand degradation (container width, not viewport).
1260
1300
  // The home-link icons add ~108px to the brand cluster, so they only appear
1261
1301
  // once the bar is wide enough to carry them AND the full menubar
@@ -1267,12 +1307,6 @@
1267
1307
  }
1268
1308
  }
1269
1309
 
1270
- @container navbar-main (max-width: 68rem) {
1271
- #navbar .site-subtitle {
1272
- display: none !important;
1273
- }
1274
- }
1275
-
1276
1310
  @container navbar-main (max-width: 50rem) {
1277
1311
  #navbar .site-title .site-title-text {
1278
1312
  max-width: 100%;
@@ -1338,6 +1372,19 @@
1338
1372
  height: 2.5rem;
1339
1373
  }
1340
1374
 
1375
+ // The square above is for BARE-GLYPH togglers. The labelled menu toggle
1376
+ // (#405) also carries a "Menu" span, and 2.5rem cannot hold glyph + gap +
1377
+ // label — the text would escape the button box and push the page past the
1378
+ // viewport at every width below lg, which is exactly what makes a
1379
+ // fixed-top bar read as "cut off". `.navbar-toggler-labeled`'s own
1380
+ // `width: auto` loses to the ID selector above on specificity (0,2,0 vs
1381
+ // 1,1,0); re-assert it here, where it outranks.
1382
+ #navbar .navbar-toggler.navbar-toggler-labeled {
1383
+ width: auto;
1384
+ min-width: 2.5rem;
1385
+ flex-shrink: 0;
1386
+ }
1387
+
1341
1388
  #navbar .nav-search-button {
1342
1389
  width: 2.5rem;
1343
1390
  height: 2.5rem;
@@ -20,20 +20,8 @@
20
20
 
21
21
  // MOVED → _sass/layouts/_navbar-extras.scss (token-aware FAB stacking)
22
22
  // MOVED → _sass/components/_cookie-banner.scss (token-aware, dark-mode-safe)
23
- // Retained shim: keep stacked-button responsive sizing inside the banner
24
- // because it is a banner-internal concern, not a banner-shell concern.
25
- .cookie-consent-banner {
26
- @media (max-width: 768px) {
27
- .btn {
28
- width: 100%;
29
- margin-bottom: 0.5rem;
30
-
31
- &:last-child {
32
- margin-bottom: 0;
33
- }
34
- }
35
- }
36
- }
23
+ // The banner's internal layout (formerly a full-width stacked-button shim
24
+ // here) lives with the shell in _sass/components/_cookie-banner.scss.
37
25
 
38
26
  // Active TOC link highlighting
39
27
  .bd-toc nav a.active {
@@ -357,14 +357,33 @@ pre.highlight > button:focus {
357
357
  opacity: 1;
358
358
  }
359
359
 
360
- .button,
361
- button:not(.copy) {
362
- display: inline-block;
363
- padding: 0 20px;
364
- border-radius: 4px;
365
- border: 1px solid #bbb;
366
- font-size: 11px;
367
- cursor: pointer;
360
+ // Extra actions inside a code block — SCOPED TO THIS COMPONENT (#412).
361
+ //
362
+ // This block used to read `.button, button:not(.copy)` with no scope at all.
363
+ // An element selector in a component partial is a site-wide rule: it restyled
364
+ // EVERY button on a consumer site that was not the theme's own copy button —
365
+ // `padding: 0 20px`, `font-size: 11px`, and a hardcoded `#bbb` border that
366
+ // bypassed the design tokens entirely. At specificity (0,1,1) it also outranked
367
+ // `.btn` and any consumer's own `.my-button`, so escaping it meant a
368
+ // specificity fight rather than a cascade (it-journey#634 had to scope to
369
+ // `.code-block-header .itj-clip-btn` to win). A component partial styles its
370
+ // own component and nothing else.
371
+ //
372
+ // The rule is kept, not deleted: a consumer that adds an action to the header
373
+ // (see the `zer0:code-block-ready` contract in assets/js/code-copy.js) should
374
+ // inherit the copy button's shape instead of arriving unstyled. Values are
375
+ // tokens now, so a skin moves them.
376
+ .code-block-header,
377
+ pre.highlight {
378
+ > .button,
379
+ > button:not(.copy) {
380
+ display: inline-block;
381
+ padding: 0 var(--zer0-space-3);
382
+ border-radius: var(--zer0-radius-sm);
383
+ border: 1px solid var(--zer0-color-border);
384
+ font-size: var(--zer0-text-sm);
385
+ cursor: pointer;
386
+ }
368
387
  }
369
388
 
370
389
  // Non-highlight fenced code blocks
@@ -53,13 +53,18 @@
53
53
  display: block;
54
54
  }
55
55
 
56
+ // No transform on :hover — a lift moves the FAB out from under a pointer
57
+ // parked on its bottom edge, hover exits, it drops back: infinite flicker.
58
+ // These are `.btn`s, so the 2px lift _ui-enhancements.scss puts on every
59
+ // button has to be cancelled explicitly, not merely not-added here.
56
60
  &:hover,
57
61
  &:focus-visible {
58
- transform: translateY(-1px);
62
+ transform: none;
59
63
  }
60
64
 
65
+ // :active is safe, because the button is held down for its whole duration.
61
66
  &:active {
62
- transform: translateY(0);
67
+ transform: translateY(1px);
63
68
  }
64
69
  }
65
70
  }
@@ -112,13 +117,18 @@
112
117
  display: block;
113
118
  }
114
119
 
120
+ // No transform on :hover — a lift moves the FAB out from under a pointer
121
+ // parked on its bottom edge, hover exits, it drops back: infinite flicker.
122
+ // These are `.btn`s, so the 2px lift _ui-enhancements.scss puts on every
123
+ // button has to be cancelled explicitly, not merely not-added here.
115
124
  &:hover,
116
125
  &:focus-visible {
117
- transform: translateY(-1px);
126
+ transform: none;
118
127
  }
119
128
 
129
+ // :active is safe, because the button is held down for its whole duration.
120
130
  &:active {
121
- transform: translateY(0);
131
+ transform: translateY(1px);
122
132
  }
123
133
  }
124
134
  }
@@ -0,0 +1,27 @@
1
+ /* ==========================================================================
2
+ extension-points-demo.css — a live consumer of the per-page `styles:` hook
3
+ ==========================================================================
4
+
5
+ Loaded ONLY by /docs/customization/extension-points/, through that page's
6
+ `styles:` frontmatter. Its job is to prove the hook works and to mark the
7
+ demo action button so the page's own prose can point at it.
8
+
9
+ Note what it does NOT have to do: fight a specificity war. Before #412,
10
+ `_sass/core/code-copy.scss` shipped a bare `button:not(.copy)` rule at
11
+ specificity (0,1,1), which outranked any single class a consumer could
12
+ write — so downstream code had to scope to `.code-block-header .my-btn`
13
+ just to set its own padding. That rule is scoped to the component now, and
14
+ a plain class is enough again.
15
+ ========================================================================== */
16
+
17
+ .zer0-demo-action {
18
+ background: transparent;
19
+ color: var(--zer0-color-text-muted);
20
+ font-family: var(--zer0-font-mono);
21
+ line-height: 1.8;
22
+ }
23
+
24
+ .zer0-demo-action:hover,
25
+ .zer0-demo-action:focus-visible {
26
+ color: inherit;
27
+ }
data/assets/css/main.scss CHANGED
@@ -44,6 +44,7 @@
44
44
  @import "components/book";
45
45
  @import "components/recipe";
46
46
  @import "components/setup-wizard";
47
+ @import "components/mermaid";
47
48
 
48
49
  // 6. Layout partials
49
50
  @import "layouts/landing";
@@ -142,11 +142,30 @@ sitemap: false
142
142
  {%- endif -%}
143
143
  {%- endfor -%}
144
144
  {%- assign outgoing = outgoing | uniq -%}
145
+ {%- comment -%}
146
+ `lastmod` and `description` (#412) — both already on `doc` in this loop,
147
+ so neither costs a new traversal. A notes/library UI needs them:
148
+ `lastmod` to sort by recency or show staleness, and `description`
149
+ because `excerpt` is body-derived while `description` is the AUTHORED
150
+ subtitle, which is the better card line whenever it exists.
151
+
152
+ Source order for the date follows the rest of the theme
153
+ (_includes/core/head.html, _layouts/article.html): `last_modified_at`,
154
+ then `lastmod`, then the document's own `date`. Emitted as ISO-8601 via
155
+ `date_to_xmlschema` so a consumer can sort strings without parsing.
156
+ Both keys are always present and always defined — `null` when the
157
+ document carries neither — rather than being conditionally omitted,
158
+ because a key that sometimes disappears is a worse contract than one
159
+ that is sometimes null.
160
+ {%- endcomment -%}
161
+ {%- assign doc_lastmod = doc.last_modified_at | default: doc.lastmod | default: doc.date -%}
145
162
  {
146
163
  "title": {{ doc.title | default: basename | jsonify }},
147
164
  "basename": {{ basename | jsonify }},
148
165
  "url": {{ doc.url | jsonify }},
149
166
  "collection": {{ doc.collection | default: nil | jsonify }},
167
+ "lastmod": {% if doc_lastmod %}{{ doc_lastmod | date_to_xmlschema | jsonify }}{% else %}null{% endif %},
168
+ "description": {{ doc.description | default: nil | jsonify }},
150
169
  "tags": {{ doc.tags | default: empty | jsonify }},
151
170
  "categories": {{ doc.categories | default: empty | jsonify }},
152
171
  "aliases": {{ aliases | jsonify }},