@tenphi/glaze 0.0.0-snapshot.c3d484c → 0.0.0-snapshot.c61bbdf
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/AGENTS.md +42 -16
- package/README.md +41 -22
- package/dist/index.cjs +311 -139
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +122 -43
- package/dist/index.d.mts +122 -43
- package/dist/index.mjs +311 -139
- package/dist/index.mjs.map +1 -1
- package/docs/contrast.mdx +702 -0
- package/docs/exporting.mdx +624 -0
- package/docs/getting-started.mdx +486 -0
- package/docs/methodology.mdx +973 -0
- package/docs/migration.md +212 -285
- package/docs/mixes.mdx +268 -0
- package/docs/okhst.mdx +596 -0
- package/docs/reference/color-definitions.md +498 -0
- package/docs/reference/color-tokens.md +390 -0
- package/docs/reference/configuration.md +437 -0
- package/docs/reference/contrast.md +276 -0
- package/docs/reference/index.md +218 -0
- package/docs/reference/output.md +594 -0
- package/docs/reference/palette.md +387 -0
- package/docs/reference/theme.md +326 -0
- package/docs/reference/utilities.md +499 -0
- package/docs/schemes.mdx +546 -0
- package/docs/shadows-and-glows.mdx +509 -0
- package/package.json +9 -1
- package/docs/api.md +0 -2021
- package/docs/methodology.md +0 -686
- package/docs/okhst.md +0 -207
package/docs/mixes.mdx
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Mixes and transparency
|
|
3
|
+
description: Blend two colors into a solid tint or a translucent wash, optionally solved for contrast.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
import Example from '../docs-site/components/Example.astro';
|
|
7
|
+
import MixRamp from '../docs-site/components/mixes/MixRamp.tsx';
|
|
8
|
+
import TransparentMix from '../docs-site/components/mixes/TransparentMix.tsx';
|
|
9
|
+
import ContrastMix from '../docs-site/components/mixes/ContrastMix.tsx';
|
|
10
|
+
|
|
11
|
+
export const washTheme = {
|
|
12
|
+
hue: 280,
|
|
13
|
+
saturation: 80,
|
|
14
|
+
colors: {
|
|
15
|
+
surface: { tone: 97 },
|
|
16
|
+
accent: { tone: 45 },
|
|
17
|
+
'wash-sm': { type: 'mix', base: 'surface', target: 'accent', value: 6, blend: 'transparent' },
|
|
18
|
+
'wash-md': { type: 'mix', base: 'surface', target: 'accent', value: 15, blend: 'transparent' },
|
|
19
|
+
'wash-lg': { type: 'mix', base: 'surface', target: 'accent', value: 30, blend: 'transparent' },
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const contrastTheme = {
|
|
24
|
+
hue: 280,
|
|
25
|
+
saturation: 80,
|
|
26
|
+
colors: {
|
|
27
|
+
surface: { tone: 95 },
|
|
28
|
+
accent: { tone: 30 },
|
|
29
|
+
'accent-border': { type: 'mix', base: 'surface', target: 'accent', value: 20, contrast: 3 },
|
|
30
|
+
'accent-wash': { type: 'mix', base: 'surface', target: 'accent', value: 4, blend: 'transparent', contrast: 1.25 },
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
A mix color blends a `base` color toward a `target` color by `value`, from
|
|
35
|
+
`0` (pure base) to `100` (pure target). An **opaque** mix produces a solid
|
|
36
|
+
color; a **transparent** mix produces the target with an opacity, for the
|
|
37
|
+
browser to composite over the base. Both read `base` and `target` per scheme,
|
|
38
|
+
so they adapt to light, dark, and high contrast automatically.
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { glaze } from '@tenphi/glaze';
|
|
42
|
+
|
|
43
|
+
const theme = glaze(280, 80);
|
|
44
|
+
|
|
45
|
+
theme.colors({
|
|
46
|
+
surface: { tone: 95 },
|
|
47
|
+
accent: { tone: 30 },
|
|
48
|
+
|
|
49
|
+
// A solid tint: 30% of the way from surface to accent.
|
|
50
|
+
'accent-tint': {
|
|
51
|
+
type: 'mix',
|
|
52
|
+
base: 'surface',
|
|
53
|
+
target: 'accent',
|
|
54
|
+
value: 30,
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
// A translucent wash: accent at 8% opacity, composited over surface.
|
|
58
|
+
'accent-hover': {
|
|
59
|
+
type: 'mix',
|
|
60
|
+
base: 'surface',
|
|
61
|
+
target: 'accent',
|
|
62
|
+
value: 8,
|
|
63
|
+
blend: 'transparent',
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Every field is listed in [Color definitions](./reference/color-definitions.md#mixcolordef).
|
|
69
|
+
|
|
70
|
+
## Opaque mix
|
|
71
|
+
|
|
72
|
+
An opaque mix (the default, `blend: 'opaque'`) interpolates between the
|
|
73
|
+
resolved base and target and emits a solid color with alpha 1. `value: 0` is
|
|
74
|
+
the base, `value: 100` is the target. Because base and target are resolved for
|
|
75
|
+
each scheme first, the mix follows them: a tint of the accent over the surface
|
|
76
|
+
in light mode is a tint of the dark-mode accent over the dark-mode surface in
|
|
77
|
+
dark mode.
|
|
78
|
+
|
|
79
|
+
### Blend space
|
|
80
|
+
|
|
81
|
+
`space` picks where an opaque mix interpolates:
|
|
82
|
+
|
|
83
|
+
| `space` | Behavior | Best for |
|
|
84
|
+
| --- | --- | --- |
|
|
85
|
+
| `'okhsl'` (default) | Perceptually uniform OKHSL interpolation: hue, saturation, and lightness each move evenly. | Design token derivation. |
|
|
86
|
+
| `'srgb'` | Linear-light sRGB channel interpolation, like CSS `color-mix(in srgb-linear, …)`. | Physical light mixing. |
|
|
87
|
+
|
|
88
|
+
The same two colors, mixed at every tenth of the way in each space:
|
|
89
|
+
|
|
90
|
+
<MixRamp
|
|
91
|
+
hue={260}
|
|
92
|
+
saturation={90}
|
|
93
|
+
colors={{
|
|
94
|
+
blue: { tone: 40, mode: 'static' },
|
|
95
|
+
yellow: { tone: 90, hue: 95, mode: 'static' },
|
|
96
|
+
}}
|
|
97
|
+
base="blue"
|
|
98
|
+
target="yellow"
|
|
99
|
+
caption="Blue (hue 260, tone 40) toward yellow (hue 95, tone 90). Top number: value. Bottom number: the result's tone. OKHSL walks the hue around the wheel through cyan and green; linear sRGB passes through desaturated colors."
|
|
100
|
+
/>
|
|
101
|
+
|
|
102
|
+
<MixRamp
|
|
103
|
+
hue={0}
|
|
104
|
+
saturation={0}
|
|
105
|
+
colors={{
|
|
106
|
+
black: { tone: 0, mode: 'static' },
|
|
107
|
+
white: { tone: 100, mode: 'static' },
|
|
108
|
+
}}
|
|
109
|
+
base="black"
|
|
110
|
+
target="white"
|
|
111
|
+
caption="Black toward white. OKHSL spaces lightness evenly; linear light gets bright quickly, so most of the ramp sits at high tones."
|
|
112
|
+
/>
|
|
113
|
+
|
|
114
|
+
### Achromatic colors
|
|
115
|
+
|
|
116
|
+
When one side of an `okhsl` mix has a saturation of zero (a color defined with
|
|
117
|
+
`saturation: 0`, such as a pure white, black, or gray), its hue carries no
|
|
118
|
+
information, so the mix takes the hue of the color that has saturation. This
|
|
119
|
+
matches the "missing component" behavior of CSS `color-mix()`: a mix from
|
|
120
|
+
such a white toward an accent stays in the accent's hue the whole way instead
|
|
121
|
+
of sweeping through other hues.
|
|
122
|
+
|
|
123
|
+
Only exactly zero counts. A white or black that keeps any saturation, such as
|
|
124
|
+
`{ tone: 100 }` in a saturated theme, still carries its hue, and a mix toward
|
|
125
|
+
an accent of another hue sweeps between the two. A mix between two
|
|
126
|
+
zero-saturation colors stays gray in either space; the spaces differ only in
|
|
127
|
+
how lightness is spread along the ramp (see black toward white above).
|
|
128
|
+
|
|
129
|
+
<MixRamp
|
|
130
|
+
hue={280}
|
|
131
|
+
saturation={80}
|
|
132
|
+
colors={{
|
|
133
|
+
white: { tone: 100, saturation: 0, mode: 'static' },
|
|
134
|
+
accent: { tone: 40, mode: 'static' },
|
|
135
|
+
}}
|
|
136
|
+
base="white"
|
|
137
|
+
target="accent"
|
|
138
|
+
spaces={['okhsl']}
|
|
139
|
+
caption="White toward a hue-280 accent in OKHSL. White has no hue, so every step uses the accent's hue while saturation and lightness move."
|
|
140
|
+
/>
|
|
141
|
+
|
|
142
|
+
## Transparent mix
|
|
143
|
+
|
|
144
|
+
With `blend: 'transparent'`, the output is the **target** color (its resolved
|
|
145
|
+
hue, saturation, and tone) with `alpha = value / 100`. Nothing is blended in
|
|
146
|
+
Glaze; the browser composites it over whatever is underneath, which should be
|
|
147
|
+
the `base`.
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
theme.colors({
|
|
151
|
+
surface: { tone: 95 },
|
|
152
|
+
black: { tone: 0, saturation: 0 },
|
|
153
|
+
hover: {
|
|
154
|
+
type: 'mix',
|
|
155
|
+
base: 'surface',
|
|
156
|
+
target: 'black',
|
|
157
|
+
value: 8,
|
|
158
|
+
blend: 'transparent',
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
// hover → the resolved `black` with alpha 0.08
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
The target is resolved like any color first. Here `black` uses the default
|
|
165
|
+
`mode: 'auto'`, so it inverts to a near-white in dark schemes and the same
|
|
166
|
+
`hover` lightens the dark surface instead of darkening it.
|
|
167
|
+
|
|
168
|
+
`space` is ignored for transparent mixes. Browsers composite `source-over` on
|
|
169
|
+
gamma-encoded sRGB: 50% black over white paints `#808080`, not the `#bcbcbc`
|
|
170
|
+
linear light would give. Glaze's contrast solver for transparent mixes models
|
|
171
|
+
exactly that, so a solved opacity renders at the contrast it was solved for.
|
|
172
|
+
|
|
173
|
+
<Example {...washTheme} visual="none" />
|
|
174
|
+
|
|
175
|
+
<TransparentMix
|
|
176
|
+
{...washTheme}
|
|
177
|
+
base="surface"
|
|
178
|
+
show={['wash-sm', 'wash-md', 'wash-lg']}
|
|
179
|
+
caption="Accent washes over the surface in your current scheme. The big number is the tone step the composite lands; the opacity is value / 100 in every scheme, so the step changes when the surface does."
|
|
180
|
+
/>
|
|
181
|
+
|
|
182
|
+
A transparent mix keeps the opacity you author. That makes it the right tool
|
|
183
|
+
for a brand-colored wash, but it does not land the same visible step on every
|
|
184
|
+
surface. For an overlay that does, use a
|
|
185
|
+
[`shadow` or `glow`](./shadows-and-glows.mdx), whose opacity is solved per
|
|
186
|
+
surface.
|
|
187
|
+
|
|
188
|
+
## Contrast on mixes
|
|
189
|
+
|
|
190
|
+
Mixes take the same `contrast` field as regular colors, with a WCAG ratio or
|
|
191
|
+
preset, or an APCA target (see [Contrast solving](./contrast.mdx)). The floor
|
|
192
|
+
is measured between the mix and its `base`. The solver keeps your `value` when
|
|
193
|
+
it already meets the floor; otherwise it moves the mix ratio (opaque) or the
|
|
194
|
+
opacity (transparent) toward the target until it does, staying as close to
|
|
195
|
+
your `value` as it can.
|
|
196
|
+
|
|
197
|
+
<Example {...contrastTheme} visual="none" />
|
|
198
|
+
|
|
199
|
+
<ContrastMix
|
|
200
|
+
{...contrastTheme}
|
|
201
|
+
show={['accent-border', 'accent-wash']}
|
|
202
|
+
caption="Each mix as authored (no floor) and with its floor, over the surface in your current scheme. The big number is the WCAG ratio against the surface; below it, the value or opacity the mix ended up with."
|
|
203
|
+
/>
|
|
204
|
+
|
|
205
|
+
- Both `value` and `contrast` accept `[normal, highContrast]` pairs, and
|
|
206
|
+
`value` follows a manual contrast level like other pairs. Contrast presets
|
|
207
|
+
escalate in high contrast as they do for regular colors
|
|
208
|
+
([high contrast](./contrast.mdx#high-contrast)).
|
|
209
|
+
- For APCA floors, `role` sets the polarity of the mix against its `base`,
|
|
210
|
+
exactly as on a regular color ([roles](./contrast.mdx#roles-and-apca-polarity)).
|
|
211
|
+
- When even the pure target cannot meet the floor, the mix settles at the
|
|
212
|
+
pure target for any `value` below 100. A `value` of 100 (including a
|
|
213
|
+
high-contrast entry of 100) has no room above, so it falls back to
|
|
214
|
+
`value: 0`: the base itself, and a transparent mix vanishes. Keep `value`
|
|
215
|
+
below 100 when you set a floor. Unlike a regular color, a mix does not warn
|
|
216
|
+
when its floor is unmet.
|
|
217
|
+
|
|
218
|
+
## Mix chaining
|
|
219
|
+
|
|
220
|
+
A mix can use another mix as its `base` or `target`:
|
|
221
|
+
|
|
222
|
+
<Example
|
|
223
|
+
hue={0}
|
|
224
|
+
saturation={0}
|
|
225
|
+
colors={{
|
|
226
|
+
white: { tone: 100, saturation: 0 },
|
|
227
|
+
black: { tone: 0, saturation: 0 },
|
|
228
|
+
gray: {
|
|
229
|
+
type: 'mix',
|
|
230
|
+
base: 'white',
|
|
231
|
+
target: 'black',
|
|
232
|
+
value: 50,
|
|
233
|
+
space: 'srgb',
|
|
234
|
+
},
|
|
235
|
+
'gray-light': {
|
|
236
|
+
type: 'mix',
|
|
237
|
+
base: 'white',
|
|
238
|
+
target: 'gray',
|
|
239
|
+
value: 50,
|
|
240
|
+
space: 'srgb',
|
|
241
|
+
},
|
|
242
|
+
}}
|
|
243
|
+
scheme="light"
|
|
244
|
+
caption="Light scheme, where the auto black sits at the light tone window's floor rather than tone 0. gray is halfway from white to black in linear sRGB; gray-light is halfway from white to gray. In dark schemes white and black invert, so the whole chain mirrors."
|
|
245
|
+
/>
|
|
246
|
+
|
|
247
|
+
A mix **cannot** reference a `shadow` or `glow` color, as `base` or as
|
|
248
|
+
`target`; Glaze throws when the theme resolves. The same restriction applies to
|
|
249
|
+
a regular color's `base`.
|
|
250
|
+
|
|
251
|
+
Other translucent colors are accepted but lose their alpha. A transparent mix
|
|
252
|
+
or a color with `opacity`, used as a mix's `base` or `target` (or as a shadow's
|
|
253
|
+
`bg`), counts as its opaque color: a mix toward a 10% accent wash comes out the
|
|
254
|
+
same as a mix toward the accent. Chain off opaque colors.
|
|
255
|
+
|
|
256
|
+
## Mix, shadow, glow, or opacity?
|
|
257
|
+
|
|
258
|
+
| You want | Use |
|
|
259
|
+
| --- | --- |
|
|
260
|
+
| The same visible darkening or lightening on any surface: state layers, elevation, drop shadows | [`shadow` / `glow`](./shadows-and-glows.mdx) |
|
|
261
|
+
| A solid color part of the way from one color to another | Opaque mix |
|
|
262
|
+
| A translucent color over a known base, such as a brand-colored wash | Transparent mix |
|
|
263
|
+
| A translucent color with a contrast floor against its base | Transparent mix with `contrast` |
|
|
264
|
+
| One fixed alpha everywhere, such as a 50% scrim | A regular color with [`opacity`](./shadows-and-glows.mdx#fixed-opacity) |
|
|
265
|
+
|
|
266
|
+
Shadows and glows solve opacity for a tone step, transparent mixes keep (or
|
|
267
|
+
solve, with `contrast`) an opacity against their base, and `opacity` is never
|
|
268
|
+
solved.
|