@tenphi/glaze 2.0.2 → 3.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.
@@ -0,0 +1,519 @@
1
+ ---
2
+ title: Shadows and glows
3
+ description: Translucent overlays that land an exact tone step on any surface with room for it, in every scheme.
4
+ ---
5
+
6
+ import Example from '../docs-site/components/Example.astro';
7
+ import StepCompare from '../docs-site/components/shadows-and-glows/StepCompare.tsx';
8
+ import ObjectInfluence from '../docs-site/components/shadows-and-glows/ObjectInfluence.tsx';
9
+ import StateLayers from '../docs-site/components/shadows-and-glows/StateLayers.tsx';
10
+ import CardShadows from '../docs-site/components/shadows-and-glows/CardShadows.tsx';
11
+ import ElevationLadder from '../docs-site/components/shadows-and-glows/ElevationLadder.tsx';
12
+ import TintedPigment from '../docs-site/components/shadows-and-glows/TintedPigment.tsx';
13
+ import Unreachable from '../docs-site/components/shadows-and-glows/Unreachable.tsx';
14
+
15
+ export const stateTheme = {
16
+ hue: 280,
17
+ saturation: 80,
18
+ colors: {
19
+ surface: { tone: 100, saturation: 0.1 },
20
+ 'surface-hover': { type: 'shadow', bg: 'surface', intensity: 4 },
21
+ 'surface-pressed': { type: 'shadow', bg: 'surface', intensity: [8, 12] },
22
+ 'accent-surface': { tone: 52, mode: 'fixed' },
23
+ 'accent-surface-hover': { type: 'shadow', bg: 'accent-surface', intensity: 4 },
24
+ 'accent-surface-pressed': { type: 'shadow', bg: 'accent-surface', intensity: [8, 12] },
25
+ 'accent-surface-highlight': { type: 'glow', bg: 'accent-surface', intensity: 5 },
26
+ 'inverse-surface': { tone: 18, saturation: 0.2, mode: 'fixed' },
27
+ 'inverse-surface-hover': { type: 'glow', bg: 'inverse-surface', intensity: 4 },
28
+ 'inverse-surface-pressed': { type: 'glow', bg: 'inverse-surface', intensity: [8, 12] },
29
+ },
30
+ };
31
+
32
+ export const cardTheme = {
33
+ hue: 280,
34
+ saturation: 80,
35
+ colors: {
36
+ surface: { tone: 100, saturation: 0.1 },
37
+ card: { base: 'surface', tone: '-3', saturation: 0.15 },
38
+ 'shadow-sm': { type: 'shadow', bg: 'surface', fg: 'card', intensity: 3 },
39
+ 'shadow-md': { type: 'shadow', bg: 'surface', fg: 'card', intensity: [6, 9] },
40
+ 'shadow-lg': { type: 'shadow', bg: 'surface', fg: 'card', intensity: [9, 14] },
41
+ },
42
+ };
43
+
44
+ export const elevationTheme = {
45
+ hue: 280,
46
+ saturation: 80,
47
+ colors: {
48
+ surface: { tone: 92, saturation: 0.1 },
49
+ 'elevation-1': { type: 'glow', bg: 'surface', intensity: 3 },
50
+ 'elevation-2': { type: 'glow', bg: 'surface', intensity: 5 },
51
+ 'elevation-3': { type: 'glow', bg: 'surface', intensity: 7 },
52
+ },
53
+ };
54
+
55
+ `shadow` and `glow` are translucent overlays. Each emits a pigment (black for a
56
+ shadow, white for a glow) with an opacity Glaze solves so that, once the
57
+ browser composites it over `bg`, the surface lands **exactly `intensity`
58
+ tones** darker (`shadow`) or lighter (`glow`), scaled by the object when you
59
+ name one. The step is the same on every surface and in every scheme where the
60
+ surface has room for it: light, mid, dark, and chromatic.
61
+
62
+ ```ts
63
+ import { glaze } from '@tenphi/glaze';
64
+
65
+ const theme = glaze(280, 80);
66
+
67
+ theme.colors({
68
+ surface: { tone: 97 },
69
+ card: { tone: 100 },
70
+ accent: { tone: 45 },
71
+
72
+ // Object-aware drop shadow: `fg` is the element casting it.
73
+ 'card-shadow': {
74
+ type: 'shadow',
75
+ bg: 'surface',
76
+ fg: 'card',
77
+ intensity: 8,
78
+ },
79
+
80
+ // No `fg`: an exact shade, for state layers and scrims.
81
+ 'surface-hover': { type: 'shadow', bg: 'surface', intensity: 5 },
82
+ 'accent-pressed': { type: 'shadow', bg: 'accent', intensity: [8, 14] },
83
+
84
+ // The mirror: dark-mode elevation, edge highlights, lightening states.
85
+ 'accent-highlight': { type: 'glow', bg: 'accent', intensity: 4 },
86
+ });
87
+ ```
88
+
89
+ Resolving this sample logs two warnings. In dark high contrast the `auto`
90
+ surface sits at tone 3, so `card-shadow` and `surface-hover` can't darken it by
91
+ their full step: they pin to black and warn, even when you don't emit
92
+ high-contrast tokens. See [Unreachable steps](#unreachable-steps).
93
+
94
+ Every field is listed in
95
+ [Color definitions](./reference/color-definitions.md#shadowcolordef--glowcolordef);
96
+ this page explains how the overlays behave.
97
+
98
+ ## The same step on every surface
99
+
100
+ `intensity` is a step on tone, the WCAG-shaped axis Glaze uses everywhere
101
+ (see [Tone and OKHST](./okhst.mdx)). For neutrals a tone step is a fixed WCAG
102
+ ratio, so 6 tones is about 1.20:1 wherever it lands, and the same number reads
103
+ the same on any surface. APCA cannot express steps this small, so overlays
104
+ are always measured in tone.
105
+
106
+ A fixed opacity cannot do this. Below, each surface carries a `shadow` with
107
+ `intensity: 6` (top patch) and a black layer at a fixed 8% (bottom patch); the
108
+ big number is the tone step each one actually paints.
109
+
110
+ <StepCompare
111
+ kind="shadow"
112
+ tones={[100, 70, 50, 25, 12]}
113
+ accentTone={50}
114
+ intensity={6}
115
+ fixedAlpha={0.08}
116
+ caption="Neutral surfaces at the listed tones, plus a hue-280 accent at tone 50. Top: shadow, intensity 6. Bottom: black at a fixed 8%. Big numbers are the measured tone step of the composite against its surface."
117
+ />
118
+
119
+ The solved shadow lands 6.0 tones everywhere, at an opacity that climbs as the
120
+ surface darkens. The fixed layer darkens white by almost 6 tones and a tone-12
121
+ surface by little more than one. Glows mirror this:
122
+
123
+ <StepCompare
124
+ kind="glow"
125
+ tones={[70, 50, 25, 12, 0]}
126
+ accentTone={50}
127
+ intensity={6}
128
+ fixedAlpha={0.08}
129
+ caption="Top: glow, intensity 6. Bottom: white at a fixed 8%. The fixed layer lifts every surface by a different amount; the solved glow lifts each one 6 tones."
130
+ />
131
+
132
+ ## How the opacity is solved
133
+
134
+ Browsers composite `source-over` on gamma-encoded sRGB: 50% black over white
135
+ paints `#808080`, not the `#bcbcbc` that mixing linear light would give. Glaze
136
+ models exactly that. The composite's luminance changes monotonically with
137
+ opacity, so a bisection finds the opacity that lands the step, on chromatic
138
+ surfaces too.
139
+
140
+ The required opacity depends heavily on the surface. A 6-tone shadow needs
141
+ α 0.081 on white, 0.107 on a tone-50 surface, and 0.477 on a tone-10 surface.
142
+
143
+ There is no opacity cap: when the step is reachable, it is exact. For a
144
+ lighter overlay, lower `intensity`.
145
+
146
+ The output is the pigment with its solved alpha. Shadows and glows appear in
147
+ every output method (`tokens()`, `tasty()`, `css()`, `json()`, and the rest)
148
+ alongside regular colors. The default pigment is neutral, so the values stay
149
+ clean:
150
+
151
+ ```text
152
+ rgb(0 0 0 / 0.0814) shadow, 6 tones on white
153
+ oklch(1 0 0 / 0.0953) glow, 6 tones on a tone-50 surface
154
+ ```
155
+
156
+ The alpha is only meaningful over the surface it was solved for. Paint the
157
+ token on top of `bg` (as a `box-shadow` color, a state-layer background, or a
158
+ pseudo-element over the surface).
159
+
160
+ ## Direction is fixed per type
161
+
162
+ A `shadow` darkens in light **and** dark schemes; a `glow` lightens in both.
163
+ Neither flips when the scheme does. Pick the type for the effect you want in
164
+ each scheme: for example, `shadow` for light-mode cards and `glow` for
165
+ dark-mode elevation. When one component needs a darkening layer in light mode
166
+ and a lightening one in dark mode, emit both tokens and wire each in the
167
+ scheme it belongs to. Both still resolve in all four schemes, so each one warns
168
+ wherever its surface has no room, including schemes where you don't use it
169
+ ([Unreachable steps](#unreachable-steps)).
170
+
171
+ `bg` and `fg` are read per scheme, so the solved opacity adapts on its own as
172
+ the surface moves in dark and high contrast. See
173
+ [Light, dark, and high contrast](./schemes.mdx) for how surfaces move between
174
+ schemes.
175
+
176
+ ## Object influence
177
+
178
+ `fg` is the object casting the shadow or emitting the glow, such as the card.
179
+ It scales the step the way bounce light does. With `fg`, the step is
180
+ `intensity × k`:
181
+
182
+ ```text
183
+ k = max(0, 1 + β · d · (t_bg − t_fg) / 100)
184
+ ```
185
+
186
+ `t_bg` and `t_fg` are the tones of the surface and the object, `d` is `+1` for
187
+ a `shadow` and `−1` for a `glow`, and `β` is `tuning.objectInfluence` (`0.5`
188
+ by default).
189
+
190
+ - An object at the surface's own tone gives exactly `intensity`, and so does
191
+ omitting `fg`.
192
+ - A darker object deepens its shadow; a lighter one softens it.
193
+ - For a glow it is the other way round: a lighter object glows stronger.
194
+ - `k` never drops below 0 and tops out at `1 + β` (a black object casting a
195
+ shadow on a white surface). `objectInfluence: 0` removes `fg`'s effect on
196
+ the step (`k = 1`); `fg` still feeds [`hueBleed`](#tinted-pigment).
197
+
198
+ Each card below casts a hard-edged `shadow` (offset, no blur) or emits a solid
199
+ `glow` ring, both with `intensity: 8` and `fg` pointed at the card:
200
+
201
+ <ObjectInfluence
202
+ intensity={8}
203
+ scenes={[
204
+ {
205
+ tone: 97,
206
+ label: 'Light surface · tone 97',
207
+ kinds: ['shadow'],
208
+ objects: [
209
+ { label: 'white card', tone: 100 },
210
+ { label: 'accent', tone: 45, chromatic: true },
211
+ { label: 'near-black', tone: 8 },
212
+ ],
213
+ },
214
+ {
215
+ tone: 12,
216
+ label: 'Dark surface · tone 12',
217
+ kinds: ['shadow', 'glow'],
218
+ objects: [
219
+ { label: 'raised card', tone: 20 },
220
+ { label: 'accent', tone: 55, chromatic: true },
221
+ ],
222
+ },
223
+ ]}
224
+ caption="Each label gives the object's tone, its factor k, and the step the composite lands (intensity 8, default β)."
225
+ />
226
+
227
+ The same numbers as a table:
228
+
229
+ | Surface | Object | `k` (`shadow`) | `shadow` step | `glow` step |
230
+ | --- | --- | --- | --- | --- |
231
+ | light 97 | white card 100 | 0.985 | 7.9 | — |
232
+ | light 97 | accent 45 | 1.26 | 10.1 | — |
233
+ | light 97 | near-black 8 | 1.445 | 11.6 | — |
234
+ | dark 12 | raised card 20 | 0.96 | 7.7 | 8.3 |
235
+ | dark 12 | accent 55 | 0.785 | 6.3 | 9.7 |
236
+
237
+ Point `fg` at the element that casts the shadow, never at text: `fg` is the
238
+ object, not a contrast reference. Like `bg`, it must not be another shadow or
239
+ glow.
240
+
241
+ ## State layers
242
+
243
+ Without `fg`, a shadow or glow is an exact shade or tint of its surface: the
244
+ right tool for hover and pressed states, which need the same visible change on
245
+ every fill.
246
+
247
+ <Example {...stateTheme} visual="none" />
248
+
249
+ <StateLayers
250
+ {...stateTheme}
251
+ rows={[
252
+ {
253
+ fill: 'surface',
254
+ label: 'Neutral fill',
255
+ layers: [
256
+ { name: 'surface-hover', label: 'Hover' },
257
+ { name: 'surface-pressed', label: 'Pressed' },
258
+ ],
259
+ },
260
+ {
261
+ fill: 'accent-surface',
262
+ label: 'Accent fill',
263
+ layers: [
264
+ { name: 'accent-surface-hover', label: 'Hover' },
265
+ { name: 'accent-surface-pressed', label: 'Pressed' },
266
+ { name: 'accent-surface-highlight', label: 'Highlight' },
267
+ ],
268
+ },
269
+ {
270
+ fill: 'inverse-surface',
271
+ label: 'Dark fill',
272
+ layers: [
273
+ { name: 'inverse-surface-hover', label: 'Hover' },
274
+ { name: 'inverse-surface-pressed', label: 'Pressed' },
275
+ ],
276
+ },
277
+ ]}
278
+ caption="Rest, hover, and pressed on three fills in your current scheme. Each number is the measured step of the layer over its fill."
279
+ />
280
+
281
+ The accent fill uses `mode: 'fixed'`, so it stays a mid tone in every scheme
282
+ and its layers land the same steps everywhere. The dark fill uses glows, so
283
+ its states lighten it. The neutral `surface` follows the scheme: in dark high
284
+ contrast it becomes black, and a shadow has nothing left to darken, so
285
+ `surface-hover` and `surface-pressed` stay fully transparent and warn (the
286
+ figure shows what they reached; see [Unreachable steps](#unreachable-steps)).
287
+ A glow on this surface fails the other way: it warns in the light schemes,
288
+ where the surface is white. No overlay fits a surface that spans the whole
289
+ tone range. For a state that darkens in light schemes and lightens in dark
290
+ ones, use a regular color relative to the surface, such as
291
+ `{ base: 'surface', tone: '-4' }`, which flips with the scheme.
292
+
293
+ ## Card drop shadows
294
+
295
+ For elevation in light schemes, define one shadow ladder with `bg` on the page
296
+ and `fg` on the card. Equal intensity steps read as evenly spaced on every
297
+ surface. Use [high-contrast pairs](#high-contrast) for shadows that should
298
+ deepen in high contrast.
299
+
300
+ <Example {...cardTheme} visual="none" />
301
+
302
+ <CardShadows
303
+ {...cardTheme}
304
+ page="surface"
305
+ card="card"
306
+ cards={[
307
+ { shadow: 'shadow-sm', geometry: '0 0.0625rem 0.25rem 0' },
308
+ { shadow: 'shadow-md', geometry: '0 0.375rem 1rem -0.125rem' },
309
+ { shadow: 'shadow-lg', geometry: '0 1rem 2.5rem -0.5rem' },
310
+ ]}
311
+ caption="Each card's box-shadow is colored by its token in your current scheme. The step is what the shadow lands where it is fully opaque."
312
+ />
313
+
314
+ A blurred `box-shadow` fades out from the token's color, so the step is the
315
+ darkest the shadow gets, right at the card's edge. In dark schemes the page
316
+ sits near black and a dark card shadow has little room: the solved opacity
317
+ climbs, and in dark high contrast the page is black, so all three shadows
318
+ stay fully transparent and warn. For elevation in dark schemes, use glows on a
319
+ surface with room in every scheme, as in the next section.
320
+
321
+ ## Dark-mode elevation
322
+
323
+ Glows are the mirror for dark-mode elevation and edge highlights: each level
324
+ lifts the surface a fixed number of tones. A glow resolves in all four
325
+ schemes, so it needs room above its surface in each of them. This ladder
326
+ sits on a light-gray surface, so the same tokens also lift cards toward white
327
+ in the light scheme.
328
+
329
+ <Example {...elevationTheme} visual="none" />
330
+
331
+ <ElevationLadder
332
+ {...elevationTheme}
333
+ surface="surface"
334
+ levels={['elevation-1', 'elevation-2', 'elevation-3']}
335
+ schemes={['dark', 'light']}
336
+ caption="The same three glows in the dark and light schemes. On the dark surface a few percent of white lifts it 3, 5, and 7 tones; on the light surface the same steps take far more opacity."
337
+ />
338
+
339
+ A glow on a white light-mode surface has nothing to lighten in SDR, so Glaze
340
+ warns, even if you only use the token in dark mode. On a tone-100 surface it
341
+ leaves the glow transparent. A white written as `'#ffffff'` parses a hair
342
+ below tone 100, so its glow pins to opaque white instead, which paints the
343
+ same over it. Give glows a surface with room in every scheme, like the
344
+ tone-92 surface here.
345
+
346
+ ## Tinted pigment
347
+
348
+ The pigment is pure black or pure white by default, so the surface keeps its
349
+ own hue under the overlay and the output stays neutral. A pigment tinted in
350
+ the surface's hue ([Josh Comeau's shadow advice](https://www.joshwcomeau.com/css/designing-shadows))
351
+ is opt-in through tuning:
352
+
353
+ - **`reach`** sets where the pigment sits: the fraction of the way from the
354
+ surface's tone to black (shadow) or white (glow). `1` is pure black or
355
+ white, the most reach; lower values let a tint show at the cost of reach.
356
+ - **`tint`** is the pigment's saturation, in the surface's hue. It shows only
357
+ with `reach` below 1, since pure black and white have no hue. A neutral
358
+ surface has no hue to give, so there the tint takes the object's hue when
359
+ `hueBleed` is above 0, and the pigment stays neutral otherwise.
360
+ - **`hueBleed`** pulls the pigment's hue from the surface's hue toward the
361
+ object's (`fg`). It needs `fg`, a `tint`, and `reach` below 1 to show.
362
+ - **`objectInfluence`** is the `β` in [object influence](#object-influence).
363
+
364
+ ```ts
365
+ theme.colors({
366
+ 'shadow-tinted': {
367
+ type: 'shadow',
368
+ bg: 'surface',
369
+ intensity: 8,
370
+ tuning: { tint: 0.3, reach: 0.85 },
371
+ },
372
+ });
373
+
374
+ glaze.configure({
375
+ shadowTuning: { objectInfluence: 0.4 },
376
+ glowTuning: { objectInfluence: 0.6 },
377
+ });
378
+ ```
379
+
380
+ `shadowTuning` and `glowTuning` set the tuning globally (or per theme or token
381
+ through the config override); a per-color `tuning` merges over them field by
382
+ field. Both take these `LightingTuning` fields; defaults are in
383
+ [Color definitions](./reference/color-definitions.md#lightingtuning) and
384
+ [Configuration](./reference/configuration.md#glazeconfig).
385
+
386
+ <TintedPigment
387
+ hue={60}
388
+ saturation={70}
389
+ surfaceSaturation={0.3}
390
+ tones={[97, 50, 12]}
391
+ intensity={8}
392
+ object={{ hue: 200, tone: 50 }}
393
+ columns={[
394
+ { label: 'neutral (default)', tuning: {} },
395
+ { label: 'tint 0.3 · reach 0.85', tuning: { tint: 0.3, reach: 0.85 } },
396
+ {
397
+ label: '+ hueBleed 0.6',
398
+ tuning: { tint: 0.3, reach: 0.85, hueBleed: 0.6, objectInfluence: 0 },
399
+ withObject: true,
400
+ },
401
+ ]}
402
+ caption="An 8-tone shadow on warm surfaces. The dot is the pigment at full opacity. The hueBleed column points fg at a hue-200 object and sets objectInfluence to 0, so only the hue changes."
403
+ />
404
+
405
+ The step is exact in every column. On light and mid surfaces a tint barely
406
+ changes the composite; on dark surfaces it costs reach and opacity. On the
407
+ figure's tone-12 surface, black reaches about 12 tones and needs α 0.545 for
408
+ the 8-tone shadow; the `tint: 0.3, reach: 0.85` pigment reaches about 10 tones
409
+ and needs α 0.706.
410
+
411
+ ## Unreachable steps
412
+
413
+ Every surface has limited room: a shadow cannot go below black, and a glow
414
+ cannot go above white. When the step is out of reach (a glow on white, a deep
415
+ shadow on near-black), Glaze pins the overlay to the pigment's strongest
416
+ composite (full opacity, or fully transparent when the pigment cannot move the
417
+ surface at all) and emits a deduplicated `console.warn`. A 6-tone glow on a
418
+ tone-100 `auto` surface logs:
419
+
420
+ ```text
421
+ glaze: glow "surface-highlight" cannot reach a 6.0-tone step in light
422
+ scheme (reached 0.0 at full strength).
423
+ Lower the intensity, move the surface away from white, or raise a
424
+ tuned-down `reach`.
425
+ ```
426
+
427
+ <Unreachable
428
+ cases={[
429
+ { kind: 'glow', tone: 100, intensity: 6 },
430
+ { kind: 'glow', tone: 97, intensity: 8 },
431
+ { kind: 'shadow', tone: 8, intensity: 12 },
432
+ ]}
433
+ caption="The big number is the step reached; asked is the step requested. A glow on white stays transparent; the other two pin to full-opacity white and black."
434
+ />
435
+
436
+ Every shadow and glow resolves in all four schemes on every resolve, whatever
437
+ `modes` you emit and wherever you use the token, so the warning fires even for
438
+ a scheme you never show. Not using a token in a scheme only keeps the pinned
439
+ value off the screen.
440
+
441
+ To fix it, lower `intensity`, or give the overlay a surface with room in
442
+ every scheme. A tuned-down `reach` also costs room; raise it back toward 1.
443
+ Otherwise, expect the warning. Watch dark high contrast in particular: it
444
+ bypasses the tone window, so a page that is white in light mode becomes black
445
+ there and every shadow on it pins and warns. A tone-97 page sits at tone 3,
446
+ which leaves room for less than 3 tones.
447
+
448
+ On a display that can show brighter than white, a glow can also go past white:
449
+ see [HDR glows](./hdr.mdx).
450
+
451
+ ## High contrast
452
+
453
+ `intensity` accepts a `[normal, highContrast]` pair, like other numeric
454
+ fields:
455
+
456
+ ```ts
457
+ theme.colors({
458
+ 'card-shadow': {
459
+ type: 'shadow',
460
+ bg: 'surface',
461
+ fg: 'card',
462
+ intensity: [8, 14],
463
+ },
464
+ });
465
+ ```
466
+
467
+ With a manual contrast level, the pair is interpolated like every other pair.
468
+ See [high-contrast pairs and the contrast level](./reference/contrast.md#manual-contrast-level).
469
+
470
+ ## Fixed opacity
471
+
472
+ For one fixed-alpha color with no opacity solve, such as a modal scrim, set
473
+ `opacity` on a regular color:
474
+
475
+ ```ts
476
+ theme.colors({
477
+ scrim: { tone: 0, saturation: 0, mode: 'static', opacity: 0.5 },
478
+ });
479
+ // scrim → 'oklch(0 0 0 / 0.5)' in every scheme
480
+ ```
481
+
482
+ `opacity` is applied after the color resolves, so its tone still adapts like
483
+ any regular color. Without `mode: 'static'`, `tone: 0` is lifted by the light
484
+ tone window and inverted into a light overlay in dark mode. A fixed alpha
485
+ gives a different step on every surface (an 8% black layer darkens white by
486
+ about 6 tones and a tone-10 surface by about 1), which is what shadows and
487
+ glows exist to avoid. For a translucent color with a contrast floor, use a
488
+ [transparent mix](./mixes.mdx#transparent-mix).
489
+
490
+ ## Outside a theme
491
+
492
+ `glaze.shadow()` and `glaze.glow()` compute an overlay without a theme. `bg`
493
+ and `fg` accept any color value Glaze parses (hex, `rgb()`, `okhsl()`, value
494
+ objects, …):
495
+
496
+ ```ts
497
+ const shade = glaze.shadow({ bg: '#ffffff', intensity: 6 });
498
+ // → { h: 0, s: 0, t: 0, alpha: 0.0814… }
499
+
500
+ glaze.format(shade, 'rgb');
501
+ // → 'rgb(0 0 0 / 0.0814)'
502
+
503
+ const lift = glaze.glow({ bg: '#1e1e22', fg: '#3a3a44', intensity: 4 });
504
+ ```
505
+
506
+ Both accept an optional `tuning` that merges over the global `shadowTuning` /
507
+ `glowTuning`.
508
+
509
+ The full input table is in
510
+ [Utilities](./reference/utilities.md#glazeshadowinput--glazeglowinput).
511
+
512
+ ## Coming from 2.x
513
+
514
+ In Glaze 2, `intensity` went through a curve in OKHSL lightness, so the same
515
+ number darkened white, mid, and dark surfaces by very different amounts, and
516
+ `fg` was a contrast reference that gave a white card on a light page almost no
517
+ shadow. 3.0 makes `intensity` an exact tone step, makes `fg` the object, and
518
+ adds `glow`. Upgrade steps are in
519
+ [3.0 — shadows and glows](./migration.md#30--shadows-and-glows).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenphi/glaze",
3
- "version": "2.0.2",
3
+ "version": "3.0.0",
4
4
  "description": "OKHST-based color theme generator with WCAG contrast solving for light, dark, and high-contrast schemes",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -53,7 +53,8 @@
53
53
  "@changesets/changelog-github": "^0.5.2",
54
54
  "@changesets/cli": "^2.29.8",
55
55
  "@eslint/js": "^10.0.1",
56
- "@tenphi/cookbook": "^0.31.1",
56
+ "@tenphi/cookbook": "^0.32.0",
57
+ "@tenphi/tasty": "^3.17.0",
57
58
  "astro": "^7.3.3",
58
59
  "eslint": "^10.0.0",
59
60
  "eslint-config-prettier": "^10.1.8",
@@ -63,7 +64,6 @@
63
64
  "tsdown": "^0.20.3",
64
65
  "typescript": "^5.9.3",
65
66
  "typescript-eslint": "^8.56.0",
66
- "vite": "^8.0.16",
67
67
  "vitest": "^4.0.18"
68
68
  },
69
69
  "scripts": {
@@ -71,13 +71,10 @@
71
71
  "docs": "astro dev",
72
72
  "docs:doctor": "cookbook doctor",
73
73
  "build:docs": "astro build",
74
- "build:site": "pnpm build:docs && GLAZE_SITE=true vite build playground",
75
- "playground": "vite playground",
76
- "build:playground": "vite build playground",
77
74
  "test": "vitest run",
78
75
  "test:watch": "vitest",
79
76
  "test:coverage": "vitest run --coverage",
80
- "typecheck": "tsc --noEmit",
77
+ "typecheck": "tsc --noEmit && tsc -p tsconfig.test.json",
81
78
  "lint": "eslint src",
82
79
  "lint:fix": "eslint src --fix",
83
80
  "format": "prettier --write \"src/**/*.ts\"",