jekyll-theme-zer0 1.25.0 → 1.27.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 +201 -0
- data/README.md +11 -28
- data/_data/authors.yml +4 -3
- data/_data/backlog.yml +54 -1
- data/_data/features.yml +101 -61
- data/_data/feedback_types.yml +148 -0
- data/_data/i18n/languages.yml +36 -0
- data/_data/navigation/README.md +1 -1
- data/_data/theme-manifest.yml +0 -2
- data/_data/ui-text.yml +36 -246
- data/_includes/README.md +4 -0
- data/_includes/components/README.md +1 -0
- data/_includes/components/author-avatar-url.html +4 -2
- data/_includes/components/env-switcher.html +3 -1
- data/_includes/components/language-toggle.html +81 -0
- data/_includes/components/page-feedback.html +252 -0
- data/_includes/components/search-modal.html +2 -2
- data/_includes/components/shortcuts-modal.html +1 -1
- data/_includes/components/translation-notice.html +27 -0
- data/_includes/content/intro.html +35 -76
- data/_includes/core/console-capture.html +89 -0
- data/_includes/core/footer.html +9 -4
- data/_includes/core/head.html +13 -0
- data/_includes/core/header.html +9 -4
- data/_includes/core/hreflang.html +33 -0
- data/_includes/core/i18n.html +36 -0
- data/_includes/navigation/breadcrumbs.html +1 -1
- data/_includes/navigation/navbar.html +5 -4
- data/_includes/navigation/sidebar-right.html +3 -2
- data/_includes/navigation/unified-drawer.html +1 -1
- data/_layouts/article.html +7 -1
- data/_layouts/default.html +5 -3
- data/_layouts/news.html +4 -2
- data/_layouts/root.html +17 -8
- data/_layouts/section.html +4 -2
- data/_sass/components/_page-feedback.scss +276 -0
- data/_sass/core/_docs-layout.scss +2 -78
- data/_sass/core/_obsidian.scss +9 -1
- data/_sass/layouts/_navbar-extras.scss +6 -1
- data/_sass/tokens/_layers.scss +4 -0
- data/assets/css/main.scss +1 -0
- data/assets/js/obsidian-graph.js +5 -1
- data/assets/js/page-feedback.js +660 -0
- data/scripts/README.md +20 -26
- data/scripts/bin/audit-consumer +1 -1
- data/scripts/bin/manifest +0 -1
- data/scripts/bin/sync-plugins +0 -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 +9 -20
- data/scripts/install/ai/prompts/wizard.system.md +8 -17
- 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/translate.rb +1114 -0
- metadata +14 -4
- data/_data/prompts.yml +0 -302
- data/_plugins/preview_image_generator.rb +0 -351
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
|
|
@@ -34,6 +35,10 @@
|
|
|
34
35
|
<!-- Only load in production builds (JEKYLL_ENV=production); the include also guards against dev hostnames -->
|
|
35
36
|
{% if jekyll.environment == "production" %}{% include analytics/google-tag-manager-head.html %}{% endif %}
|
|
36
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. -->
|
|
40
|
+
{% include core/console-capture.html %}
|
|
41
|
+
|
|
37
42
|
<!-- ================================ -->
|
|
38
43
|
<!-- JAVASCRIPT LIBRARIES -->
|
|
39
44
|
<!-- ================================ -->
|
|
@@ -89,6 +94,10 @@ window.MathJax = {
|
|
|
89
94
|
Outputs custom og:image with assets_prefix normalisation and non-Google site verification. -->
|
|
90
95
|
{% include content/seo.html %}
|
|
91
96
|
|
|
97
|
+
<!-- hreflang alternates for machine-generated translations (no-op when the
|
|
98
|
+
page has none) — see _data/i18n/manifest.yml + scripts/translate.rb -->
|
|
99
|
+
{% include core/hreflang.html %}
|
|
100
|
+
|
|
92
101
|
<!-- ========================== -->
|
|
93
102
|
<!-- STRUCTURED DATA MARKUP -->
|
|
94
103
|
<!-- ========================== -->
|
|
@@ -123,6 +132,10 @@ window.MathJax = {
|
|
|
123
132
|
<!-- Only load in production AND when an ID is configured; the include also guards against dev hostnames -->
|
|
124
133
|
{% if jekyll.environment == "production" and site.google_analytics %}{% include analytics/google-analytics.html %}{% endif %}
|
|
125
134
|
|
|
135
|
+
<!-- PostHog analytics - Configured via the `posthog:` block in _config.yml -->
|
|
136
|
+
<!-- Only load in production AND when explicitly enabled; the include itself also respects Do Not Track -->
|
|
137
|
+
{% if jekyll.environment == "production" and site.posthog.enabled %}{% include analytics/posthog.html %}{% endif %}
|
|
138
|
+
|
|
126
139
|
|
|
127
140
|
<!-- ================================ -->
|
|
128
141
|
<!-- CSS FRAMEWORKS AND LIBRARIES -->
|
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 -%}
|
|
@@ -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: '/' -%}
|
|
@@ -8,11 +8,12 @@
|
|
|
8
8
|
-->
|
|
9
9
|
|
|
10
10
|
<!-- Navigation Links - Responsive Offcanvas (inline at lg+, offcanvas below) -->
|
|
11
|
+
{%- include core/i18n.html -%}
|
|
11
12
|
<div class="offcanvas-lg offcanvas-end" tabindex="-1" id="bdNavbar" aria-labelledby="mainNavOffcanvasLabel">
|
|
12
13
|
|
|
13
14
|
<!-- Main Navigation Header - Offcanvas -->
|
|
14
15
|
<div class="offcanvas-header border-bottom">
|
|
15
|
-
<h2 class="offcanvas-title h5 mb-0" id="mainNavOffcanvasLabel">Main Navigation</h2>
|
|
16
|
+
<h2 class="offcanvas-title h5 mb-0" id="mainNavOffcanvasLabel">{{ ui.main_nav_label | default: "Main Navigation" }}</h2>
|
|
16
17
|
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close navigation menu" data-bs-target="#bdNavbar"></button>
|
|
17
18
|
</div>
|
|
18
19
|
|
|
@@ -24,7 +25,7 @@
|
|
|
24
25
|
<li class="nav-item d-lg-none">
|
|
25
26
|
<a class="nav-link" href="{{ '/' | relative_url }}" {% if page.url == '/' %}aria-current="page"{% endif %}>
|
|
26
27
|
<i class="{{site.default_icon}} bi-house" aria-hidden="true"></i>
|
|
27
|
-
Home
|
|
28
|
+
{{ ui.home_label | default: "Home" }}
|
|
28
29
|
</a>
|
|
29
30
|
</li>
|
|
30
31
|
{%- comment -%}
|
|
@@ -143,7 +144,7 @@
|
|
|
143
144
|
aria-haspopup="dialog"
|
|
144
145
|
>
|
|
145
146
|
<i class="{{site.default_icon}} bi-search" aria-hidden="true"></i>
|
|
146
|
-
Search
|
|
147
|
+
{{ ui.search_button_label | default: "Search" }}
|
|
147
148
|
</button>
|
|
148
149
|
</li>
|
|
149
150
|
<li class="nav-item d-lg-none">
|
|
@@ -156,7 +157,7 @@
|
|
|
156
157
|
aria-haspopup="dialog"
|
|
157
158
|
>
|
|
158
159
|
<i class="{{site.default_icon}} bi-gear" aria-hidden="true"></i>
|
|
159
|
-
Settings
|
|
160
|
+
{{ ui.settings_button_label | default: "Settings" }}
|
|
160
161
|
</button>
|
|
161
162
|
</li>
|
|
162
163
|
</ul>
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
includes: nav_list.html, content/toc.html
|
|
6
6
|
-->
|
|
7
7
|
|
|
8
|
+
{%- include core/i18n.html -%}
|
|
8
9
|
<!-- FOUC guard: apply hidden TOC preference before paint -->
|
|
9
10
|
<script>
|
|
10
11
|
try {
|
|
@@ -17,7 +18,7 @@
|
|
|
17
18
|
<!-- Right sidebar container -->
|
|
18
19
|
<div class="offcanvas-lg offcanvas-end" tabindex="-1" id="tocContents" aria-labelledby="tocLabel">
|
|
19
20
|
<div class="offcanvas-header">
|
|
20
|
-
<h2 class="offcanvas-title h5 mb-0" id="tocLabel">Table of Contents</h2>
|
|
21
|
+
<h2 class="offcanvas-title h5 mb-0" id="tocLabel">{{ ui.toc_title | default: "Table of Contents" }}</h2>
|
|
21
22
|
<div class="d-flex align-items-center gap-1">
|
|
22
23
|
<button type="button"
|
|
23
24
|
class="btn btn-sm btn-link text-body-secondary bd-toc-visibility-toggle d-lg-none px-2"
|
|
@@ -37,7 +38,7 @@
|
|
|
37
38
|
<div class="mb-3 d-flex align-items-center justify-content-between gap-2">
|
|
38
39
|
<strong class="d-flex align-items-center mb-0">
|
|
39
40
|
<i class="bi bi-file-text me-2" aria-hidden="true"></i>
|
|
40
|
-
On this page
|
|
41
|
+
{{ ui.toc_label | default: "On this page" }}
|
|
41
42
|
</strong>
|
|
42
43
|
<button type="button"
|
|
43
44
|
class="btn btn-sm btn-link text-body-secondary bd-toc-visibility-toggle d-none d-lg-inline-flex flex-shrink-0 px-1"
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
===================================================================
|
|
24
24
|
{%- endcomment -%}
|
|
25
25
|
{% if site.navigation.unified_mobile_drawer %}
|
|
26
|
-
{%-
|
|
26
|
+
{%- include core/i18n.html -%}
|
|
27
27
|
<div class="offcanvas offcanvas-start zer0-unified-drawer d-lg-none"
|
|
28
28
|
tabindex="-1"
|
|
29
29
|
id="zer0UnifiedDrawer"
|
data/_layouts/article.html
CHANGED
|
@@ -15,6 +15,9 @@ layout: default
|
|
|
15
15
|
Front Matter Variables:
|
|
16
16
|
- post_type: Article type (standard, featured, breaking, opinion, review, tutorial, listicle, interview)
|
|
17
17
|
- sidebar: Override sidebar visibility (true/false)
|
|
18
|
+
- show_hero: Opt into the top-of-article hero image (page.preview) for any
|
|
19
|
+
post_type without changing layout/sidebar/badges (true; featured and
|
|
20
|
+
breaking posts show the hero automatically)
|
|
18
21
|
|
|
19
22
|
Post Type Variations:
|
|
20
23
|
- standard (default): Normal article with sidebar
|
|
@@ -221,12 +224,15 @@ layout: default
|
|
|
221
224
|
preview image is set. Breaking stories were previously skipped because the
|
|
222
225
|
condition checked only post_type == "featured"; since breaking is mapped
|
|
223
226
|
before featured (see top of file), breaking+featured posts showed no hero.
|
|
227
|
+
Any other post type can opt in with `show_hero: true` in front matter —
|
|
228
|
+
this flips only the hero, leaving sidebar, typography, and badges at their
|
|
229
|
+
post_type defaults (issue #303).
|
|
224
230
|
Nested ifs keep the "only when page.preview exists" guard intact (Liquid has
|
|
225
231
|
no parentheses, so a flat `a or b and c` would misgroup).
|
|
226
232
|
{% endcomment %}
|
|
227
233
|
{% assign show_hero = false %}
|
|
228
234
|
{% if page.preview %}
|
|
229
|
-
{% if post_type == "featured" or post_type == "breaking" %}
|
|
235
|
+
{% if post_type == "featured" or post_type == "breaking" or page.show_hero %}
|
|
230
236
|
{% assign show_hero = true %}
|
|
231
237
|
{% endif %}
|
|
232
238
|
{% endif %}
|
data/_layouts/default.html
CHANGED
|
@@ -86,8 +86,10 @@ layout: root
|
|
|
86
86
|
<!-- ================================ -->
|
|
87
87
|
<!-- MAIN CONTENT AREA -->
|
|
88
88
|
<!-- ================================ -->
|
|
89
|
-
<!-- Primary content section with scroll spy for table of contents navigation
|
|
90
|
-
|
|
89
|
+
<!-- Primary content section with scroll spy for table of contents navigation.
|
|
90
|
+
Not a <main>: root.html already provides the single <main id="main-content">
|
|
91
|
+
landmark; a nested <main> here would duplicate it (issue #299). -->
|
|
92
|
+
<div class="bd-main order-1" data-bs-spy="scroll" data-bs-target="#TableOfContents" data-bs-offset="100" data-bs-smooth-scroll="true">
|
|
91
93
|
|
|
92
94
|
{%- comment -%}
|
|
93
95
|
Page introduction: title, breadcrumbs, metadata.
|
|
@@ -129,5 +131,5 @@ layout: root
|
|
|
129
131
|
{% include content/backlinks.html %}
|
|
130
132
|
{% endif %}
|
|
131
133
|
</div>
|
|
132
|
-
</
|
|
134
|
+
</div>
|
|
133
135
|
</div>
|
data/_layouts/news.html
CHANGED
|
@@ -78,7 +78,9 @@ source: "https://getbootstrap.com/docs/5.3/examples/blog/#"
|
|
|
78
78
|
</div>
|
|
79
79
|
</div>
|
|
80
80
|
|
|
81
|
-
<main
|
|
81
|
+
<!-- Not a <main>: root.html already provides the single <main id="main-content">
|
|
82
|
+
landmark; a nested <main> here would duplicate it (issue #299). -->
|
|
83
|
+
<div class="container">
|
|
82
84
|
|
|
83
85
|
{% if section_style == "magazine" %}
|
|
84
86
|
<!-- ================================ -->
|
|
@@ -578,7 +580,7 @@ source: "https://getbootstrap.com/docs/5.3/examples/blog/#"
|
|
|
578
580
|
</div>
|
|
579
581
|
</section>
|
|
580
582
|
|
|
581
|
-
</
|
|
583
|
+
</div>
|
|
582
584
|
|
|
583
585
|
<style>
|
|
584
586
|
.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: 'dark' }}" data-zer0-bg="{{ site.theme_background.enabled | default: true }}">
|
|
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-theme-skin="{{ site.theme_skin | default: 'dark' }}" data-zer0-bg="{{ site.theme_background.enabled | default: true }}">
|
|
50
50
|
<head>
|
|
51
51
|
<!-- =============================================== -->
|
|
52
52
|
<!-- HEAD SECTION: Meta tags, styles, and SEO setup -->
|
|
@@ -82,11 +82,13 @@
|
|
|
82
82
|
Enable with `navigation.unified_mobile_drawer: true` in _config.yml. -->
|
|
83
83
|
{% include navigation/unified-drawer.html %}
|
|
84
84
|
|
|
85
|
-
<!-- Search modal (site-wide) —
|
|
86
|
-
|
|
85
|
+
<!-- Search modal (site-wide) — cached once per build PER LANGUAGE
|
|
86
|
+
(the lang param keys the include-cache entry; page.lang is nil
|
|
87
|
+
on English pages, so they all still share a single render). -->
|
|
88
|
+
{% include_cached components/search-modal.html lang=page.lang %}
|
|
87
89
|
|
|
88
|
-
<!-- Keyboard shortcuts help modal (triggered by `?`) —
|
|
89
|
-
{% include_cached components/shortcuts-modal.html %}
|
|
90
|
+
<!-- Keyboard shortcuts help modal (triggered by `?`) — cached per language. -->
|
|
91
|
+
{% include_cached components/shortcuts-modal.html lang=page.lang %}
|
|
90
92
|
|
|
91
93
|
<!-- Settings offcanvas (Appearance / Site / Developer tabs) -->
|
|
92
94
|
{% include components/info-section.html %}
|
|
@@ -98,10 +100,14 @@
|
|
|
98
100
|
<!-- MAIN CONTENT AREA -->
|
|
99
101
|
<!-- ======================== -->
|
|
100
102
|
<!-- Skip-link target: keep a single, consistent #main-content anchor site-wide.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
+
This is the ONE <main> landmark per page — child layouts (default/section/
|
|
104
|
+
news) must use non-landmark wrappers (<div>) inside it (issue #299) — so
|
|
105
|
+
assistive tech, search engines, and AI content extractors can isolate the
|
|
103
106
|
primary content from the surrounding header/nav/footer chrome. -->
|
|
104
107
|
<main id="main-content">
|
|
108
|
+
<!-- Machine-translation disclosure — renders only on generated
|
|
109
|
+
translations (page.machine_translated); no-op elsewhere. -->
|
|
110
|
+
{%- include components/translation-notice.html -%}
|
|
105
111
|
{{ content }}
|
|
106
112
|
</main>
|
|
107
113
|
|
|
@@ -116,7 +122,7 @@
|
|
|
116
122
|
fresh: jekyll-include-cache stores in memory only (Jekyll 3.x has no
|
|
117
123
|
Jekyll::Cache) and clears on every build's :pre_render hook — it never
|
|
118
124
|
persists to .jekyll-cache. -->
|
|
119
|
-
{%- include_cached core/footer.html -%}
|
|
125
|
+
{%- include_cached core/footer.html lang=page.lang -%}
|
|
120
126
|
{%- include core/footer-fabs.html -%}
|
|
121
127
|
|
|
122
128
|
<!-- Privacy-compliant cookie consent banner — page-invariant. -->
|
|
@@ -125,6 +131,9 @@
|
|
|
125
131
|
<!-- AI Chat Assistant -->
|
|
126
132
|
{%- include components/ai-chat.html -%}
|
|
127
133
|
|
|
134
|
+
<!-- Page-feedback capture widget ("Improve this page" → GitHub issue) -->
|
|
135
|
+
{%- include components/page-feedback.html -%}
|
|
136
|
+
|
|
128
137
|
<!-- JavaScript dependencies and custom scripts -->
|
|
129
138
|
<div>
|
|
130
139
|
<!-- Search functionality -->
|
data/_layouts/section.html
CHANGED
|
@@ -156,7 +156,9 @@ layout: root
|
|
|
156
156
|
<!-- ================================ -->
|
|
157
157
|
<!-- MAIN CONTENT AREA -->
|
|
158
158
|
<!-- ================================ -->
|
|
159
|
-
<main
|
|
159
|
+
<!-- Not a <main>: root.html already provides the single <main id="main-content">
|
|
160
|
+
landmark; a nested <main> here would duplicate it (issue #299). -->
|
|
161
|
+
<div class="col-lg-9 col-xl-10 section-layout-main">
|
|
160
162
|
|
|
161
163
|
<!-- Section Header -->
|
|
162
164
|
<header class="section-header mb-4">
|
|
@@ -526,7 +528,7 @@ layout: root
|
|
|
526
528
|
</div>
|
|
527
529
|
</aside>
|
|
528
530
|
|
|
529
|
-
</
|
|
531
|
+
</div>
|
|
530
532
|
</div>
|
|
531
533
|
</div>
|
|
532
534
|
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Page-feedback widget — global FAB + capture modal
|
|
3
|
+
// ----------------------------------------------------------------------------
|
|
4
|
+
// Markup: _includes/components/page-feedback.html
|
|
5
|
+
// Behaviour: assets/js/page-feedback.js
|
|
6
|
+
// Tokens: shared FAB + layer scale (_sass/tokens/_spacing.scss, _layers.scss)
|
|
7
|
+
// Theming: leans on Bootstrap 5.3 CSS variables (--bs-body-bg / -color /
|
|
8
|
+
// -border-color) so it follows [data-bs-theme] light/dark for free.
|
|
9
|
+
// ============================================================================
|
|
10
|
+
|
|
11
|
+
// --- Global FAB (bottom-left, clear of the right-hand FAB stack) ------------
|
|
12
|
+
.pf-fab {
|
|
13
|
+
position: fixed;
|
|
14
|
+
bottom: var(--zer0-space-fab-offset, 1rem);
|
|
15
|
+
left: var(--zer0-space-fab-offset, 1rem);
|
|
16
|
+
z-index: var(--zer0-layer-fab-feedback, 1051);
|
|
17
|
+
|
|
18
|
+
display: inline-flex;
|
|
19
|
+
align-items: center;
|
|
20
|
+
justify-content: center;
|
|
21
|
+
width: var(--zer0-space-fab-size, 3.5rem);
|
|
22
|
+
height: var(--zer0-space-fab-size, 3.5rem);
|
|
23
|
+
padding: 0;
|
|
24
|
+
border: 0;
|
|
25
|
+
border-radius: 50%;
|
|
26
|
+
background-color: var(--zer0-color-primary, var(--bs-primary));
|
|
27
|
+
color: #fff;
|
|
28
|
+
font-size: 1.25rem;
|
|
29
|
+
opacity: .9;
|
|
30
|
+
box-shadow: var(--zer0-shadow-fab, 0 .5rem 1rem rgba(0, 0, 0, .25));
|
|
31
|
+
transition:
|
|
32
|
+
opacity var(--zer0-motion-duration-fast, 120ms) var(--zer0-motion-ease-standard, ease),
|
|
33
|
+
transform var(--zer0-motion-duration-fast, 120ms) var(--zer0-motion-ease-standard, ease);
|
|
34
|
+
|
|
35
|
+
&:hover,
|
|
36
|
+
&:focus-visible {
|
|
37
|
+
opacity: 1;
|
|
38
|
+
color: #fff;
|
|
39
|
+
transform: translateY(-2px);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Lock background scroll while the dialog is open (set by page-feedback.js).
|
|
44
|
+
.pf-no-scroll { overflow: hidden; }
|
|
45
|
+
|
|
46
|
+
// --- Modal overlay ----------------------------------------------------------
|
|
47
|
+
.pf-overlay {
|
|
48
|
+
position: fixed;
|
|
49
|
+
inset: 0;
|
|
50
|
+
z-index: var(--zer0-layer-feedback-modal, 1096); // above the cookie banner (1095)
|
|
51
|
+
display: flex;
|
|
52
|
+
align-items: flex-end; // bottom sheet on phones
|
|
53
|
+
justify-content: center;
|
|
54
|
+
background: rgba(0, 0, 0, .5);
|
|
55
|
+
opacity: 0;
|
|
56
|
+
transition: opacity var(--zer0-motion-duration, 180ms) var(--zer0-motion-ease-standard, ease);
|
|
57
|
+
|
|
58
|
+
&[hidden] { display: none; }
|
|
59
|
+
&.pf-overlay--open { opacity: 1; }
|
|
60
|
+
|
|
61
|
+
@media (min-width: 576px) {
|
|
62
|
+
align-items: center;
|
|
63
|
+
padding: var(--zer0-space-3, 1rem);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.pf-dialog {
|
|
68
|
+
display: flex;
|
|
69
|
+
flex-direction: column;
|
|
70
|
+
width: 100%;
|
|
71
|
+
max-width: 40rem;
|
|
72
|
+
max-height: 92vh;
|
|
73
|
+
overflow: hidden;
|
|
74
|
+
color: var(--bs-body-color);
|
|
75
|
+
background: var(--bs-body-bg);
|
|
76
|
+
border: 1px solid var(--bs-border-color, rgba(0, 0, 0, .15));
|
|
77
|
+
border-radius: 1rem 1rem 0 0;
|
|
78
|
+
transform: translateY(20px);
|
|
79
|
+
transition: transform var(--zer0-motion-duration, 180ms) var(--zer0-motion-ease-standard, ease);
|
|
80
|
+
|
|
81
|
+
.pf-overlay--open & { transform: translateY(0); }
|
|
82
|
+
|
|
83
|
+
@media (min-width: 576px) { border-radius: 1rem; }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// --- Header -----------------------------------------------------------------
|
|
87
|
+
.pf-header {
|
|
88
|
+
display: flex;
|
|
89
|
+
align-items: flex-start;
|
|
90
|
+
justify-content: space-between;
|
|
91
|
+
gap: var(--zer0-space-2, .5rem);
|
|
92
|
+
padding: var(--zer0-space-3, 1rem) var(--zer0-space-3, 1rem) var(--zer0-space-2, .5rem);
|
|
93
|
+
border-bottom: 1px solid var(--bs-border-color, rgba(0, 0, 0, .1));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.pf-title { display: flex; align-items: center; }
|
|
97
|
+
|
|
98
|
+
.pf-intro { font-size: .875rem; line-height: 1.4; }
|
|
99
|
+
|
|
100
|
+
.pf-close {
|
|
101
|
+
flex-shrink: 0;
|
|
102
|
+
color: var(--bs-body-color);
|
|
103
|
+
background: transparent;
|
|
104
|
+
border: 0;
|
|
105
|
+
opacity: .6;
|
|
106
|
+
&:hover, &:focus-visible { opacity: 1; }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// --- Body (scrolls) ---------------------------------------------------------
|
|
110
|
+
.pf-body {
|
|
111
|
+
flex: 1 1 auto;
|
|
112
|
+
overflow-y: auto;
|
|
113
|
+
padding: var(--zer0-space-3, 1rem);
|
|
114
|
+
display: flex;
|
|
115
|
+
flex-direction: column;
|
|
116
|
+
gap: var(--zer0-space-3, 1rem);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.pf-field { border: 0; padding: 0; margin: 0; min-width: 0; }
|
|
120
|
+
|
|
121
|
+
.pf-legend,
|
|
122
|
+
.pf-label {
|
|
123
|
+
display: block;
|
|
124
|
+
font-size: .8125rem;
|
|
125
|
+
font-weight: 600;
|
|
126
|
+
text-transform: uppercase;
|
|
127
|
+
letter-spacing: .02em;
|
|
128
|
+
color: var(--bs-secondary-color, var(--zer0-color-ink-muted, #6c757d));
|
|
129
|
+
margin-bottom: var(--zer0-space-2, .5rem);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.pf-help { font-size: .8125rem; margin: .35rem 0 0; }
|
|
133
|
+
|
|
134
|
+
// --- Type selector ----------------------------------------------------------
|
|
135
|
+
.pf-types-group {
|
|
136
|
+
font-size: .75rem;
|
|
137
|
+
font-weight: 600;
|
|
138
|
+
color: var(--bs-secondary-color, #6c757d);
|
|
139
|
+
margin: var(--zer0-space-2, .5rem) 0 .35rem;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.pf-types-grid {
|
|
143
|
+
display: grid;
|
|
144
|
+
grid-template-columns: 1fr;
|
|
145
|
+
gap: .5rem;
|
|
146
|
+
|
|
147
|
+
@media (min-width: 480px) { grid-template-columns: 1fr 1fr; }
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.pf-type {
|
|
151
|
+
display: flex;
|
|
152
|
+
align-items: flex-start;
|
|
153
|
+
gap: .625rem;
|
|
154
|
+
text-align: left;
|
|
155
|
+
padding: .625rem .75rem;
|
|
156
|
+
background: var(--bs-body-bg);
|
|
157
|
+
border: 1px solid var(--bs-border-color, rgba(0, 0, 0, .15));
|
|
158
|
+
border-radius: .625rem;
|
|
159
|
+
cursor: pointer;
|
|
160
|
+
transition: border-color 120ms ease, background-color 120ms ease;
|
|
161
|
+
|
|
162
|
+
&:hover { border-color: var(--zer0-color-primary, var(--bs-primary)); }
|
|
163
|
+
|
|
164
|
+
&:focus-visible {
|
|
165
|
+
outline: 2px solid var(--zer0-color-primary, var(--bs-primary));
|
|
166
|
+
outline-offset: 2px;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
&.pf-type--active {
|
|
170
|
+
border-color: var(--zer0-color-primary, var(--bs-primary));
|
|
171
|
+
background: color-mix(in srgb, var(--bs-primary) 10%, transparent);
|
|
172
|
+
box-shadow: inset 0 0 0 1px var(--zer0-color-primary, var(--bs-primary));
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.pf-type-icon { flex-shrink: 0; margin-top: .1rem; font-size: 1rem; color: var(--zer0-color-primary, var(--bs-primary)); }
|
|
177
|
+
.pf-type-text { display: flex; flex-direction: column; gap: .1rem; min-width: 0; }
|
|
178
|
+
.pf-type-label { font-weight: 600; font-size: .9rem; line-height: 1.2; }
|
|
179
|
+
.pf-type-desc { font-size: .78rem; line-height: 1.3; color: var(--bs-secondary-color, #6c757d); }
|
|
180
|
+
|
|
181
|
+
// --- Context / logs ---------------------------------------------------------
|
|
182
|
+
.pf-summary { cursor: pointer; font-weight: 600; font-size: .875rem; }
|
|
183
|
+
.pf-context-count { color: var(--bs-secondary-color, #6c757d); font-weight: 400; }
|
|
184
|
+
|
|
185
|
+
.pf-context-list {
|
|
186
|
+
list-style: none;
|
|
187
|
+
margin: .5rem 0 0;
|
|
188
|
+
padding: 0;
|
|
189
|
+
font-size: .8125rem;
|
|
190
|
+
|
|
191
|
+
li { display: flex; gap: .5rem; padding: .15rem 0; }
|
|
192
|
+
}
|
|
193
|
+
.pf-context-key { flex: 0 0 4rem; color: var(--bs-secondary-color, #6c757d); }
|
|
194
|
+
.pf-context-val { min-width: 0; word-break: break-word; }
|
|
195
|
+
|
|
196
|
+
.pf-logs { margin-top: .75rem; }
|
|
197
|
+
.pf-check { display: flex; align-items: center; gap: .4rem; font-size: .8125rem; font-weight: 500; }
|
|
198
|
+
.pf-logs-preview {
|
|
199
|
+
max-height: 9rem;
|
|
200
|
+
overflow: auto;
|
|
201
|
+
margin: .5rem 0 0;
|
|
202
|
+
padding: .5rem .625rem;
|
|
203
|
+
font-size: .72rem;
|
|
204
|
+
line-height: 1.4;
|
|
205
|
+
white-space: pre-wrap;
|
|
206
|
+
word-break: break-word;
|
|
207
|
+
background: var(--bs-tertiary-bg, rgba(0, 0, 0, .04));
|
|
208
|
+
border: 1px solid var(--bs-border-color, rgba(0, 0, 0, .1));
|
|
209
|
+
border-radius: .5rem;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// --- AI enrichment ----------------------------------------------------------
|
|
213
|
+
.pf-ai-head { display: flex; align-items: center; flex-wrap: wrap; gap: .5rem; }
|
|
214
|
+
.pf-ai-hint { font-size: .8125rem; }
|
|
215
|
+
.pf-ai-result { margin-top: .625rem; font-size: .875rem; }
|
|
216
|
+
.pf-ai-card {
|
|
217
|
+
padding: .75rem .875rem;
|
|
218
|
+
background: var(--bs-tertiary-bg, rgba(0, 0, 0, .04));
|
|
219
|
+
border: 1px solid var(--bs-border-color, rgba(0, 0, 0, .1));
|
|
220
|
+
border-radius: .625rem;
|
|
221
|
+
|
|
222
|
+
p { margin: 0 0 .5rem; }
|
|
223
|
+
p:last-child { margin-bottom: 0; }
|
|
224
|
+
}
|
|
225
|
+
.pf-ai-badges .badge { margin-right: .25rem; }
|
|
226
|
+
.pf-ai-questions { margin: .25rem 0 .5rem 1.1rem; padding: 0; }
|
|
227
|
+
.pf-ai-labels code { font-size: .78rem; }
|
|
228
|
+
.pf-ai-error { color: var(--bs-danger-text-emphasis, var(--bs-danger)); }
|
|
229
|
+
.pf-ai-spinner,
|
|
230
|
+
.pf-ai-note i { color: var(--zer0-color-primary, var(--bs-primary)); }
|
|
231
|
+
.pf-ai-spinner {
|
|
232
|
+
display: inline-block;
|
|
233
|
+
width: .85rem;
|
|
234
|
+
height: .85rem;
|
|
235
|
+
border: 2px solid currentColor;
|
|
236
|
+
border-right-color: transparent;
|
|
237
|
+
border-radius: 50%;
|
|
238
|
+
vertical-align: -.1rem;
|
|
239
|
+
animation: pf-spin .7s linear infinite;
|
|
240
|
+
}
|
|
241
|
+
@keyframes pf-spin { to { transform: rotate(360deg); } }
|
|
242
|
+
|
|
243
|
+
// --- Status line ------------------------------------------------------------
|
|
244
|
+
.pf-status:empty { display: none; }
|
|
245
|
+
.pf-status {
|
|
246
|
+
font-size: .8125rem;
|
|
247
|
+
margin: 0;
|
|
248
|
+
padding: .5rem .625rem;
|
|
249
|
+
border-radius: .5rem;
|
|
250
|
+
|
|
251
|
+
&.pf-status--ok { background: var(--bs-success-bg-subtle, #d1e7dd); color: var(--bs-success-text-emphasis, #0a3622); }
|
|
252
|
+
&.pf-status--warn { background: var(--bs-warning-bg-subtle, #fff3cd); color: var(--bs-warning-text-emphasis, #664d03); }
|
|
253
|
+
&.pf-status--pending { background: var(--bs-info-bg-subtle, #cff4fc); color: var(--bs-info-text-emphasis, #055160); }
|
|
254
|
+
a { color: inherit; text-decoration: underline; }
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// --- Footer -----------------------------------------------------------------
|
|
258
|
+
.pf-footer {
|
|
259
|
+
display: flex;
|
|
260
|
+
align-items: center;
|
|
261
|
+
justify-content: space-between;
|
|
262
|
+
gap: var(--zer0-space-2, .5rem);
|
|
263
|
+
padding: var(--zer0-space-2, .5rem) var(--zer0-space-3, 1rem);
|
|
264
|
+
border-top: 1px solid var(--bs-border-color, rgba(0, 0, 0, .1));
|
|
265
|
+
background: var(--bs-body-bg);
|
|
266
|
+
}
|
|
267
|
+
.pf-footer-primary { display: flex; gap: .5rem; }
|
|
268
|
+
.pf-copy { text-decoration: none; }
|
|
269
|
+
|
|
270
|
+
// --- Reduced motion ---------------------------------------------------------
|
|
271
|
+
@media (prefers-reduced-motion: reduce) {
|
|
272
|
+
.pf-fab,
|
|
273
|
+
.pf-overlay,
|
|
274
|
+
.pf-dialog { transition: none; }
|
|
275
|
+
.pf-ai-spinner { animation: none; }
|
|
276
|
+
}
|