jekyll-theme-zer0 1.29.0 → 1.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +886 -2
  3. data/_data/README.md +2 -0
  4. data/_data/ai.yml +5 -3
  5. data/_data/ai_pricing.yml +36 -0
  6. data/_data/backlog.yml +196 -22
  7. data/_data/consumers.yml +147 -5
  8. data/_data/features.yml +147 -21
  9. data/_data/feedback_types.yml +17 -12
  10. data/_data/landing.yml +5 -2
  11. data/_data/navigation/quickstart.yml +4 -0
  12. data/_data/site_builder.yml +874 -0
  13. data/_data/theme-manifest.yml +132 -122
  14. data/_data/ui-text.yml +19 -0
  15. data/_includes/README.md +11 -1
  16. data/_includes/analytics/posthog.html +30 -11
  17. data/_includes/components/README.md +1 -1
  18. data/_includes/components/admin-links.html +2 -2
  19. data/_includes/components/admin-tabs.html +2 -2
  20. data/_includes/components/ai-chat.html +14 -11
  21. data/_includes/components/analytics-dashboard.html +8 -8
  22. data/_includes/components/author-bio.html +1 -1
  23. data/_includes/components/author-card.html +10 -2
  24. data/_includes/components/author-eeat.html +4 -4
  25. data/_includes/components/background-customizer.html +8 -8
  26. data/_includes/components/background-image.html +120 -0
  27. data/_includes/components/background-settings.html +28 -15
  28. data/_includes/components/collection-manager.html +5 -5
  29. data/_includes/components/component-showcase.html +13 -13
  30. data/_includes/components/config-editor.html +12 -12
  31. data/_includes/components/config-viewer.html +8 -8
  32. data/_includes/components/cookie-consent.html +41 -41
  33. data/_includes/components/cta-button.html +7 -2
  34. data/_includes/components/dev-shortcuts.html +7 -7
  35. data/_includes/components/env-dashboard.html +8 -8
  36. data/_includes/components/env-switcher.html +9 -9
  37. data/_includes/components/feature-card.html +2 -2
  38. data/_includes/components/halfmoon.html +2 -2
  39. data/_includes/components/info-section.html +36 -36
  40. data/_includes/components/js-cdn.html +15 -15
  41. data/_includes/components/language-toggle.html +4 -4
  42. data/_includes/components/mermaid.html +79 -435
  43. data/_includes/components/nanobar.html +5 -5
  44. data/_includes/components/nav-editor.html +2 -2
  45. data/_includes/components/nav-export.html +2 -2
  46. data/_includes/components/nav-overview.html +2 -2
  47. data/_includes/components/page-feedback.html +45 -30
  48. data/_includes/components/page-views-init.html +1 -1
  49. data/_includes/components/post-card.html +22 -22
  50. data/_includes/components/post-type-badge.html +2 -2
  51. data/_includes/components/powered-by.html +2 -2
  52. data/_includes/components/preview-image.html +28 -3
  53. data/_includes/components/quick-index.html +2 -2
  54. data/_includes/components/search-modal.html +27 -2
  55. data/_includes/components/searchbar.html +2 -2
  56. data/_includes/components/svg-background.html +2 -2
  57. data/_includes/components/theme-customizer.html +2 -2
  58. data/_includes/components/theme-info.html +6 -6
  59. data/_includes/components/theme-preview-gallery.html +23 -23
  60. data/_includes/content/giscus.html +2 -2
  61. data/_includes/content/intro.html +15 -8
  62. data/_includes/content/jsonld-faq.html +2 -2
  63. data/_includes/content/jsonld-software.html +2 -2
  64. data/_includes/content/seo.html +12 -4
  65. data/_includes/content/sitemap.html +247 -290
  66. data/_includes/content/toc.html +183 -183
  67. data/_includes/core/branding.html +13 -20
  68. data/_includes/core/console-capture.html +32 -74
  69. data/_includes/core/favicon.html +49 -7
  70. data/_includes/core/footer-fabs.html +17 -3
  71. data/_includes/core/footer.html +31 -18
  72. data/_includes/core/head.html +128 -92
  73. data/_includes/core/header.html +85 -54
  74. data/_includes/core/tokens-inline.html +16 -1
  75. data/_includes/docs/bootstrap-docs.html +8 -8
  76. data/_includes/landing/landing-install-cards.html +2 -2
  77. data/_includes/landing/landing-quick-links.html +1 -1
  78. data/_includes/navigation/admin-nav.html +2 -2
  79. data/_includes/navigation/nav-tree.html +8 -8
  80. data/_includes/navigation/navbar.html +21 -18
  81. data/_includes/navigation/section-sidebar.html +16 -16
  82. data/_includes/navigation/sidebar-config.html +36 -2
  83. data/_includes/navigation/sidebar-left.html +15 -15
  84. data/_includes/navigation/sidebar-right.html +6 -6
  85. data/_includes/obsidian/full-graph.html +2 -2
  86. data/_includes/setup/claude-session.html +72 -0
  87. data/_includes/setup/prereq-checklist.html +90 -0
  88. data/_includes/setup/wizard.html +910 -291
  89. data/_includes/stats/stats-categories.html +8 -8
  90. data/_includes/stats/stats-header.html +14 -14
  91. data/_includes/stats/stats-metrics.html +12 -12
  92. data/_includes/stats/stats-no-data.html +12 -12
  93. data/_includes/stats/stats-overview.html +6 -6
  94. data/_includes/stats/stats-tags.html +8 -8
  95. data/_layouts/404.html +38 -24
  96. data/_layouts/admin.html +22 -22
  97. data/_layouts/article.html +54 -37
  98. data/_layouts/author.html +20 -20
  99. data/_layouts/authors.html +2 -2
  100. data/_layouts/book-abc.html +12 -12
  101. data/_layouts/book-story.html +15 -15
  102. data/_layouts/book.html +12 -12
  103. data/_layouts/collection.html +33 -33
  104. data/_layouts/cookbook.html +12 -12
  105. data/_layouts/default.html +27 -24
  106. data/_layouts/home.html +36 -24
  107. data/_layouts/index.html +10 -10
  108. data/_layouts/landing.html +17 -17
  109. data/_layouts/news.html +44 -44
  110. data/_layouts/note.html +40 -39
  111. data/_layouts/notebook.html +36 -35
  112. data/_layouts/recipe.html +24 -24
  113. data/_layouts/root.html +95 -55
  114. data/_layouts/section.html +23 -23
  115. data/_layouts/setup.html +3 -3
  116. data/_layouts/sitemap-collection.html +49 -49
  117. data/_layouts/stats.html +40 -40
  118. data/_layouts/tag.html +12 -12
  119. data/_layouts/welcome.html +21 -21
  120. data/_sass/components/_cookie-banner.scss +48 -0
  121. data/_sass/components/_mermaid.scss +375 -0
  122. data/_sass/components/_setup-wizard.scss +588 -41
  123. data/_sass/components/_ui-enhancements.scss +11 -0
  124. data/_sass/core/_navbar.scss +147 -100
  125. data/_sass/core/_sidebar-extras.scss +2 -14
  126. data/_sass/core/code-copy.scss +27 -8
  127. data/_sass/layouts/_navbar-extras.scss +14 -4
  128. data/assets/css/extension-points-demo.css +27 -0
  129. data/assets/css/main.scss +1 -0
  130. data/assets/data/wiki-index.json +19 -0
  131. data/assets/js/ai-chat.js +47 -5
  132. data/assets/js/auto-hide-nav.js +53 -13
  133. data/assets/js/code-copy.js +72 -1
  134. data/assets/js/extension-points-demo.js +48 -0
  135. data/assets/js/fleet-feedback-capture.js +124 -0
  136. data/assets/js/fleet-feedback.js +853 -0
  137. data/assets/js/mermaid-diagrams.js +1373 -0
  138. data/assets/js/modules/navigation/config.js +9 -6
  139. data/assets/js/modules/navigation/scroll-spy.js +315 -80
  140. data/assets/js/modules/theme/appearance.js +8 -2
  141. data/assets/js/obsidian-local-graph.js +27 -5
  142. data/assets/js/obsidian-wiki-links.js +8 -3
  143. data/assets/js/page-feedback.js +125 -192
  144. data/assets/js/search-modal.js +26 -0
  145. data/assets/js/setup-wizard.js +2119 -363
  146. data/assets/js/site-builder.js +1834 -0
  147. data/assets/js/ui-enhancements.js +11 -3
  148. data/scripts/README.md +15 -0
  149. data/scripts/ai/README.md +38 -0
  150. data/scripts/ai/api_call.rb +124 -0
  151. data/scripts/ai/usage.rb +314 -0
  152. data/scripts/ai/usage_report.rb +225 -0
  153. data/scripts/ci/test_visual_evidence_autogen.py +403 -0
  154. data/scripts/ci/visual_evidence_autogen.py +1070 -0
  155. data/scripts/content-review.rb +20 -1
  156. data/scripts/features/install-preview-generator +1 -1
  157. data/scripts/lib/README.md +12 -0
  158. data/scripts/lib/preview_generator.py +2 -2
  159. data/scripts/test/integration/mermaid +22 -8
  160. data/scripts/test/lib/run_tests.sh +1 -0
  161. data/scripts/test/lib/test_visual_evidence_autogen.sh +24 -0
  162. data/scripts/translate.rb +23 -1
  163. metadata +21 -2
@@ -20,12 +20,26 @@
20
20
  after </footer> is visually identical to the previous nesting.
21
21
  ===================================================================
22
22
  {%- endcomment -%}
23
- {%- assign _root_only_layouts = "landing,welcome,stats,sitemap-collection,setup,section,news,index,home,admin" | split: "," -%}
24
- {%- unless _root_only_layouts contains page.layout -%}
23
+ {%- comment -%}
24
+ The TOC FAB controls `#tocContents`, which navigation/sidebar-right.html
25
+ emits — and only _layouts/default.html includes that. This used to be a
26
+ DENYLIST of layouts to skip, which fails open: any layout not on the list got
27
+ the FAB whether or not it rendered the panel, and the list had already fallen
28
+ behind (404 and the book-* layouts were missing, so those pages shipped a FAB
29
+ pointing at nothing — issue #373).
30
+
31
+ Replaced with the positive gate from navigation/sidebar-config.html, which
32
+ fails safe: a new layout gets no FAB until it is declared as one that renders
33
+ the offcanvas. sidebar-config is included here rather than relied upon from
34
+ header.html because Liquid assigns propagate to the INCLUDING template, and
35
+ these are two sibling includes of root.html.
36
+ {%- endcomment -%}
37
+ {% include navigation/sidebar-config.html %}
38
+ {%- if sidebar_offcanvas_rendered -%}
25
39
  {% include navigation/toc-fab.html %}
26
40
  {%- comment -%} Local-graph FAB/panel are part of the Obsidian feature. {%- endcomment -%}
27
41
  {%- if site.obsidian.enabled != false -%}
28
42
  {% include navigation/local-graph-fab.html %}
29
43
  {% include navigation/local-graph.html %}
30
44
  {%- endif -%}
31
- {%- endunless -%}
45
+ {%- endif -%}
@@ -1,5 +1,5 @@
1
1
  {% comment %} Feature: ZER0-046 {% endcomment %}
2
- <!--
2
+ {% comment %}
3
3
  ===================================================================
4
4
  FOOTER - Comprehensive Site Footer with Branding & Navigation
5
5
  ===================================================================
@@ -33,12 +33,12 @@
33
33
  - Guard optional fields (address/phone/email) to avoid empty output
34
34
  - Use relative_url for internal links to respect baseurl
35
35
  ===================================================================
36
- -->
36
+ {% endcomment %}
37
37
 
38
38
  {%- include core/i18n.html lang=include.lang -%}
39
39
  <footer class="bd-footer border-top" role="contentinfo">
40
40
  {% assign footer_page_urls = site.html_pages | map: "url" | join: "|" | prepend: "|" | append: "|" %}
41
- <!-- Powered by Row -->
41
+ {% comment %} Powered by Row {% endcomment %}
42
42
  <div class="container-xl footer-powered-by">
43
43
  <p class="footer-powered-by-copyright text-center mb-0">
44
44
  &copy; {{ site.time | date: "%Y" }} {{ site.name | default: site.title }}
@@ -110,12 +110,12 @@
110
110
  </div>
111
111
  </div>
112
112
 
113
- <!-- Branding and Navigation Block — full-width dark background -->
113
+ {% comment %} Branding and Navigation Block — full-width dark background {% endcomment %}
114
114
  <div class="bg-dark text-light footer-dark-block">
115
115
  <div class="container-xl">
116
- <!-- Top: Site info + Quick Links + Social -->
116
+ {% comment %} Top: Site info + Quick Links + Social {% endcomment %}
117
117
  <div class="row mb-3 mb-lg-4 g-2 g-lg-3 footer-nav-columns justify-content-center">
118
- <!-- Site Info -->
118
+ {% comment %} Site Info {% endcomment %}
119
119
  <div class="col-12 col-md-3 col-lg-3 mb-2 mb-lg-3 order-0 order-md-1 order-lg-1">
120
120
  <h2 class="footer-section-title text-uppercase h6 mb-0">{{ site.title }}</h2>
121
121
  {% if site.address %}<p class="mb-1">{{ site.address }}</p>{% endif %}
@@ -123,12 +123,12 @@
123
123
  {% if site.email %}<p class="mb-0"><a href="mailto:{{ site.email }}" class="text-light text-decoration-none">{{ site.email }}</a></p>{% endif %}
124
124
  </div>
125
125
 
126
- <!-- Quick Links
126
+ {% comment %} Quick Links
127
127
  Resolution order:
128
128
  1. site.footer_quick_links (array of {label, url}) — explicit override
129
129
  2. Auto-detection: only render links whose target pages
130
130
  actually exist in the build. Prevents 404s on bare-minimum
131
- sites that haven't created /about/, /services/, etc. -->
131
+ sites that haven't created /about/, /services/, etc. {% endcomment %}
132
132
  <div class="col-6 col-md-3 col-lg-3 mb-2 mb-lg-3 order-2 order-md-2 order-lg-2">
133
133
  <h2 class="footer-section-title text-uppercase h6 mb-0">Quick Links</h2>
134
134
  <ul class="list-unstyled" aria-label="Footer quick links">
@@ -171,7 +171,7 @@
171
171
  </ul>
172
172
  </div>
173
173
 
174
- <!-- Latest Posts (dynamic) -->
174
+ {% comment %} Latest Posts (dynamic) {% endcomment %}
175
175
  {% assign posts_count = site.posts | size %}
176
176
  {% if posts_count and posts_count > 0 %}
177
177
  <div class="col-12 col-md-3 col-lg-3 mb-2 mb-lg-3 order-4 order-md-3 order-lg-3">
@@ -186,7 +186,7 @@
186
186
  </div>
187
187
  {% endif %}
188
188
 
189
- <!-- Social & RSS -->
189
+ {% comment %} Social & RSS {% endcomment %}
190
190
  <div class="col-6 col-md-3 col-lg-3 mb-2 mb-lg-3 order-3 order-md-4 order-lg-4">
191
191
  <h2 class="footer-section-title text-uppercase h6 mb-0">Follow Us</h2>
192
192
  <ul class="list-inline mb-0" aria-label="Social links">
@@ -194,7 +194,17 @@
194
194
  {% if link.url %}
195
195
  <li class="list-inline-item">
196
196
  {% capture link_icon_classes %}{{ site.default_icon }} {{ link.icon | default: "bi-link-45deg" }}{% endcapture %}
197
- <a href="{{ link.url }}" rel="nofollow" class="text-light text-decoration-none">
197
+ {%- comment -%}
198
+ aria-label is load-bearing below the md breakpoint (issue
199
+ #435): the icon is aria-hidden and the label span is
200
+ d-none there, so without it the link had NO accessible
201
+ name at all on mobile — a screen reader announced bare
202
+ "link". It matches the visible text exactly, so the
203
+ accessible name still contains the visible label at md+
204
+ (WCAG 2.5.3 Label in Name).
205
+ {%- endcomment -%}
206
+ <a href="{{ link.url }}" rel="nofollow" class="text-light text-decoration-none"
207
+ aria-label="{{ link.label }}">
198
208
  <i class="{{ link_icon_classes }}" aria-hidden="true"></i>
199
209
  <span class="d-none d-md-inline">{{ link.label }}</span>
200
210
  </a>
@@ -202,16 +212,19 @@
202
212
  {% endif %}
203
213
  {% endfor %}
204
214
  <li class="list-inline-item">
205
- <a class="text-light text-decoration-none" href="{{ '/feed.xml' | relative_url }}">
215
+ {%- comment -%} Same as the social links above (#435). {%- endcomment -%}
216
+ {%- assign _feed_label = ui.feed_label | default: "Feed" -%}
217
+ <a class="text-light text-decoration-none" href="{{ '/feed.xml' | relative_url }}"
218
+ aria-label="{{ _feed_label }}">
206
219
  <i class="{{ site.default_icon }} bi-rss" aria-hidden="true"></i>
207
- <span class="d-none d-md-inline">{{ ui.feed_label | default: "Feed" }}</span>
220
+ <span class="d-none d-md-inline">{{ _feed_label }}</span>
208
221
  </a>
209
222
  </li>
210
223
  </ul>
211
224
  </div>
212
225
  </div>
213
226
 
214
- <!-- Middle: Subscribe + Policies -->
227
+ {% comment %} Middle: Subscribe + Policies {% endcomment %}
215
228
  <div class="row mb-3 mb-lg-4 align-items-center justify-content-center g-2 footer-subscribe-row">
216
229
  <div class="col-12 col-md-8 col-lg-8">
217
230
  <form class="row g-2 footer-subscribe-form" aria-label="Newsletter signup" onsubmit="return false;">
@@ -249,7 +262,7 @@
249
262
  </div>
250
263
  </div>
251
264
 
252
- <!-- Bottom: Copyright -->
265
+ {% comment %} Bottom: Copyright {% endcomment %}
253
266
  <div class="text-center">
254
267
  <p class="mb-0">&copy; {{ site.time | date: "%Y" }} {{ site.title | default: site.name }}. All Rights Reserved.</p>
255
268
  </div>
@@ -265,9 +278,9 @@
265
278
  immediately after </footer> is visually identical to nesting them inside.
266
279
  {%- endcomment -%}
267
280
 
268
- <!-- Back to Top Button (controlled by assets/js/back-to-top.js) -->
269
- <!-- Positioning/stacking handled by _sass/components/_back-to-top.scss
270
- via the FAB tokens (bottom-up: back-to-top → chat toggle → TOC FAB) -->
281
+ {% comment %} Back to Top Button (controlled by assets/js/back-to-top.js) {% endcomment %}
282
+ {% comment %} Positioning/stacking handled by _sass/components/_back-to-top.scss
283
+ via the FAB tokens (bottom-up: back-to-top → chat toggle → TOC FAB) {% endcomment %}
271
284
  <a
272
285
  id="backToTopBtn"
273
286
  href="#"
@@ -1,5 +1,5 @@
1
1
  {% comment %} Feature: ZER0-001 {% endcomment %}
2
- <!--
2
+ {% comment %}
3
3
  ===================================================================
4
4
  HEAD - HTML Document Head Section
5
5
  ===================================================================
@@ -19,30 +19,30 @@
19
19
  - seo.html: SEO meta tags and Open Graph data
20
20
  - google-analytics.html: Google Analytics tracking
21
21
  - google-tag-manager-head.html: GTM head section
22
- - posthog.html: PostHog product analytics (opt-in via site.posthog.enabled)
22
+ - (posthog.html is included from _layouts/root.html at the end of <body>, not here)
23
23
 
24
24
  Performance Notes:
25
25
  - Scripts loaded in head for immediate availability
26
26
  - MathJax loaded asynchronously to prevent render blocking
27
27
  - Nanobar provides visual loading feedback (see components/nanobar.html)
28
28
  ===================================================================
29
- -->
29
+ {% endcomment %}
30
30
 
31
- <!-- ================================ -->
32
- <!-- ANALYTICS AND TRACKING -->
33
- <!-- ================================ -->
34
- <!-- Google Tag Manager - Must be in head section for proper tracking -->
35
- <!-- Only load in production builds (JEKYLL_ENV=production); the include also guards against dev hostnames -->
31
+ {% comment %} ================================ {% endcomment %}
32
+ {% comment %} ANALYTICS AND TRACKING {% endcomment %}
33
+ {% comment %} ================================ {% endcomment %}
34
+ {% comment %} Google Tag Manager - Must be in head section for proper tracking {% endcomment %}
35
+ {% comment %} Only load in production builds (JEKYLL_ENV=production); the include also guards against dev hostnames {% endcomment %}
36
36
  {% if jekyll.environment == "production" %}{% include analytics/google-tag-manager-head.html %}{% endif %}
37
37
 
38
- <!-- Page-feedback: early console/error capture — must run before the theme
39
- scripts below so a report can include the logs that led up to it. -->
38
+ {% comment %} Page-feedback: early console/error capture — must run before the theme
39
+ scripts below so a report can include the logs that led up to it. {% endcomment %}
40
40
  {% include core/console-capture.html %}
41
41
 
42
- <!-- ================================ -->
43
- <!-- JAVASCRIPT LIBRARIES -->
44
- <!-- ================================ -->
45
- <!-- Custom theme JavaScript - Deferred for performance -->
42
+ {% comment %} ================================ {% endcomment %}
43
+ {% comment %} JAVASCRIPT LIBRARIES {% endcomment %}
44
+ {% comment %} ================================ {% endcomment %}
45
+ {% comment %} Custom theme JavaScript - Deferred for performance {% endcomment %}
46
46
  <script defer src="{{ '/assets/js/auto-hide-nav.js' | relative_url }}"></script>
47
47
  <script defer src="{{ '/assets/js/back-to-top.js' | relative_url }}"></script>
48
48
  <script defer src="{{ '/assets/js/halfmoon.js' | relative_url }}"></script>
@@ -51,34 +51,34 @@
51
51
  <script defer src="{{ '/assets/js/table-copy.js' | relative_url }}"></script>
52
52
  <script defer src="{{ '/assets/js/ui-enhancements.js' | relative_url }}"></script>
53
53
 
54
- <!-- Page-view counter — injects its config and loads assets/js/page-views.js.
54
+ {% comment %} Page-view counter — injects its config and loads assets/js/page-views.js.
55
55
  Renders nothing on its own; components/page-views.html places the badge.
56
- Gated on site.page_views.enabled inside the include. -->
56
+ Gated on site.page_views.enabled inside the include. {% endcomment %}
57
57
  {% include components/page-views-init.html %}
58
58
 
59
- <!-- ================================ -->
60
- <!-- THIRD PARTY INTEGRATIONS -->
61
- <!-- ================================ -->
59
+ {% comment %} ================================ {% endcomment %}
60
+ {% comment %} THIRD PARTY INTEGRATIONS {% endcomment %}
61
+ {% comment %} ================================ {% endcomment %}
62
62
 
63
- <!-- Recipe scaling & unit conversion - Only on pages that render recipe
63
+ {% comment %} Recipe scaling & unit conversion - Only on pages that render recipe
64
64
  quantities: the `recipe` layout, or any page that opts in with
65
- `recipe_tools: true` (e.g. a post embedding components/recipe-*.html). -->
65
+ `recipe_tools: true` (e.g. a post embedding components/recipe-*.html). {% endcomment %}
66
66
  {% if page.layout == "recipe" or page.recipe_tools %}
67
67
  <script defer src="{{ '/assets/js/recipe-scaler.js' | relative_url }}"></script>
68
68
  {% endif %}
69
69
 
70
- <!-- Mermaid Diagrams - Conditional loading based on page front matter -->
70
+ {% comment %} Mermaid Diagrams - Conditional loading based on page front matter {% endcomment %}
71
71
  {% if page.mermaid %}
72
72
  {% include components/mermaid.html %}
73
73
  {% endif %}
74
74
 
75
- <!-- Nano Progress Bar - Visual loading indicator (config-driven) — page-invariant, cached. -->
75
+ {% comment %} Nano Progress Bar - Visual loading indicator (config-driven) — page-invariant, cached. {% endcomment %}
76
76
  {% include_cached components/nanobar.html %}
77
77
 
78
- <!-- MathJax - Conditional loading based on page front matter (like Mermaid) -->
78
+ {% comment %} MathJax - Conditional loading based on page front matter (like Mermaid) {% endcomment %}
79
79
  {% if page.mathjax %}
80
- <!-- MathJax 3 configuration — must appear before the script tag.
81
- Enables $...$ inline math (off by default in MathJax 3). -->
80
+ {% comment %} MathJax 3 configuration — must appear before the script tag.
81
+ Enables $...$ inline math (off by default in MathJax 3). {% endcomment %}
82
82
  <script>
83
83
  window.MathJax = {
84
84
  tex: {
@@ -91,33 +91,45 @@ window.MathJax = {
91
91
  <script id="MathJax-script" async src="{{ '/assets/vendor/mathjax/es5/tex-mml-chtml.js' | relative_url }}"></script>
92
92
  {% endif %}
93
93
 
94
- <!-- ================================ -->
95
- <!-- HTML DOCUMENT META DATA -->
96
- <!-- ================================ -->
97
- <!-- Essential meta tags for proper document rendering and mobile support -->
98
- <meta charset="utf-8">
94
+ {% comment %} ================================ {% endcomment %}
95
+ {% comment %} HTML DOCUMENT META DATA {% endcomment %}
96
+ {% comment %} ================================ {% endcomment %}
97
+ {%- comment -%} NOTE: the charset meta is NOT here — it lives at the very top
98
+ of _layouts/root.html so it lands inside the spec's 1024-byte window
99
+ (issue #372). Do not re-add it here. {%- endcomment -%}
99
100
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
100
101
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
101
102
 
102
- <!-- Favicon + browser identity (config-driven via `favicon:`; explicit
103
- /favicon.ico fallback so baseurl deployments resolve) — page-invariant, cached. -->
103
+ {% comment %} Favicon + browser identity (config-driven via `favicon:`; explicit
104
+ /favicon.ico fallback so baseurl deployments resolve) — page-invariant, cached. {% endcomment %}
104
105
  {% include_cached core/favicon.html %}
105
106
 
106
- <!-- ================================ -->
107
- <!-- SEO SUPPLEMENT -->
108
- <!-- ================================ -->
109
- <!-- Additional SEO tags not covered by jekyll-seo-tag plugin ( in root.html).
110
- Outputs custom og:image with assets_prefix normalisation and non-Google site verification. -->
107
+ {%- comment -%} Feed autodiscovery. jekyll-feed generates /feed.xml but emits
108
+ no link element unless feed_meta is called, so the feed was undiscoverable
109
+ to readers and crawlers even though the footer links it (issue #371).
110
+ Written out by hand rather than via feed_meta so the theme does not
111
+ hard-fail with "Unknown tag" on a consumer site that has not enabled the
112
+ plugin; the path matches the footer's own /feed.xml link. {%- endcomment -%}
113
+ {%- assign _feed_path = site.feed.path | default: 'feed.xml' -%}
114
+ <link rel="alternate" type="application/atom+xml"
115
+ title="{{ site.title | default: site.name | escape }}"
116
+ href="{{ _feed_path | relative_url }}">
117
+
118
+ {% comment %} ================================ {% endcomment %}
119
+ {% comment %} SEO SUPPLEMENT {% endcomment %}
120
+ {% comment %} ================================ {% endcomment %}
121
+ {% comment %} Additional SEO tags not covered by jekyll-seo-tag plugin ( in root.html).
122
+ Outputs custom og:image with assets_prefix normalisation and non-Google site verification. {% endcomment %}
111
123
  {% include content/seo.html %}
112
124
 
113
- <!-- hreflang alternates for machine-generated translations (no-op when the
114
- page has none) — see _data/i18n/manifest.yml + scripts/translate.rb -->
125
+ {% comment %} hreflang alternates for machine-generated translations (no-op when the
126
+ page has none) — see _data/i18n/manifest.yml + scripts/translate.rb {% endcomment %}
115
127
  {% include core/hreflang.html %}
116
128
 
117
- <!-- ========================== -->
118
- <!-- STRUCTURED DATA MARKUP -->
119
- <!-- ========================== -->
120
- <!-- Enhanced meta data for rich snippets and social media -->
129
+ {% comment %} ========================== {% endcomment %}
130
+ {% comment %} STRUCTURED DATA MARKUP {% endcomment %}
131
+ {% comment %} ========================== {% endcomment %}
132
+ {% comment %} Enhanced meta data for rich snippets and social media {% endcomment %}
121
133
  {% if page.title %}
122
134
  <meta itemprop="headline" content="{{ page.title | markdownify | strip_html | strip_newlines | escape_once }}">
123
135
  {% endif %}
@@ -135,84 +147,108 @@ window.MathJax = {
135
147
  <meta itemprop="dateModified" content="{{ page.lastmod | date_to_xmlschema }}">
136
148
  {% endif %}
137
149
 
138
- <!-- JSON-LD SoftwareApplication schema (homepage only) -->
150
+ {% comment %} JSON-LD SoftwareApplication schema (homepage only) {% endcomment %}
139
151
  {% include content/jsonld-software.html %}
140
152
 
141
- <!-- JSON-LD FAQ schema (pages with faq_items front matter) -->
153
+ {% comment %} JSON-LD FAQ schema (pages with faq_items front matter) {% endcomment %}
142
154
  {% include content/jsonld-faq.html %}
143
155
 
144
- <!-- ========================== -->
145
- <!-- ANALYTICS INTEGRATION -->
146
- <!-- ========================== -->
147
- <!-- Google Analytics tracking - Configured in _config.yml -->
148
- <!-- Only load in production AND when an ID is configured; the include also guards against dev hostnames -->
156
+ {% comment %} ========================== {% endcomment %}
157
+ {% comment %} ANALYTICS INTEGRATION {% endcomment %}
158
+ {% comment %} ========================== {% endcomment %}
159
+ {% comment %} Google Analytics tracking - Configured in _config.yml {% endcomment %}
160
+ {% comment %} Only load in production AND when an ID is configured; the include also guards against dev hostnames {% endcomment %}
149
161
  {% if jekyll.environment == "production" and site.google_analytics %}{% include analytics/google-analytics.html %}{% endif %}
150
162
 
151
- <!-- PostHog analytics - Configured via the `posthog:` block in _config.yml -->
152
- <!-- Only load in production AND when explicitly enabled; the include itself also respects Do Not Track -->
153
- {% if jekyll.environment == "production" and site.posthog.enabled %}{% include analytics/posthog.html %}{% endif %}
163
+ {% comment %} PostHog analytics is NOT included here. _layouts/root.html already
164
+ includes analytics/posthog.html at the end of <body> (since the include was
165
+ added), so this second copy, added in #317, emitted the snippet twice and
166
+ called posthog.init() twice on every production page. {% endcomment %}
154
167
 
155
168
 
156
- <!-- ================================ -->
157
- <!-- CSS FRAMEWORKS AND LIBRARIES -->
158
- <!-- ================================ -->
169
+ {% comment %} ================================ {% endcomment %}
170
+ {% comment %} CSS FRAMEWORKS AND LIBRARIES {% endcomment %}
171
+ {% comment %} ================================ {% endcomment %}
159
172
 
160
- <!-- ========================== -->
161
- <!-- COLOR MODE INIT (FOUC FIX) -->
162
- <!-- ========================== -->
163
- <!-- Must run BEFORE Bootstrap CSS so the correct data-bs-theme is applied
173
+ {% comment %} ========================== {% endcomment %}
174
+ {% comment %} COLOR MODE INIT (FOUC FIX) {% endcomment %}
175
+ {% comment %} ========================== {% endcomment %}
176
+ {% comment %} Must run BEFORE Bootstrap CSS so the correct data-bs-theme is applied
164
177
  when [data-bs-theme=dark/light] selectors are first evaluated.
165
178
  Reads site.color_mode_default (via data-color-mode-default attr on <html>)
166
- and falls back to localStorage["theme"] for the user's explicit preference. -->
179
+ and falls back to localStorage["theme"] for the user's explicit preference. {% endcomment %}
167
180
  {% include core/color-mode-init.html %}
168
181
 
169
- <!-- ========================== -->
170
- <!-- BOOTSTRAP 5 FRAMEWORK -->
171
- <!-- ========================== -->
172
- <!-- Bootstrap 5.3.3 - bundled locally (GitHub Pages–safe; refresh via scripts/vendor-install.sh) -->
182
+ {% comment %} ========================== {% endcomment %}
183
+ {% comment %} BOOTSTRAP 5 FRAMEWORK {% endcomment %}
184
+ {% comment %} ========================== {% endcomment %}
185
+ {% comment %} Bootstrap 5.3.3 - bundled locally (GitHub Pages–safe; refresh via scripts/vendor-install.sh) {% endcomment %}
173
186
  <link href="{{ '/assets/vendor/bootstrap/css/bootstrap.min.css' | relative_url }}" rel="stylesheet">
174
187
 
175
- <!-- ========================== -->
176
- <!-- ICON LIBRARIES -->
177
- <!-- ========================== -->
178
- <!-- Bootstrap Icons (bundled with theme; fonts use relative paths in CSS) -->
188
+ {% comment %} ========================== {% endcomment %}
189
+ {% comment %} ICON LIBRARIES {% endcomment %}
190
+ {% comment %} ========================== {% endcomment %}
191
+ {% comment %} Bootstrap Icons (bundled with theme; fonts use relative paths in CSS) {% endcomment %}
179
192
  <link rel="stylesheet" href="{{ '/assets/vendor/bootstrap-icons/font/bootstrap-icons.css' | relative_url }}">
180
193
 
181
- <!-- ========================== -->
182
- <!-- CUSTOM THEME STYLES -->
183
- <!-- ========================== -->
184
- <!-- Primary theme stylesheet - Core styling and layout (SCSS pipeline) -->
194
+ {% comment %} ========================== {% endcomment %}
195
+ {% comment %} CUSTOM THEME STYLES {% endcomment %}
196
+ {% comment %} ========================== {% endcomment %}
197
+ {% comment %} Primary theme stylesheet - Core styling and layout (SCSS pipeline) {% endcomment %}
185
198
  <link rel="stylesheet" href="{{ '/assets/css/main.css' | relative_url }}">
186
199
 
187
- <!-- ========================== -->
188
- <!-- DESIGN TOKEN OVERRIDES -->
189
- <!-- ========================== -->
190
- <!-- Inline <style> block emitted AFTER main.css so site-config `theme_color` keys
191
- and any per-user Appearance overrides win over compiled defaults. -->
200
+ {% comment %} ========================== {% endcomment %}
201
+ {% comment %} DESIGN TOKEN OVERRIDES {% endcomment %}
202
+ {% comment %} ========================== {% endcomment %}
203
+ {% comment %} Inline <style> block emitted AFTER main.css so site-config `theme_color` keys
204
+ and any per-user Appearance overrides win over compiled defaults. The tokens a
205
+ palette skin sets are scoped so that the skin still wins (see tokens-inline.html). {% endcomment %}
192
206
  {% include core/tokens-inline.html %}
193
207
 
194
- <!-- Optional fork overrides: opt-in via `site.user_overrides: true` in _config.yml.
195
- Loaded LAST so consumer rules win over both main.css and tokens-inline. -->
208
+ {% comment %} Optional fork overrides: opt-in via `site.user_overrides: true` in _config.yml.
209
+ Loaded LAST so consumer rules win over both main.css and tokens-inline. {% endcomment %}
196
210
  {% if site.user_overrides %}
197
211
  <link rel="stylesheet" href="{{ '/assets/css/user-overrides.css' | relative_url }}">
198
212
  {% endif %}
199
213
 
200
- <!-- Statistics page specific styles -->
214
+ {% comment %} Statistics page specific styles {% endcomment %}
201
215
  {% if page.layout == 'stats' or page.permalink == '/about/stats/' %}
202
216
  <link rel="stylesheet" href="{{ '/assets/css/stats.css' | relative_url }}">
203
217
  {% endif %}
204
218
 
205
- <!-- Consumer hook: end of <head>. Inject JSON-LD, font preloads, verification
219
+ {% comment %} Per-page stylesheets from frontmatter (#412):
220
+
221
+ ---
222
+ styles:
223
+ - /assets/css/notes.css
224
+ - https://cdn.example.com/x.css
225
+ ---
226
+
227
+ A contract, not a convention. Before this, attaching CSS to one page meant a
228
+ literal <link> inside the content file or a custom layout — which works, and
229
+ puts asset tags in prose. A single string is accepted as well as a list.
230
+ Site-relative paths go through relative_url so a baseurl deployment is
231
+ correct; absolute URLs are passed through untouched.
232
+
233
+ Emits NOTHING when the key is absent — the whitespace control here keeps a
234
+ page without `styles:` byte-identical to its previous build, which is why
235
+ the comment and the `endif` both close with `-%}`. {% endcomment -%}
236
+ {% if page.styles %}{% assign page_styles = page.styles %}{% if page_styles.first == nil %}{% assign page_styles = page_styles | split: "||" %}{% endif %}{% for style_href in page_styles %}
237
+ <link rel="stylesheet" href="{% if style_href contains '//' %}{{ style_href }}{% else %}{{ style_href | relative_url }}{% endif %}">
238
+ {%- endfor %}
239
+ {% endif -%}
240
+
241
+ {% comment %} Consumer hook: end of <head>. Inject JSON-LD, font preloads, verification
206
242
  tags, or extra meta by shadowing _includes/custom/head.html in your site —
207
- no fork of this file needed. The theme ships the stub empty. -->
243
+ no fork of this file needed. The theme ships the stub empty. {% endcomment %}
208
244
  {% include custom/head.html %}
209
245
 
210
- <!-- ========================== -->
211
- <!-- EXPERIMENTAL FRAMEWORKS -->
212
- <!-- ========================== -->
213
- <!-- Tailwind CSS - Currently disabled, enable for utility-first styling -->
214
- <!-- <script src="https://cdn.tailwindcss.com"></script> -->
246
+ {% comment %} ========================== {% endcomment %}
247
+ {% comment %} EXPERIMENTAL FRAMEWORKS {% endcomment %}
248
+ {% comment %} ========================== {% endcomment %}
249
+ {% comment %} Tailwind CSS - Currently disabled, enable for utility-first styling {% endcomment %}
250
+ {% comment %} <script src="https://cdn.tailwindcss.com"></script> {% endcomment %}
215
251
 
216
- <!-- Docs CSS - Alternative stylesheet, currently disabled due to scroll spy issues -->
217
- <!-- TODO: Replace with compiled Sass when scroll spy functionality is fixed -->
218
- <!-- Optional docs.css link removed; use _sass/core/_docs-layout.scss → main.css -->
252
+ {% comment %} Docs CSS - Alternative stylesheet, currently disabled due to scroll spy issues {% endcomment %}
253
+ {% comment %} TODO: Replace with compiled Sass when scroll spy functionality is fixed {% endcomment %}
254
+ {% comment %} Optional docs.css link removed; use _sass/core/_docs-layout.scss → main.css {% endcomment %}