jekyll-theme-zer0 1.27.0 → 1.29.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 (142) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +812 -13
  3. data/_data/backlog.yml +356 -2
  4. data/_data/consumers.yml +252 -0
  5. data/_data/features.yml +234 -5
  6. data/_data/i18n/fr.yml +103 -0
  7. data/_data/i18n/manifest.yml +1529 -0
  8. data/_data/ingredient_densities.yml +122 -0
  9. data/_data/navigation/docs.yml +2 -0
  10. data/_data/navigation/main.yml +22 -0
  11. data/_data/recipe_courses.yml +64 -0
  12. data/_data/series.yml +19 -0
  13. data/_data/theme-manifest.yml +387 -277
  14. data/_data/ui-text.yml +8 -0
  15. data/_includes/README.md +36 -3
  16. data/_includes/analytics/google-tag-manager-body.html +11 -2
  17. data/_includes/analytics/google-tag-manager-head.html +16 -6
  18. data/_includes/analytics/posthog.html +33 -4
  19. data/_includes/components/abc-letter.html +43 -0
  20. data/_includes/components/background-customizer.html +2 -2
  21. data/_includes/components/book-card.html +42 -0
  22. data/_includes/components/book-nav.html +80 -0
  23. data/_includes/components/book-plate.html +31 -0
  24. data/_includes/components/book-toc.html +47 -0
  25. data/_includes/components/bookshelf.html +68 -0
  26. data/_includes/components/card-grid.html +60 -0
  27. data/_includes/components/cookie-consent.html +4 -4
  28. data/_includes/components/data-card.html +95 -0
  29. data/_includes/components/halfmoon.html +5 -1
  30. data/_includes/components/info-section.html +8 -3
  31. data/_includes/components/language-toggle.html +168 -21
  32. data/_includes/components/page-feedback.html +65 -2
  33. data/_includes/components/page-views-init.html +55 -0
  34. data/_includes/components/page-views.html +33 -0
  35. data/_includes/components/recipe-card.html +67 -0
  36. data/_includes/components/recipe-duration.html +50 -0
  37. data/_includes/components/recipe-grams.html +58 -0
  38. data/_includes/components/recipe-index.html +96 -0
  39. data/_includes/components/recipe-ingredients.html +90 -0
  40. data/_includes/components/recipe-meta.html +96 -0
  41. data/_includes/components/recipe-nutrition.html +57 -0
  42. data/_includes/components/recipe-qty.html +73 -0
  43. data/_includes/components/recipe-ratio.html +151 -0
  44. data/_includes/components/recipe-scaler.html +73 -0
  45. data/_includes/components/recipe-steps.html +86 -0
  46. data/_includes/components/recipe-temp.html +45 -0
  47. data/_includes/components/search-modal.html +2 -2
  48. data/_includes/components/shortcuts-modal.html +3 -0
  49. data/_includes/components/theme-controls-bar.html +10 -2
  50. data/_includes/components/theme-customizer.html +8 -2
  51. data/_includes/components/theme-info.html +8 -1
  52. data/_includes/content/jsonld-software.html +22 -3
  53. data/_includes/content/seo.html +9 -3
  54. data/_includes/core/color-mode-init.html +13 -4
  55. data/_includes/core/favicon.html +46 -0
  56. data/_includes/core/footer.html +18 -16
  57. data/_includes/core/head.html +21 -0
  58. data/_includes/core/header.html +7 -4
  59. data/_includes/custom/body-end.html +18 -0
  60. data/_includes/custom/body-start.html +17 -0
  61. data/_includes/custom/footer.html +18 -0
  62. data/_includes/custom/head.html +18 -0
  63. data/_includes/navigation/local-graph.html +28 -2
  64. data/_includes/navigation/nav-tree.html +3 -3
  65. data/_includes/navigation/section-sidebar.html +93 -11
  66. data/_includes/navigation/sidebar-config.html +21 -0
  67. data/_includes/navigation/sidebar-left.html +2 -1
  68. data/_includes/navigation/sidebar-nav.html +4 -0
  69. data/_includes/navigation/sidebar-pagetree.html +150 -0
  70. data/_includes/navigation/sidebar-right.html +2 -1
  71. data/_includes/navigation/unified-drawer.html +8 -2
  72. data/_includes/obsidian/full-graph.html +165 -136
  73. data/_includes/setup/wizard.html +173 -86
  74. data/_includes/stats/stats-metrics.html +2 -2
  75. data/_layouts/404.html +260 -0
  76. data/_layouts/README.md +4 -0
  77. data/_layouts/admin.html +2 -2
  78. data/_layouts/article.html +5 -0
  79. data/_layouts/book-abc.html +106 -0
  80. data/_layouts/book-story.html +91 -0
  81. data/_layouts/book.html +113 -0
  82. data/_layouts/collection.html +17 -5
  83. data/_layouts/cookbook.html +88 -0
  84. data/_layouts/default.html +4 -4
  85. data/_layouts/home.html +5 -2
  86. data/_layouts/landing.html +9 -0
  87. data/_layouts/news.html +155 -31
  88. data/_layouts/recipe.html +274 -0
  89. data/_layouts/root.html +38 -5
  90. data/_layouts/section.html +96 -25
  91. data/_sass/components/_book.scss +423 -0
  92. data/_sass/components/_callout.scss +1 -1
  93. data/_sass/components/_footer.scss +37 -1
  94. data/_sass/components/_page-views.scss +36 -0
  95. data/_sass/components/_recipe.scss +506 -0
  96. data/_sass/components/_setup-wizard.scss +235 -0
  97. data/_sass/components/_ui-enhancements.scss +6 -6
  98. data/_sass/core/_navbar.scss +263 -15
  99. data/_sass/core/_obsidian.scss +286 -6
  100. data/_sass/layouts/_landing.scss +2 -2
  101. data/_sass/theme/_backgrounds.scss +21 -8
  102. data/_sass/tokens/_color.scss +6 -0
  103. data/_sass/tokens/_index.scss +2 -0
  104. data/_sass/tokens/_radius.scss +21 -0
  105. data/_sass/tokens/_typography.scss +4 -0
  106. data/_sass/utilities/_focus.scss +14 -0
  107. data/assets/css/main.scss +4 -0
  108. data/assets/js/auto-hide-nav.js +5 -1
  109. data/assets/js/halfmoon.js +26 -0
  110. data/assets/js/modules/navigation/navbar.js +55 -0
  111. data/assets/js/obsidian-graph.js +702 -264
  112. data/assets/js/obsidian-local-graph.js +161 -54
  113. data/assets/js/page-views.js +372 -0
  114. data/assets/js/recipe-scaler.js +501 -0
  115. data/assets/js/search-modal.js +14 -1
  116. data/assets/js/setup-wizard.js +322 -20
  117. data/scripts/README.md +29 -0
  118. data/scripts/bin/audit-consumer +39 -7
  119. data/scripts/bin/giscus-discussions +213 -14
  120. data/scripts/bin/manifest +66 -16
  121. data/scripts/bin/validate +5 -1
  122. data/scripts/ci/agent_review_result.py +164 -0
  123. data/scripts/ci/test_agent_review_result.py +172 -0
  124. data/scripts/design-system-check.rb +170 -0
  125. data/scripts/install/README.md +47 -6
  126. data/scripts/install/ai/client.sh +302 -93
  127. data/scripts/install/ai/prompts/spec.schema.json +1 -1
  128. data/scripts/install/ai/wizard.sh +10 -5
  129. data/scripts/install/apply.sh +7 -3
  130. data/scripts/install/cli.sh +54 -4
  131. data/scripts/install/config.sh +167 -0
  132. data/scripts/install/doctor.sh +38 -0
  133. data/scripts/install/plan.sh +10 -0
  134. data/scripts/install/spec.sh +15 -7
  135. data/scripts/install/template.sh +4 -0
  136. data/scripts/lib/audit.sh +42 -2
  137. data/scripts/lint-liquid-raw.rb +137 -0
  138. data/scripts/propagate.rb +277 -0
  139. data/scripts/test/lib/run_tests.sh +2 -1
  140. data/scripts/test/lib/test_agent_review_result.sh +27 -0
  141. data/scripts/translate.rb +161 -15
  142. metadata +56 -2
@@ -0,0 +1,235 @@
1
+ // =============================================================================
2
+ // Setup wizard (_includes/setup/wizard.html + assets/js/setup-wizard.js)
3
+ // =============================================================================
4
+ // Feature: ZER0-067 · Redesign: T-040 / #408
5
+ //
6
+ // Three columns at lg+ (stepper / steps / live preview), stacked below. The
7
+ // two rules that matter most:
8
+ //
9
+ // 1. `.wizard-panes` is a GRID with every pane in the same cell, so the
10
+ // container is always exactly as tall as the tallest step; `.wizard-nav`
11
+ // is then pushed to the bottom of the card with `margin-top: auto`.
12
+ // Neither alone is enough: the grid stops the CONTAINER resizing, and the
13
+ // auto margin stops the BUTTONS floating up inside a short step.
14
+ // Together, Back/Next sit at the same y offset on every step.
15
+ // 2. The preview panel is sticky and always present, so it needs its own
16
+ // scroll region rather than growing the page.
17
+ // =============================================================================
18
+
19
+ // -----------------------------------------------------------------------------
20
+ // Vertical stepper
21
+ // -----------------------------------------------------------------------------
22
+
23
+ .wizard-stepper-wrap {
24
+ top: 1.5rem; // paired with .sticky-lg-top in the markup
25
+ }
26
+
27
+ .wizard-stepper {
28
+ gap: 0.25rem;
29
+ }
30
+
31
+ .wizard-step {
32
+ display: flex;
33
+ align-items: center;
34
+ gap: 0.75rem;
35
+ width: 100%;
36
+ padding: 0.625rem 0.75rem;
37
+ min-height: 44px; // WCAG touch target
38
+ text-align: left;
39
+ color: var(--zer0-color-ink-muted);
40
+ background: none;
41
+ border: 0;
42
+ border-radius: var(--zer0-radius-sm, 0.375rem);
43
+ transition: background-color 0.15s ease, color 0.15s ease;
44
+
45
+ &:hover:not(:disabled) {
46
+ color: var(--zer0-color-ink, inherit);
47
+ background: var(--zer0-color-bg-muted);
48
+ }
49
+
50
+ &:focus-visible {
51
+ outline: 2px solid var(--bs-primary);
52
+ outline-offset: 2px;
53
+ }
54
+
55
+ // Upcoming, and not reachable until every earlier step validates.
56
+ &.is-locked,
57
+ &:disabled {
58
+ opacity: 0.45;
59
+ cursor: not-allowed;
60
+ }
61
+
62
+ &.is-active {
63
+ font-weight: 600;
64
+ color: var(--zer0-color-ink, inherit);
65
+ background: var(--zer0-color-bg-muted);
66
+
67
+ .wizard-step-marker {
68
+ color: #fff;
69
+ background: var(--bs-primary);
70
+ border-color: var(--bs-primary);
71
+ }
72
+ }
73
+
74
+ &.is-done .wizard-step-marker {
75
+ color: var(--bs-success, #198754);
76
+ border-color: var(--bs-success, #198754);
77
+ }
78
+ }
79
+
80
+ .wizard-step-marker {
81
+ display: inline-flex;
82
+ align-items: center;
83
+ justify-content: center;
84
+ flex-shrink: 0;
85
+ width: 2rem;
86
+ height: 2rem;
87
+ font-size: 0.9rem;
88
+ border: 1px solid var(--bs-border-color);
89
+ border-radius: 50%;
90
+ }
91
+
92
+ .wizard-step-text {
93
+ min-width: 0;
94
+ }
95
+
96
+ .wizard-step-label {
97
+ display: block;
98
+ overflow: hidden;
99
+ text-overflow: ellipsis;
100
+ white-space: nowrap;
101
+ }
102
+
103
+ // "Draft saved" acknowledgement. Kept in the layout (no display toggling) so
104
+ // its appearance never shifts the stepper.
105
+ .wizard-draft-chip {
106
+ margin: 0.75rem 0 0;
107
+ padding: 0.25rem 0.5rem;
108
+ font-size: 0.75rem;
109
+ color: var(--bs-success, #198754);
110
+ opacity: 0;
111
+ transition: opacity 0.2s ease;
112
+
113
+ &.is-visible {
114
+ opacity: 1;
115
+ }
116
+ }
117
+
118
+ // -----------------------------------------------------------------------------
119
+ // Step panes — the stable-height half
120
+ // -----------------------------------------------------------------------------
121
+
122
+ .wizard-panes {
123
+ // Every pane is stacked in ONE grid cell, so the row's height is
124
+ // max(pane heights) — computed by the browser, continuously, exactly.
125
+ //
126
+ // This used to be a flex column whose min-height JS measured and wrote
127
+ // inline. Measuring a box you have to un-hide, lay out, read and restore is
128
+ // a guess: it came back ~16px short of the tallest pane, and the shortest
129
+ // step's Back/Next sat 16px higher than everywhere else. Grid asks the
130
+ // layout engine the same question and gets the answer right, on every
131
+ // reflow, with no resize listener.
132
+ display: grid;
133
+ min-height: 26rem; // floor for the degenerate case of no panes
134
+
135
+ // The selector carries `.tab-content` purely for specificity: Bootstrap's
136
+ // `.tab-content > .tab-pane { display: none }` would otherwise take the
137
+ // cell's only occupant out of the grid on a tie.
138
+ &.tab-content > .tab-pane {
139
+ display: flex;
140
+ flex-direction: column;
141
+ grid-area: 1 / 1; // one cell, all panes
142
+
143
+ // Inactive panes stay in layout — that is the whole point — but must not
144
+ // be seen, focused, clicked, or read out. `visibility: hidden` does all
145
+ // four (it removes the subtree from the a11y tree and from the tab
146
+ // order), which `opacity: 0` alone would not.
147
+ visibility: hidden;
148
+ pointer-events: none;
149
+
150
+ &.active {
151
+ visibility: visible;
152
+ pointer-events: auto;
153
+ }
154
+ }
155
+
156
+ > .tab-pane > .card {
157
+ display: flex;
158
+ flex: 1 1 auto;
159
+ }
160
+
161
+ > .tab-pane > .card > .card-body {
162
+ display: flex;
163
+ flex: 1 1 auto;
164
+ flex-direction: column;
165
+ }
166
+ }
167
+
168
+ // Pinned to the bottom of the card, so a short step does not float its
169
+ // Back/Next up the page relative to a tall one.
170
+ //
171
+ // Pinning the row's BOTTOM is only half the job: the test measures the Next
172
+ // button's TOP, so anything that changes the row's height moves it. Two things
173
+ // did, and both are fixed here rather than in the assertion.
174
+ .wizard-nav {
175
+ // 1. Never a block box. Step 1's nav is `text-end` with no `d-flex`, so its
176
+ // button was an inline-block on a line box and sat on the baseline — the
177
+ // descender space below it left that button ~2px above every other step's.
178
+ // `flex-end` is the base; the later steps' `justify-content-between`
179
+ // utility (Bootstrap emits `!important`) still wins where it is applied.
180
+ display: flex;
181
+ align-items: center;
182
+ justify-content: flex-end;
183
+ gap: 0.5rem;
184
+ margin-top: auto;
185
+ padding-top: 1rem;
186
+
187
+ // 2. Never let a label wrap. In the real three-column layout the middle
188
+ // column is ~270px, and "Review & Download" wrapped to a second line —
189
+ // making that one button a line-height taller. With the row pinned to the
190
+ // bottom, the extra line pushes the button's TOP up instead of its bottom
191
+ // down, which is the 16px the offset test caught on step 4.
192
+ .btn {
193
+ white-space: nowrap;
194
+ }
195
+ }
196
+
197
+ // -----------------------------------------------------------------------------
198
+ // Persistent live preview
199
+ // -----------------------------------------------------------------------------
200
+
201
+ .wizard-preview {
202
+ top: 1.5rem; // paired with .sticky-lg-top
203
+ }
204
+
205
+ .wizard-preview-code {
206
+ max-height: 60vh;
207
+ margin-bottom: 0;
208
+ overflow: auto;
209
+ font-size: 0.78rem;
210
+ line-height: 1.45;
211
+ }
212
+
213
+ .wizard-preview-live {
214
+ font-size: 0.65rem;
215
+ text-transform: uppercase;
216
+ letter-spacing: 0.04em;
217
+ }
218
+
219
+ // Below lg the three columns stack; the preview is no longer sticky and a
220
+ // shorter scroll region reads better under the form.
221
+ @media (max-width: 991.98px) {
222
+ .wizard-preview-code {
223
+ max-height: 20rem;
224
+ }
225
+
226
+ .wizard-stepper {
227
+ flex-direction: row !important;
228
+ flex-wrap: wrap;
229
+ }
230
+
231
+ .wizard-step {
232
+ width: auto;
233
+ flex: 1 1 auto;
234
+ }
235
+ }
@@ -82,7 +82,7 @@ img {
82
82
  left: 50%;
83
83
  width: 0;
84
84
  height: 0;
85
- border-radius: 50%;
85
+ border-radius: var(--zer0-radius-circle);
86
86
  background: rgba(255, 255, 255, 0.2);
87
87
  transform: translate(-50%, -50%);
88
88
  transition: width 0.6s, height 0.6s;
@@ -405,7 +405,7 @@ a:not(.btn) {
405
405
  .btn {
406
406
  .ripple {
407
407
  position: absolute;
408
- border-radius: 50%;
408
+ border-radius: var(--zer0-radius-circle);
409
409
  background: rgba(255, 255, 255, 0.6);
410
410
  transform: scale(0);
411
411
  animation: ripple-animation 0.6s ease-out;
@@ -424,7 +424,7 @@ a:not(.btn) {
424
424
  @media (max-width: 991.98px) {
425
425
  .navbar-collapse {
426
426
  background-color: var(--bs-body-bg);
427
- border-radius: 0.5rem;
427
+ border-radius: var(--zer0-radius-lg);
428
428
  margin-top: 1rem;
429
429
  padding: 1rem;
430
430
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
@@ -432,7 +432,7 @@ a:not(.btn) {
432
432
 
433
433
  .navbar-nav .nav-link {
434
434
  padding: 0.75rem 1rem;
435
- border-radius: 0.375rem;
435
+ border-radius: var(--zer0-radius);
436
436
  transition: all 0.2s ease;
437
437
 
438
438
  &:hover {
@@ -506,7 +506,7 @@ a:not(.btn) {
506
506
  *:focus-visible {
507
507
  outline: 2px solid var(--bs-primary);
508
508
  outline-offset: 2px;
509
- border-radius: 0.25rem;
509
+ border-radius: var(--zer0-radius-sm);
510
510
  }
511
511
 
512
512
  // Enhanced active link states
@@ -530,7 +530,7 @@ a:not(.btn) {
530
530
  // Better table responsiveness
531
531
  @media (max-width: 768px) {
532
532
  .table-responsive {
533
- border-radius: 0.5rem;
533
+ border-radius: var(--zer0-radius-lg);
534
534
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
535
535
  }
536
536
 
@@ -7,8 +7,9 @@
7
7
  // - Desktop >= 992px (inline nav; label density via @container bd-nav)
8
8
  // - Mobile < 992px (offcanvas)
9
9
  // Nav label tiers (center track / bd-nav container width):
10
- // - >= 38rem (~608px): full labels (with ellipsis safety when crowded)
11
- // - < 38rem: icon-only (tooltips on lg–xl viewports)
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)
12
13
  // ==============================================================================
13
14
 
14
15
  // -----------------------------------------------------------------------------
@@ -399,16 +400,22 @@
399
400
  transform: rotate(180deg);
400
401
  }
401
402
 
402
- // Show labels whenever the center track has room (>= ~38rem / 608px).
403
- // Items keep min-width:0 + ellipsis so a crowded row degrades gracefully
404
- // before falling all the way back to the icon-only tier below.
405
- @container bd-nav (min-width: 38rem) {
403
+ // Label density degrades in three tiers, measured on the center track
404
+ // (the `bd-nav` container), so items fit instead of ellipsizing:
405
+ // >= 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) {
406
413
  #bdNavbar .navbar-nav {
407
414
  gap: 0.125rem;
408
415
  }
409
416
 
410
417
  #bdNavbar .nav-link {
411
- padding: 0.375rem 0.5rem;
418
+ padding: 0.375rem;
412
419
  }
413
420
 
414
421
  #bdNavbar .nav-link .nav-link-text {
@@ -462,14 +469,36 @@
462
469
  min-width: 1.25rem;
463
470
  min-height: 0;
464
471
  width: 1.25rem;
465
- padding-left: 0.125rem;
466
- padding-right: 0.125rem;
472
+ padding-left: 0;
473
+ padding-right: 0;
467
474
  margin-left: 0 !important;
468
475
  }
469
476
  }
470
477
 
471
- // Icon-only when the center track is too narrow even for ellipsized labels
472
- @container bd-nav (max-width: 37.99rem) {
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;
490
+ }
491
+
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;
497
+ }
498
+ }
499
+
500
+ // Icon-only when the center track is too narrow even for bare labels
501
+ @container bd-nav (max-width: 40.99rem) {
473
502
  @include navbar-nav-icon-only;
474
503
  }
475
504
  }
@@ -489,6 +518,40 @@
489
518
  --bs-offcanvas-width: min(21rem, 86vw);
490
519
  }
491
520
 
521
+ // #bdNavbar lives INSIDE the fixed header, whose stacking context sits at
522
+ // Bootstrap's $zindex-fixed (1030) — below the body-level offcanvas
523
+ // backdrop (1040). The menu's own z-index (1045) only orders it within the
524
+ // header's context, so without help the whole open menu paints BEHIND the
525
+ // backdrop and none of its links are tappable. Lift the header above the
526
+ // backdrop only while its offcanvas is opening/open/closing. Browsers
527
+ // without :has() just keep the old (dimmed-menu) behavior.
528
+ body:has(#bdNavbar.offcanvas-lg.showing) header.fixed-top,
529
+ body:has(#bdNavbar.offcanvas-lg.show) header.fixed-top,
530
+ body:has(#bdNavbar.offcanvas-lg.hiding) header.fixed-top {
531
+ z-index: 1046; // $zindex-offcanvas (1045) + 1
532
+ }
533
+
534
+ // ...but that lift must never bury Settings. #info-section is a body-level
535
+ // offcanvas at the SAME edge and the same z-index (1045), so while the nav
536
+ // menu is still on screen — including the ~300ms `.hiding` tail after a tap
537
+ // on its own "Settings" item — the lifted header painted the whole Settings
538
+ // panel out of sight. Drop the header back below the offcanvas layer for as
539
+ // long as Settings is on screen; navbar.js also closes the nav menu first so
540
+ // the two panels do not overlap at all in browsers that support it.
541
+ body:has(#info-section.offcanvas.showing) header.fixed-top,
542
+ body:has(#info-section.offcanvas.show) header.fixed-top,
543
+ body:has(#info-section.offcanvas.hiding) header.fixed-top {
544
+ z-index: 1030; // $zindex-fixed — back under the offcanvas backdrop
545
+ }
546
+
547
+ // Belt and braces for browsers without :has() — Settings outranks the header
548
+ // stacking context on its own.
549
+ #info-section.offcanvas.showing,
550
+ #info-section.offcanvas.show,
551
+ #info-section.offcanvas.hiding {
552
+ z-index: 1047; // above the lifted header (1046)
553
+ }
554
+
492
555
  // Wrapper is inert on mobile so offcanvas layout matches pre-wrapper structure
493
556
  #bdNavbar .bd-navbar-nav-viewport {
494
557
  display: contents;
@@ -818,6 +881,156 @@
818
881
  }
819
882
  }
820
883
 
884
+ // -----------------------------------------------------------------------------
885
+ // Language section inside the Settings offcanvas
886
+ // (components/language-toggle.html variant="panel")
887
+ // -----------------------------------------------------------------------------
888
+
889
+ .nav-lang-panel .nav-lang-options {
890
+ --bs-list-group-border-radius: 0.375rem;
891
+
892
+ .list-group-item {
893
+ display: flex;
894
+ align-items: center;
895
+ gap: 0.625rem;
896
+ padding: 0.5rem 0.75rem;
897
+ min-height: 44px; // WCAG touch target
898
+ font-size: 0.9375rem;
899
+ }
900
+
901
+ .nav-lang-code {
902
+ flex-shrink: 0;
903
+ min-width: 2.25rem;
904
+ padding: 0.05rem 0.35rem;
905
+ font-family: var(--zer0-font-mono);
906
+ font-size: 0.7rem;
907
+ line-height: 1.4;
908
+ text-align: center;
909
+ color: var(--zer0-color-ink-muted);
910
+ background: var(--zer0-color-bg-muted);
911
+ border-radius: var(--zer0-radius-sm);
912
+ }
913
+
914
+ .nav-lang-name {
915
+ min-width: 0;
916
+ overflow: hidden;
917
+ text-overflow: ellipsis;
918
+ white-space: nowrap;
919
+ }
920
+ }
921
+
922
+ // -----------------------------------------------------------------------------
923
+ // Language menu — shared by BOTH variants (T-038 / #406)
924
+ // -----------------------------------------------------------------------------
925
+ // The current language reads as "you are here", not as a selected nav item, so
926
+ // it gets a tint + check icon rather than Bootstrap's `.active` primary fill.
927
+ // Rows are links even when a translation does not exist yet (they fall back to
928
+ // the source page), and the reason lives in one footnote instead of on every
929
+ // row — per-row "(Not yet translated)" was what made the menu wide.
930
+
931
+ .nav-lang-item {
932
+ display: flex;
933
+ align-items: center;
934
+ gap: 0.625rem;
935
+
936
+ &.is-current {
937
+ font-weight: 600;
938
+ color: var(--zer0-color-ink, inherit);
939
+ background: var(--zer0-color-bg-muted);
940
+ }
941
+
942
+ // The marker is the footnote's anchor; the note itself carries the wording.
943
+ &.is-untranslated .nav-lang-name {
944
+ color: var(--zer0-color-ink-muted);
945
+ }
946
+ }
947
+
948
+ .nav-lang-check {
949
+ flex-shrink: 0;
950
+ margin-left: auto;
951
+ }
952
+
953
+ .nav-lang-mark {
954
+ flex-shrink: 0;
955
+ margin-left: auto;
956
+ color: var(--zer0-color-ink-muted);
957
+ }
958
+
959
+ // "auto" — this language was machine-generated (scripts/translate.rb).
960
+ .nav-lang-auto {
961
+ flex-shrink: 0;
962
+ padding: 0.05rem 0.3rem;
963
+ font-family: var(--zer0-font-mono);
964
+ font-size: 0.625rem;
965
+ line-height: 1.4;
966
+ text-transform: lowercase;
967
+ color: var(--zer0-color-ink-muted);
968
+ background: var(--zer0-color-bg-muted);
969
+ border-radius: var(--zer0-radius-sm);
970
+ }
971
+
972
+ // The one footnote that replaced the per-row notices. It is the only element
973
+ // in the menu allowed to wrap — everything else ellipsises inside the 220px.
974
+ .nav-lang-note {
975
+ margin: 0;
976
+ padding: 0.25rem 0.75rem 0.125rem;
977
+ font-size: 0.75rem;
978
+ line-height: 1.35;
979
+ color: var(--zer0-color-ink-muted);
980
+ white-space: normal;
981
+ }
982
+
983
+ .nav-lang-divider {
984
+ margin: 0.5rem 0 0.25rem;
985
+ opacity: 0.15;
986
+ }
987
+
988
+ // -----------------------------------------------------------------------------
989
+ // Navbar dropdown variant (components/language-toggle.html, default variant)
990
+ // -----------------------------------------------------------------------------
991
+
992
+ .nav-lang-toggle {
993
+ .nav-lang-button {
994
+ display: inline-flex;
995
+ align-items: center;
996
+ justify-content: center;
997
+ width: 2.375rem; // 38px square — the icon carries the whole control
998
+ height: 2.375rem;
999
+ padding: 0;
1000
+ line-height: 1;
1001
+
1002
+ // Belt and braces: if `dropdown-toggle` is ever re-added, no caret returns.
1003
+ &::after {
1004
+ display: none;
1005
+ }
1006
+ }
1007
+
1008
+ .nav-lang-menu {
1009
+ min-width: 0;
1010
+ max-width: 220px;
1011
+
1012
+ .nav-lang-item {
1013
+ // Bootstrap's dropdown-item is nowrap; the name ellipsises instead so a
1014
+ // long native name cannot push the menu past its cap.
1015
+ overflow: hidden;
1016
+
1017
+ &.is-current {
1018
+ // Beat `.dropdown-item.active`'s specificity is unnecessary (we no
1019
+ // longer emit `.active`), but a hovered current row should not flip to
1020
+ // the link-hover colour and lose the "you are here" reading.
1021
+ color: var(--zer0-color-ink, inherit);
1022
+ }
1023
+ }
1024
+
1025
+ .nav-lang-name {
1026
+ min-width: 0;
1027
+ overflow: hidden;
1028
+ text-overflow: ellipsis;
1029
+ white-space: nowrap;
1030
+ }
1031
+ }
1032
+ }
1033
+
821
1034
  // -----------------------------------------------------------------------------
822
1035
  // Navigation Tooltips
823
1036
  // Enhanced tooltips for compact desktop view
@@ -917,26 +1130,57 @@
917
1130
  gap: 0.25rem;
918
1131
  }
919
1132
 
1133
+ // Shortcut affordance chip on the search button — the design-system navbar
1134
+ // pattern (ui_kits/website/Navbar.jsx). Overrides Bootstrap's inverted <kbd>
1135
+ // so the chip reads as a quiet hint, not a highlighted key.
1136
+ #navbar .nav-search-kbd {
1137
+ align-items: center;
1138
+ margin-left: 0.1rem;
1139
+ padding: 0.05rem 0.35rem;
1140
+ font-family: var(--zer0-font-mono);
1141
+ font-size: 0.7rem;
1142
+ line-height: 1.4;
1143
+ color: var(--zer0-color-ink-muted);
1144
+ background: var(--zer0-color-bg-muted);
1145
+ border-radius: var(--zer0-radius-sm);
1146
+ }
1147
+
1148
+ // The bar spans the full viewport (header markup uses .container-fluid) with a
1149
+ // fluid gutter instead of a centred .container-xl track, so the menubar gets the
1150
+ // whole screen width. Consumers that want the old capped bar set
1151
+ // --zer0-navbar-max-width (e.g. 1320px) in their own stylesheet.
1152
+ #navbar .navbar-main {
1153
+ max-width: var(--zer0-navbar-max-width, 100%);
1154
+ margin-inline: auto;
1155
+ padding-inline: clamp(0.75rem, 1.5vw, 1.5rem);
1156
+ }
1157
+
920
1158
  // Desktop (lg+): 3-column grid — [brand/title | main nav | utilities] so nav cannot paint over Search/Settings
921
1159
  @media (min-width: 992px) {
922
1160
  #navbar .navbar-main {
923
1161
  display: grid !important;
924
1162
  grid-template-columns: minmax(0, auto) minmax(0, 1fr) auto;
925
1163
  align-items: center;
926
- column-gap: 0.5rem;
1164
+ column-gap: 0.75rem;
927
1165
  row-gap: 0.25rem;
928
1166
  flex-wrap: unset !important;
929
1167
  container-type: inline-size;
930
1168
  container-name: navbar-main;
931
1169
  }
932
1170
 
1171
+ // The brand cluster is sized by its content (grid track 1 is `auto`), so an
1172
+ // unbounded title would starve the menubar of the width its labels need.
1173
+ // Cap it at a share of the BAR — `cqi` resolves against .navbar-main (the
1174
+ // named container), where a plain percentage would resolve against the
1175
+ // content-sized track and collapse the title to nothing. The title already
1176
+ // ellipsizes past the cap.
933
1177
  #navbar .navbar-main-start {
934
1178
  grid-column: 1;
935
1179
  display: flex;
936
1180
  align-items: center;
937
1181
  gap: 0.375rem;
938
1182
  min-width: 0;
939
- max-width: none;
1183
+ max-width: 30cqi;
940
1184
  flex-shrink: 1;
941
1185
  }
942
1186
 
@@ -1012,8 +1256,12 @@
1012
1256
  max-width: none;
1013
1257
  }
1014
1258
 
1015
- // Progressive brand degradation (container width, not viewport)
1016
- @container navbar-main (min-width: 80rem) {
1259
+ // Progressive brand degradation (container width, not viewport).
1260
+ // The home-link icons add ~108px to the brand cluster, so they only appear
1261
+ // once the bar is wide enough to carry them AND the full menubar
1262
+ // (815px of items + 328px brand + 146px utilities + gaps ≈ 82rem); 86rem
1263
+ // leaves headroom for a longer site title.
1264
+ @container navbar-main (min-width: 86rem) {
1017
1265
  #navbar .navbar-home-links {
1018
1266
  display: inline-flex !important;
1019
1267
  }