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
|
@@ -14,8 +14,14 @@
|
|
|
14
14
|
===================================================================
|
|
15
15
|
-->
|
|
16
16
|
|
|
17
|
-
{
|
|
18
|
-
|
|
17
|
+
{%- comment -%}
|
|
18
|
+
_data/theme_skins.yml is optional for consumers (remote_theme cannot ship
|
|
19
|
+
_data), so fall back to the compiled skin registry. Keep this list in sync
|
|
20
|
+
with the [data-theme-skin] blocks in _sass/theme/_skins.scss.
|
|
21
|
+
{%- endcomment -%}
|
|
22
|
+
{% assign _skin_fallback = "air|aqua|dirt|neon|mint|plum|sunrise" | split: "|" %}
|
|
23
|
+
{% assign current_skin = site.theme_skin | default: site.data.theme_skins.default | default: "air" %}
|
|
24
|
+
{% assign skins = site.data.theme_skins.order | default: _skin_fallback %}
|
|
19
25
|
|
|
20
26
|
<p class="text-body-secondary mb-3">
|
|
21
27
|
Current skin: <strong>{{ current_skin }}</strong>. Click a card to preview (page-level only — does not persist).
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{% comment %} Feature: ZER0-078 {% endcomment %}
|
|
2
|
+
{%- comment -%}
|
|
3
|
+
Component: translation-notice
|
|
4
|
+
Path: _includes/components/translation-notice.html
|
|
5
|
+
Purpose: Disclosure banner shown at the top of machine-generated
|
|
6
|
+
translations: tells the reader the page was translated
|
|
7
|
+
automatically and links back to the English original.
|
|
8
|
+
Params: none — renders only when page.machine_translated is true
|
|
9
|
+
(set by scripts/translate.rb on generated pages).
|
|
10
|
+
Depends on: core/i18n.html, Bootstrap 5 alert, Bootstrap Icons
|
|
11
|
+
Notes: No-op on every human-authored page, so it is safe to include
|
|
12
|
+
unconditionally from the root layout.
|
|
13
|
+
{%- endcomment -%}
|
|
14
|
+
{%- if page.machine_translated -%}
|
|
15
|
+
{%- include core/i18n.html -%}
|
|
16
|
+
<div class="container-xl mt-3 translation-notice">
|
|
17
|
+
<div class="alert alert-info d-flex align-items-start gap-2 mb-0" role="note">
|
|
18
|
+
<i class="bi bi-translate flex-shrink-0 mt-1" aria-hidden="true"></i>
|
|
19
|
+
<span>
|
|
20
|
+
{{ ui.machine_translation_notice | default: "This page was translated automatically from the English original." }}
|
|
21
|
+
{%- if page.translation_source_url %}
|
|
22
|
+
<a class="alert-link" href="{{ page.translation_source_url | relative_url }}" hreflang="{{ site.translation.source_lang | default: 'en' }}">{{ ui.machine_translation_view_source | default: "View the original" }}</a>
|
|
23
|
+
{%- endif %}
|
|
24
|
+
</span>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
{%- endif -%}
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
- Responsive flex layout: full-width content block, frosted metadata footer with reading time and action buttons
|
|
13
13
|
- Balanced padding and typography hierarchy via bd-intro-* classes
|
|
14
14
|
- Metadata row: author, dates, category, difficulty, tags, view source
|
|
15
|
-
-->
|
|
15
|
+
-->
|
|
16
|
+
{%- include core/i18n.html -%}
|
|
16
17
|
{% if page.collection %}
|
|
17
18
|
{% capture page_dir %}{{ site.collections_dir }}{% endcapture %}
|
|
18
19
|
{% assign testvar = "collection exists" %}
|
|
@@ -133,7 +134,7 @@
|
|
|
133
134
|
{% endif %}
|
|
134
135
|
|
|
135
136
|
<div class="bd-intro" style="
|
|
136
|
-
background: linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url('{{ preview_path
|
|
137
|
+
background: linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url('{{ preview_path }}') no-repeat center center / cover;">
|
|
137
138
|
|
|
138
139
|
<div class="bd-intro-inner">
|
|
139
140
|
<div class="bd-intro-content">
|
|
@@ -161,7 +162,7 @@
|
|
|
161
162
|
<li class="bd-intro-meta-item">
|
|
162
163
|
<i class="bi bi-calendar3 bd-intro-meta-icon" aria-hidden="true"></i>
|
|
163
164
|
<time datetime="{{ intro_published | date_to_xmlschema }}" itemprop="datePublished">
|
|
164
|
-
Published {{ intro_published | date: "%b %d, %Y" }}
|
|
165
|
+
{{ ui.published_label | default: "Published" }} {{ intro_published | date: "%b %d, %Y" }}
|
|
165
166
|
</time>
|
|
166
167
|
</li>
|
|
167
168
|
{% endif %}
|
|
@@ -170,7 +171,7 @@
|
|
|
170
171
|
<li class="bd-intro-meta-item">
|
|
171
172
|
<i class="bi bi-pencil bd-intro-meta-icon" aria-hidden="true"></i>
|
|
172
173
|
<time datetime="{{ intro_updated | date_to_xmlschema }}" itemprop="dateModified">
|
|
173
|
-
Updated {{ intro_updated | date: "%b %d, %Y" }}
|
|
174
|
+
{{ ui.updated_label | default: "Updated" }} {{ intro_updated | date: "%b %d, %Y" }}
|
|
174
175
|
</time>
|
|
175
176
|
</li>
|
|
176
177
|
{% endif %}
|
|
@@ -212,7 +213,7 @@
|
|
|
212
213
|
{% if intro_source_url %}
|
|
213
214
|
<li class="bd-intro-meta-item bd-intro-meta-item--source d-none d-md-inline-flex">
|
|
214
215
|
<i class="bi bi-file-earmark-code bd-intro-meta-icon" aria-hidden="true"></i>
|
|
215
|
-
<a class="bd-intro-meta-link" href="{{ intro_source_url }}" target="_blank" rel="noopener noreferrer">View source</a>
|
|
216
|
+
<a class="bd-intro-meta-link" href="{{ intro_source_url }}" target="_blank" rel="noopener noreferrer">{{ ui.view_source | default: "View source" }}</a>
|
|
216
217
|
</li>
|
|
217
218
|
{% endif %}
|
|
218
219
|
</ul>
|
|
@@ -221,9 +222,9 @@
|
|
|
221
222
|
<p class="bd-intro-meta">
|
|
222
223
|
<i class="bi bi-clock me-1" aria-hidden="true"></i>
|
|
223
224
|
{% if page.estimated_reading_time %}
|
|
224
|
-
Estimated reading time: {{ page.estimated_reading_time }}
|
|
225
|
+
{{ ui.reading_time_label | default: "Estimated reading time:" }} {{ page.estimated_reading_time }}
|
|
225
226
|
{% else %}
|
|
226
|
-
Estimated reading time: {{ intro_read_mins }} minute{% if intro_read_mins != 1 %}s{% endif %}
|
|
227
|
+
{{ ui.reading_time_label | default: "Estimated reading time:" }} {{ intro_read_mins }} {{ ui.reading_time_minute | default: "minute" }}{% if intro_read_mins != 1 %}s{% endif %}
|
|
227
228
|
{% endif %}
|
|
228
229
|
</p>
|
|
229
230
|
|
|
@@ -233,12 +234,12 @@
|
|
|
233
234
|
<div class="dropdown">
|
|
234
235
|
<button class="btn btn-sm btn-info dropdown-toggle" type="button" id="shareDropdownBottom" data-bs-toggle="dropdown" aria-expanded="false">
|
|
235
236
|
<i class="bi bi-share" aria-hidden="true"></i>
|
|
236
|
-
<span class="d-none d-sm-inline ms-1">Share</span>
|
|
237
|
+
<span class="d-none d-sm-inline ms-1">{{ ui.share_label | default: "Share" }}</span>
|
|
237
238
|
</button>
|
|
238
239
|
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="shareDropdownBottom">
|
|
239
240
|
<li>
|
|
240
241
|
<a class="dropdown-item" href="https://reddit.com/submit?url={{ page_absolute_url | uri_escape }}&title={{ page.title | uri_escape }}" target="_blank" rel="noopener noreferrer">
|
|
241
|
-
<i class="bi bi-reddit me-2"></i>Share on Reddit
|
|
242
|
+
<i class="bi bi-reddit me-2"></i>{{ ui.share_on_label | default: "Share on" }} Reddit
|
|
242
243
|
</a>
|
|
243
244
|
</li>
|
|
244
245
|
<li>
|
|
@@ -250,12 +251,12 @@
|
|
|
250
251
|
data-share-url="{{ page_absolute_url | escape }}"
|
|
251
252
|
data-share-title="{{ linkedin_share_title | escape }}"
|
|
252
253
|
data-share-description="{{ linkedin_share_description | escape }}">
|
|
253
|
-
<i class="bi bi-linkedin me-2"></i>Share on LinkedIn
|
|
254
|
+
<i class="bi bi-linkedin me-2"></i>{{ ui.share_on_label | default: "Share on" }} LinkedIn
|
|
254
255
|
</a>
|
|
255
256
|
</li>
|
|
256
257
|
<li>
|
|
257
|
-
<a class="dropdown-item" href="https://x.com/intent/tweet?url={{ site.url | append: page.url | url_encode }}&text={{ page.title | url_encode }}" target="_blank" aria-label="Share on X">
|
|
258
|
-
<i class="bi bi-twitter-x me-2"></i>Share on X
|
|
258
|
+
<a class="dropdown-item" href="https://x.com/intent/tweet?url={{ site.url | append: page.url | url_encode }}&text={{ page.title | url_encode }}" target="_blank" aria-label="{{ ui.share_on_label | default: 'Share on' }} X">
|
|
259
|
+
<i class="bi bi-twitter-x me-2"></i>{{ ui.share_on_label | default: "Share on" }} X
|
|
259
260
|
</a>
|
|
260
261
|
</li>
|
|
261
262
|
<li><hr class="dropdown-divider"></li>
|
|
@@ -266,7 +267,7 @@
|
|
|
266
267
|
the blocking alert() the legacy markup used.
|
|
267
268
|
{%- endcomment -%}
|
|
268
269
|
<button class="dropdown-item" type="button" data-copy="{{ site.url | append: page.url }}">
|
|
269
|
-
<i class="bi bi-clipboard me-2" aria-hidden="true"></i>Copy Link
|
|
270
|
+
<i class="bi bi-clipboard me-2" aria-hidden="true"></i>{{ ui.copy_link_label | default: "Copy Link" }}
|
|
270
271
|
</button>
|
|
271
272
|
</li>
|
|
272
273
|
</ul>
|
|
@@ -284,7 +285,7 @@
|
|
|
284
285
|
rel="noopener noreferrer"
|
|
285
286
|
title="Report a problem or suggest an improvement for this page">
|
|
286
287
|
<i class="bi bi-megaphone" aria-hidden="true"></i>
|
|
287
|
-
<span class="d-none d-sm-inline ms-1">Improve</span>
|
|
288
|
+
<span class="d-none d-sm-inline ms-1">{{ ui.improve_label | default: "Improve" }}</span>
|
|
288
289
|
</a>
|
|
289
290
|
{% endif %}
|
|
290
291
|
|
|
@@ -294,7 +295,7 @@
|
|
|
294
295
|
target="_blank"
|
|
295
296
|
rel="noopener noreferrer">
|
|
296
297
|
<i class="bi bi-github" aria-hidden="true"></i>
|
|
297
|
-
<span class="d-none d-sm-inline ms-1">Edit on GitHub</span>
|
|
298
|
+
<span class="d-none d-sm-inline ms-1">{{ ui.edit_on_github | default: "Edit on GitHub" }}</span>
|
|
298
299
|
</a>
|
|
299
300
|
</div>
|
|
300
301
|
</div>
|
data/_includes/content/seo.html
CHANGED
|
@@ -37,17 +37,23 @@
|
|
|
37
37
|
{%- assign assets_prefix = site.preview_images.assets_prefix | default: '/assets' -%}
|
|
38
38
|
{%- assign auto_prefix = site.preview_images.auto_prefix | default: true -%}
|
|
39
39
|
|
|
40
|
+
{%- comment -%}
|
|
41
|
+
Guard with truthiness AND blank: in Liquid 4 `nil != blank` is TRUE, so a
|
|
42
|
+
bare `!= blank` check on an unset variable takes the branch and assigns a
|
|
43
|
+
nil seo_image — og:image was silently dropped on every page without a
|
|
44
|
+
page.header image, defeating the site.og_image fallback entirely.
|
|
45
|
+
{%- endcomment -%}
|
|
40
46
|
{%- if page_teaser_image contains '://' -%}
|
|
41
47
|
{%- comment -%} External URL - use as-is {%- endcomment -%}
|
|
42
|
-
{%- elsif auto_prefix and page_teaser_image != blank -%}
|
|
48
|
+
{%- elsif auto_prefix and page_teaser_image and page_teaser_image != blank -%}
|
|
43
49
|
{%- unless page_teaser_image contains assets_prefix -%}
|
|
44
50
|
{%- assign page_teaser_image = assets_prefix | append: page_teaser_image -%}
|
|
45
51
|
{%- endunless -%}
|
|
46
52
|
{%- endif -%}
|
|
47
53
|
|
|
48
|
-
{%- if page_large_image != blank -%}
|
|
54
|
+
{%- if page_large_image and page_large_image != blank -%}
|
|
49
55
|
{%- assign seo_image = page_large_image | absolute_url | escape -%}
|
|
50
|
-
{%- elsif page_teaser_image != blank -%}
|
|
56
|
+
{%- elsif page_teaser_image and page_teaser_image != blank -%}
|
|
51
57
|
{%- assign seo_image = page_teaser_image | absolute_url | escape -%}
|
|
52
58
|
{%- endif -%}
|
|
53
59
|
|
|
@@ -13,9 +13,12 @@
|
|
|
13
13
|
when Bootstrap's [data-bs-theme] selectors are first evaluated.
|
|
14
14
|
|
|
15
15
|
Priority order (highest → lowest):
|
|
16
|
-
1.
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
1. site.color_mode_lock — when true, config wins unconditionally:
|
|
17
|
+
visitor preference and OS scheme are ignored
|
|
18
|
+
(data-color-mode-lock attribute, set by root.html)
|
|
19
|
+
2. localStorage["theme"] — user's explicit override via Appearance panel
|
|
20
|
+
3. data-color-mode-default — site.color_mode_default config value
|
|
21
|
+
4. "auto" fallback — resolves via prefers-color-scheme
|
|
19
22
|
{%- endcomment -%}
|
|
20
23
|
<script>
|
|
21
24
|
(function () {
|
|
@@ -23,8 +26,14 @@
|
|
|
23
26
|
try {
|
|
24
27
|
var root = document.documentElement;
|
|
25
28
|
var configDefault = root.getAttribute('data-color-mode-default') || 'auto';
|
|
26
|
-
var
|
|
29
|
+
var locked = root.getAttribute('data-color-mode-lock') === 'true';
|
|
30
|
+
var stored = locked ? null : window.localStorage.getItem('theme');
|
|
27
31
|
var mode = stored || configDefault;
|
|
32
|
+
if (mode === 'auto' && locked) {
|
|
33
|
+
{%- comment -%} A locked "auto" is contradictory; treat it as dark so
|
|
34
|
+
the brand-pinned mode never flips with the OS. {%- endcomment %}
|
|
35
|
+
mode = 'dark';
|
|
36
|
+
}
|
|
28
37
|
if (mode === 'auto') {
|
|
29
38
|
root.setAttribute('data-bs-theme',
|
|
30
39
|
window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{% comment %} Feature: ZER0-079 {% endcomment %}
|
|
2
|
+
{%- comment -%}
|
|
3
|
+
Component: favicon
|
|
4
|
+
Path: _includes/core/favicon.html
|
|
5
|
+
Purpose: Emit the browser-identity tags (favicon, SVG icon, Apple touch
|
|
6
|
+
icon, web manifest, theme-color) for every page. Before this
|
|
7
|
+
include existed, sites relied on the browser's implicit
|
|
8
|
+
/favicon.ico probe — which silently 404s on sites without the
|
|
9
|
+
file and never works for baseurl (project-page) deployments.
|
|
10
|
+
Params: none — reads the optional `favicon:` block in _config.yml:
|
|
11
|
+
|
|
12
|
+
favicon:
|
|
13
|
+
ico : /favicon.ico # legacy .ico (default)
|
|
14
|
+
svg : /assets/images/favicon.svg # scalable icon (optional)
|
|
15
|
+
png : /assets/images/favicon-32.png # PNG icon (optional)
|
|
16
|
+
png_size : 32x32 # sizes attr for png (optional)
|
|
17
|
+
apple_touch : /assets/images/apple-touch.png # iOS home-screen icon (optional)
|
|
18
|
+
manifest : /site.webmanifest # PWA manifest (optional)
|
|
19
|
+
theme_color : "#0d1117" # browser chrome color (optional)
|
|
20
|
+
|
|
21
|
+
With NO config at all this still emits the explicit
|
|
22
|
+
/favicon.ico link (every consumer site carries one at root),
|
|
23
|
+
and theme_color falls back to site.theme_color.main so the
|
|
24
|
+
mobile address bar matches the site's design tokens.
|
|
25
|
+
Notes: Page-invariant — safe to render via include_cached. All paths
|
|
26
|
+
run through relative_url so baseurl deployments resolve.
|
|
27
|
+
{%- endcomment -%}
|
|
28
|
+
{%- assign _fav = site.favicon -%}
|
|
29
|
+
{%- assign _fav_ico = _fav.ico | default: '/favicon.ico' -%}
|
|
30
|
+
<link rel="icon" href="{{ _fav_ico | relative_url }}" sizes="32x32">
|
|
31
|
+
{%- if _fav.svg %}
|
|
32
|
+
<link rel="icon" type="image/svg+xml" href="{{ _fav.svg | relative_url }}">
|
|
33
|
+
{%- endif %}
|
|
34
|
+
{%- if _fav.png %}
|
|
35
|
+
<link rel="icon" type="image/png" href="{{ _fav.png | relative_url }}"{% if _fav.png_size %} sizes="{{ _fav.png_size }}"{% endif %}>
|
|
36
|
+
{%- endif %}
|
|
37
|
+
{%- if _fav.apple_touch %}
|
|
38
|
+
<link rel="apple-touch-icon" href="{{ _fav.apple_touch | relative_url }}">
|
|
39
|
+
{%- endif %}
|
|
40
|
+
{%- if _fav.manifest %}
|
|
41
|
+
<link rel="manifest" href="{{ _fav.manifest | relative_url }}">
|
|
42
|
+
{%- endif %}
|
|
43
|
+
{%- assign _fav_theme_color = _fav.theme_color | default: site.theme_color.main -%}
|
|
44
|
+
{%- if _fav_theme_color %}
|
|
45
|
+
<meta name="theme-color" content="{{ _fav_theme_color }}">
|
|
46
|
+
{%- endif %}
|
data/_includes/core/footer.html
CHANGED
|
@@ -22,15 +22,20 @@
|
|
|
22
22
|
- site.powered_by: Framework and service credits
|
|
23
23
|
- site.links: Social media and affiliate links
|
|
24
24
|
- site.default_icon: Icon framework (Bootstrap Icons)
|
|
25
|
-
-
|
|
25
|
+
- core/i18n.html: UI strings (per-page language via the `lang` param)
|
|
26
26
|
- Bootstrap 5 classes provided by the theme
|
|
27
|
-
|
|
27
|
+
|
|
28
|
+
Params:
|
|
29
|
+
- lang (optional): page language. Root layout renders this include via
|
|
30
|
+
include_cached, so it MUST pass lang=page.lang for per-language caching.
|
|
31
|
+
|
|
28
32
|
Notes:
|
|
29
33
|
- Guard optional fields (address/phone/email) to avoid empty output
|
|
30
34
|
- Use relative_url for internal links to respect baseurl
|
|
31
35
|
===================================================================
|
|
32
36
|
-->
|
|
33
37
|
|
|
38
|
+
{%- include core/i18n.html lang=include.lang -%}
|
|
34
39
|
<footer class="bd-footer border-top" role="contentinfo">
|
|
35
40
|
{% assign footer_page_urls = site.html_pages | map: "url" | join: "|" | prepend: "|" | append: "|" %}
|
|
36
41
|
<!-- Powered by Row -->
|
|
@@ -40,7 +45,7 @@
|
|
|
40
45
|
</p>
|
|
41
46
|
<div class="footer-powered-by-row">
|
|
42
47
|
<span class="footer-powered-by-label" id="footer-powered-by-label">
|
|
43
|
-
{{
|
|
48
|
+
{{ ui.powered_by | default: "Powered by" }}:
|
|
44
49
|
</span>
|
|
45
50
|
<ul class="footer-powered-by-links list-unstyled mb-0"
|
|
46
51
|
aria-labelledby="footer-powered-by-label">
|
|
@@ -197,7 +202,7 @@
|
|
|
197
202
|
<li class="list-inline-item">
|
|
198
203
|
<a class="text-light text-decoration-none" href="{{ '/feed.xml' | relative_url }}">
|
|
199
204
|
<i class="{{ site.default_icon }} bi-rss" aria-hidden="true"></i>
|
|
200
|
-
<span class="d-none d-md-inline">{{
|
|
205
|
+
<span class="d-none d-md-inline">{{ ui.feed_label | default: "Feed" }}</span>
|
|
201
206
|
</a>
|
|
202
207
|
</li>
|
|
203
208
|
</ul>
|
data/_includes/core/head.html
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
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
23
|
|
|
23
24
|
Performance Notes:
|
|
24
25
|
- Scripts loaded in head for immediate availability
|
|
@@ -86,6 +87,10 @@ window.MathJax = {
|
|
|
86
87
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
87
88
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
|
88
89
|
|
|
90
|
+
<!-- Favicon + browser identity (config-driven via `favicon:`; explicit
|
|
91
|
+
/favicon.ico fallback so baseurl deployments resolve) — page-invariant, cached. -->
|
|
92
|
+
{% include_cached core/favicon.html %}
|
|
93
|
+
|
|
89
94
|
<!-- ================================ -->
|
|
90
95
|
<!-- SEO SUPPLEMENT -->
|
|
91
96
|
<!-- ================================ -->
|
|
@@ -93,6 +98,10 @@ window.MathJax = {
|
|
|
93
98
|
Outputs custom og:image with assets_prefix normalisation and non-Google site verification. -->
|
|
94
99
|
{% include content/seo.html %}
|
|
95
100
|
|
|
101
|
+
<!-- hreflang alternates for machine-generated translations (no-op when the
|
|
102
|
+
page has none) — see _data/i18n/manifest.yml + scripts/translate.rb -->
|
|
103
|
+
{% include core/hreflang.html %}
|
|
104
|
+
|
|
96
105
|
<!-- ========================== -->
|
|
97
106
|
<!-- STRUCTURED DATA MARKUP -->
|
|
98
107
|
<!-- ========================== -->
|
|
@@ -127,6 +136,10 @@ window.MathJax = {
|
|
|
127
136
|
<!-- Only load in production AND when an ID is configured; the include also guards against dev hostnames -->
|
|
128
137
|
{% if jekyll.environment == "production" and site.google_analytics %}{% include analytics/google-analytics.html %}{% endif %}
|
|
129
138
|
|
|
139
|
+
<!-- PostHog analytics - Configured via the `posthog:` block in _config.yml -->
|
|
140
|
+
<!-- Only load in production AND when explicitly enabled; the include itself also respects Do Not Track -->
|
|
141
|
+
{% if jekyll.environment == "production" and site.posthog.enabled %}{% include analytics/posthog.html %}{% endif %}
|
|
142
|
+
|
|
130
143
|
|
|
131
144
|
<!-- ================================ -->
|
|
132
145
|
<!-- CSS FRAMEWORKS AND LIBRARIES -->
|
|
@@ -177,6 +190,11 @@ window.MathJax = {
|
|
|
177
190
|
<link rel="stylesheet" href="{{ '/assets/css/stats.css' | relative_url }}">
|
|
178
191
|
{% endif %}
|
|
179
192
|
|
|
193
|
+
<!-- Consumer hook: end of <head>. Inject JSON-LD, font preloads, verification
|
|
194
|
+
tags, or extra meta by shadowing _includes/custom/head.html in your site —
|
|
195
|
+
no fork of this file needed. The theme ships the stub empty. -->
|
|
196
|
+
{% include custom/head.html %}
|
|
197
|
+
|
|
180
198
|
<!-- ========================== -->
|
|
181
199
|
<!-- EXPERIMENTAL FRAMEWORKS -->
|
|
182
200
|
<!-- ========================== -->
|
data/_includes/core/header.html
CHANGED
|
@@ -41,9 +41,11 @@
|
|
|
41
41
|
<!-- ================================ -->
|
|
42
42
|
<!-- SKIP TO CONTENT LINK -->
|
|
43
43
|
<!-- ================================ -->
|
|
44
|
+
<!-- UI strings resolve per page language (core/i18n.html sets `ui`) -->
|
|
45
|
+
{%- include core/i18n.html -%}
|
|
44
46
|
<!-- Accessibility: allows keyboard users to skip navigation -->
|
|
45
47
|
<a href="#main-content" class="visually-hidden-focusable position-absolute top-0 start-0 z-3 m-3 btn btn-primary">
|
|
46
|
-
Skip to main content
|
|
48
|
+
{{ ui.skip_main_content | default: "Skip to main content" }}
|
|
47
49
|
</a>
|
|
48
50
|
|
|
49
51
|
<header id="navbar" class="container-fluid text-center fixed-top" role="banner">
|
|
@@ -149,17 +151,20 @@
|
|
|
149
151
|
<!-- UTILITY CONTROLS -->
|
|
150
152
|
<!-- ========================== -->
|
|
151
153
|
<div class="navbar-utility-controls d-flex align-items-center flex-shrink-0" role="group" aria-label="Utility controls">
|
|
154
|
+
<!-- Language Toggle (renders only when translation.languages is set) -->
|
|
155
|
+
{% include components/language-toggle.html %}
|
|
156
|
+
|
|
152
157
|
<!-- Search Modal Toggle -->
|
|
153
158
|
<button
|
|
154
159
|
class="btn nav-search-button"
|
|
155
160
|
type="button"
|
|
156
161
|
data-search-toggle
|
|
157
|
-
aria-label="Open search"
|
|
162
|
+
aria-label="{{ ui.shortcuts_search | default: 'Open search' }}"
|
|
158
163
|
aria-haspopup="dialog"
|
|
159
|
-
title="Search
|
|
164
|
+
title="{{ ui.search_button_label | default: 'Search' }}"
|
|
160
165
|
>
|
|
161
166
|
<i class="{{site.default_icon}} bi-search" aria-hidden="true"></i>
|
|
162
|
-
<span class="nav-link-text d-none d-xl-inline ms-1">Search</span>
|
|
167
|
+
<span class="nav-link-text d-none d-xl-inline ms-1">{{ ui.search_button_label | default: "Search" }}</span>
|
|
163
168
|
</button>
|
|
164
169
|
|
|
165
170
|
<!-- Settings Offcanvas Toggle -->
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{% comment %} Feature: ZER0-078 {% endcomment %}
|
|
2
|
+
{%- comment -%}
|
|
3
|
+
Component: hreflang
|
|
4
|
+
Path: _includes/core/hreflang.html
|
|
5
|
+
Purpose: Emit <link rel="alternate" hreflang> tags for pages that have
|
|
6
|
+
machine-generated translations, so search engines associate
|
|
7
|
+
the English original with its /fr/ (etc.) alternates.
|
|
8
|
+
Params: none — reads page.lang / page.translation_source_url and the
|
|
9
|
+
generated _data/i18n/manifest.yml (scripts/translate.rb).
|
|
10
|
+
Depends on: site.translation.* config, site.data.i18n.manifest
|
|
11
|
+
Notes: Renders nothing when translation is disabled, the manifest is
|
|
12
|
+
absent, or the current page has no translations. x-default
|
|
13
|
+
always points at the English original.
|
|
14
|
+
{%- endcomment -%}
|
|
15
|
+
{%- if site.translation.enabled -%}
|
|
16
|
+
{%- assign _hl_source = site.translation.source_lang | default: 'en' -%}
|
|
17
|
+
{%- if page.lang and page.lang != _hl_source -%}
|
|
18
|
+
{%- assign _hl_src_url = page.translation_source_url -%}
|
|
19
|
+
{%- else -%}
|
|
20
|
+
{%- assign _hl_src_url = page.url -%}
|
|
21
|
+
{%- endif -%}
|
|
22
|
+
{%- assign _hl_entry = site.data.i18n.manifest.pages[_hl_src_url] -%}
|
|
23
|
+
{%- if _hl_src_url and _hl_entry %}
|
|
24
|
+
<link rel="alternate" hreflang="{{ _hl_source }}" href="{{ _hl_src_url | absolute_url }}">
|
|
25
|
+
{%- for _hl_lang in site.translation.languages -%}
|
|
26
|
+
{%- assign _hl_t = _hl_entry[_hl_lang] -%}
|
|
27
|
+
{%- if _hl_t %}
|
|
28
|
+
<link rel="alternate" hreflang="{{ _hl_lang }}" href="{{ _hl_t.url | absolute_url }}">
|
|
29
|
+
{%- endif -%}
|
|
30
|
+
{%- endfor %}
|
|
31
|
+
<link rel="alternate" hreflang="x-default" href="{{ _hl_src_url | absolute_url }}">
|
|
32
|
+
{%- endif -%}
|
|
33
|
+
{%- endif -%}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{% comment %} Feature: ZER0-078 {% endcomment %}
|
|
2
|
+
{%- comment -%}
|
|
3
|
+
Component: i18n
|
|
4
|
+
Path: _includes/core/i18n.html
|
|
5
|
+
Purpose: Resolve the active UI-string set into the `ui` variable,
|
|
6
|
+
per-page-language aware. English strings come from
|
|
7
|
+
_data/ui-text.yml (the human-maintained source of truth);
|
|
8
|
+
alternate languages come from _data/i18n/<lang>.yml, which is
|
|
9
|
+
GENERATED by scripts/translate.rb — never hand-edited.
|
|
10
|
+
Params: lang (string, optional) — overrides page.lang. REQUIRED when
|
|
11
|
+
the caller is rendered via {% include_cached %} (pass
|
|
12
|
+
lang=page.lang there) so each language gets its own cache
|
|
13
|
+
entry instead of sharing one page-invariant render.
|
|
14
|
+
Depends on: _data/ui-text.yml, _data/i18n/<lang>.yml (generated),
|
|
15
|
+
site.translation.* config
|
|
16
|
+
Notes: Liquid assigns made inside an include are visible to the
|
|
17
|
+
caller, so after {% include core/i18n.html %} simply use
|
|
18
|
+
{{ ui.some_key | default: "English fallback" }}. Missing
|
|
19
|
+
languages or keys degrade to English/literal defaults.
|
|
20
|
+
{%- endcomment -%}
|
|
21
|
+
{%- comment -%}
|
|
22
|
+
Resolve using the FULL configured language code (e.g. "fr", "pt-BR"), not a
|
|
23
|
+
2-char slice: scripts/translate.rb writes _data/i18n/<code>.yml and sets
|
|
24
|
+
page.lang to that exact code, so a regional code like "pt-BR" must look up
|
|
25
|
+
site.data.i18n["pt-BR"] — slicing to "pt" would silently miss the file and
|
|
26
|
+
fall back to English.
|
|
27
|
+
{%- endcomment -%}
|
|
28
|
+
{%- assign _i18n_lang = include.lang | default: page.lang | default: site.translation.source_lang | default: site.locale | default: 'en' -%}
|
|
29
|
+
{%- assign _i18n_source = site.translation.source_lang | default: 'en' -%}
|
|
30
|
+
{%- assign ui = site.data.ui-text[site.locale] | default: site.data.ui-text.en -%}
|
|
31
|
+
{%- unless _i18n_lang == _i18n_source -%}
|
|
32
|
+
{%- assign _i18n_generated = site.data.i18n[_i18n_lang] -%}
|
|
33
|
+
{%- if _i18n_generated -%}
|
|
34
|
+
{%- assign ui = _i18n_generated -%}
|
|
35
|
+
{%- endif -%}
|
|
36
|
+
{%- endunless -%}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
===================================================================
|
|
3
|
+
CUSTOM HOOK — body-end
|
|
4
|
+
===================================================================
|
|
5
|
+
|
|
6
|
+
Path: _includes/custom/body-end.html
|
|
7
|
+
Purpose: Consumer extension point rendered as the LAST thing before
|
|
8
|
+
</body> — after the theme's JS bundle and analytics. Use it
|
|
9
|
+
for site-specific scripts, third-party widgets, or anything
|
|
10
|
+
that must load after the theme's JavaScript.
|
|
11
|
+
|
|
12
|
+
The theme ships this file EMPTY. Create _includes/custom/body-end.html
|
|
13
|
+
in YOUR site to shadow this stub. No theme layout needs forking.
|
|
14
|
+
|
|
15
|
+
Companion hooks: custom/head.html, custom/body-start.html,
|
|
16
|
+
custom/footer.html. See _includes/README.md § Custom hooks.
|
|
17
|
+
===================================================================
|
|
18
|
+
{%- endcomment -%}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
===================================================================
|
|
3
|
+
CUSTOM HOOK — body-start
|
|
4
|
+
===================================================================
|
|
5
|
+
|
|
6
|
+
Path: _includes/custom/body-start.html
|
|
7
|
+
Purpose: Consumer extension point rendered immediately AFTER the
|
|
8
|
+
opening <body> tag — the required position for snippets like
|
|
9
|
+
Google Tag Manager's <noscript> iframe.
|
|
10
|
+
|
|
11
|
+
The theme ships this file EMPTY. Create _includes/custom/body-start.html
|
|
12
|
+
in YOUR site to shadow this stub. No theme layout needs forking.
|
|
13
|
+
|
|
14
|
+
Companion hooks: custom/head.html, custom/footer.html,
|
|
15
|
+
custom/body-end.html. See _includes/README.md § Custom hooks.
|
|
16
|
+
===================================================================
|
|
17
|
+
{%- endcomment -%}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
===================================================================
|
|
3
|
+
CUSTOM HOOK — footer
|
|
4
|
+
===================================================================
|
|
5
|
+
|
|
6
|
+
Path: _includes/custom/footer.html
|
|
7
|
+
Purpose: Consumer extension point rendered directly AFTER the theme
|
|
8
|
+
footer (and its FAB tail), before the cookie-consent banner.
|
|
9
|
+
Use it for an extra footer strip, badges, or legal links the
|
|
10
|
+
theme footer doesn't model.
|
|
11
|
+
|
|
12
|
+
The theme ships this file EMPTY. Create _includes/custom/footer.html
|
|
13
|
+
in YOUR site to shadow this stub. No theme layout needs forking.
|
|
14
|
+
|
|
15
|
+
Companion hooks: custom/head.html, custom/body-start.html,
|
|
16
|
+
custom/body-end.html. See _includes/README.md § Custom hooks.
|
|
17
|
+
===================================================================
|
|
18
|
+
{%- endcomment -%}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
===================================================================
|
|
3
|
+
CUSTOM HOOK — head
|
|
4
|
+
===================================================================
|
|
5
|
+
|
|
6
|
+
Path: _includes/custom/head.html
|
|
7
|
+
Purpose: Consumer extension point rendered at the END of the theme's
|
|
8
|
+
<head> (after main.css, tokens-inline, and user-overrides).
|
|
9
|
+
|
|
10
|
+
The theme ships this file EMPTY. To inject site-specific markup —
|
|
11
|
+
structured data (JSON-LD), font preloads, verification tags, extra
|
|
12
|
+
meta — create _includes/custom/head.html in YOUR site and it will
|
|
13
|
+
shadow this stub. No theme layout or core include needs forking.
|
|
14
|
+
|
|
15
|
+
Companion hooks: custom/body-start.html, custom/footer.html,
|
|
16
|
+
custom/body-end.html. See _includes/README.md § Custom hooks.
|
|
17
|
+
===================================================================
|
|
18
|
+
{%- endcomment -%}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
===================================================================
|
|
15
15
|
{%- endcomment -%}
|
|
16
16
|
{% if page.url != "/" and site.breadcrumbs %}
|
|
17
|
-
{%-
|
|
17
|
+
{%- include core/i18n.html -%}
|
|
18
18
|
{%- assign label_root = ui.breadcrumbs_root | default: "Home" -%}
|
|
19
19
|
{%- assign label_aria = ui.breadcrumbs_aria | default: "Breadcrumb" -%}
|
|
20
20
|
{%- assign crumbs = page.url | remove:'/index.html' | split: '/' -%}
|
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
Opt-out: set `local_graph: false` in page front matter.
|
|
24
24
|
|
|
25
25
|
Configurable depth: `local_graph_depth: 2` in page front matter (default 1).
|
|
26
|
+
The panel's depth selector (1–3) and incoming/outgoing switches override it
|
|
27
|
+
per-browser via localStorage (zer0.obsidianLocalGraph.v1).
|
|
26
28
|
===================================================================
|
|
27
29
|
{%- endcomment -%}
|
|
28
30
|
{%- comment -%} Shared visibility resolver — sets sidebar_enabled {%- endcomment -%}
|
|
@@ -63,8 +65,18 @@
|
|
|
63
65
|
{%- if _graph_page -%}{%- break -%}{%- endif -%}
|
|
64
66
|
{%- endfor -%}
|
|
65
67
|
{%- endunless -%}
|
|
66
|
-
<div class="obsidian-local-graph-meta d-flex align-items-center justify-content-between gap-2 mb-
|
|
67
|
-
<
|
|
68
|
+
<div class="obsidian-local-graph-meta d-flex flex-wrap align-items-center justify-content-between gap-2 mb-2">
|
|
69
|
+
<div class="d-flex align-items-center gap-2">
|
|
70
|
+
<span class="small text-secondary">Depth</span>
|
|
71
|
+
<div class="btn-group btn-group-sm" role="group" aria-label="Graph depth">
|
|
72
|
+
{%- for d in (1..3) -%}
|
|
73
|
+
<input type="radio" class="btn-check" name="obsidianLgDepth"
|
|
74
|
+
id="obsidian-lg-depth-{{ d }}" autocomplete="off"
|
|
75
|
+
{% if d == lg_depth %}checked{% endif %} />
|
|
76
|
+
<label class="btn btn-outline-secondary" for="obsidian-lg-depth-{{ d }}">{{ d }}</label>
|
|
77
|
+
{%- endfor -%}
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
68
80
|
{%- if _graph_page -%}
|
|
69
81
|
<a href="{{ _graph_url | relative_url }}"
|
|
70
82
|
class="btn btn-outline-primary btn-sm"
|
|
@@ -73,6 +85,20 @@
|
|
|
73
85
|
</a>
|
|
74
86
|
{%- endif -%}
|
|
75
87
|
</div>
|
|
88
|
+
<div class="d-flex flex-wrap column-gap-3 mb-2"
|
|
89
|
+
role="group"
|
|
90
|
+
aria-label="Link directions shown in the local graph">
|
|
91
|
+
<div class="form-check form-switch mb-0">
|
|
92
|
+
<input class="form-check-input" type="checkbox" role="switch"
|
|
93
|
+
id="obsidian-lg-outgoing" checked />
|
|
94
|
+
<label class="form-check-label small" for="obsidian-lg-outgoing">Outgoing</label>
|
|
95
|
+
</div>
|
|
96
|
+
<div class="form-check form-switch mb-0">
|
|
97
|
+
<input class="form-check-input" type="checkbox" role="switch"
|
|
98
|
+
id="obsidian-lg-incoming" checked />
|
|
99
|
+
<label class="form-check-label small" for="obsidian-lg-incoming">Incoming</label>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
76
102
|
<div id="obsidian-local-graph"
|
|
77
103
|
data-depth="{{ lg_depth }}"
|
|
78
104
|
data-index-url="{{ '/assets/data/wiki-index.json' | relative_url }}"
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
<ul class="list-unstyled fw-normal pb-2 small nav-tree-root">
|
|
37
37
|
{% for item in navigation %}
|
|
38
38
|
{% assign item_id = item.title | slugify | prepend: "nav-" %}
|
|
39
|
-
{% assign is_active = page.url == item.url %}
|
|
39
|
+
{% assign is_active = false %}{% if page.url == item.url %}{% assign is_active = true %}{% endif %}
|
|
40
40
|
{% assign is_expanded = item.expanded | default: false %}
|
|
41
41
|
{% assign has_children = item.children and item.children.size > 0 %}
|
|
42
42
|
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
<ul class="list-unstyled fw-normal ps-3 nav-tree-children">
|
|
79
79
|
{% for child in item.children %}
|
|
80
80
|
{% assign child_id = child.title | slugify | prepend: "nav-" %}
|
|
81
|
-
{% assign child_active = page.url == child.url %}
|
|
81
|
+
{% assign child_active = false %}{% if page.url == child.url %}{% assign child_active = true %}{% endif %}
|
|
82
82
|
{% assign child_expanded = child.expanded | default: false %}
|
|
83
83
|
{% assign child_has_children = child.children and child.children.size > 0 %}
|
|
84
84
|
|
|
@@ -118,7 +118,7 @@
|
|
|
118
118
|
<div class="collapse{% if child_expanded %} show{% endif %}" id="{{ child_id }}">
|
|
119
119
|
<ul class="list-unstyled fw-normal ps-3 nav-tree-children">
|
|
120
120
|
{% for grandchild in child.children %}
|
|
121
|
-
{% assign gc_active = page.url == grandchild.url %}
|
|
121
|
+
{% assign gc_active = false %}{% if page.url == grandchild.url %}{% assign gc_active = true %}{% endif %}
|
|
122
122
|
<li class="mb-1 nav-tree-item" data-depth="2">
|
|
123
123
|
{% if grandchild.url %}
|
|
124
124
|
<a href="{{ grandchild.url | relative_url }}"
|