@halogen-ui/tokens 0.1.0
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/LICENSE.md +102 -0
- package/README.md +43 -0
- package/dist/CONTRAST-REPORT.md +511 -0
- package/dist/figma.json +1932 -0
- package/dist/fonts/Geist-Variable.woff2 +0 -0
- package/dist/fonts/GeistMono-Variable.woff2 +0 -0
- package/dist/fonts/LICENSE-Geist-OFL-1.1.txt +92 -0
- package/dist/fonts.css +24 -0
- package/dist/halogen.tokens.json +2061 -0
- package/dist/theme.css +859 -0
- package/dist/token-review.html +902 -0
- package/dist/tokens.css +782 -0
- package/dist/tokens.d.ts +535 -0
- package/dist/tokens.js +532 -0
- package/dist/tokens.ts +540 -0
- package/dist/tw-classgroups.json +351 -0
- package/fonts/Geist-Variable.woff2 +0 -0
- package/fonts/GeistMono-Variable.woff2 +0 -0
- package/fonts/LICENSE-Geist-OFL-1.1.txt +92 -0
- package/package.json +66 -0
- package/src/color/oklch.ts +306 -0
- package/src/contrast-pairs.ts +473 -0
- package/src/namespaces.ts +59 -0
- package/src/primitives/color.ts +404 -0
- package/src/primitives/elevation.ts +38 -0
- package/src/primitives/motion.ts +129 -0
- package/src/primitives/scale.ts +163 -0
- package/src/primitives/typography.ts +184 -0
- package/src/semantic/index.ts +415 -0
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Halogen — semantic tokens (layer 2 of 2).
|
|
3
|
+
*
|
|
4
|
+
* Aliases only. Every value here is a reference to a primitive; not one raw
|
|
5
|
+
* color, size or duration is permitted. That constraint is what makes the two
|
|
6
|
+
* layers actually two layers rather than one layer with a naming convention,
|
|
7
|
+
* and it is asserted by a test.
|
|
8
|
+
*
|
|
9
|
+
* Light and dark are authored independently rather than by inverting the ramp.
|
|
10
|
+
* They are not mirrors: dark-mode secondary text sits at `neutral-300` and
|
|
11
|
+
* light-mode secondary at `neutral-700`, because contrast against a near-black
|
|
12
|
+
* ground and contrast against an off-white ground are different problems with
|
|
13
|
+
* different answers.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export type Ref = string;
|
|
17
|
+
|
|
18
|
+
export interface SemanticTheme {
|
|
19
|
+
surface: Record<string, Ref>;
|
|
20
|
+
/** Foreground colors. Named `ink` so the Tailwind utility reads `text-ink-secondary`, not `text-text-secondary`. */
|
|
21
|
+
ink: Record<string, Ref>;
|
|
22
|
+
/** Border colors. Named `line` so the utility reads `border-line-control`, not `border-border-control`. */
|
|
23
|
+
line: Record<string, Ref>;
|
|
24
|
+
status: Record<string, Ref>;
|
|
25
|
+
viz: Record<string, Ref>;
|
|
26
|
+
gradient: Record<string, Ref>;
|
|
27
|
+
elevation: Record<string, Ref>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* -------------------------------------------------------------------------- */
|
|
31
|
+
/* Dark — the default theme */
|
|
32
|
+
/* -------------------------------------------------------------------------- */
|
|
33
|
+
|
|
34
|
+
export const dark: SemanticTheme = {
|
|
35
|
+
surface: {
|
|
36
|
+
/** elev-0. The page. */
|
|
37
|
+
ground: '{color.neutral.1000}',
|
|
38
|
+
/** elev-1. Cards. */
|
|
39
|
+
raised: '{color.neutral.950}',
|
|
40
|
+
/** elev-2. Nested panels, raised cards. */
|
|
41
|
+
overlay: '{color.neutral.900}',
|
|
42
|
+
/** elev-3. Popovers, menus, tooltips. */
|
|
43
|
+
floating: '{color.neutral.900}',
|
|
44
|
+
/** elev-4. Dialogs and sheets. */
|
|
45
|
+
modal: '{color.neutral.900}',
|
|
46
|
+
/** Secondary button and inert chip — a surface you press. */
|
|
47
|
+
subtle: '{color.neutral.900}',
|
|
48
|
+
/**
|
|
49
|
+
* An input — a surface you write into, which is a different job from one
|
|
50
|
+
* you press. Sharing a token forced one value to read as both raised and
|
|
51
|
+
* recessed, and in the light theme it made the field a gray box.
|
|
52
|
+
*/
|
|
53
|
+
field: '{color.neutral.900}',
|
|
54
|
+
'subtle-hover': '{color.neutral.850}',
|
|
55
|
+
'subtle-active': '{color.neutral.800}',
|
|
56
|
+
/** Translucent washes. These work on any surface, including inside an overlay. */
|
|
57
|
+
'state-hover': '{color.state.hover-dark}',
|
|
58
|
+
'state-active': '{color.state.active-dark}',
|
|
59
|
+
selection: '{color.state.selection-dark}',
|
|
60
|
+
/**
|
|
61
|
+
* A control knob — the switch thumb.
|
|
62
|
+
*
|
|
63
|
+
* Light in BOTH themes, which is why it needs its own token: it has to read
|
|
64
|
+
* against a dark off-track, a light off-track and an orange on-track, and no
|
|
65
|
+
* existing surface token is light in both. Pointing it at
|
|
66
|
+
* `surface-floating` made it exactly the color of the disabled track it sat
|
|
67
|
+
* on, so a disabled switch showed no thumb at all.
|
|
68
|
+
*/
|
|
69
|
+
knob: '{color.neutral.50}',
|
|
70
|
+
/** An intentionally inverted card, in the opposite theme's ground. */
|
|
71
|
+
inverse: '{color.neutral.50}',
|
|
72
|
+
/** The primary action. Brightens on hover — the label is near-black. */
|
|
73
|
+
accent: '{color.orange.base}',
|
|
74
|
+
'accent-hover': '{color.orange.bright}',
|
|
75
|
+
'accent-active': '{color.orange.dim}',
|
|
76
|
+
/** A quiet accent-tinted surface: selected row, highlighted panel. */
|
|
77
|
+
'accent-subtle': '{color.orange.surface-dark}',
|
|
78
|
+
'accent-subtle-hover': '{color.orange.surface-dark-hover}',
|
|
79
|
+
disabled: '{color.neutral.900}',
|
|
80
|
+
/** Flat tint. No backdrop-filter, ever. */
|
|
81
|
+
scrim: '{color.neutral.1000}',
|
|
82
|
+
},
|
|
83
|
+
ink: {
|
|
84
|
+
primary: '{color.neutral.50}',
|
|
85
|
+
secondary: '{color.neutral.300}',
|
|
86
|
+
tertiary: '{color.neutral.450}',
|
|
87
|
+
placeholder: '{color.neutral.450}',
|
|
88
|
+
/** Exempt from 1.4.3, but still perceivable. */
|
|
89
|
+
disabled: '{color.neutral.600}',
|
|
90
|
+
/** The one brand accent legal as text. */
|
|
91
|
+
accent: '{color.orange.base}',
|
|
92
|
+
/**
|
|
93
|
+
* Accent text on the quiet accent surface — an accent badge or a selected
|
|
94
|
+
* row. A separate token because the accent chip is darker than the page
|
|
95
|
+
* ground in the light theme, so the general `ink-accent` step (solved
|
|
96
|
+
* against the page) fails inside the very component it is for. Same shape
|
|
97
|
+
* as the status chips.
|
|
98
|
+
*/
|
|
99
|
+
'on-accent-subtle': '{color.orange.base}',
|
|
100
|
+
/** On any warm fill or gradient. Never white. */
|
|
101
|
+
'on-accent': '{color.neutral.1000}',
|
|
102
|
+
/** Text on any warm gradient. Measured at the gradient's worst stop. */
|
|
103
|
+
'on-gradient': '{color.neutral.1000}',
|
|
104
|
+
/**
|
|
105
|
+
* Text on a scrimmed surface. Light in BOTH themes, which is why it needs
|
|
106
|
+
* its own token.
|
|
107
|
+
*
|
|
108
|
+
* A scrim is near-black whatever the theme, so a light-theme caller reaching
|
|
109
|
+
* for `ink-primary` gets near-black text on a near-black surface. That is
|
|
110
|
+
* exactly what happened, and it is invisible until someone looks at the
|
|
111
|
+
* light theme.
|
|
112
|
+
*
|
|
113
|
+
* Both steps are measured against the WORST scrimmed result, which is not a
|
|
114
|
+
* gradient stop at all: `glow` fades to transparent, so the scrim can end up
|
|
115
|
+
* over the light theme's white page. Over that blend (#595856) the primary
|
|
116
|
+
* step reads 6.18:1 and the secondary 4.67:1. Every real gradient is darker
|
|
117
|
+
* than the worst case, so these are floors.
|
|
118
|
+
*/
|
|
119
|
+
'on-scrim': '{color.neutral.50}',
|
|
120
|
+
'on-scrim-secondary': '{color.neutral.200}',
|
|
121
|
+
inverse: '{color.neutral.1000}',
|
|
122
|
+
link: '{color.orange.base}',
|
|
123
|
+
'link-hover': '{color.orange.bright}',
|
|
124
|
+
},
|
|
125
|
+
/**
|
|
126
|
+
* Border names encode the WCAG 1.4.11 requirement rather than a visual weight.
|
|
127
|
+
*
|
|
128
|
+
* `border-default` used to sit here at neutral-800 and read 1.29:1 against the
|
|
129
|
+
* page ground — an input border a sighted user could barely find. Naming a
|
|
130
|
+
* token by how heavy it looks invites exactly that: a developer reaches for
|
|
131
|
+
* "default" on a text field and silently ships a 1.4.11 failure. Naming it by
|
|
132
|
+
* what it must do makes the correct choice the obvious one.
|
|
133
|
+
*/
|
|
134
|
+
line: {
|
|
135
|
+
/** Dividers and decorative card hairlines. Exempt from 1.4.11 — and barred
|
|
136
|
+
* from interactive controls by lint, because that exemption is the whole
|
|
137
|
+
* reason it is allowed to be this quiet. */
|
|
138
|
+
subtle: '{color.neutral.850}',
|
|
139
|
+
/**
|
|
140
|
+
* Structural separation: a footer bar, a toolbar edge, a section rule.
|
|
141
|
+
*
|
|
142
|
+
* The scale was missing a middle step. `subtle` is a hairline meant to
|
|
143
|
+
* disappear and `strong` is emphasis — a divider that has to actually read
|
|
144
|
+
* as a division sits between them, and pressing either into service made
|
|
145
|
+
* the barred dialog footer either invisible or shouty.
|
|
146
|
+
*/
|
|
147
|
+
divider: '{color.neutral.800}',
|
|
148
|
+
/**
|
|
149
|
+
* Matches `surface-subtle-active` (neutral-800) by explicit product
|
|
150
|
+
* decision, for a border that sits almost on the fill.
|
|
151
|
+
*
|
|
152
|
+
* This is BELOW the WCAG 1.4.11 floor: 1.29:1 against the field, where 3:1
|
|
153
|
+
* is required for a boundary that identifies a control. Nothing else
|
|
154
|
+
* identifies a dark input — the fill is 1.28:1 from the page — so in the
|
|
155
|
+
* dark theme a field is found by its placeholder and its label rather than
|
|
156
|
+
* by its edge.
|
|
157
|
+
*
|
|
158
|
+
* Recorded as a declared waiver in CONTRAST_WAIVERS, not as an oversight,
|
|
159
|
+
* so the accessibility statement can report it honestly.
|
|
160
|
+
*/
|
|
161
|
+
control: '{color.neutral.800}',
|
|
162
|
+
/** Emphasis, selection, structural separation that carries meaning. >= 3:1. */
|
|
163
|
+
strong: '{color.neutral.450}',
|
|
164
|
+
focus: '{color.orange.base}',
|
|
165
|
+
/** The halo behind a focused field. Accent at low alpha. */
|
|
166
|
+
'focus-glow': '{color.state.focus-glow}',
|
|
167
|
+
accent: '{color.orange.border-dark}',
|
|
168
|
+
/** Disabled controls are exempt from contrast minimums (WCAG 1.4.3/1.4.11). */
|
|
169
|
+
disabled: '{color.neutral.850}',
|
|
170
|
+
/** Replaces a shadow at elev-3/4 — a shadow is invisible on near-black. */
|
|
171
|
+
'edge-highlight': '{color.neutral.700}',
|
|
172
|
+
},
|
|
173
|
+
status: {
|
|
174
|
+
'success-solid': '{color.success.solid}',
|
|
175
|
+
'success-on-solid': '{color.success.on-solid}',
|
|
176
|
+
'success-ink': '{color.success.text-dark}',
|
|
177
|
+
'success-surface': '{color.success.surface-dark}',
|
|
178
|
+
'success-line': '{color.success.border-dark}',
|
|
179
|
+
'warning-solid': '{color.warning.solid}',
|
|
180
|
+
'warning-on-solid': '{color.warning.on-solid}',
|
|
181
|
+
'warning-ink': '{color.warning.text-dark}',
|
|
182
|
+
'warning-surface': '{color.warning.surface-dark}',
|
|
183
|
+
'warning-line': '{color.warning.border-dark}',
|
|
184
|
+
'error-solid': '{color.error.solid}',
|
|
185
|
+
'error-on-solid': '{color.error.on-solid}',
|
|
186
|
+
'error-ink': '{color.error.text-dark}',
|
|
187
|
+
'error-surface': '{color.error.surface-dark}',
|
|
188
|
+
'error-line': '{color.error.border-dark}',
|
|
189
|
+
// The one declared brand alias: info IS teal. See STATUS_ALIAS_EXCEPTIONS.
|
|
190
|
+
'info-solid': '{color.teal.base}',
|
|
191
|
+
'info-on-solid': '{color.neutral.1000}',
|
|
192
|
+
'info-ink': '{color.teal.base}',
|
|
193
|
+
'info-surface': '{color.teal.surface-dark}',
|
|
194
|
+
'info-line': '{color.teal.border-dark}',
|
|
195
|
+
},
|
|
196
|
+
viz: {
|
|
197
|
+
// Ordered by maximum hue separation first. Beyond series 4, change the
|
|
198
|
+
// encoding rather than reaching for series 5.
|
|
199
|
+
1: '{color.orange.base}',
|
|
200
|
+
2: '{color.teal.base}',
|
|
201
|
+
3: '{color.yellow.base}',
|
|
202
|
+
4: '{color.pink.base}',
|
|
203
|
+
5: '{color.coral.base}',
|
|
204
|
+
6: '{color.peach.base}',
|
|
205
|
+
7: '{color.neutral.300}',
|
|
206
|
+
grid: '{color.neutral.850}',
|
|
207
|
+
axis: '{color.neutral.450}',
|
|
208
|
+
// The "heat" ramp — generated with even lightness steps, not assembled
|
|
209
|
+
// from brand anchors. See heatRamp() in scripts/derive-tokens.ts.
|
|
210
|
+
'sequential-0': '{color.heat.dark.0}',
|
|
211
|
+
'sequential-1': '{color.heat.dark.1}',
|
|
212
|
+
'sequential-2': '{color.heat.dark.2}',
|
|
213
|
+
'sequential-3': '{color.heat.dark.3}',
|
|
214
|
+
'sequential-4': '{color.heat.dark.4}',
|
|
215
|
+
'diverging-neg-2': '{color.teal.dim}',
|
|
216
|
+
'diverging-neg-1': '{color.teal.bright}',
|
|
217
|
+
'diverging-0': '{color.neutral.500}',
|
|
218
|
+
'diverging-pos-1': '{color.orange.bright}',
|
|
219
|
+
'diverging-pos-2': '{color.orange.dim}',
|
|
220
|
+
},
|
|
221
|
+
gradient: {
|
|
222
|
+
ember: 'linear-gradient(135deg, {color.orange.base} 0%, {color.coral.base} 100%)',
|
|
223
|
+
solar:
|
|
224
|
+
'linear-gradient(135deg, {color.orange.base} 0%, {color.amber.base} 50%, {color.yellow.base} 100%)',
|
|
225
|
+
dusk: 'linear-gradient(135deg, {color.coral.base} 0%, {color.pink.base} 100%)',
|
|
226
|
+
glow: 'radial-gradient(circle at 50% 0%, {color.orange.surface-dark} 0%, transparent 70%)',
|
|
227
|
+
ground: 'linear-gradient(180deg, {color.neutral.1000} 0%, {color.neutral.950} 100%)',
|
|
228
|
+
},
|
|
229
|
+
elevation: {
|
|
230
|
+
/**
|
|
231
|
+
* elev-1: a card on the page. No shadow — the fill delta carries it
|
|
232
|
+
* (ground -> raised is 0.099 L here).
|
|
233
|
+
*/
|
|
234
|
+
1: '{shadow.none}',
|
|
235
|
+
/**
|
|
236
|
+
* elev-2: a panel nested inside a card. Still no shadow in this theme,
|
|
237
|
+
* because raised -> overlay is 0.046 L and clears the separation floor on
|
|
238
|
+
* fill alone. The light theme cannot do that and takes a shadow instead —
|
|
239
|
+
* which is the point of expressing elevation per theme rather than per
|
|
240
|
+
* component.
|
|
241
|
+
*/
|
|
242
|
+
2: '{shadow.none}',
|
|
243
|
+
3: '{shadow.md}',
|
|
244
|
+
4: '{shadow.lg}',
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
/* -------------------------------------------------------------------------- */
|
|
249
|
+
/* Light — a peer, not an inversion */
|
|
250
|
+
/* -------------------------------------------------------------------------- */
|
|
251
|
+
|
|
252
|
+
export const light: SemanticTheme = {
|
|
253
|
+
surface: {
|
|
254
|
+
// White page. The card therefore steps DOWN rather than up — with a white
|
|
255
|
+
// ground there is nowhere lighter to go, and the aesthetic direction wants
|
|
256
|
+
// depth from surface contrast rather than from an outline on every card.
|
|
257
|
+
ground: '{color.neutral.0}',
|
|
258
|
+
/**
|
|
259
|
+
* A near-white card, not a gray one.
|
|
260
|
+
*
|
|
261
|
+
* That leaves only 0.022 L between card and page — below the 0.04
|
|
262
|
+
* adjacent-surface rule — so the card's hairline border carries the
|
|
263
|
+
* separation instead. This is the one place the "a surface change and a
|
|
264
|
+
* border are alternatives" principle is relaxed, and it is relaxed because
|
|
265
|
+
* with a white ground there is no lighter step to move to.
|
|
266
|
+
*/
|
|
267
|
+
raised: '{color.neutral.25}',
|
|
268
|
+
// Floating surfaces return to white and rely on their shadow, so a popover
|
|
269
|
+
// still reads as above the page rather than sunk into it.
|
|
270
|
+
overlay: '{color.neutral.0}',
|
|
271
|
+
floating: '{color.neutral.0}',
|
|
272
|
+
modal: '{color.neutral.0}',
|
|
273
|
+
subtle: '{color.neutral.100}',
|
|
274
|
+
// Near-white: a field you write into should read as paper, not as a button.
|
|
275
|
+
field: '{color.neutral.25}',
|
|
276
|
+
'subtle-hover': '{color.neutral.200}',
|
|
277
|
+
'subtle-active': '{color.neutral.300}',
|
|
278
|
+
'state-hover': '{color.state.hover-light}',
|
|
279
|
+
'state-active': '{color.state.active-light}',
|
|
280
|
+
selection: '{color.state.selection-light}',
|
|
281
|
+
knob: '{color.neutral.0}',
|
|
282
|
+
inverse: '{color.neutral.1000}',
|
|
283
|
+
accent: '{color.orange.base}',
|
|
284
|
+
'accent-hover': '{color.orange.bright}',
|
|
285
|
+
'accent-active': '{color.orange.dim}',
|
|
286
|
+
'accent-subtle': '{color.orange.surface-light}',
|
|
287
|
+
'accent-subtle-hover': '{color.orange.surface-light-hover}',
|
|
288
|
+
disabled: '{color.neutral.100}',
|
|
289
|
+
scrim: '{color.neutral.1000}',
|
|
290
|
+
},
|
|
291
|
+
ink: {
|
|
292
|
+
primary: '{color.neutral.1000}',
|
|
293
|
+
secondary: '{color.neutral.700}',
|
|
294
|
+
tertiary: '{color.neutral.600}',
|
|
295
|
+
// A shade darker than tertiary: placeholders sit inside surface-subtle
|
|
296
|
+
// (neutral-100), which is darker than the page ground. neutral-600 reads
|
|
297
|
+
// 4.05:1 there — a placeholder that fails AA in the one place it appears.
|
|
298
|
+
placeholder: '{color.neutral.700}',
|
|
299
|
+
disabled: '{color.neutral.400}',
|
|
300
|
+
// NOT the brand orange: #FF641F reads 2.96:1 on white. This is the solved
|
|
301
|
+
// darkened step — the lightest orange that can legally carry text.
|
|
302
|
+
accent: '{color.orange.text-light}',
|
|
303
|
+
// Solved against the accent chip, not the page ground — the chip is darker.
|
|
304
|
+
'on-accent-subtle': '{color.orange.text-on-chip-light}',
|
|
305
|
+
'on-accent': '{color.neutral.1000}',
|
|
306
|
+
'on-gradient': '{color.neutral.1000}',
|
|
307
|
+
'on-scrim': '{color.neutral.50}',
|
|
308
|
+
'on-scrim-secondary': '{color.neutral.200}',
|
|
309
|
+
inverse: '{color.neutral.0}',
|
|
310
|
+
link: '{color.orange.text-light}',
|
|
311
|
+
// NOT orange.dim: that is a full-saturation step and reads 2.8:1 on white.
|
|
312
|
+
// Light-theme hover goes deeper, not brighter.
|
|
313
|
+
'link-hover': '{color.orange.text-light-strong}',
|
|
314
|
+
},
|
|
315
|
+
line: {
|
|
316
|
+
subtle: '{color.neutral.100}',
|
|
317
|
+
divider: '{color.neutral.200}',
|
|
318
|
+
/**
|
|
319
|
+
* Matches `surface-subtle` (neutral-100) by explicit product decision — the
|
|
320
|
+
* light-theme counterpart to the dark border sitting on its fill.
|
|
321
|
+
*
|
|
322
|
+
* BELOW the WCAG 1.4.11 floor: about 1.21:1 against the field where 3:1 is
|
|
323
|
+
* required. Declared in CONTRAST_WAIVERS rather than un-asserted.
|
|
324
|
+
*/
|
|
325
|
+
control: '{color.neutral.100}',
|
|
326
|
+
strong: '{color.neutral.700}',
|
|
327
|
+
/**
|
|
328
|
+
* The brand orange, identical to the dark theme, so the accent does not
|
|
329
|
+
* shift between themes. Product decision.
|
|
330
|
+
*
|
|
331
|
+
* It reads 2.96:1 against a white page where 1.4.11 wants 3:1 — a shortfall
|
|
332
|
+
* of about 1%, as opposed to the border's 60%. Declared as a waiver so the
|
|
333
|
+
* difference in severity is visible rather than lumped together.
|
|
334
|
+
*/
|
|
335
|
+
focus: '{color.orange.base}',
|
|
336
|
+
'focus-glow': '{color.state.focus-glow}',
|
|
337
|
+
accent: '{color.orange.border-light}',
|
|
338
|
+
disabled: '{color.neutral.200}',
|
|
339
|
+
'edge-highlight': '{color.neutral.100}',
|
|
340
|
+
},
|
|
341
|
+
status: {
|
|
342
|
+
'success-solid': '{color.success.solid}',
|
|
343
|
+
'success-on-solid': '{color.success.on-solid}',
|
|
344
|
+
'success-ink': '{color.success.text-light}',
|
|
345
|
+
'success-surface': '{color.success.surface-light}',
|
|
346
|
+
'success-line': '{color.success.border-light}',
|
|
347
|
+
'warning-solid': '{color.warning.solid}',
|
|
348
|
+
'warning-on-solid': '{color.warning.on-solid}',
|
|
349
|
+
'warning-ink': '{color.warning.text-light}',
|
|
350
|
+
'warning-surface': '{color.warning.surface-light}',
|
|
351
|
+
'warning-line': '{color.warning.border-light}',
|
|
352
|
+
'error-solid': '{color.error.solid}',
|
|
353
|
+
'error-on-solid': '{color.error.on-solid}',
|
|
354
|
+
'error-ink': '{color.error.text-light}',
|
|
355
|
+
'error-surface': '{color.error.surface-light}',
|
|
356
|
+
'error-line': '{color.error.border-light}',
|
|
357
|
+
'info-solid': '{color.teal.base}',
|
|
358
|
+
'info-on-solid': '{color.neutral.1000}',
|
|
359
|
+
// Solved against the info chip specifically — the chip is darker than the
|
|
360
|
+
// page ground, so the page-ground solve failed inside the component itself.
|
|
361
|
+
'info-ink': '{color.teal.text-on-chip-light}',
|
|
362
|
+
'info-surface': '{color.teal.surface-light}',
|
|
363
|
+
'info-line': '{color.teal.border-light}',
|
|
364
|
+
},
|
|
365
|
+
viz: {
|
|
366
|
+
// Light-theme chart marks use the solved 3:1 steps: every brand accent at
|
|
367
|
+
// full saturation is invisible-adjacent on white.
|
|
368
|
+
1: '{color.orange.mark-light}',
|
|
369
|
+
2: '{color.teal.mark-light}',
|
|
370
|
+
3: '{color.yellow.mark-light}',
|
|
371
|
+
4: '{color.pink.mark-light}',
|
|
372
|
+
5: '{color.coral.mark-light}',
|
|
373
|
+
6: '{color.peach.mark-light}',
|
|
374
|
+
7: '{color.neutral.600}',
|
|
375
|
+
grid: '{color.neutral.100}',
|
|
376
|
+
axis: '{color.neutral.600}',
|
|
377
|
+
'sequential-0': '{color.heat.light.0}',
|
|
378
|
+
'sequential-1': '{color.heat.light.1}',
|
|
379
|
+
'sequential-2': '{color.heat.light.2}',
|
|
380
|
+
'sequential-3': '{color.heat.light.3}',
|
|
381
|
+
'sequential-4': '{color.heat.light.4}',
|
|
382
|
+
'diverging-neg-2': '{color.teal.text-light}',
|
|
383
|
+
'diverging-neg-1': '{color.teal.mark-light}',
|
|
384
|
+
'diverging-0': '{color.neutral.300}',
|
|
385
|
+
'diverging-pos-1': '{color.orange.mark-light}',
|
|
386
|
+
'diverging-pos-2': '{color.orange.text-light}',
|
|
387
|
+
},
|
|
388
|
+
gradient: {
|
|
389
|
+
ember: 'linear-gradient(135deg, {color.orange.base} 0%, {color.coral.base} 100%)',
|
|
390
|
+
solar:
|
|
391
|
+
'linear-gradient(135deg, {color.orange.base} 0%, {color.amber.base} 50%, {color.yellow.base} 100%)',
|
|
392
|
+
dusk: 'linear-gradient(135deg, {color.coral.base} 0%, {color.pink.base} 100%)',
|
|
393
|
+
glow: 'radial-gradient(circle at 50% 0%, {color.orange.surface-light} 0%, transparent 70%)',
|
|
394
|
+
ground: 'linear-gradient(180deg, {color.neutral.50} 0%, {color.neutral.100} 100%)',
|
|
395
|
+
},
|
|
396
|
+
elevation: {
|
|
397
|
+
/** elev-1: a card on the page. The hairline border carries it (see `raised`). */
|
|
398
|
+
1: '{shadow.none}',
|
|
399
|
+
/**
|
|
400
|
+
* elev-2: a panel nested inside a card. **This theme needs a shadow.**
|
|
401
|
+
*
|
|
402
|
+
* A white page means the elevation ramp runs out immediately: `raised` is
|
|
403
|
+
* neutral-25 and `overlay` returns to pure white, so a nested panel has the
|
|
404
|
+
* same fill as the page (0.000 L apart) and read as a hole punched in the
|
|
405
|
+
* card rather than a panel sitting on it. Fill cannot express this step
|
|
406
|
+
* here, so the shadow does.
|
|
407
|
+
*/
|
|
408
|
+
2: '{shadow.sm}',
|
|
409
|
+
3: '{shadow.md}',
|
|
410
|
+
4: '{shadow.lg}',
|
|
411
|
+
},
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
export const themes = { dark, light } as const;
|
|
415
|
+
export type ThemeName = keyof typeof themes;
|