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,5 +1,5 @@
1
1
  {% comment %} Feature: ZER0-050 {% endcomment %}
2
- <!--
2
+ {% comment %}
3
3
  ===================================================================
4
4
  ADMIN TABS — Reusable tabbed interface for admin pages
5
5
  ===================================================================
@@ -30,7 +30,7 @@
30
30
  Dependencies: Bootstrap 5 tab/nav components, Bootstrap Icons
31
31
  Used by: Admin pages (config.md, theme.md, navigation.md, etc.)
32
32
  ===================================================================
33
- -->
33
+ {% endcomment %}
34
34
 
35
35
  {% assign tab_prefix = include.id | default: "admin" %}
36
36
  {% assign tab_list = include.tabs | split: "|" %}
@@ -14,9 +14,11 @@
14
14
  - Renders nothing unless ai_chat.enabled AND a usable auth path
15
15
  exists: proxy mode with a deployed proxy (proxy_ready: true,
16
16
  recommended) or direct mode with a non-empty api_key.
17
- - Proxy mode keeps the Anthropic key server-side (see
18
- templates/deploy/chat-proxy/). Direct mode sends the key from the
19
- browser (visible in page source) — local development only.
17
+ - Proxy mode keeps the provider key server-side (see
18
+ templates/deploy/chat-proxy/) and can answer with Claude or Grok
19
+ (ai_chat.provider; the proxy's CHAT_PROVIDER wins). Direct mode
20
+ sends an Anthropic key from the browser (visible in page source) —
21
+ local development only, Claude only.
20
22
  - GitHub actions: github.mode "url" opens pre-filled github.com
21
23
  forms (no token anywhere); "proxy" creates issues/PRs through the
22
24
  proxy's GitHub endpoints with a server-side token. Creation always
@@ -63,7 +65,7 @@
63
65
  {% assign local_edit_flag = true %}
64
66
  {% endif %}
65
67
 
66
- <!-- AI Chat Toggle Button -->
68
+ {% comment %} AI Chat Toggle Button {% endcomment %}
67
69
  <button id="aiChatToggle"
68
70
  class="ai-chat-toggle btn btn-primary rounded-circle shadow-lg"
69
71
  type="button"
@@ -74,14 +76,14 @@
74
76
  <i class="bi bi-x-lg ai-chat-icon-close d-none" aria-hidden="true"></i>
75
77
  </button>
76
78
 
77
- <!-- AI Chat Panel -->
79
+ {% comment %} AI Chat Panel {% endcomment %}
78
80
  <div id="aiChatPanel"
79
81
  class="ai-chat-panel shadow-lg rounded-3"
80
82
  role="dialog"
81
83
  aria-labelledby="aiChatTitle"
82
84
  aria-hidden="true">
83
85
 
84
- <!-- Chat Header -->
86
+ {% comment %} Chat Header {% endcomment %}
85
87
  <div class="ai-chat-header d-flex align-items-center justify-content-between p-3">
86
88
  <div class="d-flex align-items-center gap-2">
87
89
  <i class="bi {{ site.ai_chat.icon | default: 'bi-robot' }} fs-5" aria-hidden="true"></i>
@@ -95,13 +97,13 @@
95
97
  </button>
96
98
  </div>
97
99
 
98
- <!-- Chat Messages -->
100
+ {% comment %} Chat Messages {% endcomment %}
99
101
  <div id="aiChatMessages" class="ai-chat-messages p-3" role="log" aria-live="polite">
100
- <!-- Welcome message inserted by JS -->
102
+ {% comment %} Welcome message inserted by JS {% endcomment %}
101
103
  </div>
102
104
 
103
105
  {% if gh_enabled %}
104
- <!-- Quick actions (GitHub-backed flows) -->
106
+ {% comment %} Quick actions (GitHub-backed flows) {% endcomment %}
105
107
  <div class="ai-chat-chips px-3 pb-2 d-flex gap-2 flex-wrap">
106
108
  <button type="button"
107
109
  class="ai-chat-chip btn btn-sm btn-outline-secondary rounded-pill"
@@ -123,7 +125,7 @@
123
125
  </div>
124
126
  {% endif %}
125
127
 
126
- <!-- Chat Input -->
128
+ {% comment %} Chat Input {% endcomment %}
127
129
  <div class="ai-chat-input p-3">
128
130
  <form id="aiChatForm" class="d-flex gap-2" autocomplete="off">
129
131
  <label for="aiChatInput" class="visually-hidden">Chat message</label>
@@ -146,7 +148,7 @@
146
148
  </div>
147
149
  </div>
148
150
 
149
- <!-- Page Context Data (generated by Jekyll at build time) -->
151
+ {% comment %} Page Context Data (generated by Jekyll at build time) {% endcomment %}
150
152
  <script id="aiChatPageContext" type="application/json">
151
153
  {
152
154
  "page_title": {{ page.title | default: site.title | jsonify }},
@@ -179,6 +181,7 @@
179
181
  <script id="aiChatConfig" type="application/json">
180
182
  {
181
183
  "endpoint": {{ site.ai_chat.endpoint | default: "/api/chat" | jsonify }},
184
+ "provider": {{ site.ai_chat.provider | default: "anthropic" | jsonify }},
182
185
  "authMode": {{ ai_auth_mode | jsonify }},
183
186
  "apiKey": {{ site.ai_chat.api_key | jsonify }},
184
187
  "anthropicVersion": {{ site.ai_chat.anthropic_version | default: "2023-06-01" | jsonify }},
@@ -1,4 +1,4 @@
1
- <!--
1
+ {% comment %}
2
2
  ===================================================================
3
3
  ANALYTICS DASHBOARD — PostHog & tracking configuration overview
4
4
  ===================================================================
@@ -11,11 +11,11 @@
11
11
  Dependencies: Bootstrap 5 card/table/badge, site.posthog config
12
12
  Used by: pages/_about/settings/analytics.md
13
13
  ===================================================================
14
- -->
14
+ {% endcomment %}
15
15
 
16
16
  {% assign ph = site.posthog %}
17
17
 
18
- <!-- Status cards -->
18
+ {% comment %} Status cards {% endcomment %}
19
19
  <div class="row g-3 mb-4">
20
20
  <div class="col-6 col-lg-3">
21
21
  <div class="card text-center h-100 {% if ph.enabled %}border-success{% else %}border-warning{% endif %}">
@@ -55,10 +55,10 @@
55
55
  </div>
56
56
  </div>
57
57
 
58
- <!-- Configuration Details -->
58
+ {% comment %} Configuration Details {% endcomment %}
59
59
  <div class="row g-4">
60
60
 
61
- <!-- PostHog Settings -->
61
+ {% comment %} PostHog Settings {% endcomment %}
62
62
  <div class="col-lg-6">
63
63
  <div class="card h-100">
64
64
  <div class="card-header"><i class="bi bi-gear me-1"></i> PostHog Configuration</div>
@@ -105,7 +105,7 @@
105
105
  </div>
106
106
  </div>
107
107
 
108
- <!-- Privacy & Compliance -->
108
+ {% comment %} Privacy & Compliance {% endcomment %}
109
109
  <div class="col-lg-6">
110
110
  <div class="card h-100">
111
111
  <div class="card-header"><i class="bi bi-shield-lock me-1"></i> Privacy & Compliance</div>
@@ -162,7 +162,7 @@
162
162
  </div>
163
163
  </div>
164
164
 
165
- <!-- Custom Events -->
165
+ {% comment %} Custom Events {% endcomment %}
166
166
  {% if ph.custom_events %}
167
167
  <h5 class="mt-4 mb-3"><i class="bi bi-lightning me-1"></i> Custom Event Tracking</h5>
168
168
  <div class="row g-3">
@@ -221,7 +221,7 @@
221
221
  </div>
222
222
  {% endif %}
223
223
 
224
- <!-- External Links -->
224
+ {% comment %} External Links {% endcomment %}
225
225
  <div class="mt-4">
226
226
  <a href="https://us.posthog.com" target="_blank" rel="noopener" class="btn btn-outline-primary btn-sm me-2">
227
227
  <i class="bi bi-box-arrow-up-right me-1"></i> Open PostHog Dashboard
@@ -73,7 +73,7 @@
73
73
  <i class="bi bi-person-badge me-2" aria-hidden="true"></i>{{ bio_heading }}
74
74
  </h2>
75
75
 
76
- {% include components/author-card.html author=include.author style="full" %}
76
+ {% include components/author-card.html author=include.author style="full" heading_level=3 %}
77
77
 
78
78
  {% if show_profile_link and bio_profile_url %}
79
79
  <div class="mt-3">
@@ -192,10 +192,18 @@
192
192
  {% endif %}
193
193
  {% endif %}
194
194
  <div class="flex-grow-1">
195
- <h5 class="card-title mb-1" itemprop="name">
195
+ {%- comment -%}
196
+ Level is a PARAMETER, size is not. This card renders under an <h2> in
197
+ author-bio.html and under different ancestors elsewhere, so a
198
+ hardcoded level cannot be correct everywhere -- picking <h5> for its
199
+ font-size is what produced the h2 -> h5 skip in issue #436. The
200
+ default keeps the historic level for any caller that does not say.
201
+ {%- endcomment -%}
202
+ {%- assign _ac_level = include.heading_level | default: 5 -%}
203
+ <h{{ _ac_level }} class="h5 card-title mb-1" itemprop="name">
196
204
  {%- if profile_url -%}<a href="{{ profile_url }}" class="text-decoration-none text-reset" itemprop="url">{{ author_name }}</a>{%- else -%}{{ author_name }}{%- endif -%}
197
205
  {%- if author_data.ai -%} <span class="badge author-ai-badge align-middle" title="AI author persona"><i class="bi bi-robot me-1" aria-hidden="true"></i>AI</span>{%- endif -%}
198
- </h5>
206
+ </h{{ _ac_level }}>
199
207
  {% if author_data.role %}<p class="text-primary mb-2" itemprop="jobTitle">{{ author_data.role }}</p>{% endif %}
200
208
  {% if author_data.tagline %}<p class="author-tagline fst-italic text-body-secondary mb-2">{{ author_data.tagline }}</p>{% endif %}
201
209
  {% if show_bio and author_data.bio %}<p class="card-text text-muted" itemprop="description">{{ author_data.bio }}</p>{% endif %}
@@ -1,5 +1,5 @@
1
1
  {% comment %} Feature: ZER0-054 {% endcomment %}
2
- <!--
2
+ {% comment %}
3
3
  ===================================================================
4
4
  AUTHOR E-E-A-T BLOCK - Experience, Expertise, Authority, Trust
5
5
  ===================================================================
@@ -21,7 +21,7 @@
21
21
  - _data/authors.yml: Author data
22
22
  - Bootstrap 5 utility classes
23
23
  ===================================================================
24
- -->
24
+ {% endcomment %}
25
25
 
26
26
  {% assign author_key = include.author_key | default: "bamr87" %}
27
27
  {% assign author = site.data.authors[author_key] %}
@@ -36,7 +36,7 @@
36
36
  {% if author %}
37
37
 
38
38
  {% if eeat_style == "banner" %}
39
- <!-- E-E-A-T Author Banner -->
39
+ {% comment %} E-E-A-T Author Banner {% endcomment %}
40
40
  <div class="bg-body-secondary border-top border-bottom py-4 mt-5" itemscope itemtype="https://schema.org/Person">
41
41
  <div class="container-xl px-4 px-md-5">
42
42
  <div class="row align-items-center g-3">
@@ -91,7 +91,7 @@
91
91
  </div>
92
92
 
93
93
  {% elsif eeat_style == "card" %}
94
- <!-- E-E-A-T Author Card -->
94
+ {% comment %} E-E-A-T Author Card {% endcomment %}
95
95
  <div class="card border-0 shadow-sm" itemscope itemtype="https://schema.org/Person">
96
96
  <div class="card-body p-4">
97
97
  <div class="d-flex align-items-start">
@@ -1,5 +1,5 @@
1
1
  {% comment %} Feature: ZER0-065 {% endcomment %}
2
- <!--
2
+ {% comment %}
3
3
  ===================================================================
4
4
  BACKGROUND CUSTOMIZER — fffuel-style background settings panel
5
5
  ===================================================================
@@ -14,12 +14,12 @@
14
14
  - _sass/theme/_backgrounds.scss (CSS custom properties)
15
15
  - Bootstrap 5 offcanvas + form components
16
16
  ===================================================================
17
- -->
17
+ {% endcomment %}
18
18
 
19
19
  {% assign skins = "air,aqua,contrast,dark,dirt,neon,mint,plum,sunrise" | split: "," %}
20
20
  {% assign current_skin = site.theme_skin | default: "dark" %}
21
21
 
22
- <!-- Floating toggle button -->
22
+ {% comment %} Floating toggle button {% endcomment %}
23
23
  <button class="btn btn-sm btn-outline-secondary position-fixed bottom-0 end-0 m-3 rounded-circle"
24
24
  style="z-index: 1090; width: 40px; height: 40px;"
25
25
  type="button"
@@ -30,17 +30,17 @@
30
30
  <i class="bi bi-palette2"></i>
31
31
  </button>
32
32
 
33
- <!-- Offcanvas panel -->
33
+ {% comment %} Offcanvas panel {% endcomment %}
34
34
  <div class="offcanvas offcanvas-end" tabindex="-1" id="zer0BgCustomizer" aria-labelledby="zer0BgCustomizerLabel">
35
35
  <div class="offcanvas-header">
36
- <h5 class="offcanvas-title" id="zer0BgCustomizerLabel">
36
+ <h2 class="offcanvas-title h5" id="zer0BgCustomizerLabel">
37
37
  <i class="bi bi-palette2 me-2"></i>Background Settings
38
- </h5>
38
+ </h2>
39
39
  <button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
40
40
  </div>
41
41
 
42
42
  <div class="offcanvas-body">
43
- <!-- Skin selector -->
43
+ {% comment %} Skin selector {% endcomment %}
44
44
  <div class="mb-4">
45
45
  <label class="form-label fw-semibold">Theme Skin</label>
46
46
  <div class="d-flex flex-wrap gap-2" id="zer0SkinButtons">
@@ -56,7 +56,7 @@
56
56
 
57
57
  <hr>
58
58
 
59
- <!-- Background toggle -->
59
+ {% comment %} Background toggle {% endcomment %}
60
60
  <div class="mb-3">
61
61
  <div class="form-check form-switch">
62
62
  <input class="form-check-input" type="checkbox" id="zer0BgToggle" checked>
@@ -66,7 +66,7 @@
66
66
 
67
67
  <hr>
68
68
 
69
- <!-- Opacity sliders -->
69
+ {% comment %} Opacity sliders {% endcomment %}
70
70
  <div class="mb-3">
71
71
  <label class="form-label" for="zer0GradientOpacity">Gradient opacity</label>
72
72
  <input type="range" class="form-range" id="zer0GradientOpacity"
@@ -96,7 +96,7 @@
96
96
  </div>
97
97
  </div>
98
98
 
99
- <!-- Customizer interaction logic -->
99
+ {% comment %} Customizer interaction logic {% endcomment %}
100
100
  <script>
101
101
  document.addEventListener("DOMContentLoaded", function () {
102
102
  if (typeof zer0Bg === "undefined") return;
@@ -0,0 +1,114 @@
1
+ {% comment %} Feature: ZER0-004 {% endcomment %}
2
+ {% comment %}
3
+ BACKGROUND IMAGE - Cover art painted as a CSS background, announced correctly
4
+
5
+ Sibling to components/preview-image.html. Reach for THIS one when the art is
6
+ painted as a CSS background on an otherwise empty box; reach for
7
+ preview-image.html when the art is an <img> in the content flow.
8
+
9
+ WHY THIS EXISTS. A CSS background is invisible to assistive technology. A box
10
+ painted with cover art and nothing else announces nothing at all, so the
11
+ reader is told there is an element and never told what it shows. The fix is
12
+ role="img" plus an aria-label, which makes the box a single graphic with a
13
+ name. The theme already did this by hand in three places
14
+ (components/abc-letter.html, layouts/article.html rating stars,
15
+ components/theme-customizer.html) without it being a named convention.
16
+
17
+ TWO BRANCHES, and the second is not optional:
18
+
19
+ real image -> role="img" aria-label="..." (a graphic with a name)
20
+ decorative -> aria-hidden="true", and NO role and NO aria-label
21
+
22
+ Combining aria-hidden with role="img" is worse than either alone: it
23
+ announces a graphic and then hides it. A gradient placeholder carries no
24
+ information the surrounding text does not already carry, so it is hidden.
25
+
26
+ Usage:
27
+ {%- include components/background-image.html
28
+ src=post.preview
29
+ alt=post.title
30
+ class="card-cover"
31
+ style="height: 180px;"
32
+ -%}
33
+
34
+ {%- comment -%} decorative fallback: omit alt, or pass decorative=true {%- endcomment -%}
35
+ {%- include components/background-image.html class="card-cover card-cover-empty" -%}
36
+
37
+ Params:
38
+ src - image path (optional; omit for the decorative branch)
39
+ alt - accessible name; REQUIRED for the real-image branch
40
+ class - CSS classes on the box (default: "bg-cover")
41
+ style - extra inline style, appended after the background shorthand
42
+ decorative - force the decorative branch even when src is present
43
+ tag - element name (default: div)
44
+
45
+ Path convention is preview-image.html's, duplicated rather than extracted:
46
+ it is ~14 lines of Liquid, and a third shared include to save them buys less
47
+ than it costs in indirection. preview-image.html remains the source of the
48
+ convention -- change it there first.
49
+ - /images/previews/x.png -> /assets/images/previews/x.png
50
+ - /assets/images/previews/x.png -> unchanged
51
+ - https://example.com/x.png -> unchanged (external URL)
52
+
53
+ DO NOT apply this to a box that wraps text. role="img" makes the element a
54
+ single graphic and its descendants stop being exposed -- so putting it on
55
+ _includes/content/intro.html, which paints a background AND wraps the page
56
+ <h1>, would delete the heading from the accessibility tree.
57
+ {% endcomment %}
58
+ {%- assign bgi_src = include.src -%}
59
+ {%- assign bgi_alt = include.alt -%}
60
+ {%- assign bgi_class = include.class | default: "bg-cover" -%}
61
+ {%- assign bgi_style = include.style -%}
62
+ {%- assign bgi_tag = include.tag | default: "div" -%}
63
+
64
+ {%- comment -%} Decorative is the DEFAULT, and the real-image branch has to earn
65
+ it: an image AND a name AND no explicit decorative flag. Written as a
66
+ positive test on purpose -- the failure mode of getting this backwards is
67
+ role="img" aria-label="", an unnamed graphic, which is worse for a screen
68
+ reader than either branch done right. Falling back to aria-hidden is safe.
69
+
70
+ NOTE: `x == blank` does NOT match nil in Liquid 4 -- blank matches empty
71
+ strings and collections only. An earlier version of this file tested
72
+ `bgi_src == blank` and every decorative call rendered as an unnamed
73
+ graphic. Test truthiness AND emptiness, both. {%- endcomment -%}
74
+ {%- assign bgi_decorative = true -%}
75
+ {%- if include.decorative != true -%}
76
+ {%- if bgi_src and bgi_src != "" -%}
77
+ {%- if bgi_alt and bgi_alt != "" -%}
78
+ {%- assign bgi_decorative = false -%}
79
+ {%- endif -%}
80
+ {%- endif -%}
81
+ {%- endif -%}
82
+
83
+ {%- comment -%} Path resolution -- see preview-image.html {%- endcomment -%}
84
+ {%- assign bgi_assets_prefix = site.preview_images.assets_prefix | default: '/assets' -%}
85
+ {%- assign bgi_auto_prefix = site.preview_images.auto_prefix | default: true -%}
86
+ {%- if bgi_src == nil or bgi_src == "" -%}
87
+ {%- assign bgi_final_src = nil -%}
88
+ {%- elsif bgi_src contains '://' -%}
89
+ {%- assign bgi_final_src = bgi_src -%}
90
+ {%- elsif bgi_auto_prefix and bgi_src != blank -%}
91
+ {%- if bgi_src contains bgi_assets_prefix -%}
92
+ {%- assign bgi_final_src = bgi_src | relative_url -%}
93
+ {%- else -%}
94
+ {%- assign bgi_final_src = bgi_assets_prefix | append: bgi_src | relative_url -%}
95
+ {%- endif -%}
96
+ {%- elsif bgi_src != blank -%}
97
+ {%- assign bgi_final_src = bgi_src | relative_url -%}
98
+ {%- endif -%}
99
+
100
+ {%- comment -%} One capture for the whole attribute value, so "is there a style
101
+ at all" is a single test. `!= blank` would not do here: a capture that
102
+ produced nothing is an empty string, and "" == blank is true, but the
103
+ surrounding whitespace control makes that fragile to edit. Compare to ""
104
+ explicitly. {%- endcomment -%}
105
+ {%- capture bgi_background -%}
106
+ {%- if bgi_final_src %}background-image: url('{{ bgi_final_src }}'); background-size: cover; background-position: center;{% endif %}{{ bgi_style }}
107
+ {%- endcapture -%}
108
+ {%- assign bgi_background = bgi_background | strip -%}
109
+
110
+ {%- if bgi_decorative -%}
111
+ <{{ bgi_tag }} class="{{ bgi_class }}" aria-hidden="true"{% if bgi_background != "" %} style="{{ bgi_background }}"{% endif %}></{{ bgi_tag }}>
112
+ {%- else -%}
113
+ <{{ bgi_tag }} class="{{ bgi_class }}" role="img" aria-label="{{ bgi_alt | escape }}"{% if bgi_background != "" %} style="{{ bgi_background }}"{% endif %}></{{ bgi_tag }}>
114
+ {%- endif -%}
@@ -1,5 +1,5 @@
1
1
  {% comment %} Feature: ZER0-077 {% endcomment %}
2
- <!--
2
+ {% comment %}
3
3
  ===================================================================
4
4
  BACKGROUND SETTINGS — fffuel-style background controls (tab content)
5
5
  ===================================================================
@@ -17,32 +17,33 @@
17
17
  Section headings match the h6 pattern used by the parent
18
18
  info-section.html so the Appearance tab reads as one surface.
19
19
  ===================================================================
20
- -->
20
+ {% endcomment %}
21
21
 
22
22
  {% assign skins = "air,aqua,contrast,dark,dirt,neon,mint,plum,sunrise" | split: "," %}
23
23
  {% assign current_skin = site.theme_skin | default: "dark" %}
24
24
 
25
- <!-- Skin selector -->
25
+ {% comment %} Skin selector {% endcomment %}
26
26
  <div class="mb-4">
27
- <h6 class="text-body-secondary small text-uppercase fw-semibold mb-2">
27
+ <h3 class="h6 text-body-secondary small text-uppercase fw-semibold mb-2">
28
28
  <i class="bi bi-palette2 me-1" aria-hidden="true"></i>Theme Skin
29
- </h6>
29
+ </h3>
30
30
  <div class="d-flex flex-wrap gap-2" id="zer0SkinButtons" role="group" aria-label="Theme skin">
31
31
  {% for skin in skins %}
32
32
  <button class="btn btn-sm btn-outline-primary{% if skin == current_skin %} active{% endif %}"
33
33
  data-skin="{{ skin }}"
34
- type="button">
34
+ type="button"
35
+ aria-pressed="{% if skin == current_skin %}true{% else %}false{% endif %}">
35
36
  {{ skin | capitalize }}
36
37
  </button>
37
38
  {% endfor %}
38
39
  </div>
39
40
  </div>
40
41
 
41
- <!-- SVG background toggle + layer opacity -->
42
+ {% comment %} SVG background toggle + layer opacity {% endcomment %}
42
43
  <div class="mb-4">
43
- <h6 class="text-body-secondary small text-uppercase fw-semibold mb-2">
44
+ <h3 class="h6 text-body-secondary small text-uppercase fw-semibold mb-2">
44
45
  <i class="bi bi-image me-1" aria-hidden="true"></i>Background
45
- </h6>
46
+ </h3>
46
47
 
47
48
  <div class="d-flex align-items-center justify-content-between p-3 bg-body-tertiary rounded mb-3">
48
49
  <label class="form-check-label" for="zer0BgToggle">SVG backgrounds</label>
@@ -83,18 +84,32 @@
83
84
  </button>
84
85
  </div>
85
86
 
86
- <!-- Customizer interaction logic -->
87
+ {% comment %} Customizer interaction logic {% endcomment %}
87
88
  <script>
88
89
  document.addEventListener("DOMContentLoaded", function () {
89
90
  if (typeof zer0Bg === "undefined") return;
90
91
 
91
92
  // Skin buttons
93
+ // `.active` is a Bootstrap visual class and carries no accessibility
94
+ // semantics, so selection is also exposed via aria-pressed (WCAG 4.1.2),
95
+ // matching the sibling color-mode group. Both are set in one place so no
96
+ // code path can leave them disagreeing.
97
+ function markActiveSkinButton(btn) {
98
+ document.querySelectorAll("#zer0SkinButtons .btn").forEach(function (b) {
99
+ b.classList.remove("active");
100
+ b.setAttribute("aria-pressed", "false");
101
+ });
102
+ if (btn) {
103
+ btn.classList.add("active");
104
+ btn.setAttribute("aria-pressed", "true");
105
+ }
106
+ }
107
+
92
108
  document.querySelectorAll("#zer0SkinButtons [data-skin]").forEach(function (btn) {
93
109
  btn.addEventListener("click", function () {
94
110
  var skin = this.getAttribute("data-skin");
95
111
  zer0Bg.setSkin(skin);
96
- document.querySelectorAll("#zer0SkinButtons .btn").forEach(function (b) { b.classList.remove("active"); });
97
- this.classList.add("active");
112
+ markActiveSkinButton(this);
98
113
  });
99
114
  });
100
115
 
@@ -128,9 +143,7 @@ document.addEventListener("DOMContentLoaded", function () {
128
143
  document.getElementById(id + "Val").textContent = defaults[id];
129
144
  });
130
145
  toggle.checked = true;
131
- document.querySelectorAll("#zer0SkinButtons .btn").forEach(function (b) { b.classList.remove("active"); });
132
- var active = document.querySelector('#zer0SkinButtons [data-skin="{{ current_skin }}"]');
133
- if (active) active.classList.add("active");
146
+ markActiveSkinButton(document.querySelector('#zer0SkinButtons [data-skin="{{ current_skin }}"]'));
134
147
  });
135
148
  });
136
149
  </script>
@@ -1,4 +1,4 @@
1
- <!--
1
+ {% comment %}
2
2
  ===================================================================
3
3
  COLLECTION MANAGER — Jekyll collections overview
4
4
  ===================================================================
@@ -11,9 +11,9 @@
11
11
  Dependencies: Bootstrap 5 table/card/badge, site.collections
12
12
  Used by: pages/_about/settings/collections.md
13
13
  ===================================================================
14
- -->
14
+ {% endcomment %}
15
15
 
16
- <!-- Summary cards -->
16
+ {% comment %} Summary cards {% endcomment %}
17
17
  <div class="row g-3 mb-4">
18
18
  <div class="col-6 col-lg-3">
19
19
  <div class="card text-center h-100 border-primary">
@@ -61,7 +61,7 @@
61
61
  </div>
62
62
  </div>
63
63
 
64
- <!-- Collections table -->
64
+ {% comment %} Collections table {% endcomment %}
65
65
  <div class="table-responsive">
66
66
  <table class="table table-hover align-middle">
67
67
  <thead class="table-dark">
@@ -104,7 +104,7 @@
104
104
  </table>
105
105
  </div>
106
106
 
107
- <!-- Collection defaults from _config.yml -->
107
+ {% comment %} Collection defaults from _config.yml {% endcomment %}
108
108
  <h5 class="mt-4 mb-3"><i class="bi bi-sliders me-1"></i> Collection Defaults</h5>
109
109
  <div class="accordion" id="collDefaultsAccordion">
110
110
  {% for collection in site.collections %}
@@ -1,5 +1,5 @@
1
1
  {% comment %} Feature: ZER0-071 {% endcomment %}
2
- <!--
2
+ {% comment %}
3
3
  ===================================================================
4
4
  COMPONENT SHOWCASE - Live Bootstrap 5 demo gallery
5
5
  ===================================================================
@@ -33,12 +33,12 @@
33
33
  showcase safe for remote-theme consumers who may lack /docs/, /posts/,
34
34
  /categories/, /tags/, or /docs/customization/.
35
35
  ===================================================================
36
- -->
36
+ {% endcomment %}
37
37
  {% assign sections = include.sections | default: "alerts,buttons,badges,cards,accordion,tabs,progress,breadcrumbs,table,tooltips,list-group" %}
38
38
  {% assign section_list = sections | split: "," %}
39
39
 
40
40
  {% if section_list contains "alerts" %}
41
- <!-- ===== ALERTS ===== -->
41
+ {% comment %} ===== ALERTS ===== {% endcomment %}
42
42
  <h3 id="showcase-alerts">Alerts</h3>
43
43
  <p>Contextual feedback messages for user actions and system status:</p>
44
44
  <div class="row g-3 mb-4">
@@ -70,7 +70,7 @@
70
70
  {% endif %}
71
71
 
72
72
  {% if section_list contains "buttons" %}
73
- <!-- ===== BUTTONS ===== -->
73
+ {% comment %} ===== BUTTONS ===== {% endcomment %}
74
74
  <h3 id="showcase-buttons">Buttons</h3>
75
75
  <p>A complete button system with contextual colors, outlines, and sizes:</p>
76
76
  <div class="mb-4">
@@ -102,7 +102,7 @@
102
102
  {% endif %}
103
103
 
104
104
  {% if section_list contains "badges" %}
105
- <!-- ===== BADGES ===== -->
105
+ {% comment %} ===== BADGES ===== {% endcomment %}
106
106
  <h3 id="showcase-badges">Badges</h3>
107
107
  <p>Contextual labels, status indicators, and notification counters:</p>
108
108
  <div class="mb-4">
@@ -132,7 +132,7 @@
132
132
  {% endif %}
133
133
 
134
134
  {% if section_list contains "cards" %}
135
- <!-- ===== CARDS ===== -->
135
+ {% comment %} ===== CARDS ===== {% endcomment %}
136
136
  <h3 id="showcase-cards">Cards</h3>
137
137
  <p>Responsive card layouts — the building block of post listings, feature displays, and documentation:</p>
138
138
  <div class="row row-cols-1 row-cols-md-3 g-4 mb-4">
@@ -191,7 +191,7 @@
191
191
  {% endif %}
192
192
 
193
193
  {% if section_list contains "accordion" %}
194
- <!-- ===== ACCORDION ===== -->
194
+ {% comment %} ===== ACCORDION ===== {% endcomment %}
195
195
  <h3 id="showcase-accordion">Accordion</h3>
196
196
  <p>Collapsible content sections — ideal for FAQs and detailed documentation:</p>
197
197
  <div class="accordion mb-4" id="showcaseAccordion">
@@ -235,7 +235,7 @@
235
235
  {% endif %}
236
236
 
237
237
  {% if section_list contains "tabs" %}
238
- <!-- ===== TABS ===== -->
238
+ {% comment %} ===== TABS ===== {% endcomment %}
239
239
  <h3 id="showcase-tabs">Tabs</h3>
240
240
  <p>Tabbed content panels for organizing related information:</p>
241
241
  <ul class="nav nav-tabs mb-0" id="showcaseTabs" role="tablist">
@@ -281,7 +281,7 @@ theme: jekyll-theme-zer0</code></pre>
281
281
  {% endif %}
282
282
 
283
283
  {% if section_list contains "progress" %}
284
- <!-- ===== PROGRESS BARS ===== -->
284
+ {% comment %} ===== PROGRESS BARS ===== {% endcomment %}
285
285
  <h3 id="showcase-progress">Progress Bars</h3>
286
286
  <p>Visual indicators for build status, completion tracking, and loading states:</p>
287
287
  <div class="mb-4">
@@ -325,7 +325,7 @@ theme: jekyll-theme-zer0</code></pre>
325
325
  {% endif %}
326
326
 
327
327
  {% if section_list contains "breadcrumbs" %}
328
- <!-- ===== BREADCRUMBS ===== -->
328
+ {% comment %} ===== BREADCRUMBS ===== {% endcomment %}
329
329
  <h3 id="showcase-breadcrumbs">Breadcrumbs</h3>
330
330
  <p>Hierarchical navigation trail showing current page position:</p>
331
331
  {% comment %} Existence-guard the demo breadcrumb links so they never 404 on
@@ -357,7 +357,7 @@ theme: jekyll-theme-zer0</code></pre>
357
357
  {% endif %}
358
358
 
359
359
  {% if section_list contains "table" %}
360
- <!-- ===== TABLE ===== -->
360
+ {% comment %} ===== TABLE ===== {% endcomment %}
361
361
  <h3 id="showcase-table">Responsive Table</h3>
362
362
  <p>Styled tables with Bootstrap — all Markdown tables automatically receive Bootstrap styling:</p>
363
363
  <div class="table-responsive mb-4">
@@ -407,7 +407,7 @@ theme: jekyll-theme-zer0</code></pre>
407
407
  {% endif %}
408
408
 
409
409
  {% if section_list contains "tooltips" %}
410
- <!-- ===== TOOLTIPS ===== -->
410
+ {% comment %} ===== TOOLTIPS ===== {% endcomment %}
411
411
  <h3 id="showcase-tooltips">Tooltips</h3>
412
412
  <p>Interactive hints and contextual information on hover:</p>
413
413
  <div class="d-flex flex-wrap gap-3 mb-4">
@@ -436,7 +436,7 @@ document.addEventListener('DOMContentLoaded', () => {
436
436
  {% endif %}
437
437
 
438
438
  {% if section_list contains "list-group" %}
439
- <!-- ===== LIST GROUP ===== -->
439
+ {% comment %} ===== LIST GROUP ===== {% endcomment %}
440
440
  <h3 id="showcase-list-group">List Group</h3>
441
441
  <p>Organized content lists with icons, badges, and action states:</p>
442
442
  {% comment %} Existence-guard the demo list-group links (issue #219). Each target