@tenphi/glaze 0.18.0 → 1.0.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/README.md +84 -30
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -4
- package/dist/index.d.mts +8 -4
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/docs/api.md +519 -400
- package/docs/methodology.md +125 -50
- package/docs/migration.md +79 -144
- package/docs/okhst.md +116 -254
- package/package.json +1 -1
package/docs/api.md
CHANGED
|
@@ -6,12 +6,17 @@ Full reference for every public method, option, and type exported by `@tenphi/gl
|
|
|
6
6
|
|
|
7
7
|
- [Theme creation](#theme-creation)
|
|
8
8
|
- [Theme methods](#theme-methods)
|
|
9
|
+
- [DTCG](#themedtcgoptions)
|
|
10
|
+
- [DTCG Resolver-Module](#themedtcgresolveroptions)
|
|
11
|
+
- [Tailwind CSS](#themetailwindoptions)
|
|
12
|
+
- [High-contrast pairs](#high-contrast-pairs)
|
|
9
13
|
- [Color definitions](#color-definitions)
|
|
10
14
|
- [Standalone color tokens](#standalone-color-tokens)
|
|
11
15
|
- [Shadows](#shadows)
|
|
12
16
|
- [Mix colors](#mix-colors)
|
|
13
17
|
- [Palette](#palette)
|
|
14
18
|
- [Output formats](#output-formats)
|
|
19
|
+
- [Hue channel splitting](#hue-channel-splitting-splithue)
|
|
15
20
|
- [Adaptation modes](#adaptation-modes)
|
|
16
21
|
- [Light / dark scheme mapping](#light--dark-scheme-mapping)
|
|
17
22
|
- [Configuration](#configuration)
|
|
@@ -23,13 +28,13 @@ Full reference for every public method, option, and type exported by `@tenphi/gl
|
|
|
23
28
|
|
|
24
29
|
## Theme creation
|
|
25
30
|
|
|
26
|
-
| Method
|
|
27
|
-
|
|
28
|
-
| `glaze(hue, saturation?, config?)`
|
|
29
|
-
| `glaze({ hue, saturation }, config?)` | Create a theme from an options object, with optional per-theme config override.
|
|
30
|
-
| `glaze.from(data)`
|
|
31
|
-
| `glaze.fromHex(hex)`
|
|
32
|
-
| `glaze.fromRgb(r, g, b)`
|
|
31
|
+
| Method | Description |
|
|
32
|
+
| ------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
|
|
33
|
+
| `glaze(hue, saturation?, config?)` | Create a theme from hue (0–360) and saturation (0–100). Optional `config` overrides the global config for this theme. |
|
|
34
|
+
| `glaze({ hue, saturation }, config?)` | Create a theme from an options object, with optional per-theme config override. |
|
|
35
|
+
| `glaze.from(data)` | Create a theme from an exported configuration (`theme.export()` snapshot). |
|
|
36
|
+
| `glaze.fromHex(hex)` | Create a theme from a hex color (`#rgb` or `#rrggbb`). Extracts hue and saturation. |
|
|
37
|
+
| `glaze.fromRgb(r, g, b)` | Create a theme from RGB values (0–255). Extracts hue and saturation. |
|
|
33
38
|
|
|
34
39
|
```ts
|
|
35
40
|
const a = glaze(280, 80);
|
|
@@ -50,24 +55,27 @@ The optional `config` parameter is a `GlazeConfigOverride` — see [Per-instance
|
|
|
50
55
|
|
|
51
56
|
A `GlazeTheme` exposes:
|
|
52
57
|
|
|
53
|
-
| Method
|
|
54
|
-
|
|
55
|
-
| `theme.hue` (readonly)
|
|
56
|
-
| `theme.saturation` (readonly)
|
|
57
|
-
| `theme.colors(defs)`
|
|
58
|
-
| `theme.color(name)`
|
|
59
|
-
| `theme.color(name, def)`
|
|
60
|
-
| `theme.remove(name \| names[])` | Remove one or more color definitions.
|
|
61
|
-
| `theme.has(name)`
|
|
62
|
-
| `theme.list()`
|
|
63
|
-
| `theme.reset()`
|
|
64
|
-
| `theme.export()`
|
|
65
|
-
| `theme.extend(options)`
|
|
66
|
-
| `theme.resolve()`
|
|
67
|
-
| `theme.tokens(options?)`
|
|
68
|
-
| `theme.tasty(options?)`
|
|
69
|
-
| `theme.json(options?)`
|
|
70
|
-
| `theme.css(options?)`
|
|
58
|
+
| Method | Description |
|
|
59
|
+
| ------------------------------- | --------------------------------------------------------------------------------------------------- |
|
|
60
|
+
| `theme.hue` (readonly) | The hue seed (0–360). |
|
|
61
|
+
| `theme.saturation` (readonly) | The saturation seed (0–100). |
|
|
62
|
+
| `theme.colors(defs)` | Add/replace colors (additive merge — adds new, overwrites existing by name, doesn't remove others). |
|
|
63
|
+
| `theme.color(name)` | Get a color definition by name. |
|
|
64
|
+
| `theme.color(name, def)` | Set a single color definition. |
|
|
65
|
+
| `theme.remove(name \| names[])` | Remove one or more color definitions. |
|
|
66
|
+
| `theme.has(name)` | Check if a color is defined. |
|
|
67
|
+
| `theme.list()` | List all defined color names. |
|
|
68
|
+
| `theme.reset()` | Clear all color definitions. |
|
|
69
|
+
| `theme.export()` | Export the theme configuration as a JSON-safe object. |
|
|
70
|
+
| `theme.extend(options)` | Create a child theme inheriting all color definitions (see [`extend`](#themeextendoptions) below). |
|
|
71
|
+
| `theme.resolve()` | Resolve all colors and return a `Map<string, ResolvedColor>`. |
|
|
72
|
+
| `theme.tokens(options?)` | Export as a flat token map grouped by scheme variant. |
|
|
73
|
+
| `theme.tasty(options?)` | Export as [Tasty](https://tasty.style) style-to-state bindings. |
|
|
74
|
+
| `theme.json(options?)` | Export as plain JSON. |
|
|
75
|
+
| `theme.css(options?)` | Export as CSS custom property declarations. |
|
|
76
|
+
| `theme.dtcg(options?)` | Export one W3C DTCG token tree per scheme. |
|
|
77
|
+
| `theme.dtcgResolver(options?)` | Export one DTCG Resolver-Module document containing every scheme. |
|
|
78
|
+
| `theme.tailwind(options?)` | Export a Tailwind CSS v4 theme and scheme overrides. |
|
|
71
79
|
|
|
72
80
|
### `theme.colors(defs)`
|
|
73
81
|
|
|
@@ -80,8 +88,8 @@ theme.colors({ text: { tone: 30 } });
|
|
|
80
88
|
### `theme.color(name) / theme.color(name, def)`
|
|
81
89
|
|
|
82
90
|
```ts
|
|
83
|
-
theme.color('surface', { tone: 97, saturation: 0.75 });
|
|
84
|
-
const def = theme.color('surface');
|
|
91
|
+
theme.color('surface', { tone: 97, saturation: 0.75 }); // set
|
|
92
|
+
const def = theme.color('surface'); // get
|
|
85
93
|
```
|
|
86
94
|
|
|
87
95
|
### `theme.extend(options)`
|
|
@@ -100,63 +108,75 @@ const highSat = base.extend({ config: { darkTone: [10, 100] } });
|
|
|
100
108
|
|
|
101
109
|
`GlazeExtendOptions`:
|
|
102
110
|
|
|
103
|
-
| Field
|
|
104
|
-
|
|
105
|
-
| `hue`
|
|
106
|
-
| `saturation` | `number`
|
|
107
|
-
| `colors`
|
|
108
|
-
| `config`
|
|
111
|
+
| Field | Type | Description |
|
|
112
|
+
| ------------ | --------------------- | -------------------------------------------------------------------------------------------- |
|
|
113
|
+
| `hue` | `number` | Replace the hue seed. Defaults to the parent's hue. |
|
|
114
|
+
| `saturation` | `number` | Replace the saturation seed. Defaults to the parent's saturation. |
|
|
115
|
+
| `colors` | `ColorMap` | Per-theme overrides (additive merge over the inherited map). |
|
|
116
|
+
| `config` | `GlazeConfigOverride` | Config override for the child. Shallow-merged with the parent's override — child fields win. |
|
|
109
117
|
|
|
110
118
|
Colors marked with `inherit: false` on the parent are **not** copied into the child.
|
|
111
119
|
|
|
120
|
+
### `theme.resolve()`
|
|
121
|
+
|
|
122
|
+
Resolves the dependency graph and returns a
|
|
123
|
+
`Map<string, ResolvedColor>`. Export methods call it automatically; use it
|
|
124
|
+
directly for tests, diagnostics, or a custom output pipeline.
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
const resolved = theme.resolve();
|
|
128
|
+
const surface = resolved.get('surface');
|
|
129
|
+
// surface?.light.t is canonical tone on 0–1.
|
|
130
|
+
```
|
|
131
|
+
|
|
112
132
|
### `theme.tokens(options?)`
|
|
113
133
|
|
|
114
134
|
Flat token map grouped by scheme variant.
|
|
115
135
|
|
|
116
136
|
```ts
|
|
117
|
-
theme.tokens()
|
|
137
|
+
theme.tokens();
|
|
118
138
|
// → { light: { surface: 'oklch(...)' }, dark: { surface: 'oklch(...)' } }
|
|
119
139
|
```
|
|
120
140
|
|
|
121
141
|
`GlazeJsonOptions`:
|
|
122
142
|
|
|
123
|
-
| Option
|
|
124
|
-
|
|
125
|
-
| `format` | `'oklch'`
|
|
126
|
-
| `modes`
|
|
143
|
+
| Option | Default | Description |
|
|
144
|
+
| -------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
|
|
145
|
+
| `format` | `'oklch'` | Output color format. One of `'rgb' \| 'hsl' \| 'oklch'`. `'okhsl'` and `'okhst'` throw — use `tasty()` for those. |
|
|
146
|
+
| `modes` | `{ dark: true, highContrast: false }` (or global config) | Which scheme variants to include. |
|
|
127
147
|
|
|
128
148
|
### `theme.tasty(options?)`
|
|
129
149
|
|
|
130
|
-
|
|
150
|
+
Style-to-state bindings for the [Tasty](https://tasty.style) style system. Uses `#name` color token keys and state aliases. By default the dark and high-contrast variants are keyed by media-query states (`'@media(prefers-color-scheme: dark)'`, `'@media(prefers-contrast: more)'`) so tokens work without registering custom states.
|
|
131
151
|
|
|
132
152
|
```ts
|
|
133
|
-
theme.tasty()
|
|
153
|
+
theme.tasty();
|
|
134
154
|
// → {
|
|
135
|
-
// '#surface': { '': '
|
|
155
|
+
// '#surface': { '': 'oklch(...)', '@media(prefers-color-scheme: dark)': 'oklch(...)' },
|
|
136
156
|
// ...
|
|
137
157
|
// }
|
|
138
158
|
```
|
|
139
159
|
|
|
140
160
|
`GlazeTokenOptions`:
|
|
141
161
|
|
|
142
|
-
| Option
|
|
143
|
-
|
|
144
|
-
| `format`
|
|
145
|
-
| `modes`
|
|
146
|
-
| `states.dark`
|
|
147
|
-
| `states.highContrast` | `'@
|
|
148
|
-
| `splitHue`
|
|
149
|
-
| `name`
|
|
150
|
-
| `prefix`
|
|
162
|
+
| Option | Default | Description |
|
|
163
|
+
| --------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
164
|
+
| `format` | `'oklch'` | Output color format. `'okhsl'` and `'okhst'` are also supported here ([Tasty](https://tasty.style)-only spaces). |
|
|
165
|
+
| `modes` | global config | Which scheme variants to include. |
|
|
166
|
+
| `states.dark` | `'@media(prefers-color-scheme: dark)'` (or global config) | State alias for dark mode tokens. |
|
|
167
|
+
| `states.highContrast` | `'@media(prefers-contrast: more)'` (or global config) | State alias for high-contrast tokens. |
|
|
168
|
+
| `splitHue` | `false` | Emit hue as a separate custom property (`$name-hue` token + `var()` in `oklch` values). Requires `format: 'oklch'` and every color to be pastel. |
|
|
169
|
+
| `name` | `'theme'` | Base name for the theme-level hue var (`$theme-hue` / `--theme-hue`). Palette export auto-derives this from the theme name. |
|
|
170
|
+
| `prefix` | (palette only) | See [Palette](#palette). |
|
|
151
171
|
|
|
152
|
-
When both `dark` and `highContrast` modes are enabled, dark high-contrast variants are emitted under the combined key `<dark> & <highContrast>` (e.g. `'@dark & @
|
|
172
|
+
When both `dark` and `highContrast` modes are enabled, dark high-contrast variants are emitted under the combined key `<dark> & <highContrast>` (e.g. `'@media(prefers-color-scheme: dark) & @media(prefers-contrast: more)'`).
|
|
153
173
|
|
|
154
174
|
### `theme.json(options?)`
|
|
155
175
|
|
|
156
176
|
Per-color JSON map.
|
|
157
177
|
|
|
158
178
|
```ts
|
|
159
|
-
theme.json()
|
|
179
|
+
theme.json();
|
|
160
180
|
// → {
|
|
161
181
|
// surface: { light: 'oklch(...)', dark: 'oklch(...)' },
|
|
162
182
|
// text: { light: 'oklch(...)', dark: 'oklch(...)' },
|
|
@@ -172,8 +192,8 @@ CSS custom property declaration strings, grouped by scheme variant.
|
|
|
172
192
|
```ts
|
|
173
193
|
theme.css();
|
|
174
194
|
// → {
|
|
175
|
-
// light: '--surface-color:
|
|
176
|
-
// dark: '--surface-color:
|
|
195
|
+
// light: '--surface-color: oklch(...);\n--text-color: oklch(...);',
|
|
196
|
+
// dark: '--surface-color: oklch(...);\n--text-color: oklch(...);',
|
|
177
197
|
// lightContrast: '...',
|
|
178
198
|
// darkContrast: '...',
|
|
179
199
|
// }
|
|
@@ -181,12 +201,12 @@ theme.css();
|
|
|
181
201
|
|
|
182
202
|
`GlazeCssOptions`:
|
|
183
203
|
|
|
184
|
-
| Option
|
|
185
|
-
|
|
186
|
-
| `format`
|
|
187
|
-
| `suffix`
|
|
188
|
-
| `splitHue` | `false`
|
|
189
|
-
| `name`
|
|
204
|
+
| Option | Default | Description |
|
|
205
|
+
| ---------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
206
|
+
| `format` | `'oklch'` | Output color format. One of `'rgb' \| 'hsl' \| 'oklch'`. `'okhsl'` and `'okhst'` throw — use `tasty()` for those. |
|
|
207
|
+
| `suffix` | `'-color'` | Suffix appended to each CSS property name. Pass `''` for bare property names. |
|
|
208
|
+
| `splitHue` | `false` | Emit hue as a separate `--*-hue` custom property referenced via `var()` in `oklch` color values. Requires `format: 'oklch'` and every color to be pastel. Shadow/mix colors stay inline (blended hue; they do not follow `--hue` rotation). |
|
|
209
|
+
| `name` | `'theme'` | Base name for the theme-level hue var (`--theme-hue`). Palette export auto-derives this from the theme name. |
|
|
190
210
|
|
|
191
211
|
`GlazeCssResult` always contains all four keys (`light`, `dark`, `lightContrast`, `darkContrast`); empty if no colors are defined for that variant.
|
|
192
212
|
|
|
@@ -195,7 +215,7 @@ theme.css();
|
|
|
195
215
|
W3C [Design Tokens Format Module (2025.10)](https://www.designtokens.org/) documents — the vendor-neutral JSON format consumed by Figma, Tokens Studio, Style Dictionary v4+, Terrazzo, Penpot, and every DTCG-compatible tool. Returns one spec-conformant token tree per scheme variant.
|
|
196
216
|
|
|
197
217
|
```ts
|
|
198
|
-
theme.dtcg()
|
|
218
|
+
theme.dtcg();
|
|
199
219
|
// → {
|
|
200
220
|
// light: {
|
|
201
221
|
// surface: {
|
|
@@ -216,10 +236,10 @@ Write each document to its own `.tokens.json` file — one file per scheme is th
|
|
|
216
236
|
|
|
217
237
|
`GlazeDtcgOptions`:
|
|
218
238
|
|
|
219
|
-
| Option
|
|
220
|
-
|
|
221
|
-
| `colorSpace` | `'srgb'`
|
|
222
|
-
| `modes`
|
|
239
|
+
| Option | Default | Description |
|
|
240
|
+
| ------------ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
241
|
+
| `colorSpace` | `'srgb'` | Color space for `$value`. `'srgb'` emits gamma sRGB `components` (0–1) plus a `hex` hint — universally understood. `'oklch'` emits `[L, C, H]` components with no hex — Glaze-native, wide-gamut. |
|
|
242
|
+
| `modes` | global config | Which scheme variants to include. `light` is always present. |
|
|
223
243
|
|
|
224
244
|
`alpha` is included on `$value` only when the color's opacity is below 1. `$type` is always `'color'`.
|
|
225
245
|
|
|
@@ -228,7 +248,7 @@ Write each document to its own `.tokens.json` file — one file per scheme is th
|
|
|
228
248
|
A single W3C [DTCG Resolver-Module](https://www.designtokens.org/) document describing **every scheme variant in one file** — an alternative to `dtcg()`'s per-scheme files for tools that resolve sets + modifiers (e.g. Dispersa). The light document becomes `sets.base.sources[0]` (the default context); each other variant becomes a context override on a single `scheme` modifier.
|
|
229
249
|
|
|
230
250
|
```ts
|
|
231
|
-
theme.dtcgResolver({ modes: { highContrast: true } })
|
|
251
|
+
theme.dtcgResolver({ modes: { highContrast: true } });
|
|
232
252
|
// → {
|
|
233
253
|
// version: '2025.10',
|
|
234
254
|
// sets: {
|
|
@@ -272,14 +292,14 @@ theme.dtcgResolver({ modes: { highContrast: true } })
|
|
|
272
292
|
|
|
273
293
|
`GlazeDtcgResolverOptions` (extends `GlazeDtcgOptions`, so `modes` and `colorSpace` pass through):
|
|
274
294
|
|
|
275
|
-
| Option
|
|
276
|
-
|
|
277
|
-
| `colorSpace`
|
|
278
|
-
| `modes`
|
|
279
|
-
| `setName`
|
|
280
|
-
| `modifierName` | `'scheme'`
|
|
281
|
-
| `contextNames` | identity
|
|
282
|
-
| `version`
|
|
295
|
+
| Option | Default | Description |
|
|
296
|
+
| -------------- | ------------- | ----------------------------------------------------------------------------------------------------------------- |
|
|
297
|
+
| `colorSpace` | `'srgb'` | Same as `dtcg()` — flows through to every source and context. |
|
|
298
|
+
| `modes` | global config | Which scheme variants to emit as contexts. `light` is always present (the default); absent variants are omitted. |
|
|
299
|
+
| `setName` | `'base'` | Name of the single set holding the default (light) token tree. |
|
|
300
|
+
| `modifierName` | `'scheme'` | Name of the modifier describing the scheme axis. |
|
|
301
|
+
| `contextNames` | identity | Override the four context names (`light` / `dark` / `lightContrast` / `darkContrast`) — e.g. `{ dark: 'night' }`. |
|
|
302
|
+
| `version` | `'2025.10'` | Resolver document version. |
|
|
283
303
|
|
|
284
304
|
### `theme.tailwind(options?)`
|
|
285
305
|
|
|
@@ -306,13 +326,13 @@ A Tailwind CSS v4 `@theme` block (light baseline) plus dark / high-contrast over
|
|
|
306
326
|
|
|
307
327
|
`GlazeTailwindOptions`:
|
|
308
328
|
|
|
309
|
-
| Option
|
|
310
|
-
|
|
311
|
-
| `format`
|
|
312
|
-
| `namespace`
|
|
313
|
-
| `darkSelector`
|
|
314
|
-
| `highContrastSelector` | `'.high-contrast'` | Selector wrapping the light high-contrast overrides. The combined dark + high-contrast block uses `${darkSelector}${highContrastSelector}` (e.g. `.dark.high-contrast`).
|
|
315
|
-
| `modes`
|
|
329
|
+
| Option | Default | Description |
|
|
330
|
+
| ---------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
331
|
+
| `format` | `'oklch'` | Output color format for the values. |
|
|
332
|
+
| `namespace` | `'color-'` | CSS custom property namespace, forming `--<namespace><name>` (e.g. `--color-surface`). Named `namespace` to avoid clashing with the palette theme-prefix option. |
|
|
333
|
+
| `darkSelector` | `'.dark'` | Selector wrapping the dark overrides. Pass an at-rule like `'@media (prefers-color-scheme: dark)'` to drive dark mode from the OS preference (it nests `:root` automatically). |
|
|
334
|
+
| `highContrastSelector` | `'.high-contrast'` | Selector wrapping the light high-contrast overrides. The combined dark + high-contrast block uses `${darkSelector}${highContrastSelector}` (e.g. `.dark.high-contrast`). |
|
|
335
|
+
| `modes` | global config | Which scheme variants to include. The `@theme` block (light) is always emitted when colors exist. |
|
|
316
336
|
|
|
317
337
|
### `theme.export()`
|
|
318
338
|
|
|
@@ -327,6 +347,34 @@ The export contains only the configuration — not resolved color values. Resolv
|
|
|
327
347
|
|
|
328
348
|
---
|
|
329
349
|
|
|
350
|
+
## High-contrast pairs
|
|
351
|
+
|
|
352
|
+
`HCPair<T>` means either one value used in both ordinary and high-contrast
|
|
353
|
+
schemes, or an explicit `[normal, highContrast]` pair:
|
|
354
|
+
|
|
355
|
+
```ts
|
|
356
|
+
type HCPair<T> = T | [T, T];
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
It is used by `tone`, `contrast`, shadow `intensity`, and mix `value`:
|
|
360
|
+
|
|
361
|
+
```ts
|
|
362
|
+
tone: '-8'; // -8 in normal and HC
|
|
363
|
+
tone: ['-8', '-16']; // -8 normal, -16 HC
|
|
364
|
+
contrast: {
|
|
365
|
+
apca: 'content';
|
|
366
|
+
} // preset with automatic HC enhancement
|
|
367
|
+
contrast: {
|
|
368
|
+
apca: ['content', 'body'];
|
|
369
|
+
} // explicit normal/HC targets
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
For `contrast`, the pair may wrap the whole spec or live inside the selected
|
|
373
|
+
metric. An explicit HC value disables automatic APCA enhancement or WCAG preset
|
|
374
|
+
promotion for that color.
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
330
378
|
## Color definitions
|
|
331
379
|
|
|
332
380
|
`ColorDef` is a discriminated union:
|
|
@@ -337,34 +385,42 @@ type ColorDef = RegularColorDef | ShadowColorDef | MixColorDef;
|
|
|
337
385
|
|
|
338
386
|
### `RegularColorDef`
|
|
339
387
|
|
|
340
|
-
| Field
|
|
341
|
-
|
|
342
|
-
| `tone`
|
|
343
|
-
| `saturation` | `number`
|
|
344
|
-
| `hue`
|
|
345
|
-
| `base`
|
|
346
|
-
| `contrast`
|
|
347
|
-
| `mode`
|
|
348
|
-
| `autoFlip`
|
|
349
|
-
| `opacity`
|
|
350
|
-
| `pastel`
|
|
351
|
-
| `role`
|
|
352
|
-
| `inherit`
|
|
388
|
+
| Field | Type | Description |
|
|
389
|
+
| ------------ | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
390
|
+
| `tone` | `HCPair<ToneValue>` | Number = absolute (0–100). `'+N'`/`'-N'` = a signed **tone delta** from the base (requires `base`). `'max'`/`'min'` = forced to the scheme's tone extreme (no `base`). Optional HC pair `[normal, hc]`. |
|
|
391
|
+
| `saturation` | `number` | Saturation factor applied to the seed saturation (0–1). Default: `1`. |
|
|
392
|
+
| `hue` | `number \| RelativeValue` | Number = absolute (0–360). String (`'+N'`/`'-N'`) = relative to the **theme seed hue** (never to a base color). |
|
|
393
|
+
| `base` | `string` | Name of another color in the same theme — makes this a _dependent_ color. |
|
|
394
|
+
| `contrast` | `HCPair<ContrastSpec>` | Contrast floor against `base`. Requires `base`. See [`contrast`](#contrast-floor). |
|
|
395
|
+
| `mode` | `'auto' \| 'fixed' \| 'static'` | Adaptation mode. Default: `'auto'`. See [Adaptation modes](#adaptation-modes). |
|
|
396
|
+
| `autoFlip` | `boolean` | Flip out-of-bounds results (relative `tone` overshoot / unmet `contrast`) to the opposite side instead of clamping. Default: the global `autoFlip` (`true`). See [`autoFlip`](#autoflip). |
|
|
397
|
+
| `opacity` | `number` | Fixed alpha 0–1. Output includes alpha in the CSS value. Combining with `contrast` is not recommended (a `console.warn` is emitted). |
|
|
398
|
+
| `pastel` | `boolean` | Per-color override for the hue-independent "safe" chroma limit used in OKHSL↔sRGB conversions (luminance, contrast solving, output formatting). Falls through to the global / per-theme `pastel` config when omitted. Default: unset. See [Per-color `pastel`](#per-color-pastel). |
|
|
399
|
+
| `role` | `RoleInput` | Semantic role against `base` (`'text'` / `'surface'` / `'border'` or an alias). Fixes APCA contrast polarity. Resolved via: explicit `role` → name inference → opposite of the base's role → `'text'`. See [Roles](#roles). |
|
|
400
|
+
| `inherit` | `boolean` | Whether this color is inherited by child themes via `extend()`. Default: `true`. Set to `false` to make the color local to the current theme. |
|
|
353
401
|
|
|
354
402
|
#### Tone values
|
|
355
403
|
|
|
356
|
-
`tone` (0–100) replaces OKHSL lightness with a contrast-
|
|
404
|
+
`tone` (0–100) replaces authored OKHSL lightness with a contrast-shaped axis.
|
|
405
|
+
Equal tone differences give equal WCAG contrast for neutrals; chromatic results
|
|
406
|
+
can drift in measured luminance. See [OKHST in Glaze](okhst.md). To port old
|
|
407
|
+
`lightness` values, see [migration.md](migration.md).
|
|
357
408
|
|
|
358
|
-
| Form
|
|
359
|
-
|
|
360
|
-
| Number (absolute)
|
|
361
|
-
| String (
|
|
362
|
-
| Extreme
|
|
363
|
-
| HC pair
|
|
409
|
+
| Form | Example | Meaning |
|
|
410
|
+
| ------------------- | ----------------------- | --------------------------------------------------------------------------------------------- |
|
|
411
|
+
| Number (absolute) | `tone: 45` | Absolute tone 0–100. |
|
|
412
|
+
| String (tone delta) | `tone: '-52'` | Signed difference from the base color's resolved tone (requires `base`). |
|
|
413
|
+
| Extreme | `tone: 'max'` / `'min'` | Force to the scheme's highest (`'max'` = 100) or lowest (`'min'` = 0) tone. No `base` needed. |
|
|
414
|
+
| HC pair | `tone: ['-7', '-20']` | `[normal, high-contrast]`. A single value applies to both. |
|
|
364
415
|
|
|
365
416
|
**Absolute tone** on a dependent color (`base` set) positions the color independently. In dark mode it is tone-mapped (inverted + windowed) on its own. The `contrast` solver acts as a safety net.
|
|
366
417
|
|
|
367
|
-
**
|
|
418
|
+
**A tone delta** applies a signed difference to the base color's resolved tone.
|
|
419
|
+
It gives an exact contrast step for neutrals and a stable visual progression for
|
|
420
|
+
chromatic colors. In dark mode with `mode: 'auto'`, it is anchored to the
|
|
421
|
+
base's per-scheme tone. If `base + delta` falls outside `[0, 100]`, the result
|
|
422
|
+
is clamped to the boundary, or — with `autoFlip` (default on) — mirrored to the
|
|
423
|
+
other side of the base.
|
|
368
424
|
|
|
369
425
|
**Extreme tone** (`'max'` / `'min'`) forces the color to the scheme's tone extreme without a contrast hack or a magic number. `'max'` resolves to author tone 100 and `'min'` to 0; both flow through scheme mapping like an absolute tone, so under `mode: 'auto'` they invert in dark (`'max'` is lightest in light, darkest in dark). Use `mode: 'static'` to pin the same extreme across schemes, or `mode: 'fixed'` to keep the same end without inverting. No `base` required.
|
|
370
426
|
|
|
@@ -384,30 +440,42 @@ A dependent color with `base` but no `tone` inherits the base's tone (equivalent
|
|
|
384
440
|
```ts
|
|
385
441
|
type ContrastPreset = 'AA' | 'AAA' | 'AA-large' | 'AAA-large';
|
|
386
442
|
type ContrastSpec =
|
|
387
|
-
| number
|
|
388
|
-
| ContrastPreset
|
|
443
|
+
| number // bare WCAG ratio
|
|
444
|
+
| ContrastPreset // named WCAG preset
|
|
389
445
|
| { wcag: HCPair<number | ContrastPreset> }
|
|
390
|
-
| { apca: HCPair<number> };
|
|
446
|
+
| { apca: HCPair<number> }; // APCA Lc target
|
|
391
447
|
```
|
|
392
448
|
|
|
393
|
-
| Preset
|
|
394
|
-
|
|
395
|
-
| `'AA-large'`
|
|
396
|
-
| `'AA'`
|
|
397
|
-
| `'AAA-large'` | 4.5
|
|
398
|
-
| `'AAA'`
|
|
449
|
+
| Preset | WCAG ratio |
|
|
450
|
+
| ------------- | ---------- |
|
|
451
|
+
| `'AA-large'` | 3 |
|
|
452
|
+
| `'AA'` | 4.5 |
|
|
453
|
+
| `'AAA-large'` | 4.5 |
|
|
454
|
+
| `'AAA'` | 7 |
|
|
399
455
|
|
|
400
456
|
A bare number or preset means **WCAG**. Use `{ wcag }` / `{ apca }` to pick the metric explicitly. The `[normal, highContrast]` pair may live at the outer level (`[4.5, 7]`, `[{ wcag: 4.5 }, { wcag: 7 }]`) or inside the metric (`{ wcag: [4.5, 7] }`, `{ apca: [45, 60] }`).
|
|
401
457
|
|
|
402
458
|
```ts
|
|
403
|
-
contrast: 4.5
|
|
404
|
-
contrast: 'AAA'
|
|
405
|
-
contrast: {
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
contrast: {
|
|
409
|
-
|
|
410
|
-
|
|
459
|
+
contrast: 4.5; // WCAG 4.5
|
|
460
|
+
contrast: 'AAA'; // WCAG 7
|
|
461
|
+
contrast: {
|
|
462
|
+
wcag: 6;
|
|
463
|
+
} // WCAG 6
|
|
464
|
+
contrast: {
|
|
465
|
+
wcag: [4.5, 7];
|
|
466
|
+
} // WCAG 4.5 normal / 7 high-contrast (explicit)
|
|
467
|
+
contrast: {
|
|
468
|
+
apca: 60;
|
|
469
|
+
} // APCA Lc 60 normal / 75 high-contrast (auto)
|
|
470
|
+
contrast: {
|
|
471
|
+
apca: [45, 60];
|
|
472
|
+
} // APCA Lc 45 normal / 60 high-contrast (explicit)
|
|
473
|
+
contrast: {
|
|
474
|
+
apca: 'content';
|
|
475
|
+
} // APCA preset -> Lc 60 normal / 75 high-contrast (auto)
|
|
476
|
+
contrast: {
|
|
477
|
+
apca: ['content', 'body'];
|
|
478
|
+
} // Lc 60 normal / 75 high-contrast (explicit)
|
|
411
479
|
```
|
|
412
480
|
|
|
413
481
|
**WCAG HC auto-promotion:** a bare WCAG preset (no `[normal, hc]` pair at either
|
|
@@ -424,30 +492,30 @@ boosted by **+15 Lc** in high-contrast mode, the APCA analog of WCAG's
|
|
|
424
492
|
AAA-over-AA step. On by default; an explicit HC value via either pair
|
|
425
493
|
overrides it and skips the boost. The enhanced target is clamped to 106 Lc.
|
|
426
494
|
For large/bold text (where APCA caps contrast at Lc 90 to avoid glare), pass
|
|
427
|
-
an explicit HC pair to hold that ceiling
|
|
428
|
-
[`docs/okhst.md`](okhst.md) §Enhanced Level.
|
|
495
|
+
an explicit HC pair to hold that ceiling.
|
|
429
496
|
|
|
430
497
|
APCA preset keywords (Bronze Simple Mode conformance levels, role-independent):
|
|
431
498
|
`'preferred'` (Lc 90), `'body'` (75), `'content'` (60, ~AA), `'large'` (45, ~3:1),
|
|
432
|
-
`'non-text'` (30), `'min'` (15, point of invisibility).
|
|
433
|
-
[`docs/okhst.md`](okhst.md) §APCA.
|
|
499
|
+
`'non-text'` (30), `'min'` (15, point of invisibility).
|
|
434
500
|
|
|
435
|
-
The floor is applied independently per scheme
|
|
501
|
+
The floor is applied independently per scheme. If the preferred `tone` already
|
|
502
|
+
satisfies it, the tone is kept; otherwise the solver uses the tone-shaped scale
|
|
503
|
+
for a closed-form WCAG seed and fast search until the target is met.
|
|
436
504
|
|
|
437
505
|
By default, the solver crosses to the opposite side of the base color when the requested tone direction cannot satisfy the floor. This is controlled per-color by [`autoFlip`](#autoflip) (which defaults to the global `autoFlip`). Set `glaze.configure({ autoFlip: false })` — or `autoFlip: false` on a single color — to keep strict directionality: unmet colors pin to that direction's 0 or 100 tone extreme instead of falling back to the original requested value.
|
|
438
506
|
|
|
439
507
|
**Full tone spectrum in HC mode:** in high-contrast variants the `lightTone` and `darkTone` window constraints are bypassed entirely (the window is forced to `[0, 100]`). Colors can reach the full range, maximizing perceivable contrast.
|
|
440
508
|
|
|
441
|
-
**Chromatic drift (verification):** tone is contrast-uniform for grays. A chromatic swatch at a given tone shares its OKHSL lightness with the equivalent gray but drifts in real luminance, so a contrast-floored color may land slightly under its gray-tone expectation. Glaze measures the resolved result against the base and emits a deduped advisory `console.warn` when it drifts below the target. See [
|
|
509
|
+
**Chromatic drift (verification):** tone is contrast-uniform for grays. A chromatic swatch at a given tone shares its OKHSL lightness with the equivalent gray but drifts in real luminance, so a contrast-floored color may land slightly under its gray-tone expectation. Glaze measures the resolved result against the base and emits a deduped advisory `console.warn` when it drifts below the target. See [Contrast verification](okhst.md#contrast-verification).
|
|
442
510
|
|
|
443
511
|
#### Per-color hue override
|
|
444
512
|
|
|
445
513
|
```ts
|
|
446
514
|
const theme = glaze(280, 80);
|
|
447
515
|
theme.colors({
|
|
448
|
-
surface:
|
|
449
|
-
gradientEnd: { tone: 90, hue: '+20' },
|
|
450
|
-
warning:
|
|
516
|
+
surface: { tone: 97 },
|
|
517
|
+
gradientEnd: { tone: 90, hue: '+20' }, // 280 + 20 = 300
|
|
518
|
+
warning: { tone: 60, hue: 40 }, // absolute
|
|
451
519
|
});
|
|
452
520
|
```
|
|
453
521
|
|
|
@@ -461,7 +529,7 @@ Relative hue is always relative to the **theme seed hue**, not to a base color.
|
|
|
461
529
|
const theme = glaze(280, 80);
|
|
462
530
|
theme.colors({
|
|
463
531
|
plain: { tone: 50, saturation: 1 },
|
|
464
|
-
soft:
|
|
532
|
+
soft: { tone: 50, saturation: 1, pastel: true },
|
|
465
533
|
});
|
|
466
534
|
// theme.resolve().get('soft')!.light.pastel === true
|
|
467
535
|
// theme.css().light contains different rgb() triples for `--plain` and `--soft`
|
|
@@ -489,11 +557,11 @@ const child = parent.extend({
|
|
|
489
557
|
|
|
490
558
|
A color's `role` describes how it is used against its `base` and fixes **APCA contrast polarity** — which side is the foreground vs the background. APCA is asymmetric (`|apca(a,b)| ≠ |apca(b,a)|`), so the role picks the correct argument order; WCAG is symmetric and unaffected.
|
|
491
559
|
|
|
492
|
-
| Role
|
|
493
|
-
|
|
494
|
-
| `'text'`
|
|
495
|
-
| `'border'`
|
|
496
|
-
| `'surface'` | bg
|
|
560
|
+
| Role | Polarity | Use | Aliases (name inference) |
|
|
561
|
+
| ----------- | -------- | ---------------------------------------------------- | ----------------------------------------------------------------- |
|
|
562
|
+
| `'text'` | fg | Text / icons / foreground content | `text`, `fg`, `foreground`, `content`, `ink`, `label`, `stroke` |
|
|
563
|
+
| `'border'` | fg | Non-text spot elements (borders, dividers, outlines) | `border`, `divider`, `outline`, `separator`, `hairline`, `rule` |
|
|
564
|
+
| `'surface'` | bg | Backgrounds / fills | `surface`, `bg`, `background`, `fill`, `canvas`, `paper`, `layer` |
|
|
497
565
|
|
|
498
566
|
Resolution chain (per color):
|
|
499
567
|
|
|
@@ -506,8 +574,8 @@ Resolution chain (per color):
|
|
|
506
574
|
const theme = glaze(280, 60);
|
|
507
575
|
theme.colors({
|
|
508
576
|
surface: { tone: 90 },
|
|
509
|
-
text:
|
|
510
|
-
border:
|
|
577
|
+
text: { base: 'surface', contrast: { apca: 'content' } }, // inferred text
|
|
578
|
+
border: { base: 'surface', tone: '-10' }, // inferred border
|
|
511
579
|
});
|
|
512
580
|
// role fixes APCA polarity; set `pastel: true` explicitly if a border
|
|
513
581
|
// needs the hue-independent safe chroma limit.
|
|
@@ -517,32 +585,32 @@ Disable name inference with `glaze.configure({ inferRole: false })` (the base-op
|
|
|
517
585
|
|
|
518
586
|
### `ShadowColorDef`
|
|
519
587
|
|
|
520
|
-
| Field
|
|
521
|
-
|
|
522
|
-
| `type`
|
|
523
|
-
| `bg`
|
|
524
|
-
| `fg`
|
|
525
|
-
| `intensity` | `HCPair<number>` | Shadow intensity, 0–100. Supports HC pairs.
|
|
526
|
-
| `tuning`
|
|
527
|
-
| `pastel`
|
|
528
|
-
| `inherit`
|
|
588
|
+
| Field | Type | Description |
|
|
589
|
+
| ----------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
590
|
+
| `type` | `'shadow'` | Discriminator. |
|
|
591
|
+
| `bg` | `string` | Background color name — must reference a non-shadow color in the same theme. |
|
|
592
|
+
| `fg` | `string` | Optional foreground color name for tinting and intensity modulation. Must reference a non-shadow color. Omit for an achromatic shadow at full user-specified intensity. |
|
|
593
|
+
| `intensity` | `HCPair<number>` | Shadow intensity, 0–100. Supports HC pairs. |
|
|
594
|
+
| `tuning` | `ShadowTuning` | Per-color tuning overrides. Merged field-by-field with the global `shadowTuning`. |
|
|
595
|
+
| `pastel` | `boolean` | Per-color `pastel` override. See [Per-color `pastel`](#per-color-pastel). |
|
|
596
|
+
| `inherit` | `boolean` | Inheritance flag, default `true`. |
|
|
529
597
|
|
|
530
598
|
See [Shadows](#shadows) below for the algorithm and tuning details.
|
|
531
599
|
|
|
532
600
|
### `MixColorDef`
|
|
533
601
|
|
|
534
|
-
| Field
|
|
535
|
-
|
|
536
|
-
| `type`
|
|
537
|
-
| `base`
|
|
538
|
-
| `target`
|
|
539
|
-
| `value`
|
|
540
|
-
| `blend`
|
|
541
|
-
| `space`
|
|
542
|
-
| `contrast` | `HCPair<ContrastSpec>`
|
|
543
|
-
| `pastel`
|
|
544
|
-
| `role`
|
|
545
|
-
| `inherit`
|
|
602
|
+
| Field | Type | Description |
|
|
603
|
+
| ---------- | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
604
|
+
| `type` | `'mix'` | Discriminator. |
|
|
605
|
+
| `base` | `string` | "From" color name. |
|
|
606
|
+
| `target` | `string` | "To" color name. |
|
|
607
|
+
| `value` | `HCPair<number>` | Mix ratio 0–100 (0 = pure base, 100 = pure target). In `'transparent'` blend, this becomes the target's opacity. Supports HC pairs. |
|
|
608
|
+
| `blend` | `'opaque' \| 'transparent'` | Default `'opaque'`. |
|
|
609
|
+
| `space` | `'okhsl' \| 'srgb'` | Interpolation space for opaque blending. Default `'okhsl'`. Ignored for `'transparent'` (always composites in linear sRGB). |
|
|
610
|
+
| `contrast` | `HCPair<ContrastSpec>` | Optional contrast floor against `base` (WCAG or APCA — see [`contrast`](#contrast-floor)). The solver adjusts the mix ratio (opaque) or opacity (transparent). |
|
|
611
|
+
| `pastel` | `boolean` | Per-color `pastel` override. See [Per-color `pastel`](#per-color-pastel). |
|
|
612
|
+
| `role` | `RoleInput` | Semantic role of the mixed result against `base`. Same semantics as `RegularColorDef.role` (see [Roles](#roles)). |
|
|
613
|
+
| `inherit` | `boolean` | Inheritance flag, default `true`. |
|
|
546
614
|
|
|
547
615
|
See [Mix colors](#mix-colors) below.
|
|
548
616
|
|
|
@@ -562,62 +630,62 @@ glaze.color(color: GlazeFromInput | GlazeColorInput | GlazeColorValue, config?:
|
|
|
562
630
|
|
|
563
631
|
`glaze.color()` accepts **four input shapes**, discriminated by structure:
|
|
564
632
|
|
|
565
|
-
| Shape
|
|
566
|
-
|
|
567
|
-
| **Bare string**
|
|
568
|
-
| **Value object**
|
|
569
|
-
| **`{ from, ...overrides }`** | `{ from: '#1a1a2e', base: bg, contrast: 'AA' }` | Value + color overrides in one object.
|
|
570
|
-
| **Structured**
|
|
633
|
+
| Shape | Example | Notes |
|
|
634
|
+
| ---------------------------- | ----------------------------------------------- | ----------------------------------------------------------------------------------- |
|
|
635
|
+
| **Bare string** | `'#26fcb2'` | Hex or CSS color function (`rgb()`, `hsl()`, `okhsl()`, `okhst()`, `oklch()`). |
|
|
636
|
+
| **Value object** | `{ h: 152, s: 0.95, l: 0.74 }` | OKHSL, OKHST (`{ h, s, t }`), `{ r, g, b }` (sRGB 0–255), or `{ l, c, h }` (OKLCh). |
|
|
637
|
+
| **`{ from, ...overrides }`** | `{ from: '#1a1a2e', base: bg, contrast: 'AA' }` | Value + color overrides in one object. |
|
|
638
|
+
| **Structured** | `{ hue: 152, saturation: 95, tone: 74 }` | Full theme-style token (hue/saturation in 0–100, tone in 0–100). |
|
|
571
639
|
|
|
572
640
|
`GlazeColorValue` (bare string or value-object forms) accepts:
|
|
573
641
|
|
|
574
|
-
| Form
|
|
575
|
-
|
|
576
|
-
| Hex
|
|
577
|
-
| `rgb()`
|
|
578
|
-
| `hsl()`
|
|
579
|
-
| `okhsl()`
|
|
580
|
-
| `okhst()`
|
|
581
|
-
| `oklch()`
|
|
582
|
-
| `OkhslColor` object | `{ h: 152, s: 0.95, l: 0.74 }`
|
|
583
|
-
| `OkhstColor` object | `{ h: 152, s: 0.95, t: 0.70 }`
|
|
584
|
-
| `RgbColor` object
|
|
585
|
-
| `OklchColor` object | `{ l: 0.85, c: 0.18, h: 152 }`
|
|
642
|
+
| Form | Example | Notes |
|
|
643
|
+
| ------------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
|
644
|
+
| Hex | `'#26fcb2'`, `'#26fcb2ff'`, `'#abc'` | 3, 6, or 8 digits. Alpha is dropped with a `console.warn` — use `opacity` instead. |
|
|
645
|
+
| `rgb()` | `'rgb(38 252 178)'`, `'rgb(38 252 178 / 0.8)'` | Modern space syntax. Alpha dropped with warning. |
|
|
646
|
+
| `hsl()` | `'hsl(152 97% 57%)'` | Modern space syntax. Alpha dropped with warning. |
|
|
647
|
+
| `okhsl()` | `'okhsl(152 95% 74%)'` | Glaze's own emit format. Alpha dropped with warning. |
|
|
648
|
+
| `okhst()` | `'okhst(152 95% 70%)'` | OKHST tone input (third value is tone 0–100). Not native CSS; it can also be serialized by [Tasty](https://tasty.style) exports. Alpha dropped with warning. |
|
|
649
|
+
| `oklch()` | `'oklch(0.85 0.18 152)'` | Glaze's own emit format. Alpha dropped with warning. |
|
|
650
|
+
| `OkhslColor` object | `{ h: 152, s: 0.95, l: 0.74 }` | OKHSL shape (h: 0–360, s/l: 0–1). Passing 0–100 for `s`/`l` throws with a hint to use the structured form. |
|
|
651
|
+
| `OkhstColor` object | `{ h: 152, s: 0.95, t: 0.70 }` | Direct OKHST input shape (h: 0–360, s/t: 0–1). The `t` key disambiguates it from `{ h, s, l }`. |
|
|
652
|
+
| `RgbColor` object | `{ r: 38, g: 252, b: 178 }` | sRGB 0–255. RGB tuple `[r, g, b]` is not supported — use this object form. |
|
|
653
|
+
| `OklchColor` object | `{ l: 0.85, c: 0.18, h: 152 }` | OKLCh (L/C: 0–1, H: degrees), same semantics as `oklch()` strings. |
|
|
586
654
|
|
|
587
655
|
`GlazeColorInput` (structured form) is `{ hue, saturation, tone, ... }`:
|
|
588
656
|
|
|
589
|
-
| Field
|
|
590
|
-
|
|
591
|
-
| `hue`
|
|
592
|
-
| `saturation`
|
|
593
|
-
| `tone`
|
|
594
|
-
| `saturationFactor` | `number`
|
|
595
|
-
| `mode`
|
|
596
|
-
| `autoFlip`
|
|
597
|
-
| `opacity`
|
|
598
|
-
| `base`
|
|
599
|
-
| `contrast`
|
|
600
|
-
| `pastel`
|
|
601
|
-
| `role`
|
|
602
|
-
| `name`
|
|
657
|
+
| Field | Type | Description |
|
|
658
|
+
| ------------------ | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
659
|
+
| `hue` | `number` | 0–360. |
|
|
660
|
+
| `saturation` | `number` | 0–100. |
|
|
661
|
+
| `tone` | `HCPair<number \| ExtremeValue>` | 0–100 (contrast-shaped) or `'max'`/`'min'`, optional HC pair. |
|
|
662
|
+
| `saturationFactor` | `number` | Multiplier on the seed (0–1). Default: `1`. |
|
|
663
|
+
| `mode` | `AdaptationMode` | Default: `'auto'`. |
|
|
664
|
+
| `autoFlip` | `boolean` | Flip out-of-bounds results instead of clamping. Default: global `autoFlip`. |
|
|
665
|
+
| `opacity` | `number` | Fixed alpha 0–1. |
|
|
666
|
+
| `base` | `GlazeColorToken \| GlazeColorValue` | Optional dependency. See [Pairing colors](#pairing-colors). |
|
|
667
|
+
| `contrast` | `HCPair<ContrastSpec>` | Contrast floor against `base` (WCAG or APCA). Without `base`, anchored to the literal seed. |
|
|
668
|
+
| `pastel` | `boolean` | Per-color `pastel` override. Falls through to the global / per-theme `pastel` config when omitted. See [Per-color `pastel`](#per-color-pastel). |
|
|
669
|
+
| `role` | `RoleInput` | Semantic role against `base` / the seed (see [Roles](#roles)). Fixes APCA polarity. |
|
|
670
|
+
| `name` | `string` | Debug label for warnings; doesn't change output keys. Reserved names (`'value'`, `'seed'`, `'externalBase'`) are rejected. |
|
|
603
671
|
|
|
604
672
|
`GlazeFromInput` (from form) is `{ from: GlazeColorValue, ...colorOverrides }`:
|
|
605
673
|
|
|
606
|
-
| Field
|
|
607
|
-
|
|
608
|
-
| `from`
|
|
609
|
-
| `hue`
|
|
610
|
-
| `saturation`
|
|
611
|
-
| `tone`
|
|
612
|
-
| `saturationFactor` | Multiplier on the seed (0–1).
|
|
613
|
-
| `mode`
|
|
614
|
-
| `autoFlip`
|
|
615
|
-
| `contrast`
|
|
616
|
-
| `base`
|
|
617
|
-
| `opacity`
|
|
618
|
-
| `pastel`
|
|
619
|
-
| `role`
|
|
620
|
-
| `name`
|
|
674
|
+
| Field | Notes |
|
|
675
|
+
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
676
|
+
| `from` | **Required.** The source color value — same forms as `GlazeColorValue`. |
|
|
677
|
+
| `hue` | Number (absolute 0–360) or `'+N'`/`'-N'` (relative to seed, never to `base`). |
|
|
678
|
+
| `saturation` | Override seed saturation (0–100). |
|
|
679
|
+
| `tone` | Number (absolute 0–100), `'+N'`/`'-N'`, or `'max'`/`'min'`. Without `base`, relative anchors to the seed; with `base`, anchors to `base`'s tone per scheme. |
|
|
680
|
+
| `saturationFactor` | Multiplier on the seed (0–1). |
|
|
681
|
+
| `mode` | `'auto'` (default) / `'fixed'` / `'static'`. |
|
|
682
|
+
| `autoFlip` | Flip out-of-bounds results instead of clamping. Default: global `autoFlip`. |
|
|
683
|
+
| `contrast` | Contrast floor (WCAG or APCA). Without `base`, anchored to the literal seed; with `base`, solved per scheme. |
|
|
684
|
+
| `base` | `GlazeColorToken` or raw `GlazeColorValue`. See [Pairing colors](#pairing-colors). |
|
|
685
|
+
| `opacity` | Fixed alpha 0–1. Combining with `contrast` is not recommended — `console.warn` is emitted. |
|
|
686
|
+
| `pastel` | Per-color `pastel` override. Falls through to the global / per-theme `pastel` config when omitted. See [Per-color `pastel`](#per-color-pastel). |
|
|
687
|
+
| `role` | Semantic role against `base` / the seed (see [Roles](#roles)). Fixes APCA polarity. |
|
|
688
|
+
| `name` | Debug label only — surfaces in warnings/errors. Does not change output keys. |
|
|
621
689
|
|
|
622
690
|
Named CSS colors (`'red'`, `'blueviolet'`) are not supported.
|
|
623
691
|
|
|
@@ -634,71 +702,78 @@ Every input form defaults to `mode: 'auto'` so the resolved token adapts between
|
|
|
634
702
|
|
|
635
703
|
```ts
|
|
636
704
|
// Bare string — adapts automatically
|
|
637
|
-
glaze.color('#26fcb2')
|
|
705
|
+
glaze.color('#26fcb2');
|
|
638
706
|
|
|
639
707
|
// Value-object — same behavior
|
|
640
|
-
glaze.color({ h: 152, s: 0.95, l: 0.74 })
|
|
708
|
+
glaze.color({ h: 152, s: 0.95, l: 0.74 });
|
|
641
709
|
|
|
642
710
|
// OKHST value-object — tone axis
|
|
643
|
-
glaze.color({ h: 152, s: 0.95, t: 0.
|
|
711
|
+
glaze.color({ h: 152, s: 0.95, t: 0.7 });
|
|
644
712
|
|
|
645
713
|
// From form — value + color overrides
|
|
646
|
-
glaze.color({ from: '#1a1a2e', hue: '+20', contrast: 'AA' })
|
|
714
|
+
glaze.color({ from: '#1a1a2e', hue: '+20', contrast: 'AA' });
|
|
647
715
|
|
|
648
716
|
// Structured form — explicit hue/saturation/tone (0–100)
|
|
649
|
-
glaze.color({ hue: 152, saturation: 95, tone: 74 })
|
|
717
|
+
glaze.color({ hue: 152, saturation: 95, tone: 74 });
|
|
650
718
|
```
|
|
651
719
|
|
|
652
720
|
### Token methods
|
|
653
721
|
|
|
654
722
|
A `GlazeColorToken` exposes:
|
|
655
723
|
|
|
656
|
-
| Method
|
|
657
|
-
|
|
658
|
-
| `token.resolve()`
|
|
659
|
-
| `token.token(options?)`
|
|
660
|
-
| `token.tasty(options?)`
|
|
661
|
-
| `token.json(options?)`
|
|
724
|
+
| Method | Description |
|
|
725
|
+
| --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
726
|
+
| `token.resolve()` | Resolve as a `ResolvedColor` (light/dark/lightContrast/darkContrast variants). |
|
|
727
|
+
| `token.token(options?)` | Flat token map (no color-name key). Options: `format`, `modes`, `states`. |
|
|
728
|
+
| `token.tasty(options?)` | [Tasty](https://tasty.style) state map (no color-name key). Same options as `token.token`. |
|
|
729
|
+
| `token.json(options?)` | JSON map (no color-name key). Options: `format`, `modes`. |
|
|
662
730
|
| `token.css({ name, format?, suffix? })` | CSS custom property declarations grouped by scheme variant. `name` is **required** and becomes the variable identifier (`'brand'` → `--brand-color`). Defaults: `format: 'rgb'`, `suffix: '-color'` (matches `theme.css`). |
|
|
663
|
-
| `token.dtcg(options?)`
|
|
664
|
-
| `token.dtcgResolver({ name, ... })`
|
|
665
|
-
| `token.tailwind({ name, ... })`
|
|
666
|
-
| `token.export()`
|
|
731
|
+
| `token.dtcg(options?)` | DTCG color tokens, one per scheme variant (no color-name key). Each entry is a full `{ $type: 'color', $value }` token. Options: `colorSpace` (`'srgb'` \| `'oklch'`), `modes`. |
|
|
732
|
+
| `token.dtcgResolver({ name, ... })` | A single DTCG Resolver-Module document for this color, keyed by `name` across all scheme variants. `name` is **required**. Same options as `theme.dtcgResolver()` plus `name`. |
|
|
733
|
+
| `token.tailwind({ name, ... })` | Tailwind v4 `@theme` block + dark / high-contrast overrides for this color. `name` is **required** (forms `--color-<name>`). Same options as `theme.tailwind()` plus `name`. |
|
|
734
|
+
| `token.export()` | JSON-safe snapshot — pass to `glaze.colorFrom(...)` to rehydrate. |
|
|
667
735
|
|
|
668
736
|
### Per-instance config override
|
|
669
737
|
|
|
670
|
-
The optional `config`
|
|
738
|
+
The optional `config` argument (`GlazeConfigOverride`) overrides
|
|
739
|
+
resolve-relevant global fields for a token or theme. A tone window can be
|
|
740
|
+
`[lo, hi]`, `{ lo, hi, eps }`, or `false` (full range). Standalone tokens
|
|
741
|
+
snapshot omitted fields at creation; themes continue reading omitted fields
|
|
742
|
+
from the live global config at resolve time.
|
|
671
743
|
|
|
672
744
|
`GlazeConfigOverride`:
|
|
673
745
|
|
|
674
|
-
| Field
|
|
675
|
-
|
|
676
|
-
| `lightTone`
|
|
677
|
-
| `darkTone`
|
|
678
|
-
| `darkDesaturation` | `0.1`
|
|
679
|
-
| `autoFlip`
|
|
680
|
-
| `shadowTuning`
|
|
746
|
+
| Field | Default (from global) | Description |
|
|
747
|
+
| ------------------ | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
748
|
+
| `lightTone` | `[10, 100]` | Light tone window: `[lo, hi]`, `{ lo, hi, eps }`, or `false` (disable clamping). |
|
|
749
|
+
| `darkTone` | `[15, 95]` | Dark tone window: `[lo, hi]`, `{ lo, hi, eps }`, or `false` (disable clamping). |
|
|
750
|
+
| `darkDesaturation` | `0.1` | Saturation reduction in dark scheme (0–1). |
|
|
751
|
+
| `autoFlip` | `true` | Default for each color's `autoFlip`: when solving `contrast` (or applying a relative `tone` that overshoots), allow crossing to the opposite side instead of clamping. |
|
|
752
|
+
| `shadowTuning` | `undefined` | Default shadow tuning (meaningful for themes; harmless on color tokens). |
|
|
681
753
|
|
|
682
754
|
Config overrides apply to both `glaze.color()` tokens and `glaze()` themes:
|
|
683
755
|
|
|
684
756
|
```ts
|
|
685
757
|
// Standalone color — preserve raw tone in both schemes
|
|
686
|
-
glaze.color('#26fcb2', { darkTone: false })
|
|
758
|
+
glaze.color('#26fcb2', { darkTone: false });
|
|
687
759
|
|
|
688
760
|
// Restore the #000 → white dark flip (full dark range)
|
|
689
761
|
glaze.color('#000000', {
|
|
690
762
|
lightTone: false,
|
|
691
763
|
darkTone: [15, 100],
|
|
692
|
-
})
|
|
764
|
+
});
|
|
693
765
|
|
|
694
766
|
// Structured form with config override
|
|
695
|
-
glaze.color({ hue: 152, saturation: 95, tone: 74 }, { darkTone: false })
|
|
767
|
+
glaze.color({ hue: 152, saturation: 95, tone: 74 }, { darkTone: false });
|
|
696
768
|
|
|
697
769
|
// Theme with config override
|
|
698
|
-
const rawTheme = glaze(280, 80, { lightTone: false })
|
|
770
|
+
const rawTheme = glaze(280, 80, { lightTone: false });
|
|
699
771
|
```
|
|
700
772
|
|
|
701
|
-
|
|
773
|
+
Standalone color tokens snapshot their effective override at creation time.
|
|
774
|
+
Themes behave differently: overridden fields stay fixed, while fields omitted
|
|
775
|
+
from the override are read from the live global config at resolve time. See
|
|
776
|
+
[Theme config override](#theme-config-override).
|
|
702
777
|
|
|
703
778
|
### Theme config override
|
|
704
779
|
|
|
@@ -748,7 +823,8 @@ const bg = glaze.color('#1a1a2e');
|
|
|
748
823
|
const text = glaze.color({ from: '#ffffff', base: bg, contrast: 'AA' });
|
|
749
824
|
|
|
750
825
|
// Border 8 tone units lighter than `bg` in each scheme.
|
|
751
|
-
const border = glaze.color({
|
|
826
|
+
const border = glaze.color({
|
|
827
|
+
from: '#000000',
|
|
752
828
|
base: bg,
|
|
753
829
|
tone: '+8',
|
|
754
830
|
mode: 'fixed',
|
|
@@ -789,7 +865,7 @@ The `name` override appears in `console.warn` / Error messages but **does not**
|
|
|
789
865
|
```ts
|
|
790
866
|
theme.colors({
|
|
791
867
|
surface: { tone: 95 },
|
|
792
|
-
text:
|
|
868
|
+
text: { base: 'surface', tone: '-52', contrast: 'AAA' },
|
|
793
869
|
|
|
794
870
|
'shadow-sm': { type: 'shadow', bg: 'surface', fg: 'text', intensity: 5 },
|
|
795
871
|
'shadow-md': { type: 'shadow', bg: 'surface', fg: 'text', intensity: 10 },
|
|
@@ -828,20 +904,22 @@ theme.colors({
|
|
|
828
904
|
|
|
829
905
|
Fine-tune behavior per-color or globally via `glaze.configure({ shadowTuning })`. Per-color `tuning` is merged field-by-field with the global one.
|
|
830
906
|
|
|
831
|
-
| Parameter
|
|
832
|
-
|
|
833
|
-
| `saturationFactor` | `0.18`
|
|
834
|
-
| `maxSaturation`
|
|
835
|
-
| `lightnessFactor`
|
|
836
|
-
| `lightnessBounds`
|
|
837
|
-
| `minGapTarget`
|
|
838
|
-
| `alphaMax`
|
|
839
|
-
| `bgHueBlend`
|
|
907
|
+
| Parameter | Default | Description |
|
|
908
|
+
| ------------------ | -------------- | ------------------------------------------------------------------------------------- |
|
|
909
|
+
| `saturationFactor` | `0.18` | Fraction of fg saturation kept in pigment. |
|
|
910
|
+
| `maxSaturation` | `0.25` | Upper clamp on pigment saturation. |
|
|
911
|
+
| `lightnessFactor` | `0.25` | Multiplier for bg lightness → pigment lightness. |
|
|
912
|
+
| `lightnessBounds` | `[0.05, 0.20]` | Clamp range for pigment lightness. |
|
|
913
|
+
| `minGapTarget` | `0.05` | Target minimum gap between pigment and bg lightness. |
|
|
914
|
+
| `alphaMax` | `1.0` | Asymptotic maximum alpha. |
|
|
915
|
+
| `bgHueBlend` | `0.2` | Blend weight pulling pigment hue toward bg hue. `0` = pure fg hue, `1` = pure bg hue. |
|
|
840
916
|
|
|
841
917
|
```ts
|
|
842
918
|
theme.colors({
|
|
843
919
|
'shadow-soft': {
|
|
844
|
-
type: 'shadow',
|
|
920
|
+
type: 'shadow',
|
|
921
|
+
bg: 'surface',
|
|
922
|
+
intensity: 10,
|
|
845
923
|
tuning: { alphaMax: 0.3, saturationFactor: 0.1 },
|
|
846
924
|
},
|
|
847
925
|
});
|
|
@@ -869,12 +947,12 @@ const css = glaze.format(v, 'oklch');
|
|
|
869
947
|
|
|
870
948
|
`GlazeShadowInput`:
|
|
871
949
|
|
|
872
|
-
| Field
|
|
873
|
-
|
|
874
|
-
| `bg`
|
|
875
|
-
| `fg`
|
|
876
|
-
| `intensity` | `number`
|
|
877
|
-
| `tuning`
|
|
950
|
+
| Field | Type | Description |
|
|
951
|
+
| ----------- | ----------------- | ------------------------------------------------------------------------------ |
|
|
952
|
+
| `bg` | `GlazeColorValue` | Background. Any `GlazeColorValue` form. Alpha components dropped with warning. |
|
|
953
|
+
| `fg` | `GlazeColorValue` | Optional foreground. Same forms as `bg`. |
|
|
954
|
+
| `intensity` | `number` | 0–100. |
|
|
955
|
+
| `tuning` | `ShadowTuning` | Optional. |
|
|
878
956
|
|
|
879
957
|
### Fixed opacity (regular colors)
|
|
880
958
|
|
|
@@ -898,8 +976,8 @@ Produces a solid color by interpolating between `base` and `target`:
|
|
|
898
976
|
```ts
|
|
899
977
|
theme.colors({
|
|
900
978
|
surface: { tone: 95 },
|
|
901
|
-
accent:
|
|
902
|
-
tint:
|
|
979
|
+
accent: { tone: 30 },
|
|
980
|
+
tint: { type: 'mix', base: 'surface', target: 'accent', value: 30 },
|
|
903
981
|
});
|
|
904
982
|
```
|
|
905
983
|
|
|
@@ -914,10 +992,13 @@ Produces the target color with controlled opacity — useful for hover overlays:
|
|
|
914
992
|
```ts
|
|
915
993
|
theme.colors({
|
|
916
994
|
surface: { tone: 95 },
|
|
917
|
-
black:
|
|
995
|
+
black: { tone: 0, saturation: 0 },
|
|
918
996
|
hover: {
|
|
919
|
-
type: 'mix',
|
|
920
|
-
|
|
997
|
+
type: 'mix',
|
|
998
|
+
base: 'surface',
|
|
999
|
+
target: 'black',
|
|
1000
|
+
value: 8,
|
|
1001
|
+
blend: 'transparent',
|
|
921
1002
|
},
|
|
922
1003
|
});
|
|
923
1004
|
// hover → black with alpha = 0.08
|
|
@@ -927,10 +1008,10 @@ The output color has `h`, `s`, `l` from the target and `alpha = value / 100`.
|
|
|
927
1008
|
|
|
928
1009
|
### Blend space (opaque only)
|
|
929
1010
|
|
|
930
|
-
| `space`
|
|
931
|
-
|
|
932
|
-
| `'okhsl'` (default) | Perceptually uniform OKHSL interpolation. | Design token derivation.
|
|
933
|
-
| `'srgb'`
|
|
1011
|
+
| `space` | Behavior | Best for |
|
|
1012
|
+
| ------------------- | ----------------------------------------- | -------------------------------------------------------- |
|
|
1013
|
+
| `'okhsl'` (default) | Perceptually uniform OKHSL interpolation. | Design token derivation. |
|
|
1014
|
+
| `'srgb'` | Linear sRGB channel interpolation. | Matching browser compositing of CSS color-mix / overlay. |
|
|
934
1015
|
|
|
935
1016
|
Transparent blending always composites in linear sRGB (matches browser alpha compositing).
|
|
936
1017
|
|
|
@@ -963,8 +1044,20 @@ Mix colors can reference other mix colors:
|
|
|
963
1044
|
theme.colors({
|
|
964
1045
|
white: { tone: 100, saturation: 0 },
|
|
965
1046
|
black: { tone: 0, saturation: 0 },
|
|
966
|
-
gray:
|
|
967
|
-
|
|
1047
|
+
gray: {
|
|
1048
|
+
type: 'mix',
|
|
1049
|
+
base: 'white',
|
|
1050
|
+
target: 'black',
|
|
1051
|
+
value: 50,
|
|
1052
|
+
space: 'srgb',
|
|
1053
|
+
},
|
|
1054
|
+
lightGray: {
|
|
1055
|
+
type: 'mix',
|
|
1056
|
+
base: 'white',
|
|
1057
|
+
target: 'gray',
|
|
1058
|
+
value: 50,
|
|
1059
|
+
space: 'srgb',
|
|
1060
|
+
},
|
|
968
1061
|
});
|
|
969
1062
|
```
|
|
970
1063
|
|
|
@@ -986,35 +1079,41 @@ const palette = glaze.palette(
|
|
|
986
1079
|
|
|
987
1080
|
`GlazePaletteOptions`:
|
|
988
1081
|
|
|
989
|
-
| Option
|
|
990
|
-
|
|
1082
|
+
| Option | Description |
|
|
1083
|
+
| --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
991
1084
|
| `primary` | Name of the primary theme. The primary's tokens are duplicated **without** prefix in all exports, providing convenient short aliases alongside the prefixed versions. Throws if the name doesn't match any theme. |
|
|
992
1085
|
|
|
993
1086
|
A `GlazePalette` exposes:
|
|
994
1087
|
|
|
995
|
-
| Method
|
|
996
|
-
|
|
997
|
-
| `palette.tokens(options?)`
|
|
998
|
-
| `palette.tasty(options?)`
|
|
999
|
-
| `palette.json(options?)`
|
|
1000
|
-
| `palette.css(options?)`
|
|
1088
|
+
| Method | Description |
|
|
1089
|
+
| -------------------------------- | ------------------------------------------------------------ |
|
|
1090
|
+
| `palette.tokens(options?)` | Flat token map grouped by scheme variant. |
|
|
1091
|
+
| `palette.tasty(options?)` | [Tasty](https://tasty.style) style-to-state bindings. |
|
|
1092
|
+
| `palette.json(options?)` | Per-theme JSON map (no prefix needed — keyed by theme name). |
|
|
1093
|
+
| `palette.css(options?)` | CSS custom property declaration strings. |
|
|
1094
|
+
| `palette.dtcg(options?)` | Per-scheme W3C DTCG token trees. |
|
|
1095
|
+
| `palette.dtcgResolver(options?)` | One DTCG Resolver-Module document for every scheme. |
|
|
1096
|
+
| `palette.tailwind(options?)` | One Tailwind CSS v4 theme with scheme overrides. |
|
|
1001
1097
|
|
|
1002
1098
|
### `GlazePaletteExportOptions`
|
|
1003
1099
|
|
|
1004
1100
|
Shared by `tokens`, `tasty`, and `css`:
|
|
1005
1101
|
|
|
1006
|
-
| Option
|
|
1007
|
-
|
|
1008
|
-
| `prefix`
|
|
1009
|
-
| `primary` | inherits from palette creation | `string` to override, `false` to disable for this call.
|
|
1102
|
+
| Option | Default | Description |
|
|
1103
|
+
| --------- | ------------------------------ | -------------------------------------------------------------------------------------------- |
|
|
1104
|
+
| `prefix` | `true` (= `"<themeName>-"`) | `false` disables prefixing. Or pass a custom map: `{ primary: 'brand-', danger: 'error-' }`. |
|
|
1105
|
+
| `primary` | inherits from palette creation | `string` to override, `false` to disable for this call. |
|
|
1010
1106
|
|
|
1011
1107
|
Each export method also accepts its own format/options shape:
|
|
1012
1108
|
|
|
1013
|
-
| Method
|
|
1014
|
-
|
|
1015
|
-
| `palette.tokens(options?)`
|
|
1016
|
-
| `palette.tasty(options?)`
|
|
1017
|
-
| `palette.css(options?)`
|
|
1109
|
+
| Method | Additional options |
|
|
1110
|
+
| -------------------------------- | ----------------------------------------- |
|
|
1111
|
+
| `palette.tokens(options?)` | `format`, `modes` |
|
|
1112
|
+
| `palette.tasty(options?)` | `format`, `modes`, `states` |
|
|
1113
|
+
| `palette.css(options?)` | `format`, `suffix` |
|
|
1114
|
+
| `palette.dtcg(options?)` | `colorSpace`, `modes` |
|
|
1115
|
+
| `palette.dtcgResolver(options?)` | `colorSpace`, `modes`, resolver names |
|
|
1116
|
+
| `palette.tailwind(options?)` | `format`, `modes`, selectors, `namespace` |
|
|
1018
1117
|
|
|
1019
1118
|
`palette.css()` does not accept `modes`; it always returns all four CSS strings (`light`, `dark`, `lightContrast`, `darkContrast`).
|
|
1020
1119
|
|
|
@@ -1025,8 +1124,8 @@ By default all palette tokens are prefixed:
|
|
|
1025
1124
|
```ts
|
|
1026
1125
|
palette.tokens();
|
|
1027
1126
|
// → {
|
|
1028
|
-
// light: { 'primary-surface': '
|
|
1029
|
-
// dark: { 'primary-surface': '
|
|
1127
|
+
// light: { 'primary-surface': 'oklch(...)', 'danger-surface': 'oklch(...)' },
|
|
1128
|
+
// dark: { 'primary-surface': 'oklch(...)', 'danger-surface': 'oklch(...)' },
|
|
1030
1129
|
// }
|
|
1031
1130
|
```
|
|
1032
1131
|
|
|
@@ -1062,10 +1161,10 @@ const palette = glaze.palette(
|
|
|
1062
1161
|
palette.tokens();
|
|
1063
1162
|
// → {
|
|
1064
1163
|
// light: {
|
|
1065
|
-
// 'primary-surface': '
|
|
1066
|
-
// 'danger-surface': '
|
|
1067
|
-
// 'success-surface': '
|
|
1068
|
-
// 'surface': '
|
|
1164
|
+
// 'primary-surface': 'oklch(...)',
|
|
1165
|
+
// 'danger-surface': 'oklch(...)',
|
|
1166
|
+
// 'success-surface': 'oklch(...)',
|
|
1167
|
+
// 'surface': 'oklch(...)', // unprefixed alias
|
|
1069
1168
|
// },
|
|
1070
1169
|
// }
|
|
1071
1170
|
```
|
|
@@ -1091,8 +1190,8 @@ JSON export groups by theme name (no prefix needed):
|
|
|
1091
1190
|
```ts
|
|
1092
1191
|
palette.json();
|
|
1093
1192
|
// → {
|
|
1094
|
-
// primary: { surface: { light: '
|
|
1095
|
-
// danger: { surface: { light: '
|
|
1193
|
+
// primary: { surface: { light: 'oklch(...)', dark: 'oklch(...)' } },
|
|
1194
|
+
// danger: { surface: { light: 'oklch(...)', dark: 'oklch(...)' } },
|
|
1096
1195
|
// }
|
|
1097
1196
|
```
|
|
1098
1197
|
|
|
@@ -1116,7 +1215,7 @@ It does not accept `modes`; all four result fields are always returned.
|
|
|
1116
1215
|
DTCG export for a palette. Prefix defaults to `true` and the palette-level `primary` is honored (the primary theme's tokens are duplicated without prefix as aliases).
|
|
1117
1216
|
|
|
1118
1217
|
```ts
|
|
1119
|
-
palette.dtcg()
|
|
1218
|
+
palette.dtcg();
|
|
1120
1219
|
// → {
|
|
1121
1220
|
// light: {
|
|
1122
1221
|
// 'primary-surface': { $type: 'color', $value: { ... } },
|
|
@@ -1134,7 +1233,7 @@ Accepts `GlazeDtcgOptions` plus `GlazePaletteExportOptions`.
|
|
|
1134
1233
|
Resolver-Module export for a palette. Same as `theme.dtcgResolver()` but merges every theme (with prefix / `primary` aliasing) into the single `sets.base` source and each `scheme` context. Prefix defaults to `true`; the palette-level `primary` is honored.
|
|
1135
1234
|
|
|
1136
1235
|
```ts
|
|
1137
|
-
palette.dtcgResolver()
|
|
1236
|
+
palette.dtcgResolver();
|
|
1138
1237
|
// → {
|
|
1139
1238
|
// version: '2025.10',
|
|
1140
1239
|
// sets: { base: { sources: [ { 'primary-surface': {…}, 'surface': {…}, 'danger-surface': {…} } ] } },
|
|
@@ -1167,24 +1266,24 @@ Accepts `GlazeTailwindOptions` plus `GlazePaletteExportOptions`. The palette `pr
|
|
|
1167
1266
|
|
|
1168
1267
|
Control the color format with the `format` option on any export method:
|
|
1169
1268
|
|
|
1170
|
-
| Format
|
|
1171
|
-
|
|
1172
|
-
| `'
|
|
1173
|
-
| `'
|
|
1174
|
-
| `'
|
|
1175
|
-
| `'
|
|
1176
|
-
| `'
|
|
1269
|
+
| Format | Output (alpha = 1) | Output (alpha < 1) | Notes |
|
|
1270
|
+
| --------------------------------------------- | ------------------ | -------------------- | --------------------------------------------------------------------------------------------- |
|
|
1271
|
+
| `'oklch'` (default for CSS-string exports) | `oklch(L C H)` | `oklch(L C H / A)` | OKLab-based LCH. Native CSS. Required for `splitHue`. |
|
|
1272
|
+
| `'rgb'` | `rgb(R G B)` | `rgb(R G B / A)` | Rounded integers, modern space syntax. |
|
|
1273
|
+
| `'hsl'` | `hsl(H S% L%)` | `hsl(H S% L% / A)` | Modern space syntax. |
|
|
1274
|
+
| `'okhsl'` | `okhsl(H S% L%)` | `okhsl(H S% L% / A)` | Glaze's native format, not a CSS function. **[Tasty](https://tasty.style)-only** (`tasty()`, `token()`, `.tasty()`). |
|
|
1275
|
+
| `'okhst'` | `okhst(H S% T%)` | `okhst(H S% T% / A)` | OKHST tone axis. **[Tasty](https://tasty.style)-only** — same restriction as `okhsl`. |
|
|
1177
1276
|
|
|
1178
1277
|
```ts
|
|
1179
|
-
theme.tokens();
|
|
1180
|
-
theme.tokens({ format: 'rgb' });
|
|
1181
|
-
theme.tasty();
|
|
1182
|
-
theme.tasty({ format: 'okhst' });
|
|
1278
|
+
theme.tokens(); // 'oklch(0.965 0.0123 280)' (default)
|
|
1279
|
+
theme.tokens({ format: 'rgb' }); // 'rgb(244 240 250)'
|
|
1280
|
+
theme.tasty(); // 'oklch(0.965 0.0123 280)' (default)
|
|
1281
|
+
theme.tasty({ format: 'okhst' }); // 'okhst(280 60% 97%)'
|
|
1183
1282
|
```
|
|
1184
1283
|
|
|
1185
1284
|
All numeric output strips trailing zeros for cleaner CSS (e.g. `95` not `95.0`).
|
|
1186
1285
|
|
|
1187
|
-
The `format` option works on CSS-string exports: `theme.tokens()`, `theme.tasty()`, `theme.json()`, `theme.css()`, `theme.tailwind()`, the same on `palette`, and on `token.token()` / `.tasty()` / `.json()` / `.css()` / `.tailwind()`. **`okhsl` and `okhst` throw on non-Tasty exports** (`tokens`, `json`, `css`, `tailwind`) — they are not native CSS color spaces.
|
|
1286
|
+
The `format` option works on CSS-string exports: `theme.tokens()`, `theme.tasty()`, `theme.json()`, `theme.css()`, `theme.tailwind()`, the same on `palette`, and on `token.token()` / `.tasty()` / `.json()` / `.css()` / `.tailwind()`. **`okhsl` and `okhst` throw on non-[Tasty](https://tasty.style) exports** (`tokens`, `json`, `css`, `tailwind`) — they are not native CSS color spaces.
|
|
1188
1287
|
|
|
1189
1288
|
### Hue channel splitting (`splitHue`)
|
|
1190
1289
|
|
|
@@ -1210,11 +1309,11 @@ On `theme.css()`, `theme.tasty()`, `palette.css()`, `palette.tasty()`, and stand
|
|
|
1210
1309
|
|
|
1211
1310
|
`mode` controls how a color adapts across schemes:
|
|
1212
1311
|
|
|
1213
|
-
| Mode
|
|
1214
|
-
|
|
1215
|
-
| `'auto'` (default) | Full adaptation. Dark
|
|
1216
|
-
| `'fixed'`
|
|
1217
|
-
| `'static'`
|
|
1312
|
+
| Mode | Behavior |
|
|
1313
|
+
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1314
|
+
| `'auto'` (default) | Full adaptation. Dark uses **dark tone inversion** (`100 − t`) and then remaps into the dark tone window. High-contrast uses the full range. |
|
|
1315
|
+
| `'fixed'` | Color stays recognizable. Tone is _mapped_ (not inverted) into the dark window. Use for brand buttons, CTAs, status banners. |
|
|
1316
|
+
| `'static'` | No adaptation. Same tone in every scheme. |
|
|
1218
1317
|
|
|
1219
1318
|
### How relative tone adapts
|
|
1220
1319
|
|
|
@@ -1241,11 +1340,17 @@ Offsets that would push past `[0, 100]` clamp to the boundary, or — with `auto
|
|
|
1241
1340
|
|
|
1242
1341
|
## Light / dark scheme mapping
|
|
1243
1342
|
|
|
1244
|
-
The mapping is
|
|
1343
|
+
The mapping is a single tone pipeline; there is no Möbius curve. See
|
|
1344
|
+
[Scheme adaptation](okhst.md#scheme-adaptation) for the product-level model and
|
|
1345
|
+
the [canonical OKHST specification](https://github.com/tenphi/okhst) for the
|
|
1346
|
+
transfer math.
|
|
1245
1347
|
|
|
1246
1348
|
### Light scheme
|
|
1247
1349
|
|
|
1248
|
-
An authored tone (0–100) is remapped into the `lightTone`
|
|
1350
|
+
An authored tone (0–100) is remapped into the `lightTone` **tone window**.
|
|
1351
|
+
The window's `lo`/`hi` are OKHSL-lightness boundaries (0–100); authored tone is
|
|
1352
|
+
positioned within the corresponding tone interval and converted to final OKHSL
|
|
1353
|
+
lightness. `static` mode and HC variants use the full range.
|
|
1249
1354
|
|
|
1250
1355
|
```
|
|
1251
1356
|
window = lightTone // default [10, 100]
|
|
@@ -1263,7 +1368,10 @@ inverted = 100 - authorTone
|
|
|
1263
1368
|
finalTone = remap(inverted, window)
|
|
1264
1369
|
```
|
|
1265
1370
|
|
|
1266
|
-
|
|
1371
|
+
The inversion preserves authored tone spacing without a fitted curve. This is
|
|
1372
|
+
exactly contrast-even for neutrals and approximate for chromatic colors. The
|
|
1373
|
+
ordinary light/dark asymmetry lives in the two windows' `(lo, hi, eps)` values
|
|
1374
|
+
(`eps` defaults to the reference `0.05`).
|
|
1267
1375
|
|
|
1268
1376
|
**`fixed`** — remap into the dark window without inversion:
|
|
1269
1377
|
|
|
@@ -1278,7 +1386,7 @@ In high-contrast variants both windows are bypassed (forced to the full `[0, 100
|
|
|
1278
1386
|
`darkDesaturation` reduces saturation for all colors in dark scheme:
|
|
1279
1387
|
|
|
1280
1388
|
```ts
|
|
1281
|
-
S_dark = S_light * (1 - darkDesaturation) // default: 0.1
|
|
1389
|
+
S_dark = S_light * (1 - darkDesaturation); // default: 0.1
|
|
1282
1390
|
```
|
|
1283
1391
|
|
|
1284
1392
|
`static` mode skips desaturation.
|
|
@@ -1290,11 +1398,11 @@ S_dark = S_light * (1 - darkDesaturation) // default: 0.1
|
|
|
1290
1398
|
```ts
|
|
1291
1399
|
glaze.configure({
|
|
1292
1400
|
lightTone: [10, 100], // [lo, hi]; or { lo, hi, eps } / false to disable clamping
|
|
1293
|
-
darkTone: [15, 95],
|
|
1401
|
+
darkTone: [15, 95], // [lo, hi]; or { lo, hi, eps } / false to disable clamping
|
|
1294
1402
|
darkDesaturation: 0.1,
|
|
1295
1403
|
states: {
|
|
1296
|
-
dark: '@dark',
|
|
1297
|
-
highContrast: '@
|
|
1404
|
+
dark: '@media(prefers-color-scheme: dark)',
|
|
1405
|
+
highContrast: '@media(prefers-contrast: more)',
|
|
1298
1406
|
},
|
|
1299
1407
|
modes: {
|
|
1300
1408
|
dark: true,
|
|
@@ -1307,29 +1415,32 @@ glaze.configure({
|
|
|
1307
1415
|
});
|
|
1308
1416
|
```
|
|
1309
1417
|
|
|
1310
|
-
A `ToneWindow` is `[lo, hi]` (OKHSL-lightness
|
|
1418
|
+
A `ToneWindow` is `[lo, hi]` (OKHSL-lightness boundaries, reference eps — the
|
|
1419
|
+
common form), `{ lo, hi, eps }` (advanced: explicit per-scheme render eps), or
|
|
1420
|
+
`false` for the full range `[0, 100]` at the reference eps. `false` removes the
|
|
1421
|
+
boundaries, not the tone transfer.
|
|
1311
1422
|
|
|
1312
1423
|
`GlazeConfig`:
|
|
1313
1424
|
|
|
1314
|
-
| Field
|
|
1315
|
-
|
|
1316
|
-
| `lightTone`
|
|
1317
|
-
| `darkTone`
|
|
1318
|
-
| `darkDesaturation`
|
|
1319
|
-
| `states.dark`
|
|
1320
|
-
| `states.highContrast` | `'@
|
|
1321
|
-
| `modes.dark`
|
|
1322
|
-
| `modes.highContrast`
|
|
1323
|
-
| `shadowTuning`
|
|
1324
|
-
| `autoFlip`
|
|
1325
|
-
| `pastel`
|
|
1326
|
-
| `inferRole`
|
|
1327
|
-
|
|
1328
|
-
| Method
|
|
1329
|
-
|
|
1425
|
+
| Field | Default | Description |
|
|
1426
|
+
| --------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1427
|
+
| `lightTone` | `[10, 100]` | Light scheme tone window: `[lo, hi]`, `{ lo, hi, eps }`, or `false` to disable clamping. Bypassed in HC. |
|
|
1428
|
+
| `darkTone` | `[15, 95]` | Dark scheme tone window: `[lo, hi]`, `{ lo, hi, eps }`, or `false` to disable clamping. Bypassed in HC. |
|
|
1429
|
+
| `darkDesaturation` | `0.1` | Saturation reduction in dark scheme (0–1). |
|
|
1430
|
+
| `states.dark` | `'@media(prefers-color-scheme: dark)'` | State alias for dark mode tokens ([Tasty](https://tasty.style) export). Defaults to a media query so tokens react to the OS preference without registering custom states. |
|
|
1431
|
+
| `states.highContrast` | `'@media(prefers-contrast: more)'` | State alias for HC tokens ([Tasty](https://tasty.style) export). |
|
|
1432
|
+
| `modes.dark` | `true` | Include dark variants in exports. |
|
|
1433
|
+
| `modes.highContrast` | `false` | Include HC variants. |
|
|
1434
|
+
| `shadowTuning` | `undefined` | Default tuning for all shadow colors. Per-color tuning merges field-by-field. |
|
|
1435
|
+
| `autoFlip` | `true` | Default for each color's `autoFlip`. When solving `contrast` (or applying a relative `tone` that overshoots `[0, 100]`), allow crossing to the opposite side instead of clamping. With `false`, only the requested direction is considered; unmet contrasts pin the tone to that direction's extreme (and emit a warning) and overshooting offsets clamp to the boundary. Override per color via [`autoFlip`](#autoflip). |
|
|
1436
|
+
| `pastel` | `false` | Hue-independent "safe" chroma limit across all colors so scaling saturation never exceeds the sRGB boundary at any hue for the given lightness. Override per color via [`pastel`](#per-color-pastel). |
|
|
1437
|
+
| `inferRole` | `true` | Infer each color's [`role`](#roles) from its name when no explicit `role` is set. Set to `false` to opt out of name-based inference (the base-opposite and foreground-default fallbacks still apply). |
|
|
1438
|
+
|
|
1439
|
+
| Method | Description |
|
|
1440
|
+
| ------------------------- | ----------------------------------------------------------------------------------- |
|
|
1330
1441
|
| `glaze.configure(config)` | Merge into the global config. Bumps a config version that invalidates theme caches. |
|
|
1331
|
-
| `glaze.getConfig()`
|
|
1332
|
-
| `glaze.resetConfig()`
|
|
1442
|
+
| `glaze.getConfig()` | Snapshot the current resolved config (shallow copy). |
|
|
1443
|
+
| `glaze.resetConfig()` | Reset to defaults (also bumps the version counter). |
|
|
1333
1444
|
|
|
1334
1445
|
Standalone `glaze.color()` tokens snapshot the resolve-relevant fields at create time, so later `configure()` calls don't change already-created tokens. Themes merge the live global at resolve time for fields not overridden via `GlazeConfigOverride`.
|
|
1335
1446
|
|
|
@@ -1361,32 +1472,38 @@ Resolution priority (highest first):
|
|
|
1361
1472
|
|
|
1362
1473
|
## Validation
|
|
1363
1474
|
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
|
1371
|
-
|
|
|
1372
|
-
| `
|
|
1373
|
-
| `
|
|
1374
|
-
|
|
|
1375
|
-
| `
|
|
1376
|
-
|
|
|
1377
|
-
|
|
|
1378
|
-
|
|
|
1379
|
-
|
|
|
1380
|
-
|
|
|
1381
|
-
|
|
|
1382
|
-
| `
|
|
1383
|
-
|
|
|
1384
|
-
|
|
|
1385
|
-
|
|
|
1386
|
-
|
|
|
1387
|
-
|
|
|
1388
|
-
|
|
|
1389
|
-
|
|
|
1475
|
+
Invalid definitions throw before resolution when Glaze cannot produce a
|
|
1476
|
+
well-defined dependency graph. Recoverable numeric bounds are clamped. A
|
|
1477
|
+
physically unreachable contrast floor or a potentially misleading
|
|
1478
|
+
contrast/opacity combination emits `console.warn` and returns the closest
|
|
1479
|
+
available result.
|
|
1480
|
+
|
|
1481
|
+
| Condition | Behavior |
|
|
1482
|
+
| --------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
1483
|
+
| `contrast` without `base` in a **theme** color | Validation error |
|
|
1484
|
+
| Relative `tone` without `base` in a **theme** color | Validation error |
|
|
1485
|
+
| `contrast` without `base` in `glaze.color()` | Anchors against the literal seed (no error) |
|
|
1486
|
+
| Relative `tone` without `base` in `glaze.color()` | Anchors against the literal seed (no error) |
|
|
1487
|
+
| Relative `tone` overshoots `[0, 100]` | Mirror to the other side of the base (`autoFlip` on, default), or clamp to the boundary (`autoFlip` off) |
|
|
1488
|
+
| `tone` resolves outside 0–100 | Clamp silently |
|
|
1489
|
+
| `'max'` / `'min'` without `base` | Allowed — resolves to the scheme's tone extreme (root color) |
|
|
1490
|
+
| `saturation` outside 0–1 | Clamp silently |
|
|
1491
|
+
| Circular `base` references | Validation error |
|
|
1492
|
+
| `base` references non-existent name | Validation error |
|
|
1493
|
+
| Shadow `bg` references non-existent color | Validation error |
|
|
1494
|
+
| Shadow `fg` references non-existent color | Validation error |
|
|
1495
|
+
| Shadow `bg` references another shadow color | Validation error |
|
|
1496
|
+
| Shadow `fg` references another shadow color | Validation error |
|
|
1497
|
+
| Regular color `base` references a shadow color | Validation error |
|
|
1498
|
+
| Shadow `intensity` outside 0–100 | Clamp silently |
|
|
1499
|
+
| `contrast` + `opacity` combined | `console.warn` |
|
|
1500
|
+
| Mix `base` references non-existent color | Validation error |
|
|
1501
|
+
| Mix `target` references non-existent color | Validation error |
|
|
1502
|
+
| Mix `base` references a shadow color | Validation error |
|
|
1503
|
+
| Mix `target` references a shadow color | Validation error |
|
|
1504
|
+
| Mix `value` outside 0–100 | Clamp silently |
|
|
1505
|
+
| Circular references involving mix colors | Validation error |
|
|
1506
|
+
| Contrast target physically unreachable | `console.warn` (deduped per `(name, scheme, target)`); closest passing variant returned |
|
|
1390
1507
|
|
|
1391
1508
|
---
|
|
1392
1509
|
|
|
@@ -1412,19 +1529,19 @@ import {
|
|
|
1412
1529
|
} from '@tenphi/glaze';
|
|
1413
1530
|
```
|
|
1414
1531
|
|
|
1415
|
-
| Function
|
|
1416
|
-
|
|
1417
|
-
| `okhslToLinearSrgb(h, s, l)`
|
|
1418
|
-
| `okhslToSrgb(h, s, l)`
|
|
1419
|
-
| `okhslToOklab([h, s, l])`
|
|
1420
|
-
| `oklabToOkhsl([L, a, b])`
|
|
1421
|
-
| `srgbToOkhsl([r, g, b])`
|
|
1422
|
-
| `hslToSrgb(h, s, l)`
|
|
1423
|
-
| `parseHex(hex)`
|
|
1424
|
-
| `parseHexAlpha(hex)`
|
|
1425
|
-
| `relativeLuminanceFromLinearRgb(rgb)` | WCAG relative luminance from linear sRGB.
|
|
1426
|
-
| `contrastRatioFromLuminance(yA, yB)`
|
|
1427
|
-
| `gamutClampedLuminance(linearRgb)`
|
|
1532
|
+
| Function | Description |
|
|
1533
|
+
| ------------------------------------- | ------------------------------------------------------------------------ |
|
|
1534
|
+
| `okhslToLinearSrgb(h, s, l)` | OKHSL (h: 0–360, s/l: 0–1) → linear sRGB tuple. |
|
|
1535
|
+
| `okhslToSrgb(h, s, l)` | OKHSL → gamma-encoded sRGB tuple (0–1 per channel). |
|
|
1536
|
+
| `okhslToOklab([h, s, l])` | OKHSL → OKLab `[L, a, b]`. |
|
|
1537
|
+
| `oklabToOkhsl([L, a, b])` | OKLab → OKHSL. |
|
|
1538
|
+
| `srgbToOkhsl([r, g, b])` | Gamma sRGB (0–1) → OKHSL. |
|
|
1539
|
+
| `hslToSrgb(h, s, l)` | CSS HSL → sRGB tuple. |
|
|
1540
|
+
| `parseHex(hex)` | Parse `#rgb` / `#rrggbb` to sRGB tuple. Returns `null` on invalid input. |
|
|
1541
|
+
| `parseHexAlpha(hex)` | Parse `#rgb` / `#rrggbb` / `#rrggbbaa`; returns `[r, g, b, a?]`. |
|
|
1542
|
+
| `relativeLuminanceFromLinearRgb(rgb)` | WCAG relative luminance from linear sRGB. |
|
|
1543
|
+
| `contrastRatioFromLuminance(yA, yB)` | WCAG contrast ratio from two luminances. |
|
|
1544
|
+
| `gamutClampedLuminance(linearRgb)` | Relative luminance with channel clamping for out-of-gamut colors. |
|
|
1428
1545
|
|
|
1429
1546
|
### Format writers
|
|
1430
1547
|
|
|
@@ -1432,8 +1549,8 @@ import {
|
|
|
1432
1549
|
import { formatOkhsl, formatRgb, formatHsl, formatOklch } from '@tenphi/glaze';
|
|
1433
1550
|
|
|
1434
1551
|
formatOkhsl(280, 60, 95); // 'okhsl(280 60% 95%)'
|
|
1435
|
-
formatRgb(280, 60, 95);
|
|
1436
|
-
formatHsl(280, 60, 95);
|
|
1552
|
+
formatRgb(280, 60, 95); // 'rgb(244 240 250)'
|
|
1553
|
+
formatHsl(280, 60, 95); // 'hsl(280 60% 95%)'
|
|
1437
1554
|
formatOklch(280, 60, 95); // 'oklch(0.95 ... 280)'
|
|
1438
1555
|
```
|
|
1439
1556
|
|
|
@@ -1454,17 +1571,19 @@ import {
|
|
|
1454
1571
|
} from '@tenphi/glaze';
|
|
1455
1572
|
```
|
|
1456
1573
|
|
|
1457
|
-
| Function
|
|
1458
|
-
|
|
1459
|
-
| `toTone(l, eps?)`
|
|
1460
|
-
| `fromTone(t, eps?)`
|
|
1461
|
-
| `toneFromY(y, eps?)` / `yFromTone(t, eps?)` | Same transfer in luminance space (0–1).
|
|
1462
|
-
| `okhstToOkhsl({ h, s, t })`
|
|
1463
|
-
| `okhslToOkhst({ h, s, l })`
|
|
1464
|
-
| `variantToOkhsl(variant)`
|
|
1465
|
-
| `REF_EPS`
|
|
1574
|
+
| Function | Description |
|
|
1575
|
+
| ------------------------------------------- | ------------------------------------------------------------------------- |
|
|
1576
|
+
| `toTone(l, eps?)` | OKHSL lightness (0–1) → tone (0–100). Defaults to `REF_EPS`. |
|
|
1577
|
+
| `fromTone(t, eps?)` | Tone (0–100) → OKHSL lightness (0–1). Inverse of `toTone`. |
|
|
1578
|
+
| `toneFromY(y, eps?)` / `yFromTone(t, eps?)` | Same transfer in luminance space (0–1). |
|
|
1579
|
+
| `okhstToOkhsl({ h, s, t })` | OKHST → OKHSL (`{ h, s, l }`). |
|
|
1580
|
+
| `okhslToOkhst({ h, s, l })` | OKHSL → OKHST (`{ h, s, t }`). |
|
|
1581
|
+
| `variantToOkhsl(variant)` | `ResolvedColorVariant` (stores `t`) → `{ h, s, l, alpha }` for rendering. |
|
|
1582
|
+
| `REF_EPS` | Reference epsilon (`0.05`) for the canonical tone axis. |
|
|
1466
1583
|
|
|
1467
|
-
`ResolvedColorVariant`
|
|
1584
|
+
`ResolvedColorVariant` stores `{ h, s, t, alpha }` (tone, not lightness). Use
|
|
1585
|
+
`variantToOkhsl(variant).l` to recover OKHSL lightness. See
|
|
1586
|
+
[OKHST in Glaze](okhst.md) for the model.
|
|
1468
1587
|
|
|
1469
1588
|
### Contrast solver
|
|
1470
1589
|
|
|
@@ -1478,29 +1597,29 @@ import {
|
|
|
1478
1597
|
} from '@tenphi/glaze';
|
|
1479
1598
|
```
|
|
1480
1599
|
|
|
1481
|
-
| Function
|
|
1482
|
-
|
|
1483
|
-
| `findToneForContrast(opts)`
|
|
1484
|
-
| `findValueForMixContrast(opts)`
|
|
1600
|
+
| Function | Description |
|
|
1601
|
+
| ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1602
|
+
| `findToneForContrast(opts)` | Binary-search for the tone (0–1) that meets a contrast floor (WCAG or APCA) against a base color. Returns `{ tone, contrast, met, branch, flipped? }`. |
|
|
1603
|
+
| `findValueForMixContrast(opts)` | Same, but searches for a mix `value` (0–1) that meets a contrast floor between a base and a target. |
|
|
1485
1604
|
| `resolveContrastForMode(spec, isHC, polarity?, outerExplicitHC?)` | Resolves a `ContrastSpec` to `{ metric: 'wcag' \| 'apca', target }` for the requested mode (picks the normal or HC entry of any pair). In HC, applies the metric's auto-enhancement unless `outerExplicitHC` is set or the inner metric pair carries an explicit HC value: APCA +15 Lc (clamped to 106); WCAG AA → AAA / AA-large → AAA-large (AAA-family and bare numbers unchanged). |
|
|
1486
|
-
| `resolveMinContrast(value)`
|
|
1487
|
-
| `apcaContrast(yText, yBg)`
|
|
1605
|
+
| `resolveMinContrast(value)` | Resolves a `MinContrast` (WCAG preset or number) to a numeric ratio. |
|
|
1606
|
+
| `apcaContrast(yText, yBg)` | APCA Lc magnitude (0–106) for two relative luminances. |
|
|
1488
1607
|
|
|
1489
1608
|
Exported constants: `APCA_PRESETS`, `APCA_HC_ENHANCEMENT` (`15`, the Enhanced Level delta), `APCA_MAX_LC` (`106`).
|
|
1490
1609
|
|
|
1491
1610
|
`findToneForContrast` options:
|
|
1492
1611
|
|
|
1493
|
-
| Option
|
|
1494
|
-
|
|
1495
|
-
| `hue`
|
|
1496
|
-
| `saturation`
|
|
1497
|
-
| `preferredTone`
|
|
1498
|
-
| `baseLinearRgb`
|
|
1499
|
-
| `contrast`
|
|
1500
|
-
| `toneRange`
|
|
1501
|
-
| `epsilon`
|
|
1502
|
-
| `maxIterations`
|
|
1503
|
-
| `initialDirection` | higher-contrast side | Direction to search first (`'lighter'` or `'darker'`).
|
|
1504
|
-
| `flip`
|
|
1612
|
+
| Option | Default | Description |
|
|
1613
|
+
| ------------------ | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
1614
|
+
| `hue` | — | Candidate hue (0–360). |
|
|
1615
|
+
| `saturation` | — | Candidate saturation (0–1). |
|
|
1616
|
+
| `preferredTone` | — | Preferred candidate tone (0–1). Kept if it already meets the target. |
|
|
1617
|
+
| `baseLinearRgb` | — | Base color as linear sRGB tuple. |
|
|
1618
|
+
| `contrast` | — | `ResolvedContrast` (`{ metric, target }`). |
|
|
1619
|
+
| `toneRange` | `[0, 1]` | Search bounds in tone. |
|
|
1620
|
+
| `epsilon` | `1e-4` | Convergence threshold. |
|
|
1621
|
+
| `maxIterations` | `18` | Max binary-search iterations per branch. |
|
|
1622
|
+
| `initialDirection` | higher-contrast side | Direction to search first (`'lighter'` or `'darker'`). |
|
|
1623
|
+
| `flip` | `false` | When `true`, try the opposite direction if the initial one doesn't meet the target. When `false`, only the initial direction is searched — unmet contrasts pin the result to that direction's extreme. |
|
|
1505
1624
|
|
|
1506
1625
|
Result: `{ tone, contrast, met, branch: 'lighter' | 'darker' | 'preferred', flipped? }`. `flipped: true` indicates the initial direction failed and the opposite direction satisfied the target.
|