@abreen/tada 1.16.8 → 1.16.10
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.
- package/build/material-symbols.ts +0 -10
- package/package.json +1 -1
- package/src/appearance-picker/index.ts +114 -24
- package/src/appearance-picker/style.scss +21 -16
- package/src/header/README.md +5 -3
- package/src/header/_base.scss +66 -9
- package/src/search/style.scss +13 -0
- package/templates/_appearance-picker.html +12 -16
- package/templates/_top.html +90 -3
- package/assets/material-symbols/close_24px.svg +0 -1
- package/assets/material-symbols/menu_24px.svg +0 -1
|
@@ -68,16 +68,6 @@ export const MATERIAL_SYMBOLS = Object.freeze({
|
|
|
68
68
|
symbol: 'smart_display',
|
|
69
69
|
opticalSize: 24,
|
|
70
70
|
}),
|
|
71
|
-
headerMenu: Object.freeze({
|
|
72
|
-
cssVariable: '--icon-header-menu',
|
|
73
|
-
symbol: 'menu',
|
|
74
|
-
opticalSize: 24,
|
|
75
|
-
}),
|
|
76
|
-
headerClose: Object.freeze({
|
|
77
|
-
cssVariable: '--icon-header-close',
|
|
78
|
-
symbol: 'close',
|
|
79
|
-
opticalSize: 24,
|
|
80
|
-
}),
|
|
81
71
|
} as const);
|
|
82
72
|
|
|
83
73
|
type MaterialSymbol = (typeof MATERIAL_SYMBOLS)[keyof typeof MATERIAL_SYMBOLS];
|
package/package.json
CHANGED
|
@@ -4,6 +4,31 @@ export type ContrastPreference = 'standard' | 'high';
|
|
|
4
4
|
const FONT_STORAGE_KEY = 'fontPreference';
|
|
5
5
|
const CONTRAST_STORAGE_KEY = 'contrastPreference';
|
|
6
6
|
|
|
7
|
+
interface FontPreferenceRequest {
|
|
8
|
+
generation: number;
|
|
9
|
+
preference: FontPreference;
|
|
10
|
+
promise: Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface FontPreferenceLoader {
|
|
14
|
+
supported: boolean;
|
|
15
|
+
pending: FontPreferenceRequest | null;
|
|
16
|
+
failedPreference: FontPreference | null;
|
|
17
|
+
request(preference: FontPreference): FontPreferenceRequest | null;
|
|
18
|
+
isCurrent(request: FontPreferenceRequest): boolean;
|
|
19
|
+
complete(request: FontPreferenceRequest): void;
|
|
20
|
+
fail(request: FontPreferenceRequest): void;
|
|
21
|
+
cancel(): void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getFontPreferenceLoader(
|
|
25
|
+
window: Window,
|
|
26
|
+
): FontPreferenceLoader | undefined {
|
|
27
|
+
return (
|
|
28
|
+
window as Window & { __tadaFontPreferenceLoader?: FontPreferenceLoader }
|
|
29
|
+
).__tadaFontPreferenceLoader;
|
|
30
|
+
}
|
|
31
|
+
|
|
7
32
|
function getStorage(window: Window): Storage | null {
|
|
8
33
|
try {
|
|
9
34
|
return window.localStorage;
|
|
@@ -91,6 +116,12 @@ function getDefaultFontPreference(document: Document): FontPreference {
|
|
|
91
116
|
: 'sans';
|
|
92
117
|
}
|
|
93
118
|
|
|
119
|
+
function getAppliedFontPreference(document: Document): FontPreference {
|
|
120
|
+
return document.documentElement.dataset.fontPreference === 'serif'
|
|
121
|
+
? 'serif'
|
|
122
|
+
: 'sans';
|
|
123
|
+
}
|
|
124
|
+
|
|
94
125
|
function getDefaultContrastPreference(window: Window): ContrastPreference {
|
|
95
126
|
const configured =
|
|
96
127
|
window.document.documentElement.dataset.defaultContrastPreference === 'high'
|
|
@@ -138,21 +169,19 @@ export function applyContrastPreference(
|
|
|
138
169
|
}
|
|
139
170
|
}
|
|
140
171
|
|
|
141
|
-
function
|
|
172
|
+
function syncSwitch(
|
|
142
173
|
document: Document,
|
|
143
174
|
selector: string,
|
|
144
|
-
|
|
175
|
+
checked: boolean,
|
|
176
|
+
title: string,
|
|
145
177
|
): void {
|
|
146
|
-
document.
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
),
|
|
154
|
-
);
|
|
155
|
-
});
|
|
178
|
+
const control = document.querySelector<HTMLButtonElement>(selector);
|
|
179
|
+
if (!control) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
control.disabled = false;
|
|
183
|
+
control.setAttribute('aria-checked', String(checked));
|
|
184
|
+
control.title = title;
|
|
156
185
|
}
|
|
157
186
|
|
|
158
187
|
function syncAppearance(
|
|
@@ -160,11 +189,19 @@ function syncAppearance(
|
|
|
160
189
|
fontPreference: FontPreference,
|
|
161
190
|
contrastPreference: ContrastPreference,
|
|
162
191
|
): void {
|
|
163
|
-
|
|
164
|
-
syncControls(
|
|
192
|
+
syncSwitch(
|
|
165
193
|
document,
|
|
166
|
-
'[data-
|
|
167
|
-
|
|
194
|
+
'[data-font-preference-switch]',
|
|
195
|
+
fontPreference === 'serif',
|
|
196
|
+
fontPreference === 'serif' ? 'Use sans-serif fonts' : 'Use serif fonts',
|
|
197
|
+
);
|
|
198
|
+
syncSwitch(
|
|
199
|
+
document,
|
|
200
|
+
'[data-contrast-preference-switch]',
|
|
201
|
+
contrastPreference === 'high',
|
|
202
|
+
contrastPreference === 'high'
|
|
203
|
+
? 'Use standard contrast'
|
|
204
|
+
: 'Use high contrast',
|
|
168
205
|
);
|
|
169
206
|
}
|
|
170
207
|
|
|
@@ -179,29 +216,82 @@ export default function mountAppearancePicker(window: Window): () => void {
|
|
|
179
216
|
container.querySelectorAll<HTMLButtonElement>('button'),
|
|
180
217
|
);
|
|
181
218
|
const storage = getStorage(window);
|
|
219
|
+
const fontLoader = getFontPreferenceLoader(window);
|
|
182
220
|
const defaultFontPreference = getDefaultFontPreference(document);
|
|
183
221
|
const defaultContrastPreference = getDefaultContrastPreference(window);
|
|
184
|
-
|
|
222
|
+
const requestedFontPreference = getFontPreference(
|
|
223
|
+
storage,
|
|
224
|
+
defaultFontPreference,
|
|
225
|
+
);
|
|
226
|
+
let fontPreference = getAppliedFontPreference(document);
|
|
185
227
|
let contrastPreference = getContrastPreference(
|
|
186
228
|
storage,
|
|
187
229
|
defaultContrastPreference,
|
|
188
230
|
);
|
|
189
231
|
|
|
190
|
-
|
|
232
|
+
const requestedFontIsWaiting =
|
|
233
|
+
(fontLoader?.pending?.preference === requestedFontPreference ||
|
|
234
|
+
fontLoader?.failedPreference === requestedFontPreference) ??
|
|
235
|
+
false;
|
|
236
|
+
if (!requestedFontIsWaiting) {
|
|
237
|
+
fontPreference = requestedFontPreference;
|
|
238
|
+
applyFontPreference(document, fontPreference);
|
|
239
|
+
}
|
|
191
240
|
applyContrastPreference(document, contrastPreference);
|
|
192
241
|
syncAppearance(document, fontPreference, contrastPreference);
|
|
193
242
|
|
|
243
|
+
const observedRequests = new Set<number>();
|
|
244
|
+
const observeFontRequest = (request: FontPreferenceRequest) => {
|
|
245
|
+
if (observedRequests.has(request.generation)) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
observedRequests.add(request.generation);
|
|
249
|
+
request.promise.then(
|
|
250
|
+
() => {
|
|
251
|
+
if (fontLoader?.isCurrent(request)) {
|
|
252
|
+
fontPreference = request.preference;
|
|
253
|
+
saveFontPreference(storage, fontPreference, defaultFontPreference);
|
|
254
|
+
applyFontPreference(document, fontPreference);
|
|
255
|
+
fontLoader.complete(request);
|
|
256
|
+
} else {
|
|
257
|
+
fontPreference = getAppliedFontPreference(document);
|
|
258
|
+
}
|
|
259
|
+
syncAppearance(document, fontPreference, contrastPreference);
|
|
260
|
+
},
|
|
261
|
+
() => {
|
|
262
|
+
fontLoader?.fail(request);
|
|
263
|
+
fontPreference = getAppliedFontPreference(document);
|
|
264
|
+
syncAppearance(document, fontPreference, contrastPreference);
|
|
265
|
+
},
|
|
266
|
+
);
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
if (fontLoader?.pending) {
|
|
270
|
+
observeFontRequest(fontLoader.pending);
|
|
271
|
+
}
|
|
272
|
+
|
|
194
273
|
const handleClick = (event: Event) => {
|
|
195
274
|
const button = event.currentTarget as HTMLButtonElement;
|
|
196
|
-
const fontValue = button.dataset.fontPreferenceValue;
|
|
197
|
-
const contrastValue = button.dataset.contrastPreferenceValue;
|
|
198
275
|
|
|
199
|
-
if (
|
|
200
|
-
fontPreference =
|
|
276
|
+
if (button.dataset.fontPreferenceSwitch !== undefined) {
|
|
277
|
+
fontPreference = getAppliedFontPreference(document);
|
|
278
|
+
if (fontLoader?.pending) {
|
|
279
|
+
fontLoader?.cancel();
|
|
280
|
+
saveFontPreference(storage, fontPreference, defaultFontPreference);
|
|
281
|
+
syncAppearance(document, fontPreference, contrastPreference);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
const nextFontPreference = fontPreference === 'serif' ? 'sans' : 'serif';
|
|
285
|
+
const request = fontLoader?.request(nextFontPreference) ?? null;
|
|
286
|
+
if (request) {
|
|
287
|
+
observeFontRequest(request);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
fontPreference = nextFontPreference;
|
|
201
291
|
saveFontPreference(storage, fontPreference, defaultFontPreference);
|
|
202
292
|
applyFontPreference(document, fontPreference);
|
|
203
|
-
} else if (
|
|
204
|
-
contrastPreference =
|
|
293
|
+
} else if (button.dataset.contrastPreferenceSwitch !== undefined) {
|
|
294
|
+
contrastPreference = contrastPreference === 'high' ? 'standard' : 'high';
|
|
205
295
|
saveContrastPreference(
|
|
206
296
|
storage,
|
|
207
297
|
contrastPreference,
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
width: fit-content;
|
|
22
22
|
padding: var(--preference-picker-padding);
|
|
23
23
|
background: var(--bg2-color);
|
|
24
|
+
border: 0;
|
|
24
25
|
border-radius: calc(var(--border-radius) + 4px);
|
|
25
26
|
}
|
|
26
27
|
|
|
@@ -46,15 +47,15 @@
|
|
|
46
47
|
transform: translateX(calc(100% + var(--preference-picker-gap)));
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
.preference-picker:
|
|
50
|
-
.preference-picker-option[aria-pressed='true']:is(:hover, :focus)
|
|
51
|
-
)::before {
|
|
50
|
+
.preference-picker:is(:hover, :focus)::before {
|
|
52
51
|
background: var(--bg2-color);
|
|
53
52
|
}
|
|
54
53
|
|
|
55
|
-
.preference-picker:
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
.preference-picker:active:not(:disabled) {
|
|
55
|
+
background: var(--bg2-color) !important;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.preference-picker:active:not(:disabled)::before {
|
|
58
59
|
background: var(--link-active-color);
|
|
59
60
|
}
|
|
60
61
|
|
|
@@ -65,9 +66,8 @@
|
|
|
65
66
|
align-items: center;
|
|
66
67
|
justify-content: center;
|
|
67
68
|
width: var(--preference-picker-option-width);
|
|
68
|
-
height:
|
|
69
|
+
height: 1.8rem;
|
|
69
70
|
padding: 0;
|
|
70
|
-
font-size: var(--font-size-smaller);
|
|
71
71
|
color: var(--fg2-color);
|
|
72
72
|
background: transparent;
|
|
73
73
|
border: 0;
|
|
@@ -100,17 +100,22 @@
|
|
|
100
100
|
font-size-adjust: var(--font-size-adjust);
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
:root:not([data-font-preference='serif']) .font-picker-option-sans,
|
|
104
|
+
:root[data-font-preference='serif'] .font-picker-option-serif,
|
|
105
|
+
:root:not([data-contrast-preference='high']) .contrast-picker-option-standard,
|
|
106
|
+
:root[data-contrast-preference='high'] .contrast-picker-option-high {
|
|
104
107
|
color: var(--fg-color);
|
|
105
|
-
background: transparent;
|
|
106
108
|
}
|
|
107
109
|
|
|
108
|
-
.preference-picker
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
110
|
+
.preference-picker:active:not(:disabled) {
|
|
111
|
+
:root:not([data-font-preference='serif']) & .font-picker-option-sans,
|
|
112
|
+
:root[data-font-preference='serif'] & .font-picker-option-serif,
|
|
113
|
+
:root:not([data-contrast-preference='high'])
|
|
114
|
+
&
|
|
115
|
+
.contrast-picker-option-standard,
|
|
116
|
+
:root[data-contrast-preference='high'] & .contrast-picker-option-high {
|
|
117
|
+
color: var(--bg-color);
|
|
118
|
+
}
|
|
114
119
|
}
|
|
115
120
|
|
|
116
121
|
@media print {
|
package/src/header/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# The `header` component
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
CSS morphs the header's three-stroke inline SVG between menu and close states
|
|
4
|
+
and, where supported, animates its `<details>` content. The component's
|
|
5
|
+
JavaScript adds dismissal behavior for outside clicks, focus leaving the menu,
|
|
6
|
+
and the Escape key.
|
|
5
7
|
|
|
6
8
|
This component doesn't provide any other functionality related to the header.
|
|
7
9
|
If JavaScript is turned off in the web browser, the `<details>` element is still
|
|
8
|
-
functional
|
|
10
|
+
functional and its disclosure content opens immediately.
|
package/src/header/_base.scss
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
:root {
|
|
4
4
|
--header-collapsed-height: 45px;
|
|
5
|
+
--header-menu-icon-size: 24px;
|
|
5
6
|
}
|
|
6
7
|
|
|
7
8
|
header {
|
|
@@ -75,21 +76,77 @@ header details summary::marker {
|
|
|
75
76
|
content: '';
|
|
76
77
|
}
|
|
77
78
|
|
|
78
|
-
header summary
|
|
79
|
-
flex: 0 0 var(--
|
|
79
|
+
header summary .menu-icon {
|
|
80
|
+
flex: 0 0 var(--header-menu-icon-size);
|
|
80
81
|
order: -1;
|
|
81
|
-
width: var(--
|
|
82
|
-
height: var(--
|
|
82
|
+
width: var(--header-menu-icon-size);
|
|
83
|
+
height: var(--header-menu-icon-size);
|
|
83
84
|
margin-right: 8px;
|
|
84
85
|
margin-left: var(--gap);
|
|
85
|
-
|
|
86
|
+
overflow: visible;
|
|
87
|
+
pointer-events: none;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
header summary .menu-icon-line {
|
|
91
|
+
transform-origin: center;
|
|
92
|
+
transform-box: fill-box;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
96
|
+
header summary .menu-icon-line {
|
|
97
|
+
transition: transform 200ms cubic-bezier(0.2, 0, 0, 1);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
header summary .menu-icon-line-middle {
|
|
101
|
+
transition:
|
|
102
|
+
opacity 150ms ease-out,
|
|
103
|
+
transform 200ms cubic-bezier(0.2, 0, 0, 1);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
header details[open] summary .menu-icon-line-top {
|
|
108
|
+
transform: translateY(6px) rotate(45deg);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
header details[open] summary .menu-icon-line-middle {
|
|
112
|
+
opacity: 0;
|
|
113
|
+
transform: scaleX(0.8);
|
|
114
|
+
}
|
|
86
115
|
|
|
87
|
-
|
|
116
|
+
header details[open] summary .menu-icon-line-bottom {
|
|
117
|
+
transform: translateY(-6px) rotate(-45deg);
|
|
88
118
|
}
|
|
89
119
|
|
|
90
|
-
|
|
91
|
-
-
|
|
92
|
-
|
|
120
|
+
@supports selector(details::details-content) and
|
|
121
|
+
(transition-behavior: allow-discrete) {
|
|
122
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
123
|
+
:root.js header > details {
|
|
124
|
+
display: grid;
|
|
125
|
+
grid-template-rows: var(--header-collapsed-height) 0fr;
|
|
126
|
+
grid-template-columns: minmax(0, 1fr);
|
|
127
|
+
transition: grid-template-rows 200ms cubic-bezier(0.2, 0, 0, 1);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
:root.js header > details[open] {
|
|
131
|
+
grid-template-rows: var(--header-collapsed-height) 1fr;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
:root.js header > details::details-content {
|
|
135
|
+
min-block-size: 0;
|
|
136
|
+
overflow: clip;
|
|
137
|
+
opacity: 0;
|
|
138
|
+
transform: translateY(-4px);
|
|
139
|
+
transition:
|
|
140
|
+
content-visibility 200ms allow-discrete,
|
|
141
|
+
opacity 150ms ease-out,
|
|
142
|
+
transform 200ms cubic-bezier(0.2, 0, 0, 1);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
:root.js header > details[open]::details-content {
|
|
146
|
+
opacity: 1;
|
|
147
|
+
transform: none;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
93
150
|
}
|
|
94
151
|
|
|
95
152
|
header .content {
|
package/src/search/style.scss
CHANGED
|
@@ -14,6 +14,11 @@ header {
|
|
|
14
14
|
.results-container.is-showing .results {
|
|
15
15
|
pointer-events: all;
|
|
16
16
|
opacity: 1;
|
|
17
|
+
transform: none;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
&.loading .results {
|
|
21
|
+
transition: none;
|
|
17
22
|
}
|
|
18
23
|
|
|
19
24
|
@media (hover: hover) {
|
|
@@ -37,6 +42,14 @@ header {
|
|
|
37
42
|
border-radius: var(--border-radius);
|
|
38
43
|
box-shadow: var(--box-shadow);
|
|
39
44
|
opacity: 0;
|
|
45
|
+
transform: translateY(-4px) scale(0.98);
|
|
46
|
+
transform-origin: top center;
|
|
47
|
+
|
|
48
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
49
|
+
transition:
|
|
50
|
+
opacity 150ms ease-out,
|
|
51
|
+
transform 200ms cubic-bezier(0.2, 0, 0, 1);
|
|
52
|
+
}
|
|
40
53
|
|
|
41
54
|
@include user-select(none);
|
|
42
55
|
|
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
<% if (site.features.pickers) { %>
|
|
2
2
|
<div class="appearance-pickers" data-pagefind-ignore>
|
|
3
|
-
<
|
|
4
|
-
<
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
<button type="button" class="preference-picker-option contrast-picker-option-high" data-contrast-preference-value="high" aria-label="Use high contrast" aria-pressed="<%= site.defaultContrast === 'high' %>" disabled>
|
|
16
|
-
<span class="material-symbol-icon material-symbol-icon-contrast-high" aria-hidden="true"></span>
|
|
17
|
-
</button>
|
|
18
|
-
</div>
|
|
3
|
+
<button type="button" class="preference-picker font-picker" role="switch" data-font-preference-switch aria-label="Use serif fonts" aria-checked="<%= site.defaultFont === 'serif' %>" title="<%= site.defaultFont === 'serif' ? 'Use sans-serif fonts' : 'Use serif fonts' %>" disabled>
|
|
4
|
+
<span class="preference-picker-option font-picker-option-sans" data-font-preference-value="sans" aria-hidden="true">Aa</span>
|
|
5
|
+
<span class="preference-picker-option font-picker-option-serif" data-font-preference-value="serif" aria-hidden="true">Aa</span>
|
|
6
|
+
</button>
|
|
7
|
+
<button type="button" class="preference-picker contrast-picker" role="switch" data-contrast-preference-switch aria-label="Use high contrast" aria-checked="<%= site.defaultContrast === 'high' %>" title="<%= site.defaultContrast === 'high' ? 'Use standard contrast' : 'Use high contrast' %>" disabled>
|
|
8
|
+
<span class="preference-picker-option contrast-picker-option-standard" data-contrast-preference-value="standard" aria-hidden="true">
|
|
9
|
+
<span class="material-symbol-icon material-symbol-icon-contrast-standard"></span>
|
|
10
|
+
</span>
|
|
11
|
+
<span class="preference-picker-option contrast-picker-option-high" data-contrast-preference-value="high" aria-hidden="true">
|
|
12
|
+
<span class="material-symbol-icon material-symbol-icon-contrast-high"></span>
|
|
13
|
+
</span>
|
|
14
|
+
</button>
|
|
19
15
|
</div>
|
|
20
16
|
<% } %>
|
package/templates/_top.html
CHANGED
|
@@ -38,7 +38,70 @@
|
|
|
38
38
|
<% } %>
|
|
39
39
|
<title><%= page.title %><%= site.titlePostfix %></title>
|
|
40
40
|
<script>
|
|
41
|
+
(() => {
|
|
41
42
|
document.documentElement.classList.add('js');
|
|
43
|
+
const fontPreferenceFamilies = {
|
|
44
|
+
sans: ['Inter', 'Google Sans Code'],
|
|
45
|
+
serif: [
|
|
46
|
+
'<%= site.fontOverrides?.serif ? 'Tada Custom Serif' : 'Source Serif 4' %>',
|
|
47
|
+
'<%= site.fontOverrides?.serifMono ? 'Tada Custom Serif Mono' : 'Libertinus Mono' %>',
|
|
48
|
+
],
|
|
49
|
+
};
|
|
50
|
+
const fontPreferenceLoader = {
|
|
51
|
+
generation: 0,
|
|
52
|
+
pending: null,
|
|
53
|
+
failedPreference: null,
|
|
54
|
+
supported:
|
|
55
|
+
document.fonts && typeof document.fonts.load === 'function',
|
|
56
|
+
request(preference) {
|
|
57
|
+
if (!this.supported) return null;
|
|
58
|
+
if (this.pending?.preference === preference) return this.pending;
|
|
59
|
+
|
|
60
|
+
const generation = ++this.generation;
|
|
61
|
+
const [bodyFamily, monoFamily] = fontPreferenceFamilies[preference];
|
|
62
|
+
const promise = Promise.all([
|
|
63
|
+
document.fonts.load('normal 400 1em "' + bodyFamily + '"'),
|
|
64
|
+
document.fonts.load('normal 700 1em "' + bodyFamily + '"'),
|
|
65
|
+
document.fonts.load('normal 400 1em "' + monoFamily + '"'),
|
|
66
|
+
]).then(matches => {
|
|
67
|
+
if (matches.some(faces => faces.length === 0)) {
|
|
68
|
+
throw new Error('A required font face did not load');
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
const request = { generation, preference, promise };
|
|
72
|
+
this.pending = request;
|
|
73
|
+
this.failedPreference = null;
|
|
74
|
+
return request;
|
|
75
|
+
},
|
|
76
|
+
isCurrent(request) {
|
|
77
|
+
return (
|
|
78
|
+
this.pending === request &&
|
|
79
|
+
this.generation === request.generation
|
|
80
|
+
);
|
|
81
|
+
},
|
|
82
|
+
complete(request) {
|
|
83
|
+
if (this.isCurrent(request)) this.pending = null;
|
|
84
|
+
},
|
|
85
|
+
fail(request) {
|
|
86
|
+
if (!this.isCurrent(request)) return;
|
|
87
|
+
this.pending = null;
|
|
88
|
+
this.failedPreference = request.preference;
|
|
89
|
+
},
|
|
90
|
+
cancel() {
|
|
91
|
+
++this.generation;
|
|
92
|
+
this.pending = null;
|
|
93
|
+
this.failedPreference = null;
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
window.__tadaFontPreferenceLoader = fontPreferenceLoader;
|
|
97
|
+
|
|
98
|
+
const applyFontPreference = preference => {
|
|
99
|
+
if (preference === 'serif') {
|
|
100
|
+
document.documentElement.dataset.fontPreference = 'serif';
|
|
101
|
+
} else {
|
|
102
|
+
delete document.documentElement.dataset.fontPreference;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
42
105
|
try {
|
|
43
106
|
const preference = window.localStorage.getItem('fontPreference');
|
|
44
107
|
const configured =
|
|
@@ -47,10 +110,27 @@
|
|
|
47
110
|
(preference === 'serif' || preference === 'sans') &&
|
|
48
111
|
preference !== configured
|
|
49
112
|
) {
|
|
50
|
-
|
|
51
|
-
|
|
113
|
+
const request = fontPreferenceLoader.request(preference);
|
|
114
|
+
if (!request) {
|
|
115
|
+
applyFontPreference(preference);
|
|
52
116
|
} else {
|
|
53
|
-
|
|
117
|
+
request.promise.then(
|
|
118
|
+
() => {
|
|
119
|
+
let storedPreference;
|
|
120
|
+
try {
|
|
121
|
+
storedPreference =
|
|
122
|
+
window.localStorage.getItem('fontPreference');
|
|
123
|
+
} catch {}
|
|
124
|
+
if (
|
|
125
|
+
fontPreferenceLoader.isCurrent(request) &&
|
|
126
|
+
storedPreference === preference
|
|
127
|
+
) {
|
|
128
|
+
applyFontPreference(preference);
|
|
129
|
+
fontPreferenceLoader.complete(request);
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
() => fontPreferenceLoader.fail(request),
|
|
133
|
+
);
|
|
54
134
|
}
|
|
55
135
|
}
|
|
56
136
|
} catch {}
|
|
@@ -81,6 +161,7 @@
|
|
|
81
161
|
} else {
|
|
82
162
|
delete document.documentElement.dataset.contrastPreference;
|
|
83
163
|
}
|
|
164
|
+
})();
|
|
84
165
|
</script>
|
|
85
166
|
<% if (!isWatchMode) { %>
|
|
86
167
|
<script type="speculationrules">
|
|
@@ -127,6 +208,12 @@
|
|
|
127
208
|
</div>
|
|
128
209
|
<details>
|
|
129
210
|
<summary aria-label="Toggle site navigation">
|
|
211
|
+
<svg class="menu-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"
|
|
212
|
+
fill="none" stroke="currentColor" stroke-width="1.65" stroke-linecap="round">
|
|
213
|
+
<path class="menu-icon-line menu-icon-line-top" d="M3 6h18" />
|
|
214
|
+
<path class="menu-icon-line menu-icon-line-middle" d="M3 12h18" />
|
|
215
|
+
<path class="menu-icon-line menu-icon-line-bottom" d="M3 18h18" />
|
|
216
|
+
</svg>
|
|
130
217
|
<% if (site.symbol) { %><span class="logo" aria-hidden="true"><%= site.symbol %></span><% } %>
|
|
131
218
|
<span class="site-title" aria-hidden="true"><%= site.title %></span>
|
|
132
219
|
</summary>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M252-203.69 205.69-252l227-228-227-230L252-758.31l229 230 227-230L754.31-710l-227 230 227 228L708-203.69l-227-230-229 230Z"/></svg>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M128-251.23v-66h704v66H128ZM128-448v-66h704v66H128Zm0-196.77v-66h704v66H128Z"/></svg>
|