@astronox/core 0.2.2 → 0.2.4

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 (59) hide show
  1. package/package.json +1 -1
  2. package/src/admin/cache.ts +217 -0
  3. package/src/admin/index.ts +1 -0
  4. package/src/auth/firebase/client.ts +37 -7
  5. package/src/client/formSubmission.ts +177 -0
  6. package/src/client/index.ts +1 -0
  7. package/src/components/actions/Button/Button.css +1 -1
  8. package/src/components/actions/CardAction/CardAction.css +3 -3
  9. package/src/components/actions/ScrollToTop/ScrollToTop.css +11 -0
  10. package/src/components/actions/ThemeToggle/ThemeToggle.astro +59 -0
  11. package/src/components/actions/ThemeToggle/ThemeToggle.css +83 -0
  12. package/src/components/actions/ThemeToggle/themeToggleController.ts +111 -0
  13. package/src/components/content/CardBody/CardBody.css +6 -2
  14. package/src/components/content/CardFooter/CardFooter.astro +9 -0
  15. package/src/components/content/CardFooter/CardFooter.css +45 -3
  16. package/src/components/content/CardLink/CardLink.astro +12 -3
  17. package/src/components/content/CardLink/CardLink.css +32 -0
  18. package/src/components/content/MarkdownContent/MarkdownContent.css +14 -4
  19. package/src/components/content/MarkdownContent/MarkdownInline.astro +1 -1
  20. package/src/components/content/MarkdownContent/contracts.ts +1 -1
  21. package/src/components/content/MarkdownContent/markdownContentRenderer.ts +7 -4
  22. package/src/components/data-display/TextPair/TextPair.css +8 -2
  23. package/src/components/feedback/ThemeBootstrap/ThemeBootstrap.astro +31 -0
  24. package/src/components/forms/ChoiceGroup/ChoiceGroup.css +1 -1
  25. package/src/components/forms/PreparedFormBootstrap/PreparedFormBootstrap.astro +203 -0
  26. package/src/components/forms/PreparedFormBootstrap/contracts.ts +15 -0
  27. package/src/components/forms/Textarea/Textarea.astro +3 -0
  28. package/src/components/forms/Textarea/Textarea.css +8 -0
  29. package/src/components/icons/CircleSymbolIcon.css +3 -3
  30. package/src/components/index.ts +10 -0
  31. package/src/components/navigation/Breadcrumbs/Breadcrumbs.astro +5 -1
  32. package/src/components/navigation/Breadcrumbs/Breadcrumbs.css +12 -17
  33. package/src/components/navigation/DesktopNavigation.css +4 -1
  34. package/src/components/navigation/Footer/Footer.astro +102 -0
  35. package/src/components/navigation/Footer/Footer.css +155 -0
  36. package/src/components/navigation/Footer/contracts.ts +17 -0
  37. package/src/components/navigation/Header.astro +20 -2
  38. package/src/components/navigation/MobileNavigation.astro +107 -43
  39. package/src/components/navigation/MobileNavigation.css +109 -24
  40. package/src/components/navigation/NavigationBrand.css +1 -1
  41. package/src/components/navigation/NavigationControl.css +16 -2
  42. package/src/components/navigation/NavigationItemContent.astro +6 -1
  43. package/src/components/navigation/mobileNavigationController.ts +0 -16
  44. package/src/components/navigation/navigationTypes.ts +9 -1
  45. package/src/composition/Grid/Grid.css +16 -0
  46. package/src/data/firestore/client/contracts.ts +23 -0
  47. package/src/data/firestore/client/firebase.ts +45 -1
  48. package/src/display/contracts.ts +5 -1
  49. package/src/layouts/BaseLayout.astro +13 -1
  50. package/src/patterns/Combobox/comboboxController.ts +69 -0
  51. package/src/patterns/MapViewport/MapViewport.css +3 -3
  52. package/src/patterns/Table/TableBody.css +3 -3
  53. package/src/patterns/Table/TableCell.css +5 -1
  54. package/src/styles/base.css +24 -7
  55. package/src/styles/style.css +2 -0
  56. package/src/styles/tokens.css +60 -15
  57. package/src/theme/contracts.ts +2 -0
  58. package/src/theme/index.ts +1 -0
  59. package/src/theme/storage.ts +1 -0
@@ -1,7 +1,17 @@
1
1
  import { getApps, initializeApp } from 'firebase/app';
2
- import { getFirestore } from 'firebase/firestore';
2
+ import {
3
+ collection,
4
+ doc,
5
+ getFirestore,
6
+ serverTimestamp,
7
+ writeBatch,
8
+ } from 'firebase/firestore';
3
9
 
4
10
  import type { FirebaseClientConfig } from '../../../auth/firebase/contracts';
11
+ import type {
12
+ ClientFirestoreBatchOperation,
13
+ ClientFirestoreBatchResult,
14
+ } from './contracts';
5
15
 
6
16
  export function createFirebaseClientFirestore(config: FirebaseClientConfig) {
7
17
  const app =
@@ -10,3 +20,37 @@ export function createFirebaseClientFirestore(config: FirebaseClientConfig) {
10
20
 
11
21
  return getFirestore(app);
12
22
  }
23
+
24
+ export function createClientFirestoreServerTimestamp() {
25
+ return serverTimestamp();
26
+ }
27
+
28
+ export async function commitClientFirestoreBatch(
29
+ database: ReturnType<typeof createFirebaseClientFirestore>,
30
+ operations: readonly ClientFirestoreBatchOperation[],
31
+ ): Promise<ClientFirestoreBatchResult> {
32
+ const batch = writeBatch(database);
33
+ const createdIds: string[] = [];
34
+
35
+ for (const operation of operations) {
36
+ const documents = collection(database, operation.collection);
37
+ if (operation.kind === 'create') {
38
+ const reference = operation.id
39
+ ? doc(documents, operation.id)
40
+ : doc(documents);
41
+ batch.set(reference, operation.value);
42
+ createdIds.push(reference.id);
43
+ continue;
44
+ }
45
+
46
+ const reference = doc(documents, operation.id);
47
+ if (operation.kind === 'update') {
48
+ batch.update(reference, operation.value);
49
+ } else {
50
+ batch.delete(reference);
51
+ }
52
+ }
53
+
54
+ await batch.commit();
55
+ return { createdIds };
56
+ }
@@ -104,10 +104,13 @@ type DisplayCardLinkContent = {
104
104
  compactContent?: 'text' | 'media';
105
105
  compactText?: DisplayTextValue;
106
106
  compactVisual?: 'show' | 'hide';
107
+ density?: 'default' | 'relaxed';
107
108
  iconTone?: Tone;
108
- label: string;
109
+ label?: string;
109
110
  title: string;
110
111
  meta?: DisplayTextValue;
112
+ textLayout?: TextPairLayout;
113
+ textTreatment?: 'default' | 'heading';
111
114
  media?: DisplayMedia;
112
115
  mediaPosition?: 'start' | 'end';
113
116
  mediaSize?: 'default' | 'compact' | 'small';
@@ -308,6 +311,7 @@ type NavigationItemBase = {
308
311
  rel?: string;
309
312
  target?: '_blank' | '_self';
310
313
  hreflang?: string;
314
+ icon?: AstroComponent;
311
315
  media?: DisplayMedia;
312
316
  mediaFrame?: 'bordered';
313
317
  mediaShape?: 'circle' | 'rectangle';
@@ -9,6 +9,8 @@ import type { Tone } from '../theme/contracts';
9
9
  import PageSurface from './PageSurface.astro';
10
10
  import Breadcrumbs from '../components/navigation/Breadcrumbs/Breadcrumbs.astro';
11
11
  import ScrollToTop from '../components/actions/ScrollToTop/ScrollToTop.astro';
12
+ import ThemeBootstrap from '../components/feedback/ThemeBootstrap/ThemeBootstrap.astro';
13
+ import { defaultThemeStorageKey } from '../theme/storage';
12
14
 
13
15
  type Props = HTMLAttributes<'html'> & {
14
16
  meta: PageMeta;
@@ -16,13 +18,16 @@ type Props = HTMLAttributes<'html'> & {
16
18
  currentPath?: string;
17
19
  breadcrumbs?: readonly BreadcrumbItem[];
18
20
  breadcrumbLabel: string;
21
+ breadcrumbTone?: Tone;
19
22
  scrollToTopLabel?: string;
20
23
  structuredData?: readonly JsonLdNode[];
21
24
  tone?: Tone;
25
+ themeStorageKey?: string;
22
26
  };
23
27
 
24
28
  const {
25
29
  breadcrumbLabel,
30
+ breadcrumbTone,
26
31
  breadcrumbs,
27
32
  currentPath = Astro.url.pathname,
28
33
  'data-tone': dataTone,
@@ -31,6 +36,7 @@ const {
31
36
  site,
32
37
  structuredData,
33
38
  tone,
39
+ themeStorageKey = defaultThemeStorageKey,
34
40
  ...htmlAttrs
35
41
  } = Astro.props;
36
42
  const lang = meta.lang ?? site.defaultMeta.lang;
@@ -42,6 +48,7 @@ const resolvedTone = tone ?? dataTone;
42
48
  <head>
43
49
  <meta charset="utf-8" />
44
50
  <meta name="viewport" content="width=device-width, initial-scale=1" />
51
+ <ThemeBootstrap storageKey={themeStorageKey} />
45
52
  <SeoHead
46
53
  breadcrumbs={breadcrumbs}
47
54
  meta={meta}
@@ -59,7 +66,11 @@ const resolvedTone = tone ?? dataTone;
59
66
  <div class="site-main__content">
60
67
  {
61
68
  breadcrumbs?.length ? (
62
- <Breadcrumbs items={breadcrumbs} label={breadcrumbLabel} />
69
+ <Breadcrumbs
70
+ items={breadcrumbs}
71
+ label={breadcrumbLabel}
72
+ tone={breadcrumbTone}
73
+ />
63
74
  ) : null
64
75
  }
65
76
 
@@ -68,6 +79,7 @@ const resolvedTone = tone ?? dataTone;
68
79
  </PageSurface>
69
80
  </div>
70
81
  </main>
82
+ <slot name="footer" />
71
83
  </div>
72
84
  {scrollToTopLabel ? <ScrollToTop label={scrollToTopLabel} /> : null}
73
85
  </body>
@@ -17,6 +17,13 @@ export type ComboboxNumberBounds = {
17
17
  max: number;
18
18
  };
19
19
 
20
+ export type DynamicComboboxOption = {
21
+ description?: string;
22
+ disabled?: boolean;
23
+ label: string;
24
+ value: string;
25
+ };
26
+
20
27
  type NumberBounds = {
21
28
  min: number;
22
29
  max: number;
@@ -159,6 +166,68 @@ export function clearCombobox(
159
166
  root.dispatchEvent(new Event('combobox:clear'));
160
167
  }
161
168
 
169
+ export function setComboboxOptions(
170
+ root: HTMLElement,
171
+ options: readonly DynamicComboboxOption[],
172
+ selectedValues: readonly string[] = [],
173
+ ): void {
174
+ if (root.dataset.comboboxMode === 'number') return;
175
+ const optionsRoot = root.querySelector<HTMLElement>('[data-combobox-options]');
176
+ const empty = root.querySelector<HTMLElement>('[data-combobox-empty]');
177
+ if (!optionsRoot || !empty) return;
178
+
179
+ optionsRoot.querySelectorAll('[data-combobox-option]').forEach((node) => node.remove());
180
+ root.querySelectorAll('template[data-combobox-tag-template]').forEach((node) => node.remove());
181
+
182
+ options.forEach((option, index) => {
183
+ const button = document.createElement('button');
184
+ button.className = 'combobox__option';
185
+ button.id = `${root.id}-option-${index}`;
186
+ button.type = 'button';
187
+ button.role = 'option';
188
+ button.tabIndex = -1;
189
+ button.disabled = option.disabled === true;
190
+ button.setAttribute('aria-selected', 'false');
191
+ if (option.disabled) button.setAttribute('aria-disabled', 'true');
192
+ button.dataset.comboboxOption = option.value;
193
+ button.dataset.comboboxSearchText = [option.label, option.description ?? ''].join(' ');
194
+ button.dataset.interactionAccent = 'start';
195
+
196
+ const content = document.createElement('span');
197
+ content.className = 'combobox__option-content';
198
+ const label = document.createElement('span');
199
+ label.className = 'combobox__option-label';
200
+ label.textContent = option.label;
201
+ content.append(label);
202
+ if (option.description) {
203
+ const description = document.createElement('span');
204
+ description.className = 'combobox__option-description';
205
+ description.textContent = option.description;
206
+ content.append(description);
207
+ }
208
+ button.append(content);
209
+ optionsRoot.insertBefore(button, empty);
210
+
211
+ const template = document.createElement('template');
212
+ template.dataset.comboboxTagTemplate = option.value;
213
+ const tag = document.createElement('span');
214
+ tag.className = 'tag';
215
+ tag.dataset.widget = 'tag';
216
+ tag.dataset.size = 'sm';
217
+ tag.dataset.comboboxTag = option.value;
218
+ const tagLabel = document.createElement('span');
219
+ tagLabel.className = 'tag__label';
220
+ tagLabel.dataset.tagLabel = '';
221
+ tagLabel.textContent = option.label;
222
+ tag.append(tagLabel);
223
+ template.content.append(tag);
224
+ root.append(template);
225
+ });
226
+
227
+ empty.hidden = options.length > 0;
228
+ setComboboxValues(root, selectedValues);
229
+ }
230
+
162
231
  export function setComboboxNumberValue(
163
232
  root: HTMLElement,
164
233
  requestedValue: number,
@@ -155,12 +155,12 @@
155
155
  --map-viewport-cell-surface: color-mix(
156
156
  in srgb,
157
157
  var(--map-viewport-cell-seed) 38%,
158
- var(--nox-mix-light)
158
+ var(--nox-theme-surface-base)
159
159
  );
160
160
  --map-viewport-cell-surface-hover: color-mix(
161
161
  in srgb,
162
162
  var(--map-viewport-cell-seed) 52%,
163
- var(--nox-mix-light)
163
+ var(--nox-theme-surface-base)
164
164
  );
165
165
  --map-viewport-cell-background: color-mix(
166
166
  in srgb,
@@ -175,7 +175,7 @@
175
175
  --map-viewport-cell-border: color-mix(
176
176
  in srgb,
177
177
  var(--map-viewport-cell-seed) 74%,
178
- var(--nox-mix-dark)
178
+ var(--nox-theme-text-base)
179
179
  );
180
180
 
181
181
  fill: var(--map-viewport-cell-background);
@@ -130,7 +130,7 @@
130
130
  }
131
131
 
132
132
  .table-body__row {
133
- --table-primary-link-color: var(--nox-text);
133
+ --table-primary-link-color: var(--nox-content-link-color);
134
134
  --table-row-intrinsic-block-size: var(--table-row-min-block-size);
135
135
 
136
136
  display: grid;
@@ -173,7 +173,7 @@
173
173
  }
174
174
 
175
175
  .table-body__row[data-table-row-href]:focus-within {
176
- --table-primary-link-color: var(--nox-link-hover);
176
+ --table-primary-link-color: var(--nox-content-link-color);
177
177
  }
178
178
 
179
179
  .table-body__cell {
@@ -253,7 +253,7 @@
253
253
 
254
254
  @media (hover: hover) and (pointer: fine) {
255
255
  .table-body__row[data-table-row-href]:hover {
256
- --table-primary-link-color: var(--nox-link-hover);
256
+ --table-primary-link-color: var(--nox-content-link-color);
257
257
  }
258
258
  }
259
259
 
@@ -68,6 +68,10 @@
68
68
  var(--nox-motion-easing-standard);
69
69
  }
70
70
 
71
+ .table-cell__link [data-text-weight='strong'] {
72
+ font-weight: var(--nox-content-link-emphasis-font-weight);
73
+ }
74
+
71
75
  .table-cell__empty {
72
76
  color: var(--nox-text-muted);
73
77
  font-size: var(--nox-font-size-sm);
@@ -93,7 +97,7 @@
93
97
 
94
98
  @media (hover: hover) and (pointer: fine) {
95
99
  .table-cell__link:hover {
96
- color: var(--nox-link-hover);
100
+ color: var(--nox-content-link-color);
97
101
  }
98
102
  }
99
103
 
@@ -70,7 +70,20 @@ body {
70
70
  }
71
71
 
72
72
  a {
73
- color: inherit;
73
+ color: var(--nox-content-link-color);
74
+ font-weight: var(--nox-content-link-font-weight);
75
+ text-decoration: none;
76
+ text-decoration-thickness: 0.0625rem;
77
+ text-underline-offset: 0.2em;
78
+ }
79
+
80
+ a[data-tone] {
81
+ color: var(--nox-tone-color);
82
+ }
83
+
84
+ strong a,
85
+ a strong {
86
+ font-weight: var(--nox-content-link-emphasis-font-weight);
74
87
  }
75
88
 
76
89
  a:focus-visible,
@@ -83,6 +96,12 @@ button {
83
96
  font: inherit;
84
97
  }
85
98
 
99
+ @media (hover: hover) and (pointer: fine) {
100
+ a:where([href]:hover) {
101
+ text-decoration-line: underline;
102
+ }
103
+ }
104
+
86
105
  img,
87
106
  svg,
88
107
  video {
@@ -104,7 +123,6 @@ p {
104
123
  overflow-y: auto;
105
124
  background: var(--nox-page-background);
106
125
  overscroll-behavior-y: contain;
107
- scrollbar-gutter: stable both-edges;
108
126
  }
109
127
 
110
128
  .site-main {
@@ -113,18 +131,19 @@ p {
113
131
  display: grid;
114
132
  width: var(--nox-page-width);
115
133
  margin: 0 auto;
116
- padding-block: var(--site-main-content-gap) var(--nox-space-7);
117
- gap: var(--nox-layout-gap);
134
+ padding-block: var(--site-main-content-gap);
135
+ align-content: start;
118
136
  }
119
137
 
120
138
  .site-main__content {
121
139
  display: grid;
122
140
  min-width: 0;
141
+ align-content: start;
123
142
  gap: var(--site-main-content-gap);
124
143
  }
125
144
 
126
145
  .site-main:has(> .site-main__content > .breadcrumbs) {
127
- padding-block-start: 0;
146
+ padding-block: 0 var(--site-main-content-gap);
128
147
  }
129
148
 
130
149
  .site-main__content:has(> .breadcrumbs) {
@@ -134,8 +153,6 @@ p {
134
153
  @media (width < 48rem) {
135
154
  .site-main {
136
155
  --site-main-content-gap: var(--nox-space-4);
137
-
138
- padding-block-end: var(--nox-space-6);
139
156
  }
140
157
  }
141
158
 
@@ -17,6 +17,7 @@
17
17
  @import '../components/actions/ActionListItem/ActionListItem.css'
18
18
  layer(components);
19
19
  @import '../components/actions/Button/Button.css' layer(components);
20
+ @import '../components/actions/ThemeToggle/ThemeToggle.css' layer(components);
20
21
  @import '../components/actions/CardAction/CardAction.css'
21
22
  layer(components);
22
23
  @import '../components/actions/ScrollToTop/ScrollToTop.css'
@@ -60,6 +61,7 @@
60
61
  @import '../components/navigation/Breadcrumbs/Breadcrumbs.css'
61
62
  layer(components);
62
63
  @import '../components/navigation/Header.css' layer(components);
64
+ @import '../components/navigation/Footer/Footer.css' layer(components);
63
65
  @import '../components/navigation/NavigationBrand.css' layer(components);
64
66
  @import '../components/navigation/NavigationControl.css' layer(components);
65
67
  @import '../components/navigation/NavigationMenuList.css' layer(components);
@@ -9,6 +9,11 @@
9
9
  --nox-source-tone-warning: #d97706;
10
10
  --nox-source-tone-danger: #dc2626;
11
11
 
12
+ /* Shared textual-link recipe. */
13
+ --nox-content-link-color: var(--nox-source-tone-info);
14
+ --nox-content-link-font-weight: var(--nox-font-weight-regular);
15
+ --nox-content-link-emphasis-font-weight: var(--nox-font-weight-medium);
16
+
12
17
  /* Public map seeds. Component states derive their full treatment from these. */
13
18
  --nox-source-map-available: #00b86b;
14
19
  --nox-source-map-unavailable: #f43f5e;
@@ -22,6 +27,42 @@
22
27
  --nox-mix-light: #ffffff;
23
28
  --nox-mix-dark: #020617;
24
29
  --nox-tone-seed: var(--nox-source-tone-neutral);
30
+
31
+ /* Theme recipe anchors. Light mode preserves the original palette. */
32
+ --nox-theme-page-base: var(--nox-mix-light);
33
+ --nox-theme-surface-base: var(--nox-mix-light);
34
+ --nox-theme-text-base: var(--nox-mix-dark);
35
+ --nox-theme-text-disabled-base: var(--nox-mix-light);
36
+ --nox-theme-border-base: var(--nox-mix-light);
37
+ --nox-theme-inverse-base: var(--nox-mix-light);
38
+ --nox-theme-shadow-base: var(--nox-mix-dark);
39
+ }
40
+
41
+ :root[data-theme='dark'] {
42
+ color-scheme: dark;
43
+
44
+ --nox-source-tone-neutral: #8f9bad;
45
+ --nox-source-tone-primary: #a78bfa;
46
+ --nox-source-tone-info: #73a4ff;
47
+ --nox-source-tone-success: #42d392;
48
+ --nox-source-tone-warning: #f0ad55;
49
+ --nox-source-tone-danger: #f47777;
50
+
51
+ --nox-source-map-available: #42d392;
52
+ --nox-source-map-unavailable: #fb7185;
53
+ --nox-source-map-field: #fbbf24;
54
+ --nox-source-map-grass: #84cc16;
55
+ --nox-source-map-mountain: #94a3b8;
56
+ --nox-source-map-water: #38bdf8;
57
+ --nox-source-map-range: #60a5fa;
58
+
59
+ --nox-theme-page-base: #0f1218;
60
+ --nox-theme-surface-base: #171b23;
61
+ --nox-theme-text-base: #f8fafc;
62
+ --nox-theme-text-disabled-base: #657184;
63
+ --nox-theme-border-base: #252b35;
64
+ --nox-theme-inverse-base: #0f1218;
65
+ --nox-theme-shadow-base: #000000;
25
66
  }
26
67
 
27
68
  :root,
@@ -30,51 +71,55 @@
30
71
  --nox-page-background: color-mix(
31
72
  in srgb,
32
73
  var(--nox-tone-seed) 11%,
33
- var(--nox-mix-light)
74
+ var(--nox-theme-page-base)
34
75
  );
35
76
  --nox-surface: color-mix(
36
77
  in srgb,
37
78
  var(--nox-tone-seed) 2%,
38
- var(--nox-mix-light)
79
+ var(--nox-theme-surface-base)
39
80
  );
40
81
  --nox-surface-muted: color-mix(
41
82
  in srgb,
42
83
  var(--nox-tone-seed) 8%,
43
- var(--nox-mix-light)
84
+ var(--nox-theme-surface-base)
44
85
  );
45
86
  --nox-surface-strong: color-mix(
46
87
  in srgb,
47
88
  var(--nox-tone-seed) 14%,
48
- var(--nox-mix-light)
89
+ var(--nox-theme-surface-base)
49
90
  );
50
91
 
51
- --nox-text: color-mix(in srgb, var(--nox-tone-seed) 18%, var(--nox-mix-dark));
92
+ --nox-text: color-mix(
93
+ in srgb,
94
+ var(--nox-tone-seed) 18%,
95
+ var(--nox-theme-text-base)
96
+ );
52
97
  --nox-text-muted: color-mix(
53
98
  in srgb,
54
99
  var(--nox-tone-seed) 52%,
55
- var(--nox-mix-dark)
100
+ var(--nox-theme-text-base)
56
101
  );
57
102
  --nox-text-subtle: color-mix(
58
103
  in srgb,
59
104
  var(--nox-tone-seed) 68%,
60
- var(--nox-mix-dark)
105
+ var(--nox-theme-text-base)
61
106
  );
62
107
  --nox-text-disabled: color-mix(
63
108
  in srgb,
64
109
  var(--nox-tone-seed) 36%,
65
- var(--nox-mix-light)
110
+ var(--nox-theme-text-disabled-base)
66
111
  );
67
- --nox-text-inverse: var(--nox-mix-light);
112
+ --nox-text-inverse: var(--nox-theme-inverse-base);
68
113
 
69
114
  --nox-border: color-mix(
70
115
  in srgb,
71
116
  var(--nox-tone-seed) 28%,
72
- var(--nox-mix-light)
117
+ var(--nox-theme-border-base)
73
118
  );
74
119
  --nox-border-muted: color-mix(
75
120
  in srgb,
76
121
  var(--nox-tone-seed) 18%,
77
- var(--nox-mix-light)
122
+ var(--nox-theme-border-base)
78
123
  );
79
124
 
80
125
  /* Tone interaction slots. */
@@ -84,12 +129,12 @@
84
129
  --nox-tone-hover-background: color-mix(
85
130
  in srgb,
86
131
  var(--nox-tone-seed) 12%,
87
- var(--nox-mix-light)
132
+ var(--nox-theme-surface-base)
88
133
  );
89
134
  --nox-tone-active-background: color-mix(
90
135
  in srgb,
91
136
  var(--nox-tone-seed) 20%,
92
- var(--nox-mix-light)
137
+ var(--nox-theme-surface-base)
93
138
  );
94
139
  --nox-tone-border: color-mix(
95
140
  in srgb,
@@ -99,13 +144,13 @@
99
144
  --nox-tone-text: color-mix(
100
145
  in srgb,
101
146
  var(--nox-tone-seed) 72%,
102
- var(--nox-mix-dark)
147
+ var(--nox-theme-text-base)
103
148
  );
104
149
  --nox-tone-focus-ring: var(--nox-tone-seed);
105
150
  --nox-tone-shadow: color-mix(
106
151
  in srgb,
107
152
  var(--nox-tone-seed) 24%,
108
- var(--nox-mix-dark)
153
+ var(--nox-theme-shadow-base)
109
154
  );
110
155
 
111
156
  /* Shared framed-data surface slots. */
@@ -5,3 +5,5 @@ export type Tone =
5
5
  | 'success'
6
6
  | 'warning'
7
7
  | 'danger';
8
+
9
+ export type ColorTheme = 'light' | 'dark';
@@ -1 +1,2 @@
1
1
  export * from './contracts';
2
+ export * from './storage';
@@ -0,0 +1 @@
1
+ export const defaultThemeStorageKey = 'astronox:theme';