jekyll-theme-zer0 1.26.0 → 1.28.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +558 -5
- data/README.md +10 -27
- data/_data/authors.yml +4 -3
- data/_data/backlog.yml +28 -0
- data/_data/consumers.yml +147 -0
- data/_data/features.yml +142 -23
- data/_data/i18n/fr.yml +98 -0
- data/_data/i18n/languages.yml +36 -0
- data/_data/i18n/manifest.yml +1502 -0
- data/_data/navigation/docs.yml +2 -0
- data/_data/navigation/main.yml +6 -0
- data/_data/series.yml +19 -0
- data/_data/theme-manifest.yml +348 -268
- data/_data/ui-text.yml +36 -246
- data/_includes/README.md +25 -2
- data/_includes/analytics/google-tag-manager-body.html +11 -2
- data/_includes/analytics/google-tag-manager-head.html +16 -6
- data/_includes/analytics/posthog.html +33 -4
- data/_includes/components/abc-letter.html +43 -0
- data/_includes/components/author-avatar-url.html +4 -2
- data/_includes/components/book-card.html +42 -0
- data/_includes/components/book-nav.html +80 -0
- data/_includes/components/book-plate.html +31 -0
- data/_includes/components/book-toc.html +47 -0
- data/_includes/components/bookshelf.html +68 -0
- data/_includes/components/card-grid.html +60 -0
- data/_includes/components/data-card.html +95 -0
- data/_includes/components/env-switcher.html +3 -1
- data/_includes/components/halfmoon.html +5 -1
- data/_includes/components/language-toggle.html +81 -0
- data/_includes/components/page-feedback.html +65 -2
- data/_includes/components/search-modal.html +2 -2
- data/_includes/components/shortcuts-modal.html +1 -1
- data/_includes/components/theme-controls-bar.html +10 -2
- data/_includes/components/theme-customizer.html +8 -2
- data/_includes/components/translation-notice.html +27 -0
- data/_includes/content/intro.html +16 -15
- data/_includes/content/seo.html +9 -3
- data/_includes/core/color-mode-init.html +13 -4
- data/_includes/core/favicon.html +46 -0
- data/_includes/core/footer.html +9 -4
- data/_includes/core/head.html +18 -0
- data/_includes/core/header.html +9 -4
- data/_includes/core/hreflang.html +33 -0
- data/_includes/core/i18n.html +36 -0
- data/_includes/custom/body-end.html +18 -0
- data/_includes/custom/body-start.html +17 -0
- data/_includes/custom/footer.html +18 -0
- data/_includes/custom/head.html +18 -0
- data/_includes/navigation/breadcrumbs.html +1 -1
- data/_includes/navigation/local-graph.html +28 -2
- data/_includes/navigation/nav-tree.html +3 -3
- data/_includes/navigation/navbar.html +5 -4
- data/_includes/navigation/sidebar-config.html +21 -0
- data/_includes/navigation/sidebar-nav.html +4 -0
- data/_includes/navigation/sidebar-pagetree.html +150 -0
- data/_includes/navigation/sidebar-right.html +3 -2
- data/_includes/navigation/unified-drawer.html +9 -3
- data/_includes/obsidian/full-graph.html +165 -136
- data/_layouts/404.html +260 -0
- data/_layouts/README.md +2 -0
- data/_layouts/article.html +7 -1
- data/_layouts/book-abc.html +106 -0
- data/_layouts/book-story.html +91 -0
- data/_layouts/book.html +113 -0
- data/_layouts/collection.html +17 -5
- data/_layouts/default.html +5 -3
- data/_layouts/home.html +5 -2
- data/_layouts/landing.html +9 -0
- data/_layouts/news.html +159 -33
- data/_layouts/root.html +29 -9
- data/_layouts/section.html +61 -17
- data/_sass/components/_book.scss +423 -0
- data/_sass/core/_navbar.scss +13 -0
- data/_sass/core/_obsidian.scss +295 -7
- data/_sass/layouts/_navbar-extras.scss +6 -1
- data/_sass/theme/_backgrounds.scss +21 -8
- data/assets/css/main.scss +1 -0
- data/assets/js/auto-hide-nav.js +5 -1
- data/assets/js/halfmoon.js +26 -0
- data/assets/js/obsidian-graph.js +707 -265
- data/assets/js/obsidian-local-graph.js +161 -54
- data/assets/js/search-modal.js +4 -1
- data/scripts/README.md +20 -26
- data/scripts/bin/audit-consumer +1 -1
- data/scripts/bin/manifest +31 -5
- data/scripts/bin/sync-plugins +0 -1
- data/scripts/bin/validate +5 -1
- data/scripts/dev/rasterize-svg.js +65 -0
- data/scripts/features/generate-preview-images +49 -1390
- data/scripts/features/install-preview-generator +55 -33
- data/scripts/install/README.md +55 -25
- data/scripts/install/ai/client.sh +302 -93
- data/scripts/install/ai/prompts/spec.schema.json +1 -1
- data/scripts/install/ai/prompts/wizard.system.md +8 -17
- data/scripts/install/ai/wizard.sh +10 -5
- data/scripts/install/apply.sh +7 -3
- data/scripts/install/cli.sh +54 -4
- data/scripts/install/config.sh +167 -0
- data/scripts/install/doctor.sh +38 -0
- data/scripts/install/plan.sh +10 -0
- data/scripts/install/spec.sh +15 -7
- data/scripts/install/template.sh +4 -0
- data/scripts/lib/README.md +1 -5
- data/scripts/lib/install/deploy/README.md +3 -9
- data/scripts/lib/preview_generator.py +2261 -1341
- data/scripts/propagate.rb +277 -0
- data/scripts/translate.rb +1204 -0
- metadata +34 -3
- data/_plugins/preview_image_generator.rb +0 -351
data/_layouts/home.html
CHANGED
|
@@ -34,8 +34,11 @@ layout: root
|
|
|
34
34
|
- title: displayed as <h1> (and used for SEO) when present
|
|
35
35
|
- hide_title:true keep `title` for SEO but suppress the visible <h1>
|
|
36
36
|
(useful when a custom hero provides its own heading)
|
|
37
|
+
- hide_intro:true accepted as an alias of hide_title — other layouts use
|
|
38
|
+
`hide_intro` for "suppress the generated page header",
|
|
39
|
+
and consumers reasonably reach for it here too
|
|
37
40
|
- rss_subscribe:false hide the RSS subscribe link on landing/showcase homepages
|
|
38
|
-
|
|
41
|
+
All default to the original behavior, so existing homepages are unaffected.
|
|
39
42
|
|
|
40
43
|
Design Philosophy:
|
|
41
44
|
- Minimal structure allows for maximum content flexibility
|
|
@@ -56,7 +59,7 @@ layout: root
|
|
|
56
59
|
<!-- Landing/showcase pages can keep `title` for SEO but suppress the -->
|
|
57
60
|
<!-- visible heading (e.g. when a custom hero supplies its own <h1>) -->
|
|
58
61
|
<!-- with `hide_title: true`. -->
|
|
59
|
-
{%- if page.title and page.hide_title != true -%}
|
|
62
|
+
{%- if page.title and page.hide_title != true and page.hide_intro != true -%}
|
|
60
63
|
<h1 class="page-heading">{{ page.title }}</h1>
|
|
61
64
|
{%- endif -%}
|
|
62
65
|
|
data/_layouts/landing.html
CHANGED
|
@@ -182,6 +182,14 @@ layout: root
|
|
|
182
182
|
<!-- ================================ -->
|
|
183
183
|
<!-- QUICK START SECTION (data-driven)-->
|
|
184
184
|
<!-- ================================ -->
|
|
185
|
+
{%- comment -%}
|
|
186
|
+
Rendered only when the site ships a `get_started` block in
|
|
187
|
+
_data/landing.yml. The install cards advertise THEME installation
|
|
188
|
+
(gem / Docker / fork), which is right for the theme's own site but wrong
|
|
189
|
+
boilerplate on a consumer landing page — a data-less consumer gets a
|
|
190
|
+
clean page instead of another site's marketing.
|
|
191
|
+
{%- endcomment -%}
|
|
192
|
+
{%- if landing.get_started -%}
|
|
185
193
|
<section id="get-started" class="zer0-section" aria-labelledby="get-started-heading">
|
|
186
194
|
<div class="container-xl py-5 px-4 px-md-5">
|
|
187
195
|
<div class="row">
|
|
@@ -193,6 +201,7 @@ layout: root
|
|
|
193
201
|
</div>
|
|
194
202
|
</div>
|
|
195
203
|
</section>
|
|
204
|
+
{%- endif -%}
|
|
196
205
|
|
|
197
206
|
<!-- ================================ -->
|
|
198
207
|
<!-- AUTHOR / E-E-A-T SECTION -->
|
data/_layouts/news.html
CHANGED
|
@@ -15,11 +15,27 @@ source: "https://getbootstrap.com/docs/5.3/examples/blog/#"
|
|
|
15
15
|
|
|
16
16
|
Front Matter Variables:
|
|
17
17
|
- section_style: Display style (magazine, grid, list) - default: magazine
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
Section Styles:
|
|
20
20
|
- magazine (default): Hero + category blocks + featured grid
|
|
21
21
|
- grid: All posts in unified card grid
|
|
22
22
|
- list: Chronological list view across all sections
|
|
23
|
+
|
|
24
|
+
Site Config (`news:` block in _config.yml — every key optional):
|
|
25
|
+
- news.category_base : base path for per-category section pages
|
|
26
|
+
(default "/news/"; e.g. "/categories/")
|
|
27
|
+
- news.hero : false to disable the latest-post hero fallback
|
|
28
|
+
(hero still shows for breaking/featured posts)
|
|
29
|
+
- news.archives_url : path of an archives page to deep-link months to;
|
|
30
|
+
defaults to "/archives/" WHEN that page exists,
|
|
31
|
+
otherwise month entries render unlinked
|
|
32
|
+
- news.newsletter : { enabled: false } hides the subscribe band;
|
|
33
|
+
{ endpoint: "https://..." } posts the form there;
|
|
34
|
+
with no endpoint it falls back to a mailto:
|
|
35
|
+
subscribe link (site.email) or hides itself
|
|
36
|
+
Sections come from _data/navigation/posts.yml when present; otherwise
|
|
37
|
+
they are derived automatically from site.categories, so the layout
|
|
38
|
+
renders fully on a data-less consumer site.
|
|
23
39
|
|
|
24
40
|
Layout Structure (NO SIDEBAR - Full Width):
|
|
25
41
|
1. News Header: Dark header with title and section navigation
|
|
@@ -41,12 +57,41 @@ source: "https://getbootstrap.com/docs/5.3/examples/blog/#"
|
|
|
41
57
|
{% endcomment %}
|
|
42
58
|
{% assign section_style = page.section_style %}
|
|
43
59
|
|
|
44
|
-
{% comment %}
|
|
60
|
+
{% comment %}
|
|
45
61
|
Filter posts: exclude index files
|
|
46
62
|
Index files are marked with index: true in front matter
|
|
47
63
|
{% endcomment %}
|
|
48
64
|
{% assign all_posts = site.posts | where_exp: "post", "post.index != true" %}
|
|
49
65
|
|
|
66
|
+
{% comment %}
|
|
67
|
+
Resolve the news config surface once (see the header block above).
|
|
68
|
+
news_sections is a list of section NAMES: data-driven from
|
|
69
|
+
_data/navigation/posts.yml when that file exists, otherwise derived from
|
|
70
|
+
site.categories so a consumer without vendored nav data still gets a
|
|
71
|
+
fully working news homepage. Per-section url/icon are resolved at each
|
|
72
|
+
use site via section-nav lookup with a derived fallback.
|
|
73
|
+
{% endcomment %}
|
|
74
|
+
{% assign news_cfg = site.news %}
|
|
75
|
+
{% assign news_base = news_cfg.category_base | default: "/news/" %}
|
|
76
|
+
{% capture _sec_names %}{% for item in site.data.navigation.posts %}{{ item.title }}{% unless forloop.last %}|{% endunless %}{% endfor %}{% endcapture %}
|
|
77
|
+
{% if _sec_names == "" %}{% capture _sec_names %}{% for cat in site.categories limit: 8 %}{{ cat[0] }}{% unless forloop.last %}|{% endunless %}{% endfor %}{% endcapture %}{% endif %}
|
|
78
|
+
{% assign news_sections = _sec_names | split: "|" %}
|
|
79
|
+
|
|
80
|
+
{% comment %}
|
|
81
|
+
Tags index: honor the same site.tags_page convention as article.html.
|
|
82
|
+
When the key is not set explicitly, only link to /tags/ if such a page
|
|
83
|
+
actually exists in the build — a consumer without one gets unlinked tag
|
|
84
|
+
badges instead of site-wide 404 links.
|
|
85
|
+
{% endcomment %}
|
|
86
|
+
{% assign tags_base = site.tags_page | default: "/tags/" %}
|
|
87
|
+
{% assign _tags_linkable = false %}
|
|
88
|
+
{% if site.tags_page %}
|
|
89
|
+
{% assign _tags_linkable = true %}
|
|
90
|
+
{% else %}
|
|
91
|
+
{% assign _tags_index_page = site.html_pages | where: "url", tags_base | first %}
|
|
92
|
+
{% if _tags_index_page %}{% assign _tags_linkable = true %}{% endif %}
|
|
93
|
+
{% endif %}
|
|
94
|
+
|
|
50
95
|
<!-- ================================ -->
|
|
51
96
|
<!-- NEWS HEADER -->
|
|
52
97
|
<!-- ================================ -->
|
|
@@ -60,25 +105,35 @@ source: "https://getbootstrap.com/docs/5.3/examples/blog/#"
|
|
|
60
105
|
</div>
|
|
61
106
|
<div class="col-md-8">
|
|
62
107
|
<nav class="nav nav-pills justify-content-md-end flex-wrap">
|
|
63
|
-
<a class="nav-link text-white active" href="{{
|
|
108
|
+
<a class="nav-link text-white active" href="{{ page.url | relative_url }}">
|
|
64
109
|
<i class="bi bi-house me-1"></i>Home
|
|
65
110
|
</a>
|
|
66
|
-
{% for
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
{
|
|
111
|
+
{% for sec_name in news_sections %}
|
|
112
|
+
{% assign _sec_nav = site.data.navigation.posts | where: "title", sec_name | first %}
|
|
113
|
+
{% if _sec_nav %}
|
|
114
|
+
{% assign _sec_url = _sec_nav.url | replace: '/posts/', news_base %}
|
|
115
|
+
{% else %}
|
|
116
|
+
{% capture _sec_url %}{{ news_base }}{{ sec_name | slugify }}/{% endcapture %}
|
|
117
|
+
{% endif %}
|
|
118
|
+
<a class="nav-link text-white-50" href="{{ _sec_url | relative_url }}">
|
|
119
|
+
{% if _sec_nav.icon %}<i class="bi {{ _sec_nav.icon }} me-1"></i>{% endif %}
|
|
120
|
+
{{ sec_name }}
|
|
70
121
|
</a>
|
|
71
122
|
{% endfor %}
|
|
72
|
-
|
|
123
|
+
{% if _tags_linkable %}
|
|
124
|
+
<a class="nav-link text-white-50" href="{{ tags_base | relative_url }}">
|
|
73
125
|
<i class="bi bi-tags me-1"></i>Tags
|
|
74
126
|
</a>
|
|
127
|
+
{% endif %}
|
|
75
128
|
</nav>
|
|
76
129
|
</div>
|
|
77
130
|
</div>
|
|
78
131
|
</div>
|
|
79
132
|
</div>
|
|
80
133
|
|
|
81
|
-
<main
|
|
134
|
+
<!-- Not a <main>: root.html already provides the single <main id="main-content">
|
|
135
|
+
landmark; a nested <main> here would duplicate it (issue #299). -->
|
|
136
|
+
<div class="container">
|
|
82
137
|
|
|
83
138
|
{% if section_style == "magazine" %}
|
|
84
139
|
<!-- ================================ -->
|
|
@@ -90,11 +145,22 @@ source: "https://getbootstrap.com/docs/5.3/examples/blog/#"
|
|
|
90
145
|
<!-- ================================ -->
|
|
91
146
|
{% assign breaking_posts = all_posts | where: "breaking", true %}
|
|
92
147
|
{% assign hero_post = breaking_posts | first %}
|
|
93
|
-
|
|
148
|
+
|
|
94
149
|
{% unless hero_post %}
|
|
95
150
|
{% assign hero_post = all_posts | where: "featured", true | first %}
|
|
96
151
|
{% endunless %}
|
|
97
|
-
|
|
152
|
+
|
|
153
|
+
{% comment %}
|
|
154
|
+
No post is marked breaking/featured: fall back to the newest post so the
|
|
155
|
+
hero never silently vanishes on sites that don't curate front matter.
|
|
156
|
+
Opt out with `news: { hero: false }` in _config.yml.
|
|
157
|
+
{% endcomment %}
|
|
158
|
+
{% unless hero_post %}
|
|
159
|
+
{% unless news_cfg.hero == false %}
|
|
160
|
+
{% assign hero_post = all_posts | first %}
|
|
161
|
+
{% endunless %}
|
|
162
|
+
{% endunless %}
|
|
163
|
+
|
|
98
164
|
{% if hero_post %}
|
|
99
165
|
<section class="hero-section mb-5">
|
|
100
166
|
<div class="row g-0 bg-body-secondary rounded-4 overflow-hidden shadow">
|
|
@@ -126,7 +192,8 @@ source: "https://getbootstrap.com/docs/5.3/examples/blog/#"
|
|
|
126
192
|
</small>
|
|
127
193
|
{% endif %}
|
|
128
194
|
{% if hero_post.categories.size > 0 %}
|
|
129
|
-
|
|
195
|
+
{% capture _hero_cat_url %}{{ news_base }}{{ hero_post.categories | first | slugify }}/{% endcapture %}
|
|
196
|
+
<a href="{{ _hero_cat_url | relative_url }}" class="badge bg-primary text-decoration-none">
|
|
130
197
|
{{ hero_post.categories | first }}
|
|
131
198
|
</a>
|
|
132
199
|
{% endif %}
|
|
@@ -153,14 +220,20 @@ source: "https://getbootstrap.com/docs/5.3/examples/blog/#"
|
|
|
153
220
|
<!-- ================================ -->
|
|
154
221
|
<section class="section-nav mb-5">
|
|
155
222
|
<div class="row row-cols-2 row-cols-md-3 row-cols-lg-6 g-3">
|
|
156
|
-
{% for
|
|
157
|
-
{% assign section_posts = all_posts | where_exp: "post", "post.categories contains
|
|
223
|
+
{% for sec_name in news_sections %}
|
|
224
|
+
{% assign section_posts = all_posts | where_exp: "post", "post.categories contains sec_name" %}
|
|
225
|
+
{% assign _sec_nav = site.data.navigation.posts | where: "title", sec_name | first %}
|
|
226
|
+
{% if _sec_nav %}
|
|
227
|
+
{% assign _sec_url = _sec_nav.url | replace: '/posts/', news_base %}
|
|
228
|
+
{% else %}
|
|
229
|
+
{% capture _sec_url %}{{ news_base }}{{ sec_name | slugify }}/{% endcapture %}
|
|
230
|
+
{% endif %}
|
|
158
231
|
<div class="col">
|
|
159
|
-
<a href="{{
|
|
232
|
+
<a href="{{ _sec_url | relative_url }}"
|
|
160
233
|
class="card text-center h-100 text-decoration-none border-0 shadow-sm hover-lift">
|
|
161
234
|
<div class="card-body py-4">
|
|
162
|
-
<i class="bi {{
|
|
163
|
-
<h6 class="card-title mb-1">{{
|
|
235
|
+
<i class="bi {{ _sec_nav.icon | default: 'bi-folder' }} fs-2 text-primary mb-2 d-block"></i>
|
|
236
|
+
<h6 class="card-title mb-1">{{ sec_name }}</h6>
|
|
164
237
|
<small class="text-muted">{{ section_posts.size }} articles</small>
|
|
165
238
|
</div>
|
|
166
239
|
</a>
|
|
@@ -202,7 +275,8 @@ source: "https://getbootstrap.com/docs/5.3/examples/blog/#"
|
|
|
202
275
|
</div>
|
|
203
276
|
<div class="card-body">
|
|
204
277
|
{% if main_featured.categories.size > 0 %}
|
|
205
|
-
|
|
278
|
+
{% capture _mf_cat_url %}{{ news_base }}{{ main_featured.categories | first | slugify }}/{% endcapture %}
|
|
279
|
+
<a href="{{ _mf_cat_url | relative_url }}"
|
|
206
280
|
class="badge bg-primary text-decoration-none mb-2">
|
|
207
281
|
{{ main_featured.categories | first }}
|
|
208
282
|
</a>
|
|
@@ -263,16 +337,22 @@ source: "https://getbootstrap.com/docs/5.3/examples/blog/#"
|
|
|
263
337
|
<!-- ================================ -->
|
|
264
338
|
<!-- POSTS BY SECTION -->
|
|
265
339
|
<!-- ================================ -->
|
|
266
|
-
{% for
|
|
267
|
-
{% assign section_posts = all_posts | where_exp: "post", "post.categories contains
|
|
340
|
+
{% for sec_name in news_sections limit: 3 %}
|
|
341
|
+
{% assign section_posts = all_posts | where_exp: "post", "post.categories contains sec_name" %}
|
|
342
|
+
{% assign _sec_nav = site.data.navigation.posts | where: "title", sec_name | first %}
|
|
343
|
+
{% if _sec_nav %}
|
|
344
|
+
{% assign _sec_url = _sec_nav.url | replace: '/posts/', news_base %}
|
|
345
|
+
{% else %}
|
|
346
|
+
{% capture _sec_url %}{{ news_base }}{{ sec_name | slugify }}/{% endcapture %}
|
|
347
|
+
{% endif %}
|
|
268
348
|
{% if section_posts.size > 0 %}
|
|
269
349
|
<section class="section-posts mb-5 pb-4 border-bottom">
|
|
270
350
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
271
351
|
<h2 class="h4 mb-0">
|
|
272
|
-
<i class="bi {{
|
|
352
|
+
<i class="bi {{ _sec_nav.icon | default: 'bi-folder' }} text-primary me-2"></i>{{ sec_name }}
|
|
273
353
|
</h2>
|
|
274
|
-
<a href="{{
|
|
275
|
-
View All {{
|
|
354
|
+
<a href="{{ _sec_url | relative_url }}" class="btn btn-outline-secondary btn-sm">
|
|
355
|
+
View All {{ sec_name }} <i class="bi bi-arrow-right ms-1"></i>
|
|
276
356
|
</a>
|
|
277
357
|
</div>
|
|
278
358
|
|
|
@@ -349,7 +429,8 @@ source: "https://getbootstrap.com/docs/5.3/examples/blog/#"
|
|
|
349
429
|
</div>
|
|
350
430
|
<div class="card-body">
|
|
351
431
|
{% if gpost.categories.size > 0 %}
|
|
352
|
-
|
|
432
|
+
{% capture _gpost_cat_url %}{{ news_base }}{{ gpost.categories | first | slugify }}/{% endcapture %}
|
|
433
|
+
<a href="{{ _gpost_cat_url | relative_url }}" class="badge bg-primary text-decoration-none mb-2">
|
|
353
434
|
{{ gpost.categories | first }}
|
|
354
435
|
</a>
|
|
355
436
|
{% endif %}
|
|
@@ -392,7 +473,8 @@ source: "https://getbootstrap.com/docs/5.3/examples/blog/#"
|
|
|
392
473
|
<div class="card-body">
|
|
393
474
|
<div class="d-flex gap-2 mb-2 flex-wrap">
|
|
394
475
|
{% if lpost.categories.size > 0 %}
|
|
395
|
-
|
|
476
|
+
{% capture _lpost_cat_url %}{{ news_base }}{{ lpost.categories | first | slugify }}/{% endcapture %}
|
|
477
|
+
<a href="{{ _lpost_cat_url | relative_url }}" class="badge bg-primary text-decoration-none">
|
|
396
478
|
{{ lpost.categories | first }}
|
|
397
479
|
</a>
|
|
398
480
|
{% endif %}
|
|
@@ -509,15 +591,21 @@ source: "https://getbootstrap.com/docs/5.3/examples/blog/#"
|
|
|
509
591
|
{% assign unique_tags = all_tags | uniq | slice: 0, 15 %}
|
|
510
592
|
<div class="d-flex flex-wrap gap-2">
|
|
511
593
|
{% for tag in unique_tags %}
|
|
512
|
-
|
|
594
|
+
{% if _tags_linkable %}
|
|
595
|
+
<a href="{{ tags_base | relative_url }}#{{ tag | slugify }}"
|
|
513
596
|
class="badge bg-secondary text-decoration-none fs-6">
|
|
514
597
|
{{ tag }}
|
|
515
598
|
</a>
|
|
599
|
+
{% else %}
|
|
600
|
+
<span class="badge bg-secondary fs-6">{{ tag }}</span>
|
|
601
|
+
{% endif %}
|
|
516
602
|
{% endfor %}
|
|
517
603
|
</div>
|
|
518
|
-
|
|
604
|
+
{% if _tags_linkable %}
|
|
605
|
+
<a href="{{ tags_base | relative_url }}" class="btn btn-sm btn-outline-primary mt-3">
|
|
519
606
|
Browse All Tags <i class="bi bi-arrow-right ms-1"></i>
|
|
520
607
|
</a>
|
|
608
|
+
{% endif %}
|
|
521
609
|
</div>
|
|
522
610
|
</div>
|
|
523
611
|
</div>
|
|
@@ -529,6 +617,17 @@ source: "https://getbootstrap.com/docs/5.3/examples/blog/#"
|
|
|
529
617
|
<h5 class="card-title mb-3">
|
|
530
618
|
<i class="bi bi-archive me-2"></i>Archives
|
|
531
619
|
</h5>
|
|
620
|
+
{% comment %}
|
|
621
|
+
Month entries deep-link into an archives page only when one is
|
|
622
|
+
configured (news.archives_url) or a /archives/ page actually
|
|
623
|
+
exists — the theme ships no archives page, so an unconditional
|
|
624
|
+
link 404s on every consumer site.
|
|
625
|
+
{% endcomment %}
|
|
626
|
+
{% assign archives_url = news_cfg.archives_url %}
|
|
627
|
+
{% unless archives_url %}
|
|
628
|
+
{% assign _archives_page = site.html_pages | where: "url", "/archives/" | first %}
|
|
629
|
+
{% if _archives_page %}{% assign archives_url = "/archives/" %}{% endif %}
|
|
630
|
+
{% endunless %}
|
|
532
631
|
{% assign posts_by_year = all_posts | group_by_exp: "post", "post.date | date: '%Y'" %}
|
|
533
632
|
<div class="row">
|
|
534
633
|
{% for year in posts_by_year limit: 2 %}
|
|
@@ -538,11 +637,18 @@ source: "https://getbootstrap.com/docs/5.3/examples/blog/#"
|
|
|
538
637
|
<ul class="list-unstyled small">
|
|
539
638
|
{% for month in months limit: 6 %}
|
|
540
639
|
<li class="mb-1">
|
|
541
|
-
|
|
640
|
+
{% if archives_url %}
|
|
641
|
+
<a href="{{ archives_url | relative_url }}#{{ month.name | slugify }}-{{ year.name }}"
|
|
542
642
|
class="text-decoration-none">
|
|
543
643
|
{{ month.name }}
|
|
544
644
|
<span class="badge bg-secondary text-body">{{ month.items.size }}</span>
|
|
545
645
|
</a>
|
|
646
|
+
{% else %}
|
|
647
|
+
<span>
|
|
648
|
+
{{ month.name }}
|
|
649
|
+
<span class="badge bg-secondary text-body">{{ month.items.size }}</span>
|
|
650
|
+
</span>
|
|
651
|
+
{% endif %}
|
|
546
652
|
</li>
|
|
547
653
|
{% endfor %}
|
|
548
654
|
</ul>
|
|
@@ -558,15 +664,28 @@ source: "https://getbootstrap.com/docs/5.3/examples/blog/#"
|
|
|
558
664
|
<!-- ================================ -->
|
|
559
665
|
<!-- NEWSLETTER SIGNUP -->
|
|
560
666
|
<!-- ================================ -->
|
|
667
|
+
{% comment %}
|
|
668
|
+
Renders only when it can actually do something: a configured form
|
|
669
|
+
endpoint (news.newsletter.endpoint) posts for real; otherwise a
|
|
670
|
+
mailto: subscribe link on site.email. With neither — or with
|
|
671
|
+
news.newsletter.enabled: false — the band is omitted entirely
|
|
672
|
+
(the old markup shipped a form that submitted nowhere).
|
|
673
|
+
{% endcomment %}
|
|
674
|
+
{% assign _nl = news_cfg.newsletter %}
|
|
675
|
+
{% unless _nl.enabled == false %}
|
|
676
|
+
{% if _nl.endpoint or site.email %}
|
|
561
677
|
<section class="newsletter-section mb-5">
|
|
562
678
|
<div class="card border-0 bg-primary text-white">
|
|
563
679
|
<div class="card-body p-4 p-lg-5 text-center">
|
|
564
680
|
<i class="bi bi-envelope-paper fs-1 mb-3 d-block"></i>
|
|
565
|
-
<h3 class="mb-3">Stay Updated</h3>
|
|
566
|
-
<p class="mb-4 opacity-75">Subscribe to get the latest news and updates delivered to your inbox
|
|
567
|
-
|
|
681
|
+
<h3 class="mb-3">{{ _nl.heading | default: "Stay Updated" }}</h3>
|
|
682
|
+
<p class="mb-4 opacity-75">{{ _nl.lead | default: "Subscribe to get the latest news and updates delivered to your inbox." }}</p>
|
|
683
|
+
{% if _nl.endpoint %}
|
|
684
|
+
<form class="row g-2 justify-content-center" method="post" action="{{ _nl.endpoint }}">
|
|
568
685
|
<div class="col-md-4">
|
|
569
|
-
<
|
|
686
|
+
<label for="news-newsletter-email" class="visually-hidden">Email address</label>
|
|
687
|
+
<input type="email" id="news-newsletter-email" name="email" required
|
|
688
|
+
class="form-control form-control-lg" placeholder="Enter your email">
|
|
570
689
|
</div>
|
|
571
690
|
<div class="col-auto">
|
|
572
691
|
<button type="submit" class="btn btn-light btn-lg">
|
|
@@ -574,11 +693,18 @@ source: "https://getbootstrap.com/docs/5.3/examples/blog/#"
|
|
|
574
693
|
</button>
|
|
575
694
|
</div>
|
|
576
695
|
</form>
|
|
696
|
+
{% else %}
|
|
697
|
+
<a class="btn btn-light btn-lg" href="mailto:{{ site.email }}?subject=Subscribe%20to%20{{ site.title | uri_escape }}">
|
|
698
|
+
Subscribe <i class="bi bi-send ms-1"></i>
|
|
699
|
+
</a>
|
|
700
|
+
{% endif %}
|
|
577
701
|
</div>
|
|
578
702
|
</div>
|
|
579
703
|
</section>
|
|
704
|
+
{% endif %}
|
|
705
|
+
{% endunless %}
|
|
580
706
|
|
|
581
|
-
</
|
|
707
|
+
</div>
|
|
582
708
|
|
|
583
709
|
<style>
|
|
584
710
|
.hover-lift {
|
data/_layouts/root.html
CHANGED
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
{%- else -%}
|
|
47
47
|
{%- assign _bs_theme = 'dark' -%}
|
|
48
48
|
{%- endif -%}
|
|
49
|
-
<html lang="{{ site.locale | slice: 0,2 | default: 'en' }}" class="no-js" data-bs-theme="{{ _bs_theme }}" data-color-mode-default="{{ _cm }}" data-theme-skin="{{ site.theme_skin | default: '
|
|
49
|
+
<html lang="{{ page.lang | default: site.locale | slice: 0, 2 | default: 'en' }}" class="no-js" data-bs-theme="{{ _bs_theme }}" data-color-mode-default="{{ _cm }}" data-color-mode-lock="{{ site.color_mode_lock | default: false }}" data-theme-skin="{{ site.theme_skin | default: 'air' }}" data-zer0-bg="{{ site.theme_background.enabled | default: true }}">
|
|
50
50
|
<head>
|
|
51
51
|
<!-- =============================================== -->
|
|
52
52
|
<!-- HEAD SECTION: Meta tags, styles, and SEO setup -->
|
|
@@ -67,7 +67,11 @@
|
|
|
67
67
|
<!-- ============================================== -->
|
|
68
68
|
<!-- BODY SECTION: Site structure and content area -->
|
|
69
69
|
<!-- ============================================== -->
|
|
70
|
-
|
|
70
|
+
|
|
71
|
+
<!-- Consumer hook: first thing inside <body> (e.g. GTM noscript).
|
|
72
|
+
Empty stub shipped by the theme; shadow _includes/custom/body-start.html to use. -->
|
|
73
|
+
{% include custom/body-start.html %}
|
|
74
|
+
|
|
71
75
|
<!-- Inline SVG symbols for consistent iconography throughout the site -->
|
|
72
76
|
<!-- Page-invariant: cached once per build via jekyll-include-cache. -->
|
|
73
77
|
{% include_cached components/svg.html %}
|
|
@@ -82,11 +86,13 @@
|
|
|
82
86
|
Enable with `navigation.unified_mobile_drawer: true` in _config.yml. -->
|
|
83
87
|
{% include navigation/unified-drawer.html %}
|
|
84
88
|
|
|
85
|
-
<!-- Search modal (site-wide) —
|
|
86
|
-
|
|
89
|
+
<!-- Search modal (site-wide) — cached once per build PER LANGUAGE
|
|
90
|
+
(the lang param keys the include-cache entry; page.lang is nil
|
|
91
|
+
on English pages, so they all still share a single render). -->
|
|
92
|
+
{% include_cached components/search-modal.html lang=page.lang %}
|
|
87
93
|
|
|
88
|
-
<!-- Keyboard shortcuts help modal (triggered by `?`) —
|
|
89
|
-
{% include_cached components/shortcuts-modal.html %}
|
|
94
|
+
<!-- Keyboard shortcuts help modal (triggered by `?`) — cached per language. -->
|
|
95
|
+
{% include_cached components/shortcuts-modal.html lang=page.lang %}
|
|
90
96
|
|
|
91
97
|
<!-- Settings offcanvas (Appearance / Site / Developer tabs) -->
|
|
92
98
|
{% include components/info-section.html %}
|
|
@@ -98,10 +104,14 @@
|
|
|
98
104
|
<!-- MAIN CONTENT AREA -->
|
|
99
105
|
<!-- ======================== -->
|
|
100
106
|
<!-- Skip-link target: keep a single, consistent #main-content anchor site-wide.
|
|
101
|
-
|
|
102
|
-
|
|
107
|
+
This is the ONE <main> landmark per page — child layouts (default/section/
|
|
108
|
+
news) must use non-landmark wrappers (<div>) inside it (issue #299) — so
|
|
109
|
+
assistive tech, search engines, and AI content extractors can isolate the
|
|
103
110
|
primary content from the surrounding header/nav/footer chrome. -->
|
|
104
111
|
<main id="main-content">
|
|
112
|
+
<!-- Machine-translation disclosure — renders only on generated
|
|
113
|
+
translations (page.machine_translated); no-op elsewhere. -->
|
|
114
|
+
{%- include components/translation-notice.html -%}
|
|
105
115
|
{{ content }}
|
|
106
116
|
</main>
|
|
107
117
|
|
|
@@ -116,9 +126,15 @@
|
|
|
116
126
|
fresh: jekyll-include-cache stores in memory only (Jekyll 3.x has no
|
|
117
127
|
Jekyll::Cache) and clears on every build's :pre_render hook — it never
|
|
118
128
|
persists to .jekyll-cache. -->
|
|
119
|
-
{%- include_cached core/footer.html -%}
|
|
129
|
+
{%- include_cached core/footer.html lang=page.lang -%}
|
|
120
130
|
{%- include core/footer-fabs.html -%}
|
|
121
131
|
|
|
132
|
+
<!-- Consumer hook: after the theme footer, before the consent banner.
|
|
133
|
+
Empty stub shipped by the theme; shadow _includes/custom/footer.html to use.
|
|
134
|
+
Deliberately OUTSIDE the cached footer include so page-dependent
|
|
135
|
+
consumer markup renders per page. -->
|
|
136
|
+
{%- include custom/footer.html -%}
|
|
137
|
+
|
|
122
138
|
<!-- Privacy-compliant cookie consent banner — page-invariant. -->
|
|
123
139
|
{%- include_cached components/cookie-consent.html -%}
|
|
124
140
|
|
|
@@ -142,5 +158,9 @@
|
|
|
142
158
|
|
|
143
159
|
<!-- Analytics Integration -->
|
|
144
160
|
{%- include analytics/posthog.html -%}
|
|
161
|
+
|
|
162
|
+
<!-- Consumer hook: last thing before </body> (site scripts, widgets).
|
|
163
|
+
Empty stub shipped by the theme; shadow _includes/custom/body-end.html to use. -->
|
|
164
|
+
{%- include custom/body-end.html -%}
|
|
145
165
|
</body>
|
|
146
166
|
</html>
|
data/_layouts/section.html
CHANGED
|
@@ -46,6 +46,34 @@ layout: root
|
|
|
46
46
|
{% comment %} SECTION DISCOVERY LOGIC {% endcomment %}
|
|
47
47
|
{% comment %} ================================ {% endcomment %}
|
|
48
48
|
|
|
49
|
+
{% comment %}
|
|
50
|
+
Same `news:` config surface as _layouts/news.html:
|
|
51
|
+
- news.category_base (default "/news/") — base path for section pages
|
|
52
|
+
- news.index_url (default "/news/") — the news homepage this section
|
|
53
|
+
belongs to (breadcrumb + "Browse all news")
|
|
54
|
+
Section pills fall back to site.categories when _data/navigation/posts.yml
|
|
55
|
+
is absent, so the layout works on a data-less consumer site.
|
|
56
|
+
{% endcomment %}
|
|
57
|
+
{% assign news_cfg = site.news %}
|
|
58
|
+
{% assign news_base = news_cfg.category_base | default: "/news/" %}
|
|
59
|
+
{% assign news_index_url = news_cfg.index_url | default: "/news/" %}
|
|
60
|
+
{% capture _sec_names %}{% for item in site.data.navigation.posts %}{{ item.title }}{% unless forloop.last %}|{% endunless %}{% endfor %}{% endcapture %}
|
|
61
|
+
{% if _sec_names == "" %}{% capture _sec_names %}{% for cat in site.categories limit: 8 %}{{ cat[0] }}{% unless forloop.last %}|{% endunless %}{% endfor %}{% endcapture %}{% endif %}
|
|
62
|
+
{% assign news_sections = _sec_names | split: "|" %}
|
|
63
|
+
|
|
64
|
+
{% comment %}
|
|
65
|
+
Tags index link policy — identical to news.html: honor site.tags_page when
|
|
66
|
+
set, otherwise only link when a /tags/ page exists in the build.
|
|
67
|
+
{% endcomment %}
|
|
68
|
+
{% assign tags_base = site.tags_page | default: "/tags/" %}
|
|
69
|
+
{% assign _tags_linkable = false %}
|
|
70
|
+
{% if site.tags_page %}
|
|
71
|
+
{% assign _tags_linkable = true %}
|
|
72
|
+
{% else %}
|
|
73
|
+
{% assign _tags_index_page = site.html_pages | where: "url", tags_base | first %}
|
|
74
|
+
{% if _tags_index_page %}{% assign _tags_linkable = true %}{% endif %}
|
|
75
|
+
{% endif %}
|
|
76
|
+
|
|
49
77
|
{% assign path_parts = page.path | split: "/" %}
|
|
50
78
|
{% assign section_name = "" %}
|
|
51
79
|
{% for part in path_parts %}
|
|
@@ -99,7 +127,7 @@ layout: root
|
|
|
99
127
|
<nav aria-label="breadcrumb">
|
|
100
128
|
<ol class="breadcrumb mb-0">
|
|
101
129
|
<li class="breadcrumb-item">
|
|
102
|
-
<a href="{{
|
|
130
|
+
<a href="{{ news_index_url | relative_url }}" class="text-white-50 text-decoration-none">
|
|
103
131
|
<i class="bi bi-newspaper me-1"></i>News
|
|
104
132
|
</a>
|
|
105
133
|
</li>
|
|
@@ -123,15 +151,21 @@ layout: root
|
|
|
123
151
|
<div class="container-fluid bg-body-tertiary border-bottom py-2 mb-4">
|
|
124
152
|
<div class="container-fluid px-4">
|
|
125
153
|
<nav class="nav nav-pills flex-nowrap overflow-auto" style="scrollbar-width: none;">
|
|
126
|
-
{% for
|
|
154
|
+
{% for sec_name in news_sections %}
|
|
127
155
|
{% assign is_active = false %}
|
|
128
|
-
{% if
|
|
156
|
+
{% if sec_name == page.category or sec_name == page.title %}
|
|
129
157
|
{% assign is_active = true %}
|
|
130
158
|
{% endif %}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
{%
|
|
134
|
-
|
|
159
|
+
{% assign _sec_nav = site.data.navigation.posts | where: "title", sec_name | first %}
|
|
160
|
+
{% if _sec_nav %}
|
|
161
|
+
{% assign _sec_url = _sec_nav.url | replace: '/posts/', news_base %}
|
|
162
|
+
{% else %}
|
|
163
|
+
{% capture _sec_url %}{{ news_base }}{{ sec_name | slugify }}/{% endcapture %}
|
|
164
|
+
{% endif %}
|
|
165
|
+
<a class="nav-link text-nowrap {% if is_active %}active{% else %}text-body-secondary{% endif %}"
|
|
166
|
+
href="{{ _sec_url | relative_url }}">
|
|
167
|
+
{% if _sec_nav.icon %}<i class="bi {{ _sec_nav.icon }} me-1"></i>{% endif %}
|
|
168
|
+
{{ sec_name }}
|
|
135
169
|
</a>
|
|
136
170
|
{% endfor %}
|
|
137
171
|
</nav>
|
|
@@ -156,7 +190,9 @@ layout: root
|
|
|
156
190
|
<!-- ================================ -->
|
|
157
191
|
<!-- MAIN CONTENT AREA -->
|
|
158
192
|
<!-- ================================ -->
|
|
159
|
-
<main
|
|
193
|
+
<!-- Not a <main>: root.html already provides the single <main id="main-content">
|
|
194
|
+
landmark; a nested <main> here would duplicate it (issue #299). -->
|
|
195
|
+
<div class="col-lg-9 col-xl-10 section-layout-main">
|
|
160
196
|
|
|
161
197
|
<!-- Section Header -->
|
|
162
198
|
<header class="section-header mb-4">
|
|
@@ -253,9 +289,11 @@ layout: root
|
|
|
253
289
|
<h3 class="h4 mb-0">
|
|
254
290
|
<i class="bi bi-tag text-primary me-2"></i>{{ subcat | capitalize }}
|
|
255
291
|
</h3>
|
|
256
|
-
|
|
292
|
+
{% if _tags_linkable %}
|
|
293
|
+
<a href="{{ tags_base | relative_url }}#{{ subcat | slugify }}" class="btn btn-outline-secondary btn-sm">
|
|
257
294
|
View All <i class="bi bi-arrow-right ms-1"></i>
|
|
258
295
|
</a>
|
|
296
|
+
{% endif %}
|
|
259
297
|
</div>
|
|
260
298
|
|
|
261
299
|
<div class="row row-cols-1 row-cols-md-2 row-cols-xl-3 g-4">
|
|
@@ -494,7 +532,7 @@ layout: root
|
|
|
494
532
|
<div class="text-center py-5">
|
|
495
533
|
<i class="bi bi-inbox fs-1 text-muted"></i>
|
|
496
534
|
<p class="text-muted mt-3">No articles in this section yet.</p>
|
|
497
|
-
<a href="{{
|
|
535
|
+
<a href="{{ news_index_url | relative_url }}" class="btn btn-outline-primary">
|
|
498
536
|
Browse all news
|
|
499
537
|
</a>
|
|
500
538
|
</div>
|
|
@@ -508,15 +546,21 @@ layout: root
|
|
|
508
546
|
<i class="bi bi-compass me-2"></i>Explore Other Sections
|
|
509
547
|
</h3>
|
|
510
548
|
<div class="row row-cols-2 row-cols-md-3 row-cols-lg-6 g-3">
|
|
511
|
-
{% for
|
|
512
|
-
{% unless
|
|
513
|
-
{% assign other_section_posts = all_site_posts | where_exp: "post", "post.categories contains
|
|
549
|
+
{% for sec_name in news_sections %}
|
|
550
|
+
{% unless sec_name == page.category or sec_name == page.title %}
|
|
551
|
+
{% assign other_section_posts = all_site_posts | where_exp: "post", "post.categories contains sec_name" %}
|
|
552
|
+
{% assign _sec_nav = site.data.navigation.posts | where: "title", sec_name | first %}
|
|
553
|
+
{% if _sec_nav %}
|
|
554
|
+
{% assign _sec_url = _sec_nav.url | replace: '/posts/', news_base %}
|
|
555
|
+
{% else %}
|
|
556
|
+
{% capture _sec_url %}{{ news_base }}{{ sec_name | slugify }}/{% endcapture %}
|
|
557
|
+
{% endif %}
|
|
514
558
|
<div class="col">
|
|
515
|
-
<a href="{{
|
|
559
|
+
<a href="{{ _sec_url | relative_url }}"
|
|
516
560
|
class="card text-center h-100 text-decoration-none border-0 shadow-sm hover-lift">
|
|
517
561
|
<div class="card-body py-3">
|
|
518
|
-
<i class="bi {{
|
|
519
|
-
<h6 class="card-title mb-1 small">{{
|
|
562
|
+
<i class="bi {{ _sec_nav.icon | default: 'bi-folder' }} fs-4 text-primary mb-2 d-block"></i>
|
|
563
|
+
<h6 class="card-title mb-1 small">{{ sec_name }}</h6>
|
|
520
564
|
<small class="text-muted">{{ other_section_posts.size }} articles</small>
|
|
521
565
|
</div>
|
|
522
566
|
</a>
|
|
@@ -526,7 +570,7 @@ layout: root
|
|
|
526
570
|
</div>
|
|
527
571
|
</aside>
|
|
528
572
|
|
|
529
|
-
</
|
|
573
|
+
</div>
|
|
530
574
|
</div>
|
|
531
575
|
</div>
|
|
532
576
|
|