jekyll-theme-zer0 1.31.0 → 1.31.2
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 +1048 -1035
- data/README.md +17 -3
- data/_data/backlog.yml +3 -3
- data/_data/i18n/fr.yml +30 -14
- data/_data/i18n/manifest.yml +63 -45
- data/_data/theme-manifest.yml +11 -11
- data/_includes/components/halfmoon.html +5 -3
- data/_includes/content/intro.html +14 -3
- data/_includes/core/footer.html +3 -1
- data/_includes/core/head.html +4 -17
- data/_includes/navigation/breadcrumbs.html +21 -15
- data/_sass/core/_navbar.scss +30 -4
- data/_sass/theme/_skins.scss +6 -2
- data/assets/js/halfmoon.js +11 -1
- data/assets/js/search-modal.js +35 -3
- data/scripts/bin/validate +1 -0
- data/scripts/validate-features.rb +5 -8
- metadata +2 -2
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
validity). The trail uses BreadcrumbList microdata, so it's eligible for
|
|
12
12
|
Google rich results, and the first crumb label is sourced from
|
|
13
13
|
site.data.ui-text for i18n.
|
|
14
|
+
|
|
15
|
+
Crumbs that aren't links are visible but omitted from the
|
|
16
|
+
BreadcrumbList, because a ListItem without a resolvable `item`
|
|
17
|
+
invalidates the list (#512). `bc_pos` counts only structured items,
|
|
18
|
+
so `position` stays contiguous (1..n).
|
|
14
19
|
===================================================================
|
|
15
20
|
{%- endcomment -%}
|
|
16
21
|
{% if page.url != "/" and site.breadcrumbs %}
|
|
@@ -19,6 +24,7 @@
|
|
|
19
24
|
{%- assign label_aria = ui.breadcrumbs_aria | default: "Breadcrumb" -%}
|
|
20
25
|
{%- assign crumbs = page.url | remove:'/index.html' | split: '/' -%}
|
|
21
26
|
{%- assign section = crumbs[1] | default: '' -%}
|
|
27
|
+
{%- assign bc_pos = 1 -%}
|
|
22
28
|
|
|
23
29
|
<nav class="breadcrumbs" aria-label="{{ label_aria }}">
|
|
24
30
|
<ol class="breadcrumb mb-0" itemscope itemtype="https://schema.org/BreadcrumbList">
|
|
@@ -50,41 +56,41 @@
|
|
|
50
56
|
{%- if _section_page -%}{%- break -%}{%- endif -%}
|
|
51
57
|
{%- endfor -%}
|
|
52
58
|
{%- endunless -%}
|
|
53
|
-
|
|
54
|
-
{%-
|
|
59
|
+
{%- if _section_page -%}
|
|
60
|
+
{%- assign bc_pos = bc_pos | plus: 1 -%}
|
|
61
|
+
<li class="breadcrumb-item" itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
|
|
55
62
|
<a href="{{ _section_url | relative_url }}" itemprop="item">
|
|
56
63
|
<span itemprop="name">{{ section_label }}</span>
|
|
57
64
|
</a>
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
<
|
|
62
|
-
|
|
65
|
+
<meta itemprop="position" content="{{ bc_pos }}" />
|
|
66
|
+
</li>
|
|
67
|
+
{%- else -%}
|
|
68
|
+
<li class="breadcrumb-item">{{ section_label }}</li>
|
|
69
|
+
{%- endif -%}
|
|
70
|
+
{%- assign bc_pos = bc_pos | plus: 1 -%}
|
|
63
71
|
<li class="breadcrumb-item active" aria-current="page" itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
|
|
64
72
|
<span itemprop="name">{{ page.title }}</span>
|
|
65
|
-
<meta itemprop="position" content="
|
|
73
|
+
<meta itemprop="position" content="{{ bc_pos }}" />
|
|
66
74
|
</li>
|
|
67
75
|
{%- else -%}
|
|
68
76
|
{%- for crumb in crumbs offset: 1 -%}
|
|
69
|
-
{%- assign position = forloop.index | plus: 1 -%}
|
|
70
77
|
{%- assign crumb_label = crumb | replace:'-',' ' | remove:'.html' | capitalize -%}
|
|
71
78
|
{%- if forloop.last -%}
|
|
79
|
+
{%- assign bc_pos = bc_pos | plus: 1 -%}
|
|
72
80
|
<li class="breadcrumb-item active" aria-current="page" itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
|
|
73
81
|
<span itemprop="name">{{ page.title }}</span>
|
|
74
|
-
<meta itemprop="position" content="{{
|
|
82
|
+
<meta itemprop="position" content="{{ bc_pos }}" />
|
|
75
83
|
</li>
|
|
76
84
|
{%- elsif forloop.first -%}
|
|
85
|
+
{%- assign bc_pos = bc_pos | plus: 1 -%}
|
|
77
86
|
<li class="breadcrumb-item" itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
|
|
78
87
|
<a href="{{ '/' | append: crumb | append: '/' | relative_url }}" itemprop="item">
|
|
79
88
|
<span itemprop="name">{{ crumb_label }}</span>
|
|
80
89
|
</a>
|
|
81
|
-
<meta itemprop="position" content="{{
|
|
90
|
+
<meta itemprop="position" content="{{ bc_pos }}" />
|
|
82
91
|
</li>
|
|
83
92
|
{%- else -%}
|
|
84
|
-
<li class="breadcrumb-item"
|
|
85
|
-
<span itemprop="name">{{ crumb_label }}</span>
|
|
86
|
-
<meta itemprop="position" content="{{ position }}" />
|
|
87
|
-
</li>
|
|
93
|
+
<li class="breadcrumb-item">{{ crumb_label }}</li>
|
|
88
94
|
{%- endif -%}
|
|
89
95
|
{%- endfor -%}
|
|
90
96
|
{%- endif -%}
|
data/_sass/core/_navbar.scss
CHANGED
|
@@ -502,9 +502,12 @@
|
|
|
502
502
|
position: relative;
|
|
503
503
|
}
|
|
504
504
|
|
|
505
|
+
// 1.5rem (24px) is the WCAG 2.2 SC 2.5.8 target-size minimum: the
|
|
506
|
+
// chevron overlay below is this wide, so the link reserves the same.
|
|
507
|
+
// (It was 1.25rem = 20px, which axe flagged as `target-size`.)
|
|
505
508
|
#bdNavbar .nav-hover-dropdown > .nav-link:first-child {
|
|
506
509
|
border-radius: 0.375rem;
|
|
507
|
-
padding-right: 1.
|
|
510
|
+
padding-right: 1.5rem;
|
|
508
511
|
}
|
|
509
512
|
|
|
510
513
|
#bdNavbar .nav-hover-dropdown > .dropdown-toggle-split {
|
|
@@ -516,9 +519,9 @@
|
|
|
516
519
|
display: flex;
|
|
517
520
|
align-items: center;
|
|
518
521
|
justify-content: center;
|
|
519
|
-
width: 1.
|
|
520
|
-
min-width: 1.
|
|
521
|
-
min-height:
|
|
522
|
+
width: 1.5rem;
|
|
523
|
+
min-width: 1.5rem;
|
|
524
|
+
min-height: 1.5rem;
|
|
522
525
|
margin: 0 !important;
|
|
523
526
|
padding: 0;
|
|
524
527
|
background-color: transparent;
|
|
@@ -1367,6 +1370,29 @@
|
|
|
1367
1370
|
vertical-align: middle;
|
|
1368
1371
|
}
|
|
1369
1372
|
|
|
1373
|
+
// Phones: at the 20px .navbar-brand size a typical title loses its tail
|
|
1374
|
+
// once the sidebar toggle, logo, search and labelled Menu toggle share the
|
|
1375
|
+
// bar ("Lifehacker.dev" -> "Lifehacker…" on post pages at 400px, 121px
|
|
1376
|
+
// available for 133px of text). Step the title down so it fits; the
|
|
1377
|
+
// ellipsis above still covers titles that are longer still. The logo's
|
|
1378
|
+
// Bootstrap margin-end (1rem) also stacks on the brand group's 0.5rem gap
|
|
1379
|
+
// there; the gap alone is enough spacing and hands the title 16px back.
|
|
1380
|
+
@media (max-width: 575.98px) {
|
|
1381
|
+
#navbar .site-title-text {
|
|
1382
|
+
font-size: 1rem;
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
#navbar .navbar-brand-group > .navbar-brand {
|
|
1386
|
+
margin-right: 0;
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
@media (max-width: 374.98px) {
|
|
1391
|
+
#navbar .site-title-text {
|
|
1392
|
+
font-size: 0.9375rem;
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1370
1396
|
#navbar .navbar-toggler {
|
|
1371
1397
|
width: 2.5rem;
|
|
1372
1398
|
height: 2.5rem;
|
data/_sass/theme/_skins.scss
CHANGED
|
@@ -78,11 +78,15 @@
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
// Links (Bootstrap + theme)
|
|
81
|
-
a
|
|
81
|
+
// `.badge` is excluded so tag/category chips rendered as <a class="badge
|
|
82
|
+
// bg-*"> keep Bootstrap's badge text colour (--bs-badge-color, #fff) instead
|
|
83
|
+
// of the skin link colour. On neon that was #5e22cc on bg-primary #8338ec
|
|
84
|
+
// (1.45:1) and on bg-secondary (1.74:1).
|
|
85
|
+
a:not(.btn):not(.nav-link):not(.dropdown-item):not(.badge) {
|
|
82
86
|
color: var(--bs-link-color);
|
|
83
87
|
}
|
|
84
88
|
|
|
85
|
-
a:not(.btn):hover {
|
|
89
|
+
a:not(.btn):not(.badge):hover {
|
|
86
90
|
color: var(--bs-link-hover-color);
|
|
87
91
|
}
|
|
88
92
|
|
data/assets/js/halfmoon.js
CHANGED
|
@@ -39,6 +39,16 @@
|
|
|
39
39
|
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
// The MODE the visitor has selected (light/dark/auto) — what the toggle
|
|
43
|
+
// buttons represent. Unlike getPreferredTheme() it never resolves `auto`
|
|
44
|
+
// to the OS scheme, so a first visit on an auto site marks Auto (#521).
|
|
45
|
+
const getPreferredMode = () => {
|
|
46
|
+
if (isLocked) {
|
|
47
|
+
return configDefault
|
|
48
|
+
}
|
|
49
|
+
return getStoredTheme() || configDefault
|
|
50
|
+
}
|
|
51
|
+
|
|
42
52
|
const setTheme = theme => {
|
|
43
53
|
if (theme === 'auto') {
|
|
44
54
|
document.documentElement.setAttribute('data-bs-theme', (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'))
|
|
@@ -95,7 +105,7 @@
|
|
|
95
105
|
})
|
|
96
106
|
|
|
97
107
|
window.addEventListener('DOMContentLoaded', () => {
|
|
98
|
-
showActiveTheme(
|
|
108
|
+
showActiveTheme(getPreferredMode())
|
|
99
109
|
|
|
100
110
|
document.querySelectorAll('[data-bs-theme-value]')
|
|
101
111
|
.forEach(toggle => {
|
data/assets/js/search-modal.js
CHANGED
|
@@ -73,8 +73,32 @@
|
|
|
73
73
|
bootstrap.Modal.getOrCreateInstance(modalEl).show();
|
|
74
74
|
};
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
// Where focus goes when the modal closes. Bootstrap only restores
|
|
77
|
+
// focus for data-bs-toggle triggers, and this modal is opened from
|
|
78
|
+
// code ("/", Ctrl/Cmd+K, the toggle's click handler), so on Escape
|
|
79
|
+
// focus used to fall back to <body> (WCAG 2.4.3 Focus Order).
|
|
80
|
+
let returnFocusTo = null;
|
|
81
|
+
|
|
82
|
+
const isVisible = (el) => !!el && el.isConnected && el.getClientRects().length > 0;
|
|
83
|
+
|
|
84
|
+
const rememberOpener = (preferred) => {
|
|
85
|
+
const active = document.activeElement;
|
|
86
|
+
if (preferred && isVisible(preferred)) {
|
|
87
|
+
returnFocusTo = preferred;
|
|
88
|
+
} else if (active && active !== document.body && !modalEl.contains(active) && isVisible(active)) {
|
|
89
|
+
returnFocusTo = active;
|
|
90
|
+
} else {
|
|
91
|
+
// Opened from the keyboard with nothing focused: return to
|
|
92
|
+
// the visible Search button, the control that owns the dialog.
|
|
93
|
+
returnFocusTo = Array.from(document.querySelectorAll('[data-search-toggle]')).find(isVisible) || null;
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const openModal = (preferredOpener) => {
|
|
77
98
|
if (typeof bootstrap === 'undefined') return;
|
|
99
|
+
if (!modalEl.classList.contains('show')) {
|
|
100
|
+
rememberOpener(preferredOpener instanceof Element ? preferredOpener : null);
|
|
101
|
+
}
|
|
78
102
|
const cookieEl = document.getElementById('cookieSettingsModal');
|
|
79
103
|
const infoEl = document.getElementById('info-section');
|
|
80
104
|
const drawerEl = document.getElementById('zer0UnifiedDrawer');
|
|
@@ -102,13 +126,13 @@
|
|
|
102
126
|
}
|
|
103
127
|
|
|
104
128
|
// Open modal when keyboard shortcut requests search
|
|
105
|
-
document.addEventListener('navigation:searchRequest', openModal);
|
|
129
|
+
document.addEventListener('navigation:searchRequest', () => openModal());
|
|
106
130
|
|
|
107
131
|
// Open modal when clicking a search toggle button
|
|
108
132
|
document.querySelectorAll('[data-search-toggle]').forEach((button) => {
|
|
109
133
|
button.addEventListener('click', (event) => {
|
|
110
134
|
event.preventDefault();
|
|
111
|
-
openModal();
|
|
135
|
+
openModal(button);
|
|
112
136
|
});
|
|
113
137
|
});
|
|
114
138
|
|
|
@@ -152,6 +176,14 @@
|
|
|
152
176
|
searchInput.value = '';
|
|
153
177
|
}
|
|
154
178
|
clearResults();
|
|
179
|
+
// Only restore focus if nothing else claimed it (e.g. a result
|
|
180
|
+
// link navigating away, or the info-section offcanvas opening).
|
|
181
|
+
const target = returnFocusTo;
|
|
182
|
+
returnFocusTo = null;
|
|
183
|
+
const active = document.activeElement;
|
|
184
|
+
if (target && isVisible(target) && (!active || active === document.body || modalEl.contains(active))) {
|
|
185
|
+
try { target.focus({ preventScroll: true }); } catch (e) { target.focus(); }
|
|
186
|
+
}
|
|
155
187
|
});
|
|
156
188
|
|
|
157
189
|
// Prevent empty submissions, and keep submissions in-modal when the
|
data/scripts/bin/validate
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
# .github/instructions/features.instructions.md.
|
|
10
10
|
#
|
|
11
11
|
# HARD failures (exit 1):
|
|
12
|
-
# -
|
|
12
|
+
# - _data/features.yml (the single registry) is missing
|
|
13
13
|
# - the header is missing a `# Last Updated:` line
|
|
14
14
|
# - schema violations (missing required field, bad id/version, duplicate id)
|
|
15
15
|
# - implemented:false without `removed_in:`
|
|
@@ -28,7 +28,6 @@
|
|
|
28
28
|
require 'date'
|
|
29
29
|
require 'yaml'
|
|
30
30
|
|
|
31
|
-
MASTER = 'features/features.yml'
|
|
32
31
|
MIRROR = '_data/features.yml'
|
|
33
32
|
REQUIRED = %w[id title description implemented version link docs tags date].freeze
|
|
34
33
|
STRICT = ENV['FEATURES_STRICT'] == '1'
|
|
@@ -52,12 +51,10 @@ def ref_paths(refs)
|
|
|
52
51
|
refs.values.flat_map { |v| v.is_a?(Array) ? v : [v] }.select { |v| v.is_a?(String) }
|
|
53
52
|
end
|
|
54
53
|
|
|
55
|
-
|
|
54
|
+
die "#{MIRROR} is missing" unless File.file?(MIRROR)
|
|
56
55
|
|
|
57
|
-
# 1.
|
|
58
|
-
|
|
59
|
-
die "#{MASTER} and #{MIRROR} differ — run `cp #{MASTER} #{MIRROR}` (must be byte-identical)"
|
|
60
|
-
end
|
|
56
|
+
# 1. Single registry — _data/features.yml is the only copy (the former
|
|
57
|
+
# features/features.yml master was removed as a duplicate, UPS-WORK-06).
|
|
61
58
|
|
|
62
59
|
data = load_yaml(MIRROR)
|
|
63
60
|
feats = data && data['features']
|
|
@@ -146,4 +143,4 @@ unless warnings.empty?
|
|
|
146
143
|
end
|
|
147
144
|
|
|
148
145
|
active = feats.count { |f| f['implemented'] != false }
|
|
149
|
-
puts "Feature registry valid: #{feats.length} entries (#{active} active), refs resolved,
|
|
146
|
+
puts "Feature registry valid: #{feats.length} entries (#{active} active), refs resolved, _data registry (single copy)"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jekyll-theme-zer0
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.31.
|
|
4
|
+
version: 1.31.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Amr Abdel
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-18 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: jekyll
|