@abreen/tada 1.15.13 → 1.16.1

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 (51) hide show
  1. package/README.md +4 -0
  2. package/assets/material-symbols/celebration_24px.svg +1 -0
  3. package/assets/material-symbols/close_24px.svg +1 -0
  4. package/assets/material-symbols/contrast_24px.svg +1 -0
  5. package/assets/material-symbols/contrast_rtl_off_24px.svg +1 -0
  6. package/assets/material-symbols/info_24px.svg +1 -0
  7. package/assets/material-symbols/menu_24px.svg +1 -0
  8. package/assets/material-symbols/north_east_24px.svg +1 -0
  9. package/assets/material-symbols/search_24px.svg +1 -0
  10. package/assets/material-symbols/south_east_24px.svg +1 -0
  11. package/assets/material-symbols/tag_24px.svg +1 -0
  12. package/assets/material-symbols/warning_24px.svg +1 -0
  13. package/bin/validators.ts +2 -0
  14. package/build/bundle.ts +7 -12
  15. package/build/generate-fonts.ts +17 -0
  16. package/build/material-symbols.ts +107 -0
  17. package/build/site-variables.ts +2 -0
  18. package/build/types.d.ts +2 -0
  19. package/build/utils/markdown.ts +11 -43
  20. package/build/utils/render.ts +24 -8
  21. package/build/watch/assets.ts +3 -3
  22. package/fonts/libertinus-mono/LICENSE.txt +99 -0
  23. package/fonts/libertinus-mono/woff2/LibertinusMono-Regular.woff2 +0 -0
  24. package/fonts/source-serif-4/LICENSE.txt +91 -0
  25. package/fonts/source-serif-4/woff2/SourceSerif4-Italic-VariableFont_opsz,wght.woff2 +0 -0
  26. package/fonts/source-serif-4/woff2/SourceSerif4-VariableFont_opsz,wght.woff2 +0 -0
  27. package/package.json +2 -1
  28. package/schema/site.schema.json +10 -0
  29. package/src/_alerts.scss +1 -1
  30. package/src/_base.scss +2 -16
  31. package/src/anchor/index.ts +14 -10
  32. package/src/anchor/style.scss +10 -10
  33. package/src/appearance-picker/index.ts +214 -0
  34. package/src/appearance-picker/style.scss +130 -0
  35. package/src/code.scss +1 -0
  36. package/src/header/_base.scss +34 -6
  37. package/src/header/style.scss +8 -0
  38. package/src/index.ts +2 -0
  39. package/src/material-symbols.scss +11 -0
  40. package/src/navigate/lifecycle.ts +2 -0
  41. package/src/search/style.scss +0 -1
  42. package/src/slides/style.scss +1 -0
  43. package/src/style.scss +30 -28
  44. package/src/toc/style.scss +3 -1
  45. package/src/trace/style.scss +1 -0
  46. package/templates/_appearance-picker.html +18 -0
  47. package/templates/_theme.scss +43 -13
  48. package/templates/_top.html +38 -2
  49. package/templates/code.html +1 -0
  50. package/templates/default.html +1 -0
  51. package/templates/literate.html +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abreen/tada",
3
- "version": "1.15.13",
3
+ "version": "1.16.1",
4
4
  "type": "module",
5
5
  "description": "A static site generator",
6
6
  "license": "MIT",
@@ -17,6 +17,7 @@
17
17
  "build/",
18
18
  "src/",
19
19
  "templates/",
20
+ "assets/",
20
21
  "fonts/",
21
22
  "init/",
22
23
  "schema/",
@@ -48,6 +48,16 @@
48
48
  "default": 700
49
49
  },
50
50
  "defaultTimeZone": { "type": "string" },
51
+ "defaultFont": {
52
+ "type": "string",
53
+ "enum": ["sans", "serif"],
54
+ "default": "sans"
55
+ },
56
+ "defaultContrast": {
57
+ "type": "string",
58
+ "enum": ["standard", "high"],
59
+ "default": "standard"
60
+ },
51
61
  "vars": { "type": "object" },
52
62
  "extensionToShikiLanguage": {
53
63
  "type": "object",
package/src/_alerts.scss CHANGED
@@ -88,7 +88,7 @@ i.warning {
88
88
  }
89
89
 
90
90
  .alert.note .title::before {
91
- @include contained-mask(var(--icon-note));
91
+ @include contained-mask(var(--icon-info));
92
92
  }
93
93
 
94
94
  .alert .title {
package/src/_base.scss CHANGED
@@ -33,18 +33,7 @@ body {
33
33
  font-family: var(--font);
34
34
  font-size: var(--font-size);
35
35
  font-weight: var(--font-weight);
36
-
37
- /*
38
- * Enable Inter's features:
39
- * liga - standard ligatures
40
- * calt - contextual alternates
41
- * ss07 - square punctuation
42
- * ss08 - square quotes
43
- * cv01 - alternate 1
44
- * cv08 - uppercase I with serif
45
- * cv10 - capital G with spur
46
- */
47
- font-feature-settings: 'liga', 'calt', 'ss07', 'ss08', 'cv01', 'cv08', 'cv10';
36
+ font-feature-settings: var(--font-feature-settings);
48
37
  line-height: var(--line-height);
49
38
  color: var(--fg-color);
50
39
  background-color: var(--bg-color);
@@ -94,6 +83,7 @@ pre,
94
83
  tt,
95
84
  code {
96
85
  font-family: var(--mono-font);
86
+ font-feature-settings: var(--mono-font-feature-settings);
97
87
  }
98
88
 
99
89
  tt,
@@ -182,10 +172,6 @@ input[type='search'] {
182
172
  min-width: 0;
183
173
  padding-left: 2rem;
184
174
  background-color: var(--bg-color);
185
- background-image: var(--icon-search);
186
- background-repeat: no-repeat;
187
- background-position: left 6px center;
188
- background-size: 16px 16px;
189
175
  border-radius: 18px;
190
176
 
191
177
  &:disabled {
@@ -2,7 +2,14 @@ function getElements(parent: HTMLElement): HTMLHeadingElement[] {
2
2
  return Array.from(parent.querySelectorAll('h1, h2, h3, h4, h5, h6'));
3
3
  }
4
4
 
5
- function createIcon(window: Window, type: 'hash' | 'present'): SVGSVGElement {
5
+ function createHeadingAnchorIcon(window: Window): HTMLSpanElement {
6
+ const icon = window.document.createElement('span');
7
+ icon.className = 'material-symbol-icon material-symbol-icon-heading-anchor';
8
+ icon.setAttribute('aria-hidden', 'true');
9
+ return icon;
10
+ }
11
+
12
+ function createPresentIcon(window: Window): SVGSVGElement {
6
13
  const svg = window.document.createElementNS(
7
14
  'http://www.w3.org/2000/svg',
8
15
  'svg',
@@ -17,13 +24,10 @@ function createIcon(window: Window, type: 'hash' | 'present'): SVGSVGElement {
17
24
  svg.setAttribute('stroke-linecap', 'round');
18
25
  svg.setAttribute('stroke-linejoin', 'round');
19
26
 
20
- const paths =
21
- type === 'hash'
22
- ? ['M10 3 8 21', 'M16 3l-2 18', 'M4 9h17', 'M3 15h17']
23
- : [
24
- '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',
25
- 'M9 20h6',
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
+ ];
27
31
 
28
32
  for (const d of paths) {
29
33
  const path = window.document.createElementNS(
@@ -82,7 +86,7 @@ export default (window: Window) => {
82
86
  while (el.firstChild) {
83
87
  link.appendChild(el.firstChild);
84
88
  }
85
- link.appendChild(createIcon(window, 'hash'));
89
+ link.appendChild(createHeadingAnchorIcon(window));
86
90
  el.appendChild(link);
87
91
 
88
92
  const handleClick = () => {
@@ -103,7 +107,7 @@ export default (window: Window) => {
103
107
  button.className = 'heading-present-button';
104
108
  button.setAttribute('aria-label', 'Present from this slide');
105
109
  button.title = 'Present from this slide';
106
- button.appendChild(createIcon(window, 'present'));
110
+ button.appendChild(createPresentIcon(window));
107
111
  el.appendChild(button);
108
112
 
109
113
  const slide = el.parentElement;
@@ -7,7 +7,7 @@ a.heading-anchor {
7
7
  @include subdued-link;
8
8
  }
9
9
 
10
- .heading-anchor svg,
10
+ .heading-anchor .material-symbol-icon,
11
11
  .heading-present-button svg {
12
12
  display: inline-block;
13
13
  width: 1em;
@@ -15,7 +15,7 @@ a.heading-anchor {
15
15
  vertical-align: middle;
16
16
  }
17
17
 
18
- .heading-anchor svg {
18
+ .heading-anchor .material-symbol-icon {
19
19
  margin-left: 4px;
20
20
  vertical-align: middle;
21
21
  opacity: 0;
@@ -39,7 +39,7 @@ a.heading-anchor {
39
39
  opacity: 0;
40
40
  }
41
41
 
42
- a.heading-anchor:focus-visible svg,
42
+ a.heading-anchor:focus-visible .material-symbol-icon,
43
43
  .heading-present-button:focus {
44
44
  opacity: 1;
45
45
  }
@@ -65,12 +65,12 @@ body.is-presenting .heading-present-button {
65
65
  background: transparent;
66
66
  }
67
67
 
68
- h1:hover a.heading-anchor svg,
69
- h2:hover a.heading-anchor svg,
70
- h3:hover a.heading-anchor svg,
71
- h4:hover a.heading-anchor svg,
72
- h5:hover a.heading-anchor svg,
73
- h6:hover a.heading-anchor svg,
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
74
  h1:hover .heading-present-button,
75
75
  h2:hover .heading-present-button,
76
76
  h3:hover .heading-present-button,
@@ -88,7 +88,7 @@ body.is-presenting .heading-present-button {
88
88
  }
89
89
 
90
90
  @media print {
91
- .heading-anchor svg,
91
+ .heading-anchor .material-symbol-icon,
92
92
  .heading-present-button {
93
93
  display: none;
94
94
  }
@@ -0,0 +1,214 @@
1
+ export type FontPreference = 'sans' | 'serif';
2
+ export type ContrastPreference = 'standard' | 'high';
3
+
4
+ const FONT_STORAGE_KEY = 'fontPreference';
5
+ const CONTRAST_STORAGE_KEY = 'contrastPreference';
6
+
7
+ function getStorage(window: Window): Storage | null {
8
+ try {
9
+ return window.localStorage;
10
+ } catch {
11
+ return null;
12
+ }
13
+ }
14
+
15
+ function getStoredPreference<T extends string>(
16
+ storage: Storage | null,
17
+ key: string,
18
+ storedValues: readonly T[],
19
+ defaultValue: T,
20
+ ): T {
21
+ try {
22
+ const storedValue = storage?.getItem(key);
23
+ return storedValues.includes(storedValue as T)
24
+ ? (storedValue as T)
25
+ : defaultValue;
26
+ } catch {
27
+ return defaultValue;
28
+ }
29
+ }
30
+
31
+ function saveStoredPreference<T extends string>(
32
+ storage: Storage | null,
33
+ key: string,
34
+ preference: T,
35
+ defaultValue: T,
36
+ ): void {
37
+ try {
38
+ if (preference === defaultValue) {
39
+ storage?.removeItem(key);
40
+ } else {
41
+ storage?.setItem(key, preference);
42
+ }
43
+ } catch {
44
+ // Storage can be unavailable in privacy-restricted contexts.
45
+ }
46
+ }
47
+
48
+ export function getFontPreference(
49
+ storage: Storage | null,
50
+ defaultValue: FontPreference = 'sans',
51
+ ): FontPreference {
52
+ return getStoredPreference(
53
+ storage,
54
+ FONT_STORAGE_KEY,
55
+ ['sans', 'serif'],
56
+ defaultValue,
57
+ );
58
+ }
59
+
60
+ export function getContrastPreference(
61
+ storage: Storage | null,
62
+ defaultValue: ContrastPreference = 'standard',
63
+ ): ContrastPreference {
64
+ return getStoredPreference(
65
+ storage,
66
+ CONTRAST_STORAGE_KEY,
67
+ ['standard', 'high'],
68
+ defaultValue,
69
+ );
70
+ }
71
+
72
+ export function saveFontPreference(
73
+ storage: Storage | null,
74
+ preference: FontPreference,
75
+ defaultValue: FontPreference = 'sans',
76
+ ): void {
77
+ saveStoredPreference(storage, FONT_STORAGE_KEY, preference, defaultValue);
78
+ }
79
+
80
+ export function saveContrastPreference(
81
+ storage: Storage | null,
82
+ preference: ContrastPreference,
83
+ defaultValue: ContrastPreference = 'standard',
84
+ ): void {
85
+ saveStoredPreference(storage, CONTRAST_STORAGE_KEY, preference, defaultValue);
86
+ }
87
+
88
+ function getDefaultFontPreference(document: Document): FontPreference {
89
+ return document.documentElement.dataset.defaultFontPreference === 'serif'
90
+ ? 'serif'
91
+ : 'sans';
92
+ }
93
+
94
+ function getDefaultContrastPreference(document: Document): ContrastPreference {
95
+ return document.documentElement.dataset.defaultContrastPreference === 'high'
96
+ ? 'high'
97
+ : 'standard';
98
+ }
99
+
100
+ export function applyFontPreference(
101
+ document: Document,
102
+ preference: FontPreference,
103
+ ): void {
104
+ if (preference === 'serif') {
105
+ if (document.documentElement.dataset.fontPreference !== preference) {
106
+ document.documentElement.dataset.fontPreference = preference;
107
+ }
108
+ } else if (document.documentElement.dataset.fontPreference !== undefined) {
109
+ delete document.documentElement.dataset.fontPreference;
110
+ }
111
+ }
112
+
113
+ export function applyContrastPreference(
114
+ document: Document,
115
+ preference: ContrastPreference,
116
+ ): void {
117
+ if (preference === 'high') {
118
+ if (document.documentElement.dataset.contrastPreference !== preference) {
119
+ document.documentElement.dataset.contrastPreference = preference;
120
+ }
121
+ } else if (
122
+ document.documentElement.dataset.contrastPreference !== undefined
123
+ ) {
124
+ delete document.documentElement.dataset.contrastPreference;
125
+ }
126
+ }
127
+
128
+ function syncControls(
129
+ document: Document,
130
+ selector: string,
131
+ preference: string,
132
+ ): void {
133
+ document.querySelectorAll<HTMLButtonElement>(selector).forEach(button => {
134
+ button.disabled = false;
135
+ button.setAttribute(
136
+ 'aria-pressed',
137
+ String(
138
+ button.dataset.fontPreferenceValue === preference ||
139
+ button.dataset.contrastPreferenceValue === preference,
140
+ ),
141
+ );
142
+ });
143
+ }
144
+
145
+ function syncAppearance(
146
+ document: Document,
147
+ fontPreference: FontPreference,
148
+ contrastPreference: ContrastPreference,
149
+ ): void {
150
+ syncControls(document, '[data-font-preference-value]', fontPreference);
151
+ syncControls(
152
+ document,
153
+ '[data-contrast-preference-value]',
154
+ contrastPreference,
155
+ );
156
+ }
157
+
158
+ export default function mountAppearancePicker(window: Window): () => void {
159
+ const { document } = window;
160
+ const container = document.querySelector<HTMLElement>('.appearance-pickers');
161
+ if (!container) {
162
+ return () => {};
163
+ }
164
+
165
+ const buttons = Array.from(
166
+ container.querySelectorAll<HTMLButtonElement>('button'),
167
+ );
168
+ const storage = getStorage(window);
169
+ const defaultFontPreference = getDefaultFontPreference(document);
170
+ const defaultContrastPreference = getDefaultContrastPreference(document);
171
+ let fontPreference = getFontPreference(storage, defaultFontPreference);
172
+ let contrastPreference = getContrastPreference(
173
+ storage,
174
+ defaultContrastPreference,
175
+ );
176
+
177
+ applyFontPreference(document, fontPreference);
178
+ applyContrastPreference(document, contrastPreference);
179
+ syncAppearance(document, fontPreference, contrastPreference);
180
+
181
+ const handleClick = (event: Event) => {
182
+ const button = event.currentTarget as HTMLButtonElement;
183
+ const fontValue = button.dataset.fontPreferenceValue;
184
+ const contrastValue = button.dataset.contrastPreferenceValue;
185
+
186
+ if (fontValue === 'sans' || fontValue === 'serif') {
187
+ fontPreference = fontValue;
188
+ saveFontPreference(storage, fontPreference, defaultFontPreference);
189
+ applyFontPreference(document, fontPreference);
190
+ } else if (contrastValue === 'standard' || contrastValue === 'high') {
191
+ contrastPreference = contrastValue;
192
+ saveContrastPreference(
193
+ storage,
194
+ contrastPreference,
195
+ defaultContrastPreference,
196
+ );
197
+ applyContrastPreference(document, contrastPreference);
198
+ } else {
199
+ return;
200
+ }
201
+
202
+ syncAppearance(document, fontPreference, contrastPreference);
203
+ };
204
+
205
+ for (const button of buttons) {
206
+ button.addEventListener('click', handleClick);
207
+ }
208
+
209
+ return () => {
210
+ for (const button of buttons) {
211
+ button.removeEventListener('click', handleClick);
212
+ }
213
+ };
214
+ }
@@ -0,0 +1,130 @@
1
+ @use '../mixins' as *;
2
+
3
+ .appearance-pickers {
4
+ display: flex;
5
+ gap: 0.5rem;
6
+ justify-content: center;
7
+ margin: 2rem auto 1rem;
8
+ }
9
+
10
+ .preference-picker {
11
+ --preference-picker-option-width: 2.5rem;
12
+ --preference-picker-gap: 0.5rem;
13
+ --preference-picker-padding: 6px;
14
+
15
+ @include user-select(none);
16
+
17
+ position: relative;
18
+ display: grid;
19
+ grid-template-columns: repeat(2, var(--preference-picker-option-width));
20
+ gap: var(--preference-picker-gap);
21
+ width: fit-content;
22
+ padding: var(--preference-picker-padding);
23
+ background: var(--bg2-color);
24
+ border-radius: calc(var(--border-radius) + 4px);
25
+ }
26
+
27
+ .preference-picker::before {
28
+ position: absolute;
29
+ top: var(--preference-picker-padding);
30
+ bottom: var(--preference-picker-padding);
31
+ left: var(--preference-picker-padding);
32
+ width: var(--preference-picker-option-width);
33
+ content: '';
34
+ background: var(--bg-color);
35
+ border-radius: var(--border-radius);
36
+ box-shadow: 0 0 0 var(--control-border-width) var(--fg2-color);
37
+ transform: translateX(0);
38
+
39
+ @media (prefers-reduced-motion: no-preference) {
40
+ transition: transform 0.2s ease;
41
+ }
42
+ }
43
+
44
+ :root[data-font-preference='serif'] .font-picker::before,
45
+ :root[data-contrast-preference='high'] .contrast-picker::before {
46
+ transform: translateX(calc(100% + var(--preference-picker-gap)));
47
+ }
48
+
49
+ .preference-picker:has(
50
+ .preference-picker-option[aria-pressed='true']:is(:hover, :focus)
51
+ )::before {
52
+ background: var(--bg2-color);
53
+ }
54
+
55
+ .preference-picker:has(
56
+ .preference-picker-option[aria-pressed='true']:active:not(:disabled)
57
+ )::before {
58
+ background: var(--link-active-color);
59
+ }
60
+
61
+ .preference-picker-option {
62
+ position: relative;
63
+ z-index: 1;
64
+ display: inline-flex;
65
+ align-items: center;
66
+ justify-content: center;
67
+ width: var(--preference-picker-option-width);
68
+ height: 2rem;
69
+ padding: 0;
70
+ font-size: var(--font-size-smaller);
71
+ color: var(--fg2-color);
72
+ background: transparent;
73
+ border: 0;
74
+ border-radius: var(--border-radius);
75
+ }
76
+
77
+ .font-picker-option-sans {
78
+ font-family:
79
+ -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial,
80
+ sans-serif;
81
+ font-feature-settings: normal;
82
+ }
83
+
84
+ .font-picker-option-serif {
85
+ // Avoid requesting the bundled serif face just to render the inactive
86
+ // preview on a default sans page.
87
+ font-family: var(--serif-preview-font);
88
+ font-feature-settings: var(--serif-font-feature-settings);
89
+ }
90
+
91
+ :root:not([data-font-preference='serif']) .font-picker-option-sans {
92
+ font-family: var(--sans-font);
93
+ font-feature-settings: var(--sans-font-feature-settings);
94
+ }
95
+
96
+ :root[data-font-preference='serif'] .font-picker-option-serif {
97
+ 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);
111
+ }
112
+
113
+ .preference-picker-option[aria-pressed='true'] {
114
+ color: var(--fg-color);
115
+ background: transparent;
116
+ }
117
+
118
+ .preference-picker-option[aria-pressed='true']:is(:hover, :focus) {
119
+ background: transparent;
120
+ }
121
+
122
+ .preference-picker-option[aria-pressed='true']:active:not(:disabled) {
123
+ background: transparent !important;
124
+ }
125
+
126
+ @media print {
127
+ .appearance-pickers {
128
+ display: none;
129
+ }
130
+ }
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-feature-settings: var(--mono-font-feature-settings);
38
39
  color: var(--fg2-color);
39
40
  text-align: right;
40
41
  text-decoration: none;
@@ -16,6 +16,10 @@ header {
16
16
  view-transition-name: site-header;
17
17
  }
18
18
 
19
+ :root[data-contrast-preference='high'] header {
20
+ background: var(--bg-color);
21
+ }
22
+
19
23
  header details summary {
20
24
  position: relative;
21
25
  display: flex;
@@ -56,15 +60,20 @@ header details summary::marker {
56
60
  }
57
61
 
58
62
  header summary::after {
63
+ flex: 0 0 24px;
59
64
  order: -1;
65
+ width: 24px;
60
66
  height: 24px;
61
67
  margin-right: 8px;
62
68
  margin-left: var(--gap);
63
- content: var(--icon-chevron);
69
+ content: '';
70
+
71
+ @include contained-mask(var(--icon-header-menu));
64
72
  }
65
73
 
66
74
  header details[open] summary::after {
67
- transform: rotate(90deg);
75
+ -webkit-mask-image: var(--icon-header-close);
76
+ mask-image: var(--icon-header-close);
68
77
  }
69
78
 
70
79
  header .content {
@@ -131,16 +140,35 @@ header .results-container {
131
140
  }
132
141
 
133
142
  header .search-controls {
143
+ position: relative;
134
144
  display: flex;
135
145
  align-items: center;
136
146
  height: var(--header-collapsed-height);
137
147
 
148
+ @media (width <= 400px) {
149
+ display: none;
150
+ }
151
+
152
+ &::before {
153
+ position: absolute;
154
+ top: 50%;
155
+ left: 7px;
156
+ z-index: 1;
157
+ width: 22px;
158
+ height: 22px;
159
+ pointer-events: none;
160
+ content: '';
161
+ transform: translateY(-50%);
162
+
163
+ @include contained-mask(var(--icon-search));
164
+ }
165
+
166
+ &:has(input:disabled)::before {
167
+ opacity: 0.4;
168
+ }
169
+
138
170
  input.search {
139
171
  width: 180px;
140
-
141
- @media (width <= 400px) {
142
- display: none;
143
- }
144
172
  }
145
173
  }
146
174
 
@@ -50,10 +50,18 @@ header nav div > p {
50
50
  @include user-select(none);
51
51
  }
52
52
 
53
+ :root[data-contrast-preference='high'] header:has(details[open]) {
54
+ background: var(--bg2-color);
55
+ }
56
+
53
57
  @media (hover: hover) {
54
58
  header:has(details[open]):hover {
55
59
  background: var(--bg-color-translucent);
56
60
  }
61
+
62
+ :root[data-contrast-preference='high'] header:hover {
63
+ background: var(--bg2-color);
64
+ }
57
65
  }
58
66
 
59
67
  header .results-container {
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import './style.scss';
2
2
 
3
+ import './material-symbols.scss';
3
4
  import './anchor/style.scss';
4
5
  import './code.scss';
5
6
  import './code/style.scss';
@@ -12,6 +13,7 @@ import './slides/style.scss';
12
13
  import './timezone/style.scss';
13
14
  import './toc/style.scss';
14
15
  import './trace/style.scss';
16
+ import './appearance-picker/style.scss';
15
17
  import './navigate/style.scss';
16
18
  import './page-update/style.scss';
17
19
 
@@ -0,0 +1,11 @@
1
+ @use 'mixins' as *;
2
+
3
+ .material-symbol-icon {
4
+ @include contained-mask(var(--material-symbol-icon));
5
+
6
+ display: block;
7
+ }
8
+
9
+ .material-symbol-icon-heading-anchor {
10
+ --material-symbol-icon: var(--icon-heading-anchor);
11
+ }
@@ -6,6 +6,7 @@ import mountCode from '../code';
6
6
  import mountTrace from '../trace';
7
7
  import mountPrint from '../print';
8
8
  import mountSlides from '../slides';
9
+ import mountAppearancePicker from '../appearance-picker';
9
10
  import { scheduleTask } from '../util';
10
11
 
11
12
  const PER_PAGE_COMPONENTS: Record<
@@ -20,6 +21,7 @@ const PER_PAGE_COMPONENTS: Record<
20
21
  trace: mountTrace,
21
22
  slides: mountSlides,
22
23
  print: mountPrint,
24
+ appearancePicker: mountAppearancePicker,
23
25
  };
24
26
 
25
27
  let cleanups: (() => void)[] = [];
@@ -2,7 +2,6 @@
2
2
 
3
3
  header {
4
4
  .search-controls {
5
- display: flex;
6
5
  flex-wrap: wrap;
7
6
  gap: 0.5rem;
8
7
  align-items: center;
@@ -94,6 +94,7 @@ body.is-presenting header,
94
94
  body.is-presenting .title-and-info,
95
95
  body.is-presenting nav.toc,
96
96
  body.is-presenting footer,
97
+ body.is-presenting .appearance-pickers,
97
98
  body.is-presenting .slides-header,
98
99
  body.is-presenting .trace-toolbar {
99
100
  display: none;