jekyll-theme-zer0 1.28.0 → 1.30.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 (191) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1143 -13
  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 +507 -2
  7. data/_data/consumers.yml +157 -9
  8. data/_data/features.yml +303 -20
  9. data/_data/feedback_types.yml +17 -12
  10. data/_data/i18n/fr.yml +12 -7
  11. data/_data/i18n/manifest.yml +39 -12
  12. data/_data/ingredient_densities.yml +122 -0
  13. data/_data/landing.yml +5 -2
  14. data/_data/navigation/main.yml +16 -0
  15. data/_data/navigation/quickstart.yml +4 -0
  16. data/_data/recipe_courses.yml +64 -0
  17. data/_data/site_builder.yml +874 -0
  18. data/_data/theme-manifest.yml +160 -124
  19. data/_data/ui-text.yml +26 -0
  20. data/_includes/README.md +26 -2
  21. data/_includes/analytics/posthog.html +2 -2
  22. data/_includes/components/admin-links.html +2 -2
  23. data/_includes/components/admin-tabs.html +2 -2
  24. data/_includes/components/ai-chat.html +14 -11
  25. data/_includes/components/analytics-dashboard.html +8 -8
  26. data/_includes/components/author-bio.html +1 -1
  27. data/_includes/components/author-card.html +10 -2
  28. data/_includes/components/author-eeat.html +4 -4
  29. data/_includes/components/background-customizer.html +10 -10
  30. data/_includes/components/background-image.html +114 -0
  31. data/_includes/components/background-settings.html +28 -15
  32. data/_includes/components/collection-manager.html +5 -5
  33. data/_includes/components/component-showcase.html +13 -13
  34. data/_includes/components/config-editor.html +12 -12
  35. data/_includes/components/config-viewer.html +8 -8
  36. data/_includes/components/cookie-consent.html +15 -15
  37. data/_includes/components/cta-button.html +7 -2
  38. data/_includes/components/dev-shortcuts.html +7 -7
  39. data/_includes/components/env-dashboard.html +8 -8
  40. data/_includes/components/env-switcher.html +9 -9
  41. data/_includes/components/feature-card.html +2 -2
  42. data/_includes/components/halfmoon.html +2 -2
  43. data/_includes/components/info-section.html +42 -37
  44. data/_includes/components/js-cdn.html +15 -15
  45. data/_includes/components/language-toggle.html +168 -21
  46. data/_includes/components/mermaid.html +72 -435
  47. data/_includes/components/nanobar.html +5 -5
  48. data/_includes/components/nav-editor.html +2 -2
  49. data/_includes/components/nav-export.html +2 -2
  50. data/_includes/components/nav-overview.html +2 -2
  51. data/_includes/components/page-feedback.html +45 -30
  52. data/_includes/components/page-views-init.html +55 -0
  53. data/_includes/components/page-views.html +33 -0
  54. data/_includes/components/post-card.html +22 -22
  55. data/_includes/components/post-type-badge.html +2 -2
  56. data/_includes/components/powered-by.html +2 -2
  57. data/_includes/components/preview-image.html +6 -0
  58. data/_includes/components/quick-index.html +2 -2
  59. data/_includes/components/recipe-card.html +67 -0
  60. data/_includes/components/recipe-duration.html +50 -0
  61. data/_includes/components/recipe-grams.html +58 -0
  62. data/_includes/components/recipe-index.html +96 -0
  63. data/_includes/components/recipe-ingredients.html +90 -0
  64. data/_includes/components/recipe-meta.html +96 -0
  65. data/_includes/components/recipe-nutrition.html +57 -0
  66. data/_includes/components/recipe-qty.html +73 -0
  67. data/_includes/components/recipe-ratio.html +151 -0
  68. data/_includes/components/recipe-scaler.html +73 -0
  69. data/_includes/components/recipe-steps.html +86 -0
  70. data/_includes/components/recipe-temp.html +45 -0
  71. data/_includes/components/search-modal.html +29 -4
  72. data/_includes/components/searchbar.html +2 -2
  73. data/_includes/components/shortcuts-modal.html +3 -0
  74. data/_includes/components/svg-background.html +2 -2
  75. data/_includes/components/theme-customizer.html +2 -2
  76. data/_includes/components/theme-info.html +14 -7
  77. data/_includes/components/theme-preview-gallery.html +22 -22
  78. data/_includes/content/giscus.html +2 -2
  79. data/_includes/content/intro.html +8 -8
  80. data/_includes/content/jsonld-faq.html +2 -2
  81. data/_includes/content/jsonld-software.html +24 -5
  82. data/_includes/content/seo.html +4 -4
  83. data/_includes/content/sitemap.html +27 -27
  84. data/_includes/content/toc.html +183 -183
  85. data/_includes/core/branding.html +6 -6
  86. data/_includes/core/console-capture.html +32 -74
  87. data/_includes/core/favicon.html +49 -7
  88. data/_includes/core/footer-fabs.html +17 -3
  89. data/_includes/core/footer.html +49 -34
  90. data/_includes/core/head.html +110 -86
  91. data/_includes/core/header.html +76 -54
  92. data/_includes/docs/bootstrap-docs.html +8 -8
  93. data/_includes/landing/landing-install-cards.html +2 -2
  94. data/_includes/landing/landing-quick-links.html +1 -1
  95. data/_includes/navigation/admin-nav.html +2 -2
  96. data/_includes/navigation/nav-tree.html +8 -8
  97. data/_includes/navigation/navbar.html +12 -12
  98. data/_includes/navigation/section-sidebar.html +109 -27
  99. data/_includes/navigation/sidebar-config.html +36 -2
  100. data/_includes/navigation/sidebar-left.html +17 -16
  101. data/_includes/navigation/sidebar-right.html +8 -7
  102. data/_includes/obsidian/full-graph.html +2 -2
  103. data/_includes/setup/claude-session.html +72 -0
  104. data/_includes/setup/prereq-checklist.html +90 -0
  105. data/_includes/setup/wizard.html +924 -222
  106. data/_includes/stats/stats-categories.html +8 -8
  107. data/_includes/stats/stats-header.html +14 -14
  108. data/_includes/stats/stats-metrics.html +14 -14
  109. data/_includes/stats/stats-no-data.html +12 -12
  110. data/_includes/stats/stats-overview.html +6 -6
  111. data/_includes/stats/stats-tags.html +8 -8
  112. data/_layouts/404.html +38 -24
  113. data/_layouts/README.md +2 -0
  114. data/_layouts/admin.html +24 -24
  115. data/_layouts/article.html +43 -33
  116. data/_layouts/author.html +20 -20
  117. data/_layouts/authors.html +2 -2
  118. data/_layouts/book-abc.html +12 -12
  119. data/_layouts/book-story.html +15 -15
  120. data/_layouts/book.html +12 -12
  121. data/_layouts/collection.html +33 -33
  122. data/_layouts/cookbook.html +88 -0
  123. data/_layouts/default.html +31 -28
  124. data/_layouts/home.html +23 -23
  125. data/_layouts/index.html +10 -10
  126. data/_layouts/landing.html +17 -17
  127. data/_layouts/news.html +44 -44
  128. data/_layouts/note.html +38 -38
  129. data/_layouts/notebook.html +34 -34
  130. data/_layouts/recipe.html +274 -0
  131. data/_layouts/root.html +92 -55
  132. data/_layouts/section.html +62 -33
  133. data/_layouts/setup.html +3 -3
  134. data/_layouts/sitemap-collection.html +49 -49
  135. data/_layouts/stats.html +40 -40
  136. data/_layouts/tag.html +12 -12
  137. data/_layouts/welcome.html +21 -21
  138. data/_sass/components/_callout.scss +1 -1
  139. data/_sass/components/_footer.scss +37 -1
  140. data/_sass/components/_mermaid.scss +375 -0
  141. data/_sass/components/_page-views.scss +36 -0
  142. data/_sass/components/_recipe.scss +506 -0
  143. data/_sass/components/_setup-wizard.scss +764 -0
  144. data/_sass/components/_ui-enhancements.scss +6 -6
  145. data/_sass/core/_navbar.scss +261 -46
  146. data/_sass/layouts/_landing.scss +2 -2
  147. data/_sass/layouts/_navbar-extras.scss +14 -4
  148. data/_sass/tokens/_color.scss +6 -0
  149. data/_sass/tokens/_index.scss +2 -0
  150. data/_sass/tokens/_radius.scss +21 -0
  151. data/_sass/tokens/_typography.scss +4 -0
  152. data/_sass/utilities/_focus.scss +14 -0
  153. data/assets/css/main.scss +4 -0
  154. data/assets/js/ai-chat.js +47 -5
  155. data/assets/js/fleet-feedback-capture.js +124 -0
  156. data/assets/js/fleet-feedback.js +853 -0
  157. data/assets/js/mermaid-diagrams.js +1267 -0
  158. data/assets/js/modules/navigation/config.js +9 -6
  159. data/assets/js/modules/navigation/navbar.js +55 -0
  160. data/assets/js/modules/navigation/scroll-spy.js +315 -80
  161. data/assets/js/modules/theme/appearance.js +8 -2
  162. data/assets/js/obsidian-wiki-links.js +8 -3
  163. data/assets/js/page-feedback.js +125 -192
  164. data/assets/js/page-views.js +372 -0
  165. data/assets/js/recipe-scaler.js +501 -0
  166. data/assets/js/search-modal.js +36 -0
  167. data/assets/js/setup-wizard.js +2279 -226
  168. data/assets/js/site-builder.js +1834 -0
  169. data/assets/js/ui-enhancements.js +11 -3
  170. data/scripts/README.md +44 -0
  171. data/scripts/ai/README.md +38 -0
  172. data/scripts/ai/api_call.rb +124 -0
  173. data/scripts/ai/usage.rb +314 -0
  174. data/scripts/ai/usage_report.rb +225 -0
  175. data/scripts/bin/audit-consumer +39 -7
  176. data/scripts/bin/giscus-discussions +213 -14
  177. data/scripts/bin/manifest +35 -12
  178. data/scripts/ci/agent_review_result.py +164 -0
  179. data/scripts/ci/test_agent_review_result.py +172 -0
  180. data/scripts/ci/test_visual_evidence_autogen.py +341 -0
  181. data/scripts/ci/visual_evidence_autogen.py +1060 -0
  182. data/scripts/content-review.rb +20 -1
  183. data/scripts/design-system-check.rb +170 -0
  184. data/scripts/lib/audit.sh +42 -2
  185. data/scripts/lint-liquid-raw.rb +137 -0
  186. data/scripts/test/integration/mermaid +22 -8
  187. data/scripts/test/lib/run_tests.sh +3 -1
  188. data/scripts/test/lib/test_agent_review_result.sh +27 -0
  189. data/scripts/test/lib/test_visual_evidence_autogen.sh +24 -0
  190. data/scripts/translate.rb +94 -16
  191. metadata +48 -2
@@ -1,4 +1,4 @@
1
- <!--
1
+ {% comment %}
2
2
  ===================================================================
3
3
  NAV TREE - Flat YAML Navigation Renderer
4
4
  ===================================================================
@@ -28,7 +28,7 @@
28
28
  Usage:
29
29
  include navigation/nav-tree.html nav="docs"
30
30
  ===================================================================
31
- -->
31
+ {% endcomment %}
32
32
 
33
33
  {% assign navigation = site.data.navigation[include.nav] %}
34
34
 
@@ -42,7 +42,7 @@
42
42
 
43
43
  <li class="mb-1 nav-tree-item" data-depth="0">
44
44
  {% if has_children %}
45
- <!-- Parent item with children - collapsible -->
45
+ {% comment %} Parent item with children - collapsible {% endcomment %}
46
46
  <div class="d-flex align-items-center">
47
47
  {% if item.url %}
48
48
  <a href="{{ item.url | relative_url }}"
@@ -73,7 +73,7 @@
73
73
  {% endif %}
74
74
  </div>
75
75
 
76
- <!-- Children container (Level 1) -->
76
+ {% comment %} Children container (Level 1) {% endcomment %}
77
77
  <div class="collapse{% if is_expanded %} show{% endif %}" id="{{ item_id }}">
78
78
  <ul class="list-unstyled fw-normal ps-3 nav-tree-children">
79
79
  {% for child in item.children %}
@@ -84,7 +84,7 @@
84
84
 
85
85
  <li class="mb-1 nav-tree-item" data-depth="1">
86
86
  {% if child_has_children %}
87
- <!-- Level 1 parent with children -->
87
+ {% comment %} Level 1 parent with children {% endcomment %}
88
88
  <div class="d-flex align-items-center">
89
89
  {% if child.url %}
90
90
  <a href="{{ child.url | relative_url }}"
@@ -114,7 +114,7 @@
114
114
  {% endif %}
115
115
  </div>
116
116
 
117
- <!-- Children container (Level 2) -->
117
+ {% comment %} Children container (Level 2) {% endcomment %}
118
118
  <div class="collapse{% if child_expanded %} show{% endif %}" id="{{ child_id }}">
119
119
  <ul class="list-unstyled fw-normal ps-3 nav-tree-children">
120
120
  {% for grandchild in child.children %}
@@ -137,7 +137,7 @@
137
137
  </ul>
138
138
  </div>
139
139
  {% else %}
140
- <!-- Level 1 leaf item -->
140
+ {% comment %} Level 1 leaf item {% endcomment %}
141
141
  {% if child.url %}
142
142
  <a href="{{ child.url | relative_url }}"
143
143
  class="nav-tree-link d-inline-flex align-items-center rounded py-1 px-2 text-decoration-none{% if child_active %} active{% endif %}">
@@ -156,7 +156,7 @@
156
156
  </ul>
157
157
  </div>
158
158
  {% else %}
159
- <!-- Root level leaf item -->
159
+ {% comment %} Root level leaf item {% endcomment %}
160
160
  {% if item.url %}
161
161
  <a href="{{ item.url | relative_url }}"
162
162
  class="nav-tree-link d-inline-flex align-items-center rounded py-1 px-2 text-decoration-none{% if is_active %} active{% endif %}">
@@ -1,25 +1,25 @@
1
1
  {% comment %} Features: ZER0-049, ZER0-055 {% endcomment %}
2
- <!--
2
+ {% comment %}
3
3
  file: navbar.html
4
4
  path: /includes/navbar.html
5
5
  includes:
6
6
  purpose: Offcanvas main navigation following Bootstrap 5 best practices
7
7
  with hover-activated dropdowns for desktop and accessible interactions
8
- -->
8
+ {% endcomment %}
9
9
 
10
- <!-- Navigation Links - Responsive Offcanvas (inline at lg+, offcanvas below) -->
10
+ {% comment %} Navigation Links - Responsive Offcanvas (inline at lg+, offcanvas below) {% endcomment %}
11
11
  {%- include core/i18n.html -%}
12
12
  <div class="offcanvas-lg offcanvas-end" tabindex="-1" id="bdNavbar" aria-labelledby="mainNavOffcanvasLabel">
13
13
 
14
- <!-- Main Navigation Header - Offcanvas -->
14
+ {% comment %} Main Navigation Header - Offcanvas {% endcomment %}
15
15
  <div class="offcanvas-header border-bottom">
16
16
  <h2 class="offcanvas-title h5 mb-0" id="mainNavOffcanvasLabel">{{ ui.main_nav_label | default: "Main Navigation" }}</h2>
17
17
  <button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close navigation menu" data-bs-target="#bdNavbar"></button>
18
18
  </div>
19
19
 
20
- <!-- Main Navigation Body - Offcanvas -->
20
+ {% comment %} Main Navigation Body - Offcanvas {% endcomment %}
21
21
  <div class="offcanvas-body">
22
- <!-- Desktop: width is the grid middle track; labels ellipsis or icon-only via container query -->
22
+ {% comment %} Desktop: width is the grid middle track; labels ellipsis or icon-only via container query {% endcomment %}
23
23
  <div class="bd-navbar-nav-viewport">
24
24
  <ul class="navbar-nav justify-content-lg-center text-start flex-grow-1">
25
25
  <li class="nav-item d-lg-none">
@@ -44,7 +44,7 @@
44
44
 
45
45
  {%- if has_children -%}
46
46
  <li class="nav-item dropdown d-flex align-items-center nav-hover-dropdown">
47
- <!-- Parent link navigates directly -->
47
+ {% comment %} Parent link navigates directly {% endcomment %}
48
48
  <a
49
49
  class="nav-link"
50
50
  href="{{ link.url | relative_url }}"
@@ -58,9 +58,9 @@
58
58
  <span class="nav-link-text">{{ link.title }}</span>
59
59
  </a>
60
60
 
61
- <!-- Split toggle opens the dropdown (click on mobile, hover on desktop) -->
62
- <!-- Note: No data-bs-toggle="dropdown" — custom JS handles all interactions
63
- to avoid Bootstrap's native handler conflicting with our toggle logic -->
61
+ {% comment %} Split toggle opens the dropdown (click on mobile, hover on desktop) {% endcomment %}
62
+ {% comment %} Note: No data-bs-toggle="dropdown" — custom JS handles all interactions
63
+ to avoid Bootstrap's native handler conflicting with our toggle logic {% endcomment %}
64
64
  <button
65
65
  class="nav-link dropdown-toggle dropdown-toggle-split ms-1"
66
66
  type="button"
@@ -165,8 +165,8 @@
165
165
  </div>
166
166
  </div>
167
167
 
168
- <!--
168
+ {% comment %}
169
169
  Navigation styles are now in _sass/core/_navbar.scss
170
170
  Navigation scripts are now in assets/js/navigation.js
171
171
  These are loaded automatically via the theme's asset pipeline
172
- -->
172
+ {% endcomment %}
@@ -1,4 +1,4 @@
1
- <!--
1
+ {% comment %}
2
2
  ===================================================================
3
3
  SECTION SIDEBAR - Fixed Topic Navigation
4
4
  ===================================================================
@@ -12,32 +12,72 @@
12
12
  - sub_categories: Array of unique tags/topics from section posts
13
13
  - page_title: Title of the current section
14
14
  - sidebar_id: Unique ID for the sidebar (default: "sectionSidebar")
15
-
15
+ - section_style: The parent layout's display style, one of grid | list |
16
+ magazine (default: "grid"). Decides whether topics render as scroll
17
+ anchors or as filter buttons — see the Topic controls note below.
18
+
16
19
  Features:
17
20
  - Desktop: Fixed sticky sidebar on the left
18
21
  - Mobile: Offcanvas drawer
19
22
  - Topic navigation with article counts
20
23
  - Section statistics
21
- - Smooth scroll to sections
24
+ - Smooth scroll to sections (magazine style, where scroll targets exist)
25
+ - Card filtering via [data-filter] (grid/list styles)
22
26
 
23
27
  Dependencies:
24
28
  - Bootstrap 5 offcanvas component
25
29
  - Bootstrap Icons
26
30
  ===================================================================
27
- -->
31
+ {% endcomment %}
28
32
 
29
33
  {% assign sidebar_id = include.sidebar_id | default: "sectionSidebar" %}
30
34
  {% assign section_posts = include.section_posts %}
31
35
  {% assign sub_categories = include.sub_categories %}
32
36
  {% assign page_title = include.page_title | default: page.title %}
37
+ {% assign section_style = include.section_style | default: "grid" %}
38
+
39
+ {% comment %}
40
+ Topic controls: link vs filter.
41
+
42
+ Only the `magazine` branch of _layouts/section.html emits per-subcategory
43
+ `<section id="{{ subcat | slugify }}">` scroll targets, and only for the first
44
+ 5 sub-categories that still have a post once the featured hero is excluded.
45
+ The `grid` and `list` branches emit no sub-topic ids at all — they filter
46
+ `[data-tags]` cards through `[data-filter]` buttons instead.
33
47
 
34
- <!-- ================================ -->
35
- <!-- DESKTOP SIDEBAR (Fixed Sticky) -->
36
- <!-- ================================ -->
48
+ So an <a href="#slug"> here is only ever valid under magazine, and only for
49
+ that exact set. Emitting one anywhere else produced guaranteed-dead anchors:
50
+ invalid markup, a dead click for keyboard/AT users, and a scrollspy that could
51
+ never activate. `_linkable_subcats` below reproduces the layout's set exactly;
52
+ every other style renders filter buttons, which the layout's existing
53
+ `[data-filter]` handler already wires up with no extra JS.
54
+ {% endcomment %}
55
+ {% assign _anchors_resolve = false %}
56
+ {% if section_style == "magazine" %}{% assign _anchors_resolve = true %}{% endif %}
57
+
58
+ {% assign _linkable_subcats = "" | split: "" %}
59
+ {% if _anchors_resolve %}
60
+ {% assign _featured = section_posts | where: "featured", true | first %}
61
+ {% unless _featured %}{% assign _featured = section_posts | first %}{% endunless %}
62
+ {% for subcat in sub_categories limit: 5 %}
63
+ {% assign _subcat_posts = section_posts | where_exp: "post", "post.tags contains subcat" %}
64
+ {% assign _remaining = 0 %}
65
+ {% for p in _subcat_posts %}
66
+ {% unless p.url == _featured.url %}{% assign _remaining = _remaining | plus: 1 %}{% endunless %}
67
+ {% endfor %}
68
+ {% if _remaining > 0 %}
69
+ {% assign _linkable_subcats = _linkable_subcats | push: subcat %}
70
+ {% endif %}
71
+ {% endfor %}
72
+ {% endif %}
73
+
74
+ {% comment %} ================================ {% endcomment %}
75
+ {% comment %} DESKTOP SIDEBAR (Fixed Sticky) {% endcomment %}
76
+ {% comment %} ================================ {% endcomment %}
37
77
  <aside class="section-sidebar-desktop d-none d-lg-block w-100">
38
78
  <div class="position-sticky" style="top: 80px;">
39
79
 
40
- <!-- Topics Card -->
80
+ {% comment %} Topics Card {% endcomment %}
41
81
  <div class="card border-0 shadow-sm">
42
82
  <div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
43
83
  <h5 class="mb-0">
@@ -47,21 +87,37 @@
47
87
  </div>
48
88
  <div class="card-body p-0">
49
89
  <nav class="nav flex-column sidebar-nav">
90
+ {% if _anchors_resolve %}
50
91
  <a class="nav-link active" href="#all-posts" data-section="all-posts">
51
92
  <i class="bi bi-grid me-2"></i>
52
93
  <span class="nav-text">All Articles</span>
53
94
  <span class="badge bg-secondary ms-auto">{{ section_posts.size }}</span>
54
95
  </a>
55
- {% for subcat in sub_categories limit: 15 %}
96
+ {% for subcat in _linkable_subcats %}
56
97
  {% assign subcat_posts = section_posts | where_exp: "post", "post.tags contains subcat" %}
57
- {% if subcat_posts.size > 0 %}
58
98
  <a class="nav-link" href="#{{ subcat | slugify }}" data-section="{{ subcat | slugify }}">
59
99
  <i class="bi bi-tag me-2"></i>
60
100
  <span class="nav-text">{{ subcat | capitalize }}</span>
61
101
  <span class="badge bg-light text-dark ms-auto">{{ subcat_posts.size }}</span>
62
102
  </a>
103
+ {% endfor %}
104
+ {% else %}
105
+ <button type="button" class="nav-link text-start active" data-filter="all">
106
+ <i class="bi bi-grid me-2"></i>
107
+ <span class="nav-text">All Articles</span>
108
+ <span class="badge bg-secondary ms-auto">{{ section_posts.size }}</span>
109
+ </button>
110
+ {% for subcat in sub_categories limit: 15 %}
111
+ {% assign subcat_posts = section_posts | where_exp: "post", "post.tags contains subcat" %}
112
+ {% if subcat_posts.size > 0 %}
113
+ <button type="button" class="nav-link text-start" data-filter="{{ subcat | slugify }}">
114
+ <i class="bi bi-tag me-2"></i>
115
+ <span class="nav-text">{{ subcat | capitalize }}</span>
116
+ <span class="badge bg-light text-dark ms-auto">{{ subcat_posts.size }}</span>
117
+ </button>
63
118
  {% endif %}
64
119
  {% endfor %}
120
+ {% endif %}
65
121
  </nav>
66
122
  </div>
67
123
  {% comment %} Existence-gate the tags index: a remote-theme consumer without
@@ -78,7 +134,7 @@
78
134
  {% endif %}
79
135
  </div>
80
136
 
81
- <!-- Section Stats -->
137
+ {% comment %} Section Stats {% endcomment %}
82
138
  <div class="card border-0 shadow-sm mt-3">
83
139
  <div class="card-body">
84
140
  <h6 class="text-muted mb-3">
@@ -102,31 +158,45 @@
102
158
  </div>
103
159
  </aside>
104
160
 
105
- <!-- ================================ -->
106
- <!-- MOBILE OFFCANVAS SIDEBAR -->
107
- <!-- ================================ -->
161
+ {% comment %} ================================ {% endcomment %}
162
+ {% comment %} MOBILE OFFCANVAS SIDEBAR {% endcomment %}
163
+ {% comment %} ================================ {% endcomment %}
108
164
  <div class="offcanvas offcanvas-start" tabindex="-1" id="{{ sidebar_id }}" aria-labelledby="{{ sidebar_id }}Label">
109
165
  <div class="offcanvas-header bg-primary text-white">
110
- <h5 class="offcanvas-title" id="{{ sidebar_id }}Label">
166
+ <h2 class="offcanvas-title h5" id="{{ sidebar_id }}Label">
111
167
  <i class="bi bi-bookmark me-2"></i>{{ page_title }} Topics
112
- </h5>
168
+ </h2>
113
169
  <button type="button" class="btn-close btn-close-white" data-bs-dismiss="offcanvas" aria-label="Close"></button>
114
170
  </div>
115
171
  <div class="offcanvas-body p-0">
116
172
  <nav class="nav flex-column">
173
+ {% if _anchors_resolve %}
117
174
  <a class="nav-link active bg-light py-3 border-bottom" href="#all-posts" data-bs-dismiss="offcanvas">
118
175
  <i class="bi bi-grid me-2"></i>All Articles
119
176
  <span class="badge bg-secondary float-end">{{ section_posts.size }}</span>
120
177
  </a>
121
- {% for subcat in sub_categories %}
178
+ {% for subcat in _linkable_subcats %}
122
179
  {% assign subcat_posts = section_posts | where_exp: "post", "post.tags contains subcat" %}
123
- {% if subcat_posts.size > 0 %}
124
180
  <a class="nav-link text-body-secondary py-3 border-bottom" href="#{{ subcat | slugify }}" data-bs-dismiss="offcanvas">
125
181
  <i class="bi bi-tag me-2"></i>{{ subcat | capitalize }}
126
182
  <span class="badge bg-light text-dark float-end">{{ subcat_posts.size }}</span>
127
183
  </a>
184
+ {% endfor %}
185
+ {% else %}
186
+ <button type="button" class="nav-link text-start active bg-light py-3 border-bottom w-100" data-filter="all" data-bs-dismiss="offcanvas">
187
+ <i class="bi bi-grid me-2"></i>All Articles
188
+ <span class="badge bg-secondary float-end">{{ section_posts.size }}</span>
189
+ </button>
190
+ {% for subcat in sub_categories %}
191
+ {% assign subcat_posts = section_posts | where_exp: "post", "post.tags contains subcat" %}
192
+ {% if subcat_posts.size > 0 %}
193
+ <button type="button" class="nav-link text-start text-body-secondary py-3 border-bottom w-100" data-filter="{{ subcat | slugify }}" data-bs-dismiss="offcanvas">
194
+ <i class="bi bi-tag me-2"></i>{{ subcat | capitalize }}
195
+ <span class="badge bg-light text-dark float-end">{{ subcat_posts.size }}</span>
196
+ </button>
128
197
  {% endif %}
129
198
  {% endfor %}
199
+ {% endif %}
130
200
  </nav>
131
201
  {% if _tags_page %}
132
202
  <div class="p-3">
@@ -138,9 +208,9 @@
138
208
  </div>
139
209
  </div>
140
210
 
141
- <!-- ================================ -->
142
- <!-- SIDEBAR STYLES -->
143
- <!-- ================================ -->
211
+ {% comment %} ================================ {% endcomment %}
212
+ {% comment %} SIDEBAR STYLES {% endcomment %}
213
+ {% comment %} ================================ {% endcomment %}
144
214
  <style>
145
215
  /* Sidebar Navigation */
146
216
  .sidebar-nav .nav-link {
@@ -169,6 +239,16 @@
169
239
  overflow: hidden;
170
240
  text-overflow: ellipsis;
171
241
  }
242
+
243
+ /* In grid/list styles the topic controls are <button data-filter> (there is
244
+ no per-topic scroll target to link to). Bootstrap's .nav-link already
245
+ clears button background/border; these are the two properties a <button>
246
+ still needs to render identically to the anchor form. */
247
+ .sidebar-nav button.nav-link,
248
+ .offcanvas button.nav-link {
249
+ width: 100%;
250
+ text-align: left;
251
+ }
172
252
 
173
253
  /* Offcanvas mobile styling */
174
254
  .offcanvas .nav-link {
@@ -178,9 +258,9 @@
178
258
  }
179
259
  </style>
180
260
 
181
- <!-- ================================ -->
182
- <!-- SIDEBAR JAVASCRIPT -->
183
- <!-- ================================ -->
261
+ {% comment %} ================================ {% endcomment %}
262
+ {% comment %} SIDEBAR JAVASCRIPT {% endcomment %}
263
+ {% comment %} ================================ {% endcomment %}
184
264
  <script>
185
265
  (function() {
186
266
  // Smooth scroll for sidebar links
@@ -194,8 +274,10 @@
194
274
  if (target) {
195
275
  target.scrollIntoView({ behavior: 'smooth' });
196
276
 
197
- // Update active state in sidebar
198
- document.querySelectorAll('.sidebar-nav .nav-link').forEach(link => {
277
+ // Update active state in sidebar. Scoped to anchors: in grid/list
278
+ // styles the topic controls are <button data-filter> elements whose
279
+ // active state belongs to the layout's filter handler, not here.
280
+ document.querySelectorAll('.sidebar-nav a.nav-link').forEach(link => {
199
281
  link.classList.remove('active');
200
282
  });
201
283
 
@@ -220,7 +302,7 @@
220
302
  entries.forEach(entry => {
221
303
  if (entry.isIntersecting) {
222
304
  const id = entry.target.getAttribute('id');
223
- document.querySelectorAll('.sidebar-nav .nav-link').forEach(link => {
305
+ document.querySelectorAll('.sidebar-nav a.nav-link').forEach(link => {
224
306
  link.classList.remove('active');
225
307
  if (link.getAttribute('href') === `#${id}`) {
226
308
  link.classList.add('active');
@@ -16,8 +16,13 @@
16
16
  "categories", "tags", or the name of a
17
17
  _data/navigation/*.yml file (nil = none)
18
18
  sidebar_has_content — true only when sidebar_nav will actually
19
- render items (gates the left column and
20
- the mobile toggle)
19
+ render items (gates the left column)
20
+ sidebar_offcanvas_rendered
21
+ — true only when the CURRENT LAYOUT emits
22
+ the #bdSidebar / #tocContents offcanvases
23
+ at all. sidebar_has_content is not enough
24
+ to gate a toggle: see the note at the
25
+ bottom of this file (issue #373).
21
26
  sidebar_title — heading text for the sidebar panel
22
27
  sidebar_icon — Bootstrap Icons class for the heading
23
28
  Config precedence (most specific wins):
@@ -134,3 +139,32 @@
134
139
  {%- if page.sidebar.icon -%}
135
140
  {%- assign sidebar_icon = page.sidebar.icon -%}
136
141
  {%- endif -%}
142
+
143
+ {%- comment -%}
144
+ 7. Does THIS LAYOUT actually render the offcanvas panels? (issue #373)
145
+
146
+ `sidebar_has_content` answers "is there nav content", which is what gates the
147
+ left column inside _layouts/default.html. It is NOT sufficient to gate the
148
+ TOGGLES, because core/header.html and core/footer-fabs.html are included from
149
+ _layouts/root.html — so they render on every layout, while `#bdSidebar`
150
+ (navigation/sidebar-left.html) and `#tocContents` (navigation/sidebar-right.html)
151
+ are emitted by _layouts/default.html alone.
152
+
153
+ The result was a dead control plus a dangling ARIA reference on every page
154
+ whose layout inherits `root` directly: measured at 37 pages of a 415-page
155
+ build, including all of `/about/**` (layout: admin) and 404/CHANGELOG.
156
+ Clicking did nothing, and assistive tech followed aria-controls to an element
157
+ that was not there.
158
+
159
+ There is no Liquid API for "resolve the layout inheritance chain", so the set
160
+ of layouts that reach default.html is listed here explicitly. It is pinned by
161
+ a test (test/test_core.sh :: test_sidebar_offcanvas_layout_gate) that reads
162
+ the front matter of every _layouts/*.html and fails if reality drifts from
163
+ this list — the same invariant pattern #425 used for the pixel-gate filter.
164
+ Add a layout here when, and only when, its `layout:` chain reaches default.
165
+ {%- endcomment -%}
166
+ {%- assign _sidebar_offcanvas_layouts = "default,article,author,authors,collection,cookbook,note,notebook,recipe,tag" | split: "," -%}
167
+ {%- assign sidebar_offcanvas_rendered = false -%}
168
+ {%- if _sidebar_offcanvas_layouts contains page.layout -%}
169
+ {%- assign sidebar_offcanvas_rendered = true -%}
170
+ {%- endif -%}
@@ -1,5 +1,5 @@
1
1
  {% comment %} Feature: ZER0-008 {% endcomment %}
2
- <!--
2
+ {% comment %}
3
3
  ===================================================================
4
4
  SIDEBAR LEFT - Dynamic Navigation Sidebar
5
5
  ===================================================================
@@ -27,14 +27,14 @@
27
27
  - Heading text/icon: page.sidebar.title/icon → collection metadata →
28
28
  site.sidebar.title/icon → "Browse docs"
29
29
  ===================================================================
30
- -->
30
+ {% endcomment %}
31
31
 
32
32
  {% include navigation/sidebar-config.html %}
33
33
 
34
- <!-- ================================ -->
35
- <!-- SIDEBAR NAVIGATION CONTAINER -->
36
- <!-- ================================ -->
37
- <!-- Responsive offcanvas sidebar - full-width on mobile, fixed on desktop -->
34
+ {% comment %} ================================ {% endcomment %}
35
+ {% comment %} SIDEBAR NAVIGATION CONTAINER {% endcomment %}
36
+ {% comment %} ================================ {% endcomment %}
37
+ {% comment %} Responsive offcanvas sidebar - full-width on mobile, fixed on desktop {% endcomment %}
38
38
  <script>
39
39
  try {
40
40
  if (localStorage.getItem('zer0-nav-sidebar-visible') === 'false') {
@@ -44,10 +44,10 @@
44
44
  </script>
45
45
  <div class="offcanvas-lg offcanvas-start" tabindex="-1" id="bdSidebar" aria-labelledby="bdSidebarOffcanvasLabel">
46
46
 
47
- <!-- ========================== -->
48
- <!-- SIDEBAR HEADER -->
49
- <!-- ========================== -->
50
- <!-- Mobile-only header with close button -->
47
+ {% comment %} ========================== {% endcomment %}
48
+ {% comment %} SIDEBAR HEADER {% endcomment %}
49
+ {% comment %} ========================== {% endcomment %}
50
+ {% comment %} Mobile-only header with close button {% endcomment %}
51
51
  <div class="offcanvas-header">
52
52
  <h2 class="offcanvas-title h5 mb-0" id="bdSidebarOffcanvasLabel">{{ sidebar_title }}</h2>
53
53
  <div class="d-flex align-items-center gap-1">
@@ -63,10 +63,10 @@
63
63
  </div>
64
64
  </div>
65
65
 
66
- <!-- ========================== -->
67
- <!-- SIDEBAR CONTENT AREA -->
68
- <!-- ========================== -->
69
- <!-- Scrollable content area with dynamic navigation options -->
66
+ {% comment %} ========================== {% endcomment %}
67
+ {% comment %} SIDEBAR CONTENT AREA {% endcomment %}
68
+ {% comment %} ========================== {% endcomment %}
69
+ {% comment %} Scrollable content area with dynamic navigation options {% endcomment %}
70
70
  <div class="offcanvas-body overflow-auto">
71
71
 
72
72
  <div class="bd-sidebar-desktop-header mb-3 d-none d-lg-flex align-items-center justify-content-between gap-2 w-100">
@@ -79,13 +79,14 @@
79
79
  aria-controls="bdSidebar"
80
80
  aria-expanded="true"
81
81
  aria-label="Hide documentation sidebar"
82
- title="Hide documentation sidebar">
82
+ aria-keyshortcuts="b"
83
+ title="Hide documentation sidebar (b)">
83
84
  <i class="bi bi-layout-sidebar-inset" aria-hidden="true"></i>
84
85
  <span class="visually-hidden">Hide documentation sidebar</span>
85
86
  </button>
86
87
  </div>
87
88
 
88
- <!-- Resolved navigation content (see navigation/sidebar-nav.html) -->
89
+ {% comment %} Resolved navigation content (see navigation/sidebar-nav.html) {% endcomment %}
89
90
  {% include navigation/sidebar-nav.html id="sidebar-content" %}
90
91
 
91
92
  </div>
@@ -1,12 +1,12 @@
1
1
  {% comment %} Features: ZER0-008, ZER0-010 {% endcomment %}
2
- <!--
2
+ {% comment %}
3
3
  file: sidebar-right.html
4
4
  path: _includes/sidebar-right.html
5
5
  includes: nav_list.html, content/toc.html
6
- -->
6
+ {% endcomment %}
7
7
 
8
8
  {%- include core/i18n.html -%}
9
- <!-- FOUC guard: apply hidden TOC preference before paint -->
9
+ {% comment %} FOUC guard: apply hidden TOC preference before paint {% endcomment %}
10
10
  <script>
11
11
  try {
12
12
  if (localStorage.getItem('zer0-nav-toc-visible') === 'false') {
@@ -15,7 +15,7 @@
15
15
  } catch (e) {}
16
16
  </script>
17
17
 
18
- <!-- Right sidebar container -->
18
+ {% comment %} Right sidebar container {% endcomment %}
19
19
  <div class="offcanvas-lg offcanvas-end" tabindex="-1" id="tocContents" aria-labelledby="tocLabel">
20
20
  <div class="offcanvas-header">
21
21
  <h2 class="offcanvas-title h5 mb-0" id="tocLabel">{{ ui.toc_title | default: "Table of Contents" }}</h2>
@@ -32,9 +32,9 @@
32
32
  </div>
33
33
  </div>
34
34
  <div class="offcanvas-body">
35
- <!-- Table of contents -->
35
+ {% comment %} Table of contents {% endcomment %}
36
36
  <nav id="TableOfContents" role="navigation" aria-label="Table of Contents">
37
- <!-- TOC title -->
37
+ {% comment %} TOC title {% endcomment %}
38
38
  <div class="mb-3 d-flex align-items-center justify-content-between gap-2">
39
39
  <strong class="d-flex align-items-center mb-0">
40
40
  <i class="bi bi-file-text me-2" aria-hidden="true"></i>
@@ -45,7 +45,8 @@
45
45
  aria-controls="tocContents"
46
46
  aria-expanded="true"
47
47
  aria-label="Hide table of contents"
48
- title="Hide table of contents">
48
+ aria-keyshortcuts="t"
49
+ title="Hide table of contents (t)">
49
50
  <i class="bi bi-layout-sidebar-inset-reverse" aria-hidden="true"></i>
50
51
  <span class="visually-hidden">Hide table of contents</span>
51
52
  </button>
@@ -197,9 +197,9 @@ unresolved targets show up as dashed red nodes so you can find dangling
197
197
  links at a glance. The graph is regenerated every Jekyll build; nothing
198
198
  runs client-side except cytoscape's force layout.
199
199
 
200
- <!-- Cytoscape.js is vendored (no runtime CDN) and lazy-loaded by
200
+ {% comment %} Cytoscape.js is vendored (no runtime CDN) and lazy-loaded by
201
201
  obsidian-graph.js from window.OBSIDIAN_CONFIG.cytoscapeUrl, only on this
202
- page, with an in-page recovery message if the fetch fails. -->
202
+ page, with an in-page recovery message if the fetch fails. {% endcomment %}
203
203
  {% if site.obsidian.enabled != false %}
204
204
  <script src="{{ '/assets/js/obsidian-graph.js' | relative_url }}?v={{ site.time | date: '%s' }}" defer></script>
205
205
  {% endif %}
@@ -0,0 +1,72 @@
1
+ {% comment %} Feature: ZER0-086, ZER0-087 {% endcomment %}
2
+ {%- comment -%}
3
+ Component: setup/claude-session
4
+ Path: _includes/setup/claude-session.html
5
+ Purpose: The embedded AI session that rides alongside every Site
6
+ Builder step: connection state, provider + mode badges,
7
+ transcript, suggested prompts, and the composer with a Stop
8
+ button for long tool runs. The assistant (Claude or Grok —
9
+ whichever the Connect step connected) sees the whole wizard
10
+ state on every turn and acts on it through tools (fill fields,
11
+ run checks, read theme source, write or modify the project,
12
+ generate images, run Docker).
13
+ Params: none — configured by #siteBuilderConfig in setup/wizard.html
14
+ Depends on: Bootstrap 5.3 + Icons, assets/js/site-builder.js,
15
+ _sass/components/_setup-wizard.scss
16
+ Notes:
17
+ - Proxy-only. Nothing renders a credential; the panel shows a connect
18
+ prompt until GET {endpoint}/status answers AND the chosen provider has
19
+ a key (data-ready="true"), then goes live.
20
+ - Rendering is escape-first (a limited Markdown subset); action cards are
21
+ built with createElement/textContent — see ai-chat.instructions.md.
22
+ - role="log" + aria-live so screen readers hear replies; Escape does not
23
+ close the panel (it is a page region, not a dialog).
24
+ {%- endcomment -%}
25
+
26
+ <section class="sb-panel card" id="siteBuilderPanel" aria-labelledby="sb-title" data-state="offline" data-ready="false" data-mode="guided">
27
+ <div class="card-header d-flex align-items-center justify-content-between gap-2 py-2">
28
+ <div class="d-flex align-items-center gap-2 min-w-0">
29
+ <i class="bi bi-stars" aria-hidden="true"></i>
30
+ <h2 class="h6 mb-0 text-truncate" id="sb-title">AI session</h2>
31
+ <span class="badge rounded-pill text-bg-light border sb-mode-badge" id="sb-mode-badge" hidden>guided</span>
32
+ </div>
33
+ <div class="d-flex align-items-center gap-2">
34
+ <span class="sb-status-badge badge rounded-pill text-bg-secondary" id="sb-status-badge" aria-live="polite">offline</span>
35
+ <button class="btn btn-sm btn-link text-body p-0" type="button" id="sb-clear" title="Clear the conversation" aria-label="Clear the conversation">
36
+ <i class="bi bi-eraser" aria-hidden="true"></i>
37
+ </button>
38
+ <button class="btn btn-sm btn-link text-body p-0" type="button" id="sb-collapse" aria-expanded="true" aria-controls="sb-body" title="Collapse">
39
+ <i class="bi bi-chevron-up" aria-hidden="true"></i>
40
+ </button>
41
+ </div>
42
+ </div>
43
+
44
+ <div class="sb-body" id="sb-body">
45
+ <div class="sb-messages p-3" id="sb-messages" role="log" aria-live="polite" aria-relevant="additions">
46
+ <div class="sb-offline text-body-secondary small" id="sb-offline">
47
+ <p class="mb-1"><strong>Not connected.</strong> Start the dev proxy and connect Claude or Grok on step 1. The assistant can then read your answers, fill in the forms, check your machine, read the theme's source, write or modify your project, generate images and run the build.</p>
48
+ <p class="mb-0">Everything else on this page works without it.</p>
49
+ </div>
50
+ </div>
51
+
52
+ <div class="sb-chips px-3 pb-2 d-flex gap-2 flex-wrap" id="sb-chips" aria-label="Suggested prompts"></div>
53
+
54
+ <form class="sb-composer p-3 pt-0" id="sb-form" autocomplete="off">
55
+ <label for="sb-input" class="visually-hidden">Message the assistant</label>
56
+ <div class="input-group">
57
+ <textarea class="form-control" id="sb-input" rows="1" maxlength="6000"
58
+ placeholder="Connect a provider to start chatting" disabled></textarea>
59
+ <button class="btn btn-primary" type="submit" id="sb-send" aria-label="Send" disabled>
60
+ <i class="bi bi-send" aria-hidden="true"></i>
61
+ </button>
62
+ <button class="btn btn-outline-danger" type="button" id="sb-stop" aria-label="Stop the current run" hidden disabled>
63
+ <i class="bi bi-stop-fill" aria-hidden="true"></i> Stop
64
+ </button>
65
+ </div>
66
+ <div class="d-flex justify-content-between align-items-center mt-1">
67
+ <small class="text-body-secondary sb-disclaimer" id="sb-disclaimer">The assistant sees every answer on this page. Actions ask for confirmation first.</small>
68
+ <small class="text-body-secondary" id="sb-context-note"></small>
69
+ </div>
70
+ </form>
71
+ </div>
72
+ </section>