@abreen/tada 1.16.1 → 1.16.3

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.
Files changed (57) hide show
  1. package/README.md +15 -0
  2. package/assets/material-symbols/celebration_24px.svg +1 -1
  3. package/assets/material-symbols/close_24px.svg +1 -1
  4. package/assets/material-symbols/contrast_20px.svg +1 -0
  5. package/assets/material-symbols/contrast_rtl_off_20px.svg +1 -0
  6. package/assets/material-symbols/info_20px.svg +1 -0
  7. package/assets/material-symbols/info_40px.svg +1 -0
  8. package/assets/material-symbols/menu_24px.svg +1 -1
  9. package/assets/material-symbols/open_in_new_20px.svg +1 -0
  10. package/assets/material-symbols/search_20px.svg +1 -0
  11. package/assets/material-symbols/smart_display_24px.svg +1 -0
  12. package/assets/material-symbols/south_east_20px.svg +1 -0
  13. package/assets/material-symbols/warning_20px.svg +1 -0
  14. package/assets/material-symbols/warning_40px.svg +1 -0
  15. package/build/bundle.ts +23 -4
  16. package/build/custom-fonts.ts +313 -0
  17. package/build/generate-fonts.ts +29 -3
  18. package/build/material-symbols.ts +58 -20
  19. package/build/pipeline.ts +1 -1
  20. package/build/types.d.ts +29 -0
  21. package/build/utils/render.ts +13 -5
  22. package/build/watch/build-full.ts +1 -1
  23. package/build/watch/build-incremental.ts +1 -1
  24. package/build/watch/validation.ts +18 -7
  25. package/init/content/markdown.md +2 -1
  26. package/package.json +2 -2
  27. package/schema/site.schema.json +70 -0
  28. package/src/_alerts.scss +17 -12
  29. package/src/_base.scss +10 -0
  30. package/src/_layout.scss +2 -2
  31. package/src/anchor/index.ts +2 -35
  32. package/src/anchor/style.scss +4 -28
  33. package/src/appearance-picker/index.ts +18 -5
  34. package/src/appearance-picker/style.scss +3 -13
  35. package/src/code.scss +1 -0
  36. package/src/header/_base.scss +10 -6
  37. package/src/material-symbols.scss +16 -3
  38. package/src/navigate/runtime.ts +13 -0
  39. package/src/question/style.scss +1 -2
  40. package/src/search/style.scss +5 -3
  41. package/src/style.scss +15 -7
  42. package/src/timezone/index.ts +42 -2
  43. package/src/timezone/style.scss +0 -6
  44. package/src/timezone/time-format.ts +13 -0
  45. package/src/toc/style.scss +16 -10
  46. package/src/trace/style.scss +1 -0
  47. package/templates/_theme.scss +16 -4
  48. package/templates/_top.html +23 -11
  49. package/templates/code.html +1 -1
  50. package/assets/material-symbols/contrast_24px.svg +0 -1
  51. package/assets/material-symbols/contrast_rtl_off_24px.svg +0 -1
  52. package/assets/material-symbols/info_24px.svg +0 -1
  53. package/assets/material-symbols/north_east_24px.svg +0 -1
  54. package/assets/material-symbols/search_24px.svg +0 -1
  55. package/assets/material-symbols/south_east_24px.svg +0 -1
  56. package/assets/material-symbols/tag_24px.svg +0 -1
  57. package/assets/material-symbols/warning_24px.svg +0 -1
package/build/types.d.ts CHANGED
@@ -3,6 +3,34 @@ import type { TadaProjectScan } from './source-model';
3
3
 
4
4
  export type PlainTextLanguage = 'text' | 'txt' | 'plain';
5
5
 
6
+ export interface SerifFontTuning {
7
+ scale?: number;
8
+ lineHeight?: number;
9
+ headingScale?: number;
10
+ headingWeight?: 400 | 700;
11
+ fontSizeAdjust?: number;
12
+ }
13
+
14
+ export interface SerifMonoFontTuning {
15
+ scale?: number;
16
+ lineHeight?: number;
17
+ fontSizeAdjust?: number;
18
+ }
19
+
20
+ export interface FontFamilyOverride<Tuning = unknown> {
21
+ regular: string;
22
+ italic?: string;
23
+ bold?: string;
24
+ boldItalic?: string;
25
+ features?: string[];
26
+ tuning?: Tuning;
27
+ }
28
+
29
+ export interface FontOverrides {
30
+ serif?: FontFamilyOverride<SerifFontTuning>;
31
+ serifMono?: FontFamilyOverride<SerifMonoFontTuning>;
32
+ }
33
+
6
34
  /** Site configuration loaded from site.dev.yaml/yml/json or site.prod.yaml/yml/json */
7
35
  export interface SiteVariables {
8
36
  base: string;
@@ -18,6 +46,7 @@ export interface SiteVariables {
18
46
  defaultTimeZone: string;
19
47
  defaultFont?: 'sans' | 'serif';
20
48
  defaultContrast?: 'standard' | 'high';
49
+ fontOverrides?: FontOverrides;
21
50
  features: FeatureConfig;
22
51
  extensionToShikiLanguage?: Record<
23
52
  string,
@@ -8,6 +8,7 @@ import { B } from '../colors';
8
8
  import createTemplateGlobals from '../template-globals';
9
9
  import { getExtensionToShikiLanguage } from '../site-variables';
10
10
  import { getDefaultFontPreloadFiles } from '../generate-fonts';
11
+ import { encodePublicAssetPath } from '../custom-fonts';
11
12
  import { config } from '../templates';
12
13
  import { render } from '../templates';
13
14
  import {
@@ -184,12 +185,19 @@ export function injectAssetTags(
184
185
  )
185
186
  .join('');
186
187
 
187
- const fontPreloadTags = getDefaultFontPreloadFiles(siteVariables.defaultFont)
188
- .filter(f => fs.existsSync(path.join(distDir, f)))
189
- .map(
190
- f =>
191
- `<link rel="preload" href="${normalizeOutputPath('/' + f)}" as="font" type="font/woff2" crossorigin>`,
188
+ const fontPreloadTags = getDefaultFontPreloadFiles(siteVariables)
189
+ .filter(
190
+ preload =>
191
+ preload.source === 'public' ||
192
+ fs.existsSync(path.join(distDir, preload.filePath)),
192
193
  )
194
+ .map(preload => {
195
+ const urlPath =
196
+ preload.source === 'public'
197
+ ? encodePublicAssetPath(preload.filePath)
198
+ : preload.filePath;
199
+ return `<link rel="preload" href="${normalizeOutputPath('/' + urlPath)}" as="font" type="font/woff2" crossorigin>`;
200
+ })
193
201
  .join('');
194
202
 
195
203
  return html
@@ -32,7 +32,7 @@ export async function buildFull({
32
32
  try {
33
33
  const siteVariables = getDevSiteVariables();
34
34
  const scan = scanProject(siteVariables);
35
- const configDiagnostics = validateConfig(scan);
35
+ const configDiagnostics = validateConfig(scan, siteVariables);
36
36
  if (configDiagnostics.length > 0) {
37
37
  return buildFailedWithDiagnostics(outputDir, configDiagnostics);
38
38
  }
@@ -34,7 +34,7 @@ export async function buildIncremental({
34
34
  const outputDir = makeTempBuildDir(distDir);
35
35
  try {
36
36
  const siteVariables = snapshot.siteVariables;
37
- const configDiagnostics = validateConfig(plan.scan);
37
+ const configDiagnostics = validateConfig(plan.scan, siteVariables);
38
38
  if (configDiagnostics.length > 0) {
39
39
  return buildFailedWithDiagnostics(outputDir, configDiagnostics);
40
40
  }
@@ -2,6 +2,8 @@ import { B } from '../colors';
2
2
  import { makeLogger } from '../log';
3
3
  import { config, getConfigFileName } from '../templates';
4
4
  import { validateConfigLinks } from '../validate-config-links';
5
+ import { validateCustomFontOverrides } from '../custom-fonts';
6
+ import type { SiteVariables } from '../types';
5
7
  import type { WatchDiagnostic } from './types';
6
8
  import { assertNoOutputPathConflicts, type TadaProjectScan } from './snapshot';
7
9
 
@@ -11,20 +13,29 @@ export function diagnosticsFromMessages(messages: string[]): WatchDiagnostic[] {
11
13
  return messages.map(message => ({ message }));
12
14
  }
13
15
 
14
- export function validateConfig(scan: TadaProjectScan): WatchDiagnostic[] {
16
+ export function validateConfig(
17
+ scan: TadaProjectScan,
18
+ siteVariables: SiteVariables,
19
+ ): WatchDiagnostic[] {
20
+ const diagnostics = diagnosticsFromMessages(
21
+ validateCustomFontOverrides({
22
+ fontOverrides: siteVariables.fontOverrides,
23
+ publicDir: scan.publicDir,
24
+ publicFiles: scan.publicFiles,
25
+ }),
26
+ );
15
27
  const conflicts = assertNoOutputPathConflicts(scan);
16
28
  if (conflicts.length === 0) {
17
- return [];
29
+ return diagnostics;
18
30
  }
19
31
  for (const relPath of conflicts) {
20
32
  log.error`content/${B`${relPath}`} conflicts with public/${B`${relPath}`}`;
21
33
  }
22
34
  const noun = conflicts.length === 1 ? 'file' : 'files';
23
- return [
24
- {
25
- message: `${conflicts.length} ${noun} in content/ and public/ have the same path`,
26
- },
27
- ];
35
+ diagnostics.push({
36
+ message: `${conflicts.length} ${noun} in content/ and public/ have the same path`,
37
+ });
38
+ return diagnostics;
28
39
  }
29
40
 
30
41
  export function validateProjectConfigLinks(
@@ -414,7 +414,8 @@ Here's a time range:
414
414
  <time datetime="12:45">12:45</time>--<time datetime="13:45">1:45 pm</time>
415
415
 
416
416
  - When a `<time>` element is adjusted,
417
- * the appearance of the element changes slightly to indicate it was updated,
417
+ * the selected time zone abbreviation is shown after its AM/PM period (or
418
+ after the final endpoint of a time range),
418
419
  * hovering over the text produces a tooltip telling the user the original time
419
420
  and time zone, and
420
421
  * if the time adjustment crosses a day boundary, *(next day)* or *(prev. day)*
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abreen/tada",
3
- "version": "1.16.1",
3
+ "version": "1.16.3",
4
4
  "type": "module",
5
5
  "description": "A static site generator",
6
6
  "license": "MIT",
@@ -77,7 +77,7 @@
77
77
  "png-to-ico": "^3.0.1",
78
78
  "quote-quote": "^1.2.0",
79
79
  "sass": "^1.99.0",
80
- "sharp": "^0.34.5",
80
+ "sharp": "^0.35.3",
81
81
  "shiki": "^4.0.2",
82
82
  "string-strip-html": "^8.5.0"
83
83
  },
@@ -58,6 +58,14 @@
58
58
  "enum": ["standard", "high"],
59
59
  "default": "standard"
60
60
  },
61
+ "fontOverrides": {
62
+ "type": "object",
63
+ "properties": {
64
+ "serif": { "$ref": "#/$defs/serifFontOverride" },
65
+ "serifMono": { "$ref": "#/$defs/serifMonoFontOverride" }
66
+ },
67
+ "additionalProperties": false
68
+ },
61
69
  "vars": { "type": "object" },
62
70
  "extensionToShikiLanguage": {
63
71
  "type": "object",
@@ -65,5 +73,67 @@
65
73
  },
66
74
  "shikiLanguages": { "type": "array", "items": { "type": "string" } }
67
75
  },
76
+ "$defs": {
77
+ "serifFontOverride": {
78
+ "type": "object",
79
+ "required": ["regular"],
80
+ "properties": {
81
+ "regular": { "$ref": "#/$defs/publicWoff2Path" },
82
+ "italic": { "$ref": "#/$defs/publicWoff2Path" },
83
+ "bold": { "$ref": "#/$defs/publicWoff2Path" },
84
+ "boldItalic": { "$ref": "#/$defs/publicWoff2Path" },
85
+ "features": {
86
+ "type": "array",
87
+ "items": { "type": "string", "pattern": "^[A-Za-z0-9]{4}$" },
88
+ "uniqueItems": true
89
+ },
90
+ "tuning": {
91
+ "type": "object",
92
+ "properties": {
93
+ "scale": { "$ref": "#/$defs/fontScale" },
94
+ "lineHeight": { "$ref": "#/$defs/fontLineHeight" },
95
+ "headingScale": { "$ref": "#/$defs/fontScale" },
96
+ "headingWeight": { "type": "integer", "enum": [400, 700] },
97
+ "fontSizeAdjust": { "$ref": "#/$defs/fontSizeAdjust" }
98
+ },
99
+ "additionalProperties": false
100
+ }
101
+ },
102
+ "additionalProperties": false
103
+ },
104
+ "serifMonoFontOverride": {
105
+ "type": "object",
106
+ "required": ["regular"],
107
+ "properties": {
108
+ "regular": { "$ref": "#/$defs/publicWoff2Path" },
109
+ "italic": { "$ref": "#/$defs/publicWoff2Path" },
110
+ "bold": { "$ref": "#/$defs/publicWoff2Path" },
111
+ "boldItalic": { "$ref": "#/$defs/publicWoff2Path" },
112
+ "features": {
113
+ "type": "array",
114
+ "items": { "type": "string", "pattern": "^[A-Za-z0-9]{4}$" },
115
+ "uniqueItems": true
116
+ },
117
+ "tuning": {
118
+ "type": "object",
119
+ "properties": {
120
+ "scale": { "$ref": "#/$defs/fontScale" },
121
+ "lineHeight": { "$ref": "#/$defs/fontLineHeight" },
122
+ "fontSizeAdjust": { "$ref": "#/$defs/fontSizeAdjust" }
123
+ },
124
+ "additionalProperties": false
125
+ }
126
+ },
127
+ "additionalProperties": false
128
+ },
129
+ "fontScale": { "type": "number", "minimum": 0.75, "maximum": 1.5 },
130
+ "fontLineHeight": { "type": "number", "minimum": 1, "maximum": 2.5 },
131
+ "fontSizeAdjust": { "type": "number", "exclusiveMinimum": 0 },
132
+ "publicWoff2Path": {
133
+ "type": "string",
134
+ "minLength": 6,
135
+ "pattern": "\\.woff2$"
136
+ }
137
+ },
68
138
  "additionalProperties": false
69
139
  }
package/src/_alerts.scss CHANGED
@@ -18,6 +18,8 @@
18
18
 
19
19
  .alert .title {
20
20
  padding: 10px 6px;
21
+ padding-top: 18px;
22
+ padding-left: 58px;
21
23
  }
22
24
 
23
25
  .alert .content {
@@ -67,32 +69,35 @@
67
69
 
68
70
  .alert .title::before {
69
71
  position: absolute;
70
- top: 8px;
71
- left: 8px;
72
- width: 32px;
73
- height: 32px;
72
+ top: 12px;
73
+ left: 10px;
74
+ width: var(--alert-icon-size);
75
+ height: var(--alert-icon-size);
74
76
  content: '';
77
+
78
+ @include contained-mask(var(--alert-icon));
75
79
  }
76
80
 
77
- .alert.warning .title::before {
78
- @include contained-mask(var(--icon-warning));
81
+ .alert.warning {
82
+ --alert-icon: var(--icon-warning);
83
+ --alert-icon-size: var(--icon-warning-size);
79
84
  }
80
85
 
81
86
  i.warning {
82
- @include contained-mask(var(--icon-warning));
87
+ @include contained-mask(var(--icon-warning-compact));
83
88
 
84
89
  display: inline-block;
85
- width: 1em;
86
- height: 1em;
90
+ width: var(--icon-warning-compact-size);
91
+ height: var(--icon-warning-compact-size);
87
92
  vertical-align: sub;
88
93
  }
89
94
 
90
- .alert.note .title::before {
91
- @include contained-mask(var(--icon-info));
95
+ .alert.note {
96
+ --alert-icon: var(--icon-info);
97
+ --alert-icon-size: var(--icon-info-size);
92
98
  }
93
99
 
94
100
  .alert .title {
95
- padding-left: 48px;
96
101
  margin: 0;
97
102
  font-weight: 700;
98
103
 
package/src/_base.scss CHANGED
@@ -32,6 +32,7 @@ h1 {
32
32
  body {
33
33
  font-family: var(--font);
34
34
  font-size: var(--font-size);
35
+ font-size-adjust: var(--font-size-adjust);
35
36
  font-weight: var(--font-weight);
36
37
  font-feature-settings: var(--font-feature-settings);
37
38
  line-height: var(--line-height);
@@ -83,9 +84,18 @@ pre,
83
84
  tt,
84
85
  code {
85
86
  font-family: var(--mono-font);
87
+ font-size-adjust: var(--mono-font-size-adjust);
86
88
  font-feature-settings: var(--mono-font-feature-settings);
87
89
  }
88
90
 
91
+ [font-family='var(--mono-font)'] {
92
+ font-size-adjust: var(--mono-font-size-adjust);
93
+ }
94
+
95
+ .katex {
96
+ font-size-adjust: none;
97
+ }
98
+
89
99
  tt,
90
100
  code {
91
101
  padding: var(--mono-padding);
package/src/_layout.scss CHANGED
@@ -223,8 +223,8 @@ footer {
223
223
 
224
224
  &::after {
225
225
  display: inline-block;
226
- width: 1.5em;
227
- height: 1.5em;
226
+ width: var(--icon-tada-size);
227
+ height: var(--icon-tada-size);
228
228
  margin-left: 0.2em;
229
229
  vertical-align: middle;
230
230
  content: '';
@@ -2,45 +2,13 @@ function getElements(parent: HTMLElement): HTMLHeadingElement[] {
2
2
  return Array.from(parent.querySelectorAll('h1, h2, h3, h4, h5, h6'));
3
3
  }
4
4
 
5
- function createHeadingAnchorIcon(window: Window): HTMLSpanElement {
5
+ function createPresentIcon(window: Window): HTMLSpanElement {
6
6
  const icon = window.document.createElement('span');
7
- icon.className = 'material-symbol-icon material-symbol-icon-heading-anchor';
7
+ icon.className = 'material-symbol-icon material-symbol-icon-heading-present';
8
8
  icon.setAttribute('aria-hidden', 'true');
9
9
  return icon;
10
10
  }
11
11
 
12
- function createPresentIcon(window: Window): SVGSVGElement {
13
- const svg = window.document.createElementNS(
14
- 'http://www.w3.org/2000/svg',
15
- 'svg',
16
- );
17
- svg.setAttribute('aria-hidden', 'true');
18
- svg.setAttribute('viewBox', '0 0 24 24');
19
- svg.setAttribute('width', '16');
20
- svg.setAttribute('height', '16');
21
- svg.setAttribute('fill', 'none');
22
- svg.setAttribute('stroke', 'currentColor');
23
- svg.setAttribute('stroke-width', '1.5');
24
- svg.setAttribute('stroke-linecap', 'round');
25
- svg.setAttribute('stroke-linejoin', 'round');
26
-
27
- const paths = [
28
- 'M5 5h14a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1Z',
29
- 'M9 20h6',
30
- ];
31
-
32
- for (const d of paths) {
33
- const path = window.document.createElementNS(
34
- 'http://www.w3.org/2000/svg',
35
- 'path',
36
- );
37
- path.setAttribute('d', d);
38
- svg.appendChild(path);
39
- }
40
-
41
- return svg;
42
- }
43
-
44
12
  function getSlideIndex(slide: HTMLElement): number {
45
13
  const rawIndex = slide.getAttribute('data-slide-index');
46
14
  if (rawIndex != null) {
@@ -86,7 +54,6 @@ export default (window: Window) => {
86
54
  while (el.firstChild) {
87
55
  link.appendChild(el.firstChild);
88
56
  }
89
- link.appendChild(createHeadingAnchorIcon(window));
90
57
  el.appendChild(link);
91
58
 
92
59
  const handleClick = () => {
@@ -7,28 +7,12 @@ a.heading-anchor {
7
7
  @include subdued-link;
8
8
  }
9
9
 
10
- .heading-anchor .material-symbol-icon,
11
- .heading-present-button svg {
12
- display: inline-block;
13
- width: 1em;
14
- height: 1em;
15
- vertical-align: middle;
16
- }
17
-
18
- .heading-anchor .material-symbol-icon {
19
- margin-left: 4px;
20
- vertical-align: middle;
21
- opacity: 0;
22
- }
23
-
24
10
  .heading-present-button {
25
- display: inline-flex;
26
- align-items: center;
27
- justify-content: center;
28
- width: 1em;
29
- height: 1em;
11
+ display: inline-block;
12
+ width: var(--icon-heading-present-size);
13
+ height: var(--icon-heading-present-size);
30
14
  padding: 0;
31
- margin-left: 4px;
15
+ margin-left: 8px;
32
16
  font: inherit;
33
17
  line-height: 1;
34
18
  vertical-align: middle;
@@ -39,7 +23,6 @@ a.heading-anchor {
39
23
  opacity: 0;
40
24
  }
41
25
 
42
- a.heading-anchor:focus-visible .material-symbol-icon,
43
26
  .heading-present-button:focus {
44
27
  opacity: 1;
45
28
  }
@@ -65,12 +48,6 @@ body.is-presenting .heading-present-button {
65
48
  background: transparent;
66
49
  }
67
50
 
68
- h1:hover a.heading-anchor .material-symbol-icon,
69
- h2:hover a.heading-anchor .material-symbol-icon,
70
- h3:hover a.heading-anchor .material-symbol-icon,
71
- h4:hover a.heading-anchor .material-symbol-icon,
72
- h5:hover a.heading-anchor .material-symbol-icon,
73
- h6:hover a.heading-anchor .material-symbol-icon,
74
51
  h1:hover .heading-present-button,
75
52
  h2:hover .heading-present-button,
76
53
  h3:hover .heading-present-button,
@@ -88,7 +65,6 @@ body.is-presenting .heading-present-button {
88
65
  }
89
66
 
90
67
  @media print {
91
- .heading-anchor .material-symbol-icon,
92
68
  .heading-present-button {
93
69
  display: none;
94
70
  }
@@ -91,10 +91,23 @@ function getDefaultFontPreference(document: Document): FontPreference {
91
91
  : 'sans';
92
92
  }
93
93
 
94
- function getDefaultContrastPreference(document: Document): ContrastPreference {
95
- return document.documentElement.dataset.defaultContrastPreference === 'high'
96
- ? 'high'
97
- : 'standard';
94
+ function getDefaultContrastPreference(window: Window): ContrastPreference {
95
+ const configured =
96
+ window.document.documentElement.dataset.defaultContrastPreference === 'high'
97
+ ? 'high'
98
+ : 'standard';
99
+
100
+ if (configured === 'high' || typeof window.matchMedia !== 'function') {
101
+ return configured;
102
+ }
103
+
104
+ try {
105
+ return window.matchMedia('(prefers-contrast: more)').matches
106
+ ? 'high'
107
+ : configured;
108
+ } catch {
109
+ return configured;
110
+ }
98
111
  }
99
112
 
100
113
  export function applyFontPreference(
@@ -167,7 +180,7 @@ export default function mountAppearancePicker(window: Window): () => void {
167
180
  );
168
181
  const storage = getStorage(window);
169
182
  const defaultFontPreference = getDefaultFontPreference(document);
170
- const defaultContrastPreference = getDefaultContrastPreference(document);
183
+ const defaultContrastPreference = getDefaultContrastPreference(window);
171
184
  let fontPreference = getFontPreference(storage, defaultFontPreference);
172
185
  let contrastPreference = getContrastPreference(
173
186
  storage,
@@ -78,6 +78,7 @@
78
78
  font-family:
79
79
  -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial,
80
80
  sans-serif;
81
+ font-size-adjust: none;
81
82
  font-feature-settings: normal;
82
83
  }
83
84
 
@@ -85,6 +86,7 @@
85
86
  // Avoid requesting the bundled serif face just to render the inactive
86
87
  // preview on a default sans page.
87
88
  font-family: var(--serif-preview-font);
89
+ font-size-adjust: none;
88
90
  font-feature-settings: var(--serif-font-feature-settings);
89
91
  }
90
92
 
@@ -95,19 +97,7 @@
95
97
 
96
98
  :root[data-font-preference='serif'] .font-picker-option-serif {
97
99
  font-family: var(--serif-font);
98
- }
99
-
100
- .material-symbol-icon {
101
- width: 1.25rem;
102
- height: 1.25rem;
103
- }
104
-
105
- .material-symbol-icon-contrast-standard {
106
- --material-symbol-icon: var(--icon-contrast-standard);
107
- }
108
-
109
- .material-symbol-icon-contrast-high {
110
- --material-symbol-icon: var(--icon-contrast-high);
100
+ font-size-adjust: var(--font-size-adjust);
111
101
  }
112
102
 
113
103
  .preference-picker-option[aria-pressed='true'] {
package/src/code.scss CHANGED
@@ -35,6 +35,7 @@ body.code {
35
35
  padding: 0 0.75em;
36
36
  margin-right: var(--gap);
37
37
  font-family: var(--mono-font);
38
+ font-size-adjust: var(--mono-font-size-adjust);
38
39
  font-feature-settings: var(--mono-font-feature-settings);
39
40
  color: var(--fg2-color);
40
41
  text-align: right;
@@ -60,10 +60,10 @@ header details summary::marker {
60
60
  }
61
61
 
62
62
  header summary::after {
63
- flex: 0 0 24px;
63
+ flex: 0 0 var(--icon-header-menu-size);
64
64
  order: -1;
65
- width: 24px;
66
- height: 24px;
65
+ width: var(--icon-header-menu-size);
66
+ height: var(--icon-header-menu-size);
67
67
  margin-right: 8px;
68
68
  margin-left: var(--gap);
69
69
  content: '';
@@ -97,7 +97,10 @@ header summary .site-title {
97
97
  margin: 0 8px 0 0;
98
98
  overflow: hidden;
99
99
  text-overflow: ellipsis;
100
- font-weight: 500;
100
+
101
+ // Values above 500 resolve to the supplied bold face when a custom family
102
+ // provides only fixed 400 and 700 weights.
103
+ font-weight: 600;
101
104
  white-space: nowrap;
102
105
  pointer-events: none;
103
106
  }
@@ -154,8 +157,8 @@ header .search-controls {
154
157
  top: 50%;
155
158
  left: 7px;
156
159
  z-index: 1;
157
- width: 22px;
158
- height: 22px;
160
+ width: var(--icon-search-size);
161
+ height: var(--icon-search-size);
159
162
  pointer-events: none;
160
163
  content: '';
161
164
  transform: translateY(-50%);
@@ -169,6 +172,7 @@ header .search-controls {
169
172
 
170
173
  input.search {
171
174
  width: 180px;
175
+ padding-left: calc(2rem - 1px);
172
176
  }
173
177
  }
174
178
 
@@ -1,11 +1,24 @@
1
1
  @use 'mixins' as *;
2
2
 
3
3
  .material-symbol-icon {
4
+ display: block;
5
+ width: var(--material-symbol-icon-size);
6
+ height: var(--material-symbol-icon-size);
7
+
4
8
  @include contained-mask(var(--material-symbol-icon));
9
+ }
5
10
 
6
- display: block;
11
+ .material-symbol-icon-heading-present {
12
+ --material-symbol-icon: var(--icon-heading-present);
13
+ --material-symbol-icon-size: var(--icon-heading-present-size);
14
+ }
15
+
16
+ .material-symbol-icon-contrast-standard {
17
+ --material-symbol-icon: var(--icon-contrast-standard);
18
+ --material-symbol-icon-size: var(--icon-contrast-standard-size);
7
19
  }
8
20
 
9
- .material-symbol-icon-heading-anchor {
10
- --material-symbol-icon: var(--icon-heading-anchor);
21
+ .material-symbol-icon-contrast-high {
22
+ --material-symbol-icon: var(--icon-contrast-high);
23
+ --material-symbol-icon-size: var(--icon-contrast-high-size);
11
24
  }
@@ -138,6 +138,18 @@ function isActiveNavigation(controller: AbortController): boolean {
138
138
  return currentAbortController === controller && !controller.signal.aborted;
139
139
  }
140
140
 
141
+ function prefersReducedMotion(window: Window): boolean {
142
+ if (typeof window.matchMedia !== 'function') {
143
+ return false;
144
+ }
145
+
146
+ try {
147
+ return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
148
+ } catch {
149
+ return false;
150
+ }
151
+ }
152
+
141
153
  export function initNavigation(window: Window): void {
142
154
  currentAbortController = null;
143
155
  historyIndex = 0;
@@ -305,6 +317,7 @@ export async function navigateToUrl(
305
317
 
306
318
  if (
307
319
  useViewTransition &&
320
+ !prefersReducedMotion(window) &&
308
321
  typeof transitionDoc.startViewTransition === 'function'
309
322
  ) {
310
323
  const titleEl = document.querySelector('.title-and-info');
@@ -62,8 +62,7 @@
62
62
  margin-bottom: 0;
63
63
  }
64
64
 
65
- .question-a-body > *,
66
- .question-a-body > * * {
65
+ .question-a-body > * {
67
66
  @media (prefers-reduced-motion: no-preference) {
68
67
  transition:
69
68
  color 0.25s,