jekyll-theme-zer0 1.30.0 → 1.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +171 -0
- data/_data/backlog.yml +30 -7
- data/_data/consumers.yml +99 -0
- data/_data/features.yml +1 -1
- data/_data/theme-manifest.yml +35 -33
- data/_data/ui-text.yml +1 -0
- data/_includes/README.md +1 -1
- data/_includes/analytics/posthog.html +28 -9
- data/_includes/components/README.md +1 -1
- data/_includes/components/background-image.html +7 -1
- data/_includes/components/cookie-consent.html +30 -30
- data/_includes/components/mermaid.html +14 -7
- data/_includes/components/preview-image.html +22 -3
- data/_includes/components/theme-preview-gallery.html +1 -1
- data/_includes/content/intro.html +7 -0
- data/_includes/content/seo.html +8 -0
- data/_includes/content/sitemap.html +220 -263
- data/_includes/core/branding.html +8 -15
- data/_includes/core/head.html +29 -5
- data/_includes/core/header.html +14 -2
- data/_includes/core/tokens-inline.html +16 -1
- data/_includes/navigation/navbar.html +9 -6
- data/_includes/setup/wizard.html +5 -1
- data/_layouts/article.html +15 -3
- data/_layouts/home.html +13 -1
- data/_layouts/note.html +2 -1
- data/_layouts/notebook.html +2 -1
- data/_layouts/root.html +22 -0
- data/_sass/components/_cookie-banner.scss +48 -0
- data/_sass/components/_setup-wizard.scss +19 -1
- data/_sass/components/_ui-enhancements.scss +11 -0
- data/_sass/core/_navbar.scss +137 -70
- data/_sass/core/_sidebar-extras.scss +2 -14
- data/_sass/core/code-copy.scss +27 -8
- data/assets/css/extension-points-demo.css +27 -0
- data/assets/data/wiki-index.json +19 -0
- data/assets/js/auto-hide-nav.js +53 -13
- data/assets/js/code-copy.js +72 -1
- data/assets/js/extension-points-demo.js +48 -0
- data/assets/js/mermaid-diagrams.js +128 -22
- data/assets/js/obsidian-local-graph.js +27 -5
- data/assets/js/setup-wizard.js +7 -2
- data/scripts/ci/test_visual_evidence_autogen.py +64 -2
- data/scripts/ci/visual_evidence_autogen.py +14 -4
- data/scripts/features/install-preview-generator +1 -1
- data/scripts/lib/README.md +12 -0
- data/scripts/lib/preview_generator.py +2 -2
- metadata +4 -2
|
@@ -5,14 +5,16 @@
|
|
|
5
5
|
|
|
6
6
|
File: branding.html
|
|
7
7
|
Path: _includes/core/branding.html
|
|
8
|
-
Purpose: Display site
|
|
9
|
-
|
|
8
|
+
Purpose: Display the site logo and title in the navbar
|
|
9
|
+
|
|
10
|
+
The navbar carries logo + title ONLY. `site.subtitle` used to render here as
|
|
11
|
+
a second `.navbar-brand` at lg+, which spent bar width the menubar needed for
|
|
12
|
+
its labels; it now belongs to the home hero (_layouts/home.html) — see #405.
|
|
13
|
+
|
|
10
14
|
Dependencies:
|
|
11
15
|
- site.title: Main site title from _config.yml
|
|
12
|
-
- site.subtitle: Optional subtitle from _config.yml
|
|
13
16
|
- site.default_icon: Icon framework class (e.g., 'bi')
|
|
14
17
|
- site.title_icon: Icon for title display
|
|
15
|
-
- site.subtitle_icon: Icon for subtitle display
|
|
16
18
|
===================================================================
|
|
17
19
|
{% endcomment %}
|
|
18
20
|
{% comment %} Color Schema Override
|
|
@@ -43,14 +45,5 @@
|
|
|
43
45
|
</a>
|
|
44
46
|
</div>
|
|
45
47
|
|
|
46
|
-
{% comment %}
|
|
47
|
-
|
|
48
|
-
{%- if _subtitle != "" -%}
|
|
49
|
-
<div class="navbar-brand site-subtitle d-none d-lg-inline">
|
|
50
|
-
<a class="nav-link" href="{{ '/' | relative_url }}" aria-label="{{ _subtitle }} — {{ site.title }} home">
|
|
51
|
-
<span class="site-subtitle-text">
|
|
52
|
-
{{ _subtitle }}
|
|
53
|
-
</span>
|
|
54
|
-
</a>
|
|
55
|
-
</div>
|
|
56
|
-
{% endif %}
|
|
48
|
+
{% comment %} The subtitle is deliberately NOT rendered here — see the header
|
|
49
|
+
comment above and the home hero in _layouts/home.html (#405). {% endcomment %}
|
data/_includes/core/head.html
CHANGED
|
@@ -19,7 +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
|
|
22
|
+
- (posthog.html is included from _layouts/root.html at the end of <body>, not here)
|
|
23
23
|
|
|
24
24
|
Performance Notes:
|
|
25
25
|
- Scripts loaded in head for immediate availability
|
|
@@ -160,9 +160,10 @@ window.MathJax = {
|
|
|
160
160
|
{% comment %} Only load in production AND when an ID is configured; the include also guards against dev hostnames {% endcomment %}
|
|
161
161
|
{% if jekyll.environment == "production" and site.google_analytics %}{% include analytics/google-analytics.html %}{% endif %}
|
|
162
162
|
|
|
163
|
-
{% comment %} PostHog analytics
|
|
164
|
-
|
|
165
|
-
|
|
163
|
+
{% comment %} PostHog analytics is NOT included here. _layouts/root.html already
|
|
164
|
+
includes analytics/posthog.html at the end of <body> (since the include was
|
|
165
|
+
added), so this second copy, added in #317, emitted the snippet twice and
|
|
166
|
+
called posthog.init() twice on every production page. {% endcomment %}
|
|
166
167
|
|
|
167
168
|
|
|
168
169
|
{% comment %} ================================ {% endcomment %}
|
|
@@ -200,7 +201,8 @@ window.MathJax = {
|
|
|
200
201
|
{% comment %} DESIGN TOKEN OVERRIDES {% endcomment %}
|
|
201
202
|
{% comment %} ========================== {% endcomment %}
|
|
202
203
|
{% comment %} Inline <style> block emitted AFTER main.css so site-config `theme_color` keys
|
|
203
|
-
and any per-user Appearance overrides win over compiled defaults.
|
|
204
|
+
and any per-user Appearance overrides win over compiled defaults. The tokens a
|
|
205
|
+
palette skin sets are scoped so that the skin still wins (see tokens-inline.html). {% endcomment %}
|
|
204
206
|
{% include core/tokens-inline.html %}
|
|
205
207
|
|
|
206
208
|
{% comment %} Optional fork overrides: opt-in via `site.user_overrides: true` in _config.yml.
|
|
@@ -214,6 +216,28 @@ window.MathJax = {
|
|
|
214
216
|
<link rel="stylesheet" href="{{ '/assets/css/stats.css' | relative_url }}">
|
|
215
217
|
{% endif %}
|
|
216
218
|
|
|
219
|
+
{% comment %} Per-page stylesheets from frontmatter (#412):
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
styles:
|
|
223
|
+
- /assets/css/notes.css
|
|
224
|
+
- https://cdn.example.com/x.css
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
A contract, not a convention. Before this, attaching CSS to one page meant a
|
|
228
|
+
literal <link> inside the content file or a custom layout — which works, and
|
|
229
|
+
puts asset tags in prose. A single string is accepted as well as a list.
|
|
230
|
+
Site-relative paths go through relative_url so a baseurl deployment is
|
|
231
|
+
correct; absolute URLs are passed through untouched.
|
|
232
|
+
|
|
233
|
+
Emits NOTHING when the key is absent — the whitespace control here keeps a
|
|
234
|
+
page without `styles:` byte-identical to its previous build, which is why
|
|
235
|
+
the comment and the `endif` both close with `-%}`. {% endcomment -%}
|
|
236
|
+
{% if page.styles %}{% assign page_styles = page.styles %}{% if page_styles.first == nil %}{% assign page_styles = page_styles | split: "||" %}{% endif %}{% for style_href in page_styles %}
|
|
237
|
+
<link rel="stylesheet" href="{% if style_href contains '//' %}{{ style_href }}{% else %}{{ style_href | relative_url }}{% endif %}">
|
|
238
|
+
{%- endfor %}
|
|
239
|
+
{% endif -%}
|
|
240
|
+
|
|
217
241
|
{% comment %} Consumer hook: end of <head>. Inject JSON-LD, font preloads, verification
|
|
218
242
|
tags, or extra meta by shadowing _includes/custom/head.html in your site —
|
|
219
243
|
no fork of this file needed. The theme ships the stub empty. {% endcomment %}
|
data/_includes/core/header.html
CHANGED
|
@@ -210,15 +210,27 @@
|
|
|
210
210
|
{%- assign _menu_target = "#bdNavbar" -%}
|
|
211
211
|
{%- assign _menu_controls = "bdNavbar" -%}
|
|
212
212
|
{%- endif -%}
|
|
213
|
+
{% comment %} Below lg this is a LABELLED control, not a bare glyph (#405).
|
|
214
|
+
Two toggles coexist here — the sidebar hamburger and this one — and
|
|
215
|
+
three-dots vs. list gave no clue which opened what. The gear is
|
|
216
|
+
`d-lg-none`-excluded above (desktop only; on mobile its contents are
|
|
217
|
+
reached through the drawer), so labelling this one is what makes the
|
|
218
|
+
pair unambiguous.
|
|
219
|
+
aria-label matches the visible "Menu" text verbatim (rather than the
|
|
220
|
+
older, more descriptive "Toggle main navigation") so the accessible
|
|
221
|
+
name CONTAINS the visible label in every locale — WCAG 2.5.3 Label in
|
|
222
|
+
Name — instead of only coincidentally in English. {% endcomment %}
|
|
223
|
+
{%- assign _menu_toggle_label = ui.nav_menu_toggle_label | default: "Menu" -%}
|
|
213
224
|
<div class="bd-navbar-toggle d-lg-none">
|
|
214
|
-
<button class="navbar-toggler p-2"
|
|
225
|
+
<button class="navbar-toggler navbar-toggler-labeled p-2"
|
|
215
226
|
type="button"
|
|
216
227
|
data-bs-toggle="offcanvas"
|
|
217
228
|
data-bs-target="{{ _menu_target }}"
|
|
218
229
|
aria-controls="{{ _menu_controls }}"
|
|
219
|
-
aria-label="
|
|
230
|
+
aria-label="{{ _menu_toggle_label }}"
|
|
220
231
|
aria-expanded="false">
|
|
221
232
|
<span class="bi bi-three-dots" aria-hidden="true"></span>
|
|
233
|
+
<span class="navbar-toggler-text">{{ _menu_toggle_label }}</span>
|
|
222
234
|
</button>
|
|
223
235
|
</div>
|
|
224
236
|
</div>
|
|
@@ -16,21 +16,36 @@
|
|
|
16
16
|
|
|
17
17
|
Only emits a key if the config value is present, so the default cascade
|
|
18
18
|
remains intact when consumers omit `theme_color` from _config.yml.
|
|
19
|
+
|
|
20
|
+
Precedence (T-048 / #459): the active skin beats `theme_color`.
|
|
21
|
+
The skin palettes in _sass/theme/_skins.scss set --zer0-color-primary,
|
|
22
|
+
--zer0-color-link and --zer0-color-accent together with --bs-primary.
|
|
23
|
+
So the config values for those three tokens are only emitted when no
|
|
24
|
+
palette skin is active. Otherwise this block would win on source order,
|
|
25
|
+
because `:root` and `[data-theme-skin]` have the same specificity, and the
|
|
26
|
+
theme layer would disagree with Bootstrap. `palette_skins` must list every
|
|
27
|
+
skin that _skins.scss gives a palette; test/visual/features/appearance.spec.js
|
|
28
|
+
checks that.
|
|
19
29
|
===================================================================
|
|
20
30
|
{%- endcomment -%}
|
|
21
31
|
{%- assign tc = site.theme_color -%}
|
|
32
|
+
{%- assign palette_skins = "air,aqua,dirt,neon,mint,plum,sunrise" | split: "," -%}
|
|
22
33
|
{%- if tc -%}
|
|
23
34
|
<style id="zer0-tokens-inline">
|
|
24
35
|
:root {
|
|
25
|
-
{%- if tc.main %} --zer0-color-primary: {{ tc.main }};{%- endif %}
|
|
26
36
|
{%- if tc.secondary %} --zer0-color-secondary: {{ tc.secondary }};{%- endif %}
|
|
27
37
|
{%- if tc.red %} --zer0-color-danger: {{ tc.red }};{%- endif %}
|
|
28
38
|
{%- if tc.yellow %} --zer0-color-warning: {{ tc.yellow }};{%- endif %}
|
|
29
39
|
{%- if tc.green %} --zer0-color-success: {{ tc.green }};{%- endif %}
|
|
30
40
|
{%- if tc.teal %} --zer0-color-info: {{ tc.teal }};{%- endif %}
|
|
41
|
+
}
|
|
42
|
+
{%- if tc.main or tc.blue or tc.purple %}
|
|
43
|
+
:root{% for s in palette_skins %}:not([data-theme-skin="{{ s }}"]){% endfor %} {
|
|
44
|
+
{%- if tc.main %} --zer0-color-primary: {{ tc.main }};{%- endif %}
|
|
31
45
|
{%- if tc.blue %} --zer0-color-link: {{ tc.blue }};{%- endif %}
|
|
32
46
|
{%- if tc.purple %} --zer0-color-accent: {{ tc.purple }};{%- endif %}
|
|
33
47
|
}
|
|
48
|
+
{%- endif %}
|
|
34
49
|
</style>
|
|
35
50
|
{%- endif -%}
|
|
36
51
|
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
class="nav-link"
|
|
50
50
|
href="{{ link.url | relative_url }}"
|
|
51
51
|
aria-label="{{ link.title }}"
|
|
52
|
-
{
|
|
52
|
+
{% if link.url == page.url %}aria-current="page"{% endif %}
|
|
53
53
|
title="{{ link.title }}"
|
|
54
54
|
>
|
|
55
55
|
{%- if link.icon -%}
|
|
@@ -61,8 +61,12 @@
|
|
|
61
61
|
{% comment %} Split toggle opens the dropdown (click on mobile, hover on desktop) {% endcomment %}
|
|
62
62
|
{% comment %} Note: No data-bs-toggle="dropdown" — custom JS handles all interactions
|
|
63
63
|
to avoid Bootstrap's native handler conflicting with our toggle logic {% endcomment %}
|
|
64
|
+
{% comment %} No `ms-1`: at lg+ the chevron is absolutely positioned over the
|
|
65
|
+
parent link's reserved padding (_navbar.scss), so a left margin would
|
|
66
|
+
re-open the dead zone between label and chevron that #405 closed. Below
|
|
67
|
+
lg the offcanvas layout keeps them as ordinary siblings. {% endcomment %}
|
|
64
68
|
<button
|
|
65
|
-
class="nav-link dropdown-toggle dropdown-toggle-split
|
|
69
|
+
class="nav-link dropdown-toggle dropdown-toggle-split"
|
|
66
70
|
type="button"
|
|
67
71
|
id="dropdown-{{ link.title | slugify }}"
|
|
68
72
|
aria-expanded="false"
|
|
@@ -78,8 +82,7 @@
|
|
|
78
82
|
<a
|
|
79
83
|
class="dropdown-item"
|
|
80
84
|
href="{{ child.url | relative_url }}"
|
|
81
|
-
|
|
82
|
-
{%- if child.url == page.url -%} aria-current="page"{%- endif -%}
|
|
85
|
+
{% if child.url == page.url %}aria-current="page"{% endif %}
|
|
83
86
|
>
|
|
84
87
|
{%- if child.icon -%}
|
|
85
88
|
<i class="{{site.default_icon}} {{ child.icon }} me-2" aria-hidden="true"></i>
|
|
@@ -96,7 +99,7 @@
|
|
|
96
99
|
class="nav-link"
|
|
97
100
|
href="{{ link.url | relative_url }}"
|
|
98
101
|
aria-label="{{ link.title }}"
|
|
99
|
-
{
|
|
102
|
+
{% if link.url == page.url %}aria-current="page"{% endif %}
|
|
100
103
|
title="{{ link.title }}"
|
|
101
104
|
>
|
|
102
105
|
{%- if link.icon -%}
|
|
@@ -125,7 +128,7 @@
|
|
|
125
128
|
href="{{ col_url | relative_url }}"
|
|
126
129
|
aria-label="{{ col_title }}"
|
|
127
130
|
title="{{ col_title }}"
|
|
128
|
-
{
|
|
131
|
+
{% if page.collection == collection.label %}aria-current="page"{% endif %}
|
|
129
132
|
>
|
|
130
133
|
<span class="nav-link-text">{{ col_title }}</span>
|
|
131
134
|
</a>
|
data/_includes/setup/wizard.html
CHANGED
|
@@ -62,7 +62,11 @@
|
|
|
62
62
|
</p>
|
|
63
63
|
</div>
|
|
64
64
|
<div class="d-flex align-items-center gap-2">
|
|
65
|
-
|
|
65
|
+
{% comment %} No `hidden` here: the chip must hold its box from first paint, or the
|
|
66
|
+
first debounced save grows this header — permanently — and every offset below it
|
|
67
|
+
moves with it (issue #265). _setup-wizard.scss hides it with `visibility`
|
|
68
|
+
instead, which keeps the box and still keeps it out of the a11y tree. {% endcomment %}
|
|
69
|
+
<span id="wizard-draft-chip" class="wizard-draft-chip" aria-live="polite">
|
|
66
70
|
<i class="bi bi-cloud-check" aria-hidden="true"></i> Draft saved
|
|
67
71
|
</span>
|
|
68
72
|
<button class="btn btn-outline-secondary btn-sm" type="button" id="btn-reset"
|
data/_layouts/article.html
CHANGED
|
@@ -76,7 +76,12 @@ layout: default
|
|
|
76
76
|
{% endif %}
|
|
77
77
|
{% endif %}
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
{% comment %} No main role and no generic main id on this element: root.html already
|
|
80
|
+
provides the page's single <main id="main-content"> landmark, and a main role here
|
|
81
|
+
both duplicated it and suppressed this element's native `article` landmark (issue
|
|
82
|
+
#484, the same class of defect as #299). The generic id had no referents either —
|
|
83
|
+
the skip link targets #main-content. Asserted by test/visual/core/landmarks.spec.js. {% endcomment %}
|
|
84
|
+
<article class="post h-entry" itemscope itemtype="https://schema.org/BlogPosting">
|
|
80
85
|
|
|
81
86
|
{% comment %} ================================ {% endcomment %}
|
|
82
87
|
{% comment %} BREAKING NEWS ALERT BANNER {% endcomment %}
|
|
@@ -234,6 +239,10 @@ layout: default
|
|
|
234
239
|
post_type defaults (issue #303).
|
|
235
240
|
Nested ifs keep the "only when page.preview exists" guard intact (Liquid has
|
|
236
241
|
no parentheses, so a flat `a or b and c` would misgroup).
|
|
242
|
+
The hero is the above-the-fold LCP element, so it loads eagerly at high
|
|
243
|
+
priority (the include defaults to lazy); its box is reserved in CSS by
|
|
244
|
+
`.featured-hero img` (components/_ui-enhancements.scss), not by width/height
|
|
245
|
+
attributes, because preview assets vary in shape (issue #485).
|
|
237
246
|
{% endcomment %}
|
|
238
247
|
{% assign show_hero = false %}
|
|
239
248
|
{% if page.preview %}
|
|
@@ -247,7 +256,10 @@ layout: default
|
|
|
247
256
|
src=page.preview
|
|
248
257
|
alt=page.title
|
|
249
258
|
class="w-100 rounded-3 shadow"
|
|
250
|
-
style="max-height: 500px; object-fit: cover;"
|
|
259
|
+
style="max-height: 500px; object-fit: cover;"
|
|
260
|
+
loading="eager"
|
|
261
|
+
fetchpriority="high"
|
|
262
|
+
decoding="async"
|
|
251
263
|
%}
|
|
252
264
|
{% if page.preview_caption %}
|
|
253
265
|
<figcaption class="text-muted small mt-2 text-center">{{ page.preview_caption }}</figcaption>
|
|
@@ -469,7 +481,7 @@ layout: default
|
|
|
469
481
|
{% endif %}
|
|
470
482
|
<a href="{{ rpost.url | relative_url }}" class="text-decoration-none">
|
|
471
483
|
{% assign rpost_img = rpost.preview | default: site.teaser %}
|
|
472
|
-
{% include components/preview-image.html src=rpost_img alt=rpost.title class="card-img-top" %}
|
|
484
|
+
{% include components/preview-image.html src=rpost_img alt=rpost.title class="card-img-top" width="1536" height="1024" %}
|
|
473
485
|
</a>
|
|
474
486
|
</div>
|
|
475
487
|
<div class="card-body">
|
data/_layouts/home.html
CHANGED
|
@@ -62,7 +62,19 @@ layout: root
|
|
|
62
62
|
{%- if page.title and page.hide_title != true and page.hide_intro != true -%}
|
|
63
63
|
<h1 class="page-heading">{{ page.title }}</h1>
|
|
64
64
|
{%- endif -%}
|
|
65
|
-
|
|
65
|
+
|
|
66
|
+
{% comment %} ========================== {% endcomment %}
|
|
67
|
+
{% comment %} SITE SUBTITLE (HOME HERO) {% endcomment %}
|
|
68
|
+
{% comment %} ========================== {% endcomment %}
|
|
69
|
+
{% comment %} `site.subtitle` reads here, on the home hero, instead of in the {% endcomment %}
|
|
70
|
+
{% comment %} navbar, where it competed with the menubar for bar width (#405). {% endcomment %}
|
|
71
|
+
{% comment %} Suppressed alongside the heading, so a custom hero that supplies {% endcomment %}
|
|
72
|
+
{% comment %} its own copy is not doubled up. {% endcomment %}
|
|
73
|
+
{%- assign _subtitle = site.subtitle | strip -%}
|
|
74
|
+
{%- if _subtitle != "" and page.hide_title != true and page.hide_intro != true -%}
|
|
75
|
+
<p class="site-subtitle-text home-subtitle lead text-body-secondary">{{ _subtitle }}</p>
|
|
76
|
+
{%- endif -%}
|
|
77
|
+
|
|
66
78
|
{% comment %} ========================== {% endcomment %}
|
|
67
79
|
{% comment %} MAIN CONTENT AREA {% endcomment %}
|
|
68
80
|
{% comment %} ========================== {% endcomment %}
|
data/_layouts/note.html
CHANGED
|
@@ -38,7 +38,8 @@ layout: default
|
|
|
38
38
|
===================================================================
|
|
39
39
|
{% endcomment %}
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
{% comment %} No main role and no generic main id — see article.html (issue #484). {% endcomment %}
|
|
42
|
+
<article class="note-article h-entry" itemscope itemtype="https://schema.org/Article">
|
|
42
43
|
|
|
43
44
|
{% comment %} ================================ {% endcomment %}
|
|
44
45
|
{% comment %} NOTE HEADER {% endcomment %}
|
data/_layouts/notebook.html
CHANGED
|
@@ -34,7 +34,8 @@ layout: default
|
|
|
34
34
|
===================================================================
|
|
35
35
|
{% endcomment %}
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
{% comment %} No main role and no generic main id — see article.html (issue #484). {% endcomment %}
|
|
38
|
+
<article class="notebook-article h-entry" itemscope itemtype="https://schema.org/TechArticle">
|
|
38
39
|
|
|
39
40
|
{% comment %} ================================ {% endcomment %}
|
|
40
41
|
{% comment %} NOTEBOOK HEADER {% endcomment %}
|
data/_layouts/root.html
CHANGED
|
@@ -193,6 +193,28 @@
|
|
|
193
193
|
{% include_cached components/js-cdn.html %}
|
|
194
194
|
</div>
|
|
195
195
|
|
|
196
|
+
{% comment %} Per-page scripts from frontmatter (#412):
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
scripts:
|
|
200
|
+
- /assets/js/notes-clipper.js
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
The counterpart of `styles:` in _includes/core/head.html, loaded here
|
|
204
|
+
so a page script runs AFTER the theme's own bundle (js-cdn.html) and
|
|
205
|
+
can rely on the contracts it publishes — `zer0:code-block-ready`, for
|
|
206
|
+
one. Deferred, so it never blocks the parse. A single string is
|
|
207
|
+
accepted as well as a list; site-relative paths go through
|
|
208
|
+
relative_url, absolute URLs pass through untouched.
|
|
209
|
+
|
|
210
|
+
Emits NOTHING when the key is absent, so a page without `scripts:`
|
|
211
|
+
is byte-identical to its previous build — hence the `-%}` on both
|
|
212
|
+
the comment and the `endif`. {% endcomment -%}
|
|
213
|
+
{% if page.scripts %}{% assign page_scripts = page.scripts %}{% if page_scripts.first == nil %}{% assign page_scripts = page_scripts | split: "||" %}{% endif %}{% for script_src in page_scripts %}
|
|
214
|
+
<script defer src="{% if script_src contains '//' %}{{ script_src }}{% else %}{{ script_src | relative_url }}{% endif %}"></script>
|
|
215
|
+
{%- endfor %}
|
|
216
|
+
{% endif -%}
|
|
217
|
+
|
|
196
218
|
{% comment %} Analytics Integration {% endcomment %}
|
|
197
219
|
{%- include analytics/posthog.html -%}
|
|
198
220
|
|
|
@@ -38,3 +38,51 @@
|
|
|
38
38
|
transform: translateY(0);
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
+
|
|
42
|
+
// ----------------------------------------------------------------------------
|
|
43
|
+
// Compact bar layout
|
|
44
|
+
// ----------------------------------------------------------------------------
|
|
45
|
+
// The banner must never eat the first screen. Phones get a two-line summary
|
|
46
|
+
// over ONE row of three equal buttons (the old full-width stack was 324px at
|
|
47
|
+
// 400px wide); md+ is a single row, copy left and actions right. Button
|
|
48
|
+
// height is left to .btn-sm and the touch-device 44px minimum in
|
|
49
|
+
// components/_ui-enhancements.scss.
|
|
50
|
+
.cookie-consent-banner__inner {
|
|
51
|
+
display: flex;
|
|
52
|
+
flex-direction: column;
|
|
53
|
+
gap: 0.5rem;
|
|
54
|
+
padding-block: 0.5rem;
|
|
55
|
+
|
|
56
|
+
@media (min-width: 768px) {
|
|
57
|
+
flex-direction: row;
|
|
58
|
+
align-items: center;
|
|
59
|
+
gap: 1rem;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.cookie-consent-banner__text {
|
|
64
|
+
line-height: 1.35;
|
|
65
|
+
|
|
66
|
+
@media (min-width: 768px) {
|
|
67
|
+
flex: 1 1 auto;
|
|
68
|
+
min-width: 0;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.cookie-consent-banner__actions {
|
|
73
|
+
display: flex;
|
|
74
|
+
gap: 0.5rem;
|
|
75
|
+
|
|
76
|
+
.btn {
|
|
77
|
+
flex: 1 1 0;
|
|
78
|
+
white-space: nowrap;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@media (min-width: 768px) {
|
|
82
|
+
flex: 0 0 auto;
|
|
83
|
+
|
|
84
|
+
.btn {
|
|
85
|
+
flex: 0 0 auto;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -104,14 +104,32 @@
|
|
|
104
104
|
|
|
105
105
|
// "Draft saved" acknowledgement. Kept in the layout (no display toggling) so
|
|
106
106
|
// its appearance never shifts the header.
|
|
107
|
+
//
|
|
108
|
+
// That was always the intent of this rule, and for a long time it was only the
|
|
109
|
+
// intent: the markup carried `hidden` and setup-wizard.js cleared it on the
|
|
110
|
+
// first debounced save, so the chip went from `display: none` to a 26px box
|
|
111
|
+
// while the "Start over" `.btn-sm` beside it is 19px. The header grew and
|
|
112
|
+
// stayed grown, one-way, taking `#wizardTabContent` and every Back/Next row
|
|
113
|
+
// below it down with it (issue #265) — 7px where the header's right-hand block
|
|
114
|
+
// had already wrapped onto its own line, a whole wrapped line where those 7px
|
|
115
|
+
// were what tipped it over.
|
|
116
|
+
//
|
|
117
|
+
// `visibility` rather than `display` is what makes the prose above true: the
|
|
118
|
+
// box is reserved from first paint, so nothing moves when the chip appears,
|
|
119
|
+
// and `visibility: hidden` still keeps it out of the accessibility tree while
|
|
120
|
+
// it has nothing to say. The `visibility` transition is discrete — it flips to
|
|
121
|
+
// visible at the start of the fade in and back to hidden at the end of the
|
|
122
|
+
// fade out, so the text is never readable at opacity 0.
|
|
107
123
|
.wizard-draft-chip {
|
|
108
124
|
padding: 0.25rem 0.5rem;
|
|
109
125
|
font-size: 0.75rem;
|
|
110
126
|
color: var(--bs-success, #198754);
|
|
127
|
+
visibility: hidden;
|
|
111
128
|
opacity: 0;
|
|
112
|
-
transition: opacity 0.2s ease;
|
|
129
|
+
transition: opacity 0.2s ease, visibility 0.2s ease;
|
|
113
130
|
|
|
114
131
|
&.is-visible {
|
|
132
|
+
visibility: visible;
|
|
115
133
|
opacity: 1;
|
|
116
134
|
}
|
|
117
135
|
}
|
|
@@ -53,6 +53,17 @@ img {
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
// Article hero (_layouts/article.html, figure.featured-hero) — the LCP image.
|
|
57
|
+
// Reserve its box before it loads so the text below never shifts (issue #485).
|
|
58
|
+
// A fixed ratio in CSS rather than width/height attributes: preview assets are
|
|
59
|
+
// 3:2 (1024x683) generated, but some are portrait or external, and a declared
|
|
60
|
+
// intrinsic size that disagrees with the file would itself cause the shift.
|
|
61
|
+
// object-fit: cover (inline on the <img>) crops, never distorts; the inline
|
|
62
|
+
// max-height: 500px still caps the box on wide viewports.
|
|
63
|
+
.featured-hero img {
|
|
64
|
+
aspect-ratio: 3 / 2;
|
|
65
|
+
}
|
|
66
|
+
|
|
56
67
|
// Hero content animations
|
|
57
68
|
.bg-primary .container-xl {
|
|
58
69
|
animation: fadeInUp 0.8s ease-out;
|