@tenphi/glaze 0.0.0-snapshot.042c9e4

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 ADDED
@@ -0,0 +1,1124 @@
1
+ <p align="center">
2
+ <img src="assets/glaze.svg" width="128" height="128" alt="Glaze logo">
3
+ </p>
4
+
5
+ <h1 align="center">Glaze</h1>
6
+
7
+ <p align="center">
8
+ OKHSL-based color theme generator with WCAG contrast solving
9
+ </p>
10
+
11
+ <p align="center">
12
+ <a href="https://www.npmjs.com/package/@tenphi/glaze"><img src="https://img.shields.io/npm/v/@tenphi/glaze.svg" alt="npm version"></a>
13
+ <a href="https://github.com/tenphi/glaze/actions/workflows/ci.yml"><img src="https://github.com/tenphi/glaze/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
14
+ <a href="https://github.com/tenphi/glaze/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/@tenphi/glaze.svg" alt="license"></a>
15
+ </p>
16
+
17
+ ---
18
+
19
+ Glaze generates robust **light**, **dark**, and **high-contrast** color schemes from a single hue/saturation seed. It preserves WCAG contrast ratios for UI color pairs via explicit dependency declarations — no hidden role math, no magic multipliers.
20
+
21
+ ## Features
22
+
23
+ - **OKHSL color space** — perceptually uniform hue and saturation
24
+ - **WCAG 2 contrast solving** — automatic lightness adjustment to meet AA/AAA targets
25
+ - **Mix colors** — blend two colors with OKHSL or sRGB interpolation, opaque or transparent, with optional contrast solving
26
+ - **Shadow colors** — OKHSL-native shadow computation with automatic alpha, fg/bg tinting, and per-scheme adaptation
27
+ - **Light + Dark + High-Contrast** — all schemes from one definition
28
+ - **Per-color hue override** — absolute or relative hue shifts within a theme
29
+ - **Multi-format output** — `okhsl`, `rgb`, `hsl`, `oklch` with modern CSS space syntax
30
+ - **CSS custom properties export** — ready-to-use `--var: value;` declarations per scheme
31
+ - **Import/Export** — serialize and restore theme configurations
32
+ - **Create from hex/RGB** — start from an existing brand color
33
+ - **Zero dependencies** — pure math, runs anywhere (Node.js, browser, edge)
34
+ - **Tree-shakeable ESM + CJS** — dual-format package
35
+ - **TypeScript-first** — full type definitions included
36
+
37
+ ## Installation
38
+
39
+ ```bash
40
+ pnpm add @tenphi/glaze
41
+ ```
42
+
43
+ ```bash
44
+ npm install @tenphi/glaze
45
+ ```
46
+
47
+ ```bash
48
+ yarn add @tenphi/glaze
49
+ ```
50
+
51
+ ## Quick Start
52
+
53
+ ```ts
54
+ import { glaze } from '@tenphi/glaze';
55
+
56
+ // Create a theme from a hue (0–360) and saturation (0–100)
57
+ const primary = glaze(280, 80);
58
+
59
+ // Define colors with explicit lightness and contrast relationships
60
+ primary.colors({
61
+ surface: { lightness: 97, saturation: 0.75 },
62
+ text: { base: 'surface', lightness: '-52', contrast: 'AAA' },
63
+ border: { base: 'surface', lightness: ['-7', '-20'], contrast: 'AA-large' },
64
+ 'accent-fill': { lightness: 52, mode: 'fixed' },
65
+ 'accent-text': { base: 'accent-fill', lightness: '+48', contrast: 'AA', mode: 'fixed' },
66
+ });
67
+
68
+ // Create status themes by rotating the hue
69
+ const danger = primary.extend({ hue: 23 });
70
+ const success = primary.extend({ hue: 157 });
71
+
72
+ // Compose into a palette and export
73
+ const palette = glaze.palette({ primary, danger, success });
74
+ const tokens = palette.tokens({ primary: 'primary' });
75
+ // → { light: { 'primary-surface': 'okhsl(...)', 'surface': 'okhsl(...)', ... }, dark: { ... } }
76
+ ```
77
+
78
+ ## Core Concepts
79
+
80
+ ### One Theme = One Hue Family
81
+
82
+ A single `glaze` theme is tied to one hue/saturation seed. Status colors (danger, success, warning) are derived via `extend`, which inherits all color definitions and replaces the seed.
83
+
84
+ Individual colors can override the hue via the `hue` prop (see [Per-Color Hue Override](#per-color-hue-override)), but the primary purpose of a theme is to scope colors with the same hue.
85
+
86
+ ### Color Definitions
87
+
88
+ Every color is defined explicitly. No implicit roles — every value is stated.
89
+
90
+ #### Root Colors (explicit position)
91
+
92
+ ```ts
93
+ primary.colors({
94
+ surface: { lightness: 97, saturation: 0.75 },
95
+ border: { lightness: 90, saturation: 0.20 },
96
+ });
97
+ ```
98
+
99
+ - `lightness` — lightness in the light scheme (0–100)
100
+ - `saturation` — saturation factor applied to the seed saturation (0–1, default: `1`)
101
+
102
+ #### Dependent Colors (relative to base)
103
+
104
+ ```ts
105
+ primary.colors({
106
+ surface: { lightness: 97, saturation: 0.75 },
107
+ text: { base: 'surface', lightness: '-52', contrast: 'AAA' },
108
+ });
109
+ ```
110
+
111
+ - `base` — name of another color in the same theme
112
+ - `lightness` — position of this color (see [Lightness Values](#lightness-values))
113
+ - `contrast` — ensures the WCAG contrast ratio meets a target floor against the base
114
+
115
+ ### Lightness Values
116
+
117
+ The `lightness` prop accepts two forms:
118
+
119
+ | Form | Example | Meaning |
120
+ |---|---|---|
121
+ | Number (absolute) | `lightness: 45` | Absolute lightness 0–100 |
122
+ | String (relative) | `lightness: '-52'` | Relative to base color's lightness |
123
+
124
+ **Absolute lightness** on a dependent color (with `base`) positions the color independently. In dark mode, it is dark-mapped on its own. The `contrast` WCAG solver acts as a safety net.
125
+
126
+ **Relative lightness** applies a signed delta to the base color's resolved lightness. In dark mode with `auto` adaptation, the sign flips automatically.
127
+
128
+ ```ts
129
+ // Relative: 97 - 52 = 45 in light mode
130
+ 'text': { base: 'surface', lightness: '-52' }
131
+
132
+ // Absolute: lightness 45 in light mode, dark-mapped independently
133
+ 'text': { base: 'surface', lightness: 45 }
134
+ ```
135
+
136
+ A dependent color with `base` but no `lightness` inherits the base's lightness (equivalent to a delta of 0).
137
+
138
+ ### Per-Color Hue Override
139
+
140
+ Individual colors can override the theme's hue. The `hue` prop accepts:
141
+
142
+ | Form | Example | Meaning |
143
+ |---|---|---|
144
+ | Number (absolute) | `hue: 120` | Absolute hue 0–360 |
145
+ | String (relative) | `hue: '+20'` | Relative to the **theme seed** hue |
146
+
147
+ **Important:** Relative hue is always relative to the **theme seed hue**, not to a base color's hue.
148
+
149
+ ```ts
150
+ const theme = glaze(280, 80);
151
+ theme.colors({
152
+ surface: { lightness: 97 },
153
+ // Gradient end — slight hue shift from seed (280 + 20 = 300)
154
+ gradientEnd: { lightness: 90, hue: '+20' },
155
+ // Entirely different hue
156
+ warning: { lightness: 60, hue: 40 },
157
+ });
158
+ ```
159
+
160
+ ### contrast (WCAG Floor)
161
+
162
+ Ensures the WCAG contrast ratio meets a target floor. Accepts a numeric ratio or a preset string:
163
+
164
+ ```ts
165
+ type MinContrast = number | 'AA' | 'AAA' | 'AA-large' | 'AAA-large';
166
+ ```
167
+
168
+ | Preset | Ratio |
169
+ |---|---|
170
+ | `'AA'` | 4.5 |
171
+ | `'AAA'` | 7 |
172
+ | `'AA-large'` | 3 |
173
+ | `'AAA-large'` | 4.5 |
174
+
175
+ You can also pass any numeric ratio directly (e.g., `contrast: 4.5`, `contrast: 7`, `contrast: 11`).
176
+
177
+ The constraint is applied independently for each scheme. If the `lightness` already satisfies the floor, it's kept. Otherwise, the solver adjusts lightness until the target is met.
178
+
179
+ ### High-Contrast via Array Values
180
+
181
+ `lightness` and `contrast` accept a `[normal, high-contrast]` pair:
182
+
183
+ ```ts
184
+ 'border': { base: 'surface', lightness: ['-7', '-20'], contrast: 'AA-large' }
185
+ // ↑ ↑
186
+ // normal high-contrast
187
+ ```
188
+
189
+ A single value applies to both modes. All control is local and explicit.
190
+
191
+ ```ts
192
+ 'text': { base: 'surface', lightness: '-52', contrast: 'AAA' }
193
+ 'border': { base: 'surface', lightness: ['-7', '-20'], contrast: 'AA-large' }
194
+ 'muted': { base: 'surface', lightness: ['-35', '-50'], contrast: ['AA-large', 'AA'] }
195
+ ```
196
+
197
+ **Full lightness spectrum in HC mode:** In high-contrast variants, the `lightLightness` and `darkLightness` window constraints are bypassed entirely. Colors can reach the full 0–100 lightness range, maximizing perceivable contrast. Normal (non-HC) variants continue to use the configured windows.
198
+
199
+ ## Theme Color Management
200
+
201
+ ### Adding Colors
202
+
203
+ `.colors(defs)` performs an **additive merge** — it adds new colors and overwrites existing ones by name, but does not remove other colors:
204
+
205
+ ```ts
206
+ const theme = glaze(280, 80);
207
+ theme.colors({ surface: { lightness: 97 } });
208
+ theme.colors({ text: { lightness: 30 } });
209
+ // Both 'surface' and 'text' are now defined
210
+ ```
211
+
212
+ ### Single Color Getter/Setter
213
+
214
+ `.color(name)` returns the definition, `.color(name, def)` sets it:
215
+
216
+ ```ts
217
+ theme.color('surface', { lightness: 97, saturation: 0.75 }); // set
218
+ const def = theme.color('surface'); // get → { lightness: 97, saturation: 0.75 }
219
+ ```
220
+
221
+ ### Removing Colors
222
+
223
+ `.remove(name)` or `.remove([name1, name2])` deletes color definitions:
224
+
225
+ ```ts
226
+ theme.remove('surface');
227
+ theme.remove(['text', 'border']);
228
+ ```
229
+
230
+ ### Introspection
231
+
232
+ ```ts
233
+ theme.has('surface'); // → true/false
234
+ theme.list(); // → ['surface', 'text', 'border', ...]
235
+ ```
236
+
237
+ ### Clearing All Colors
238
+
239
+ ```ts
240
+ theme.reset(); // removes all color definitions
241
+ ```
242
+
243
+ ## Import / Export
244
+
245
+ Serialize a theme's configuration (hue, saturation, color definitions) to a plain JSON-safe object, and restore it later:
246
+
247
+ ```ts
248
+ // Export
249
+ const snapshot = theme.export();
250
+ // → { hue: 280, saturation: 80, colors: { surface: { lightness: 97, saturation: 0.75 }, ... } }
251
+
252
+ const jsonString = JSON.stringify(snapshot);
253
+
254
+ // Import
255
+ const restored = glaze.from(JSON.parse(jsonString));
256
+ // restored is a fully functional GlazeTheme
257
+ ```
258
+
259
+ The export contains only the configuration — not resolved color values. Resolved values are recomputed on demand.
260
+
261
+ ## Standalone Color Token
262
+
263
+ Create a single color token without a full theme:
264
+
265
+ ```ts
266
+ const accent = glaze.color({ hue: 280, saturation: 80, lightness: 52, mode: 'fixed' });
267
+
268
+ accent.resolve(); // → ResolvedColor with light/dark/lightContrast/darkContrast
269
+ accent.token(); // → { '': 'okhsl(...)', '@dark': 'okhsl(...)' } (tasty format)
270
+ accent.tasty(); // → { '': 'okhsl(...)', '@dark': 'okhsl(...)' } (same as token)
271
+ accent.json(); // → { light: 'okhsl(...)', dark: 'okhsl(...)' }
272
+ ```
273
+
274
+ Standalone colors are always root colors (no `base`/`contrast`).
275
+
276
+ ## From Existing Colors
277
+
278
+ Create a theme from an existing brand color by extracting its OKHSL hue and saturation:
279
+
280
+ ```ts
281
+ // From hex
282
+ const brand = glaze.fromHex('#7a4dbf');
283
+
284
+ // From RGB (0–255)
285
+ const brand = glaze.fromRgb(122, 77, 191);
286
+ ```
287
+
288
+ The resulting theme has the extracted hue and saturation. Add colors as usual:
289
+
290
+ ```ts
291
+ brand.colors({
292
+ surface: { lightness: 97, saturation: 0.75 },
293
+ text: { base: 'surface', lightness: '-52', contrast: 'AAA' },
294
+ });
295
+ ```
296
+
297
+ ## Shadow Colors
298
+
299
+ Shadow colors are colors with computed alpha. Instead of a parallel shadow system, they extend the existing color pipeline. All math is done natively in OKHSL.
300
+
301
+ ### Defining Shadow Colors
302
+
303
+ Shadow colors use `type: 'shadow'` and reference a `bg` (background) color and optionally an `fg` (foreground) color for tinting and intensity modulation:
304
+
305
+ ```ts
306
+ theme.colors({
307
+ surface: { lightness: 95 },
308
+ text: { base: 'surface', lightness: '-52', contrast: 'AAA' },
309
+
310
+ 'shadow-sm': { type: 'shadow', bg: 'surface', fg: 'text', intensity: 5 },
311
+ 'shadow-md': { type: 'shadow', bg: 'surface', fg: 'text', intensity: 10 },
312
+ 'shadow-lg': { type: 'shadow', bg: 'surface', fg: 'text', intensity: 20 },
313
+ });
314
+ ```
315
+
316
+ Shadow colors are included in all output methods (`tokens()`, `tasty()`, `css()`, `json()`) alongside regular colors:
317
+
318
+ ```ts
319
+ theme.tokens({ format: 'oklch' });
320
+ // light: { 'shadow-md': 'oklch(0.15 0.009 282 / 0.1)', ... }
321
+ // dark: { 'shadow-md': 'oklch(0.06 0.004 0 / 0.49)', ... }
322
+ ```
323
+
324
+ ### How Shadows Work
325
+
326
+ The shadow algorithm computes a dark, low-saturation pigment color and an alpha value that produces the desired visual intensity:
327
+
328
+ 1. **Contrast weight** — when `fg` is provided, shadow strength scales with `|l_bg - l_fg|`. Dark text on a light background produces a strong shadow; near-background-lightness elements produce barely visible shadows.
329
+ 2. **Pigment color** — hue blended between fg and bg, low saturation, dark lightness.
330
+ 3. **Alpha** — computed via a `tanh` curve that saturates smoothly toward `alphaMax` (default 0.6), ensuring well-separated shadow levels even on dark backgrounds.
331
+
332
+ ### Achromatic Shadows
333
+
334
+ Omit `fg` for a pure achromatic shadow at full user-specified intensity:
335
+
336
+ ```ts
337
+ theme.colors({
338
+ 'drop-shadow': { type: 'shadow', bg: 'surface', intensity: 12 },
339
+ });
340
+ ```
341
+
342
+ ### High-Contrast Intensity
343
+
344
+ `intensity` supports `[normal, highContrast]` pairs:
345
+
346
+ ```ts
347
+ theme.colors({
348
+ 'shadow-card': { type: 'shadow', bg: 'surface', fg: 'text', intensity: [10, 20] },
349
+ });
350
+ ```
351
+
352
+ ### Fixed Opacity (Regular Colors)
353
+
354
+ For a simple fixed-alpha color (no shadow algorithm), use `opacity` on a regular color:
355
+
356
+ ```ts
357
+ theme.colors({
358
+ overlay: { lightness: 0, opacity: 0.5 },
359
+ });
360
+ // → 'oklch(0 0 0 / 0.5)'
361
+ ```
362
+
363
+ ### Shadow Tuning
364
+
365
+ Fine-tune shadow behavior per-color or globally:
366
+
367
+ ```ts
368
+ // Per-color tuning
369
+ theme.colors({
370
+ 'shadow-soft': {
371
+ type: 'shadow', bg: 'surface', intensity: 10,
372
+ tuning: { alphaMax: 0.3, saturationFactor: 0.1 },
373
+ },
374
+ });
375
+
376
+ // Global tuning
377
+ glaze.configure({
378
+ shadowTuning: { alphaMax: 0.5, bgHueBlend: 0.3 },
379
+ });
380
+ ```
381
+
382
+ Available tuning parameters:
383
+
384
+ | Parameter | Default | Description |
385
+ |---|---|---|
386
+ | `saturationFactor` | 0.18 | Fraction of fg saturation kept in pigment |
387
+ | `maxSaturation` | 0.25 | Upper clamp on pigment saturation |
388
+ | `lightnessFactor` | 0.25 | Multiplier for bg lightness to pigment lightness |
389
+ | `lightnessBounds` | [0.05, 0.20] | Clamp range for pigment lightness |
390
+ | `minGapTarget` | 0.05 | Target minimum gap between pigment and bg lightness |
391
+ | `alphaMax` | 0.6 | Asymptotic maximum alpha |
392
+ | `bgHueBlend` | 0.2 | Blend weight pulling pigment hue toward bg hue |
393
+
394
+ ### Standalone Shadow Computation
395
+
396
+ Compute a shadow outside of a theme:
397
+
398
+ ```ts
399
+ const v = glaze.shadow({
400
+ bg: '#f0eef5',
401
+ fg: '#1a1a2e',
402
+ intensity: 10,
403
+ });
404
+ // → { h: 280, s: 0.14, l: 0.2, alpha: 0.1 }
405
+
406
+ const css = glaze.format(v, 'oklch');
407
+ // → 'oklch(0.15 0.014 280 / 0.1)'
408
+ ```
409
+
410
+ ### Consuming in CSS
411
+
412
+ ```css
413
+ .card {
414
+ box-shadow: 0 2px 6px var(--shadow-sm-color),
415
+ 0 8px 24px var(--shadow-md-color);
416
+ }
417
+ ```
418
+
419
+ ## Mix Colors
420
+
421
+ Mix colors blend two existing colors together. Use them for hover overlays, tints, shades, and any derived color that sits between two reference colors.
422
+
423
+ ### Opaque Mix
424
+
425
+ Produces a solid color by interpolating between `base` and `target`:
426
+
427
+ ```ts
428
+ theme.colors({
429
+ surface: { lightness: 95 },
430
+ accent: { lightness: 30 },
431
+
432
+ // 30% of the way from surface toward accent
433
+ tint: { type: 'mix', base: 'surface', target: 'accent', value: 30 },
434
+ });
435
+ ```
436
+
437
+ - `value` — mix ratio 0–100 (0 = pure base, 100 = pure target)
438
+ - The result is a fully opaque color (alpha = 1)
439
+ - Adapts to light/dark/HC schemes automatically via the resolved base and target
440
+
441
+ ### Transparent Mix
442
+
443
+ Produces the target color with a controlled opacity — useful for hover overlays:
444
+
445
+ ```ts
446
+ theme.colors({
447
+ surface: { lightness: 95 },
448
+ black: { lightness: 0, saturation: 0 },
449
+
450
+ hover: {
451
+ type: 'mix',
452
+ base: 'surface',
453
+ target: 'black',
454
+ value: 8,
455
+ blend: 'transparent',
456
+ },
457
+ });
458
+ // hover → target color (black) with alpha = 0.08
459
+ ```
460
+
461
+ The output color has `h`, `s`, `l` from the target and `alpha = value / 100`.
462
+
463
+ ### Blend Space
464
+
465
+ By default, opaque mixing interpolates in OKHSL (perceptually uniform, consistent with Glaze's model). Use `space: 'srgb'` for linear sRGB interpolation, which matches browser compositing:
466
+
467
+ ```ts
468
+ theme.colors({
469
+ surface: { lightness: 95 },
470
+ accent: { lightness: 30 },
471
+
472
+ // sRGB blend — matches what the browser would render
473
+ hover: { type: 'mix', base: 'surface', target: 'accent', value: 20, space: 'srgb' },
474
+ });
475
+ ```
476
+
477
+ | Space | Behavior | Best for |
478
+ |---|---|---|
479
+ | `'okhsl'` (default) | Perceptually uniform OKHSL interpolation | Design token derivation |
480
+ | `'srgb'` | Linear sRGB channel interpolation | Matching browser compositing |
481
+
482
+ The `space` option only affects opaque blending. Transparent blending always composites in linear sRGB (matching browser alpha compositing).
483
+
484
+ ### Contrast Solving
485
+
486
+ Mix colors support the same `contrast` prop as regular colors. The solver adjusts the mix ratio (opaque) or opacity (transparent) to meet the WCAG target:
487
+
488
+ ```ts
489
+ theme.colors({
490
+ surface: { lightness: 95 },
491
+ accent: { lightness: 30 },
492
+
493
+ // Ensure the mixed color has at least AA contrast against surface
494
+ tint: {
495
+ type: 'mix',
496
+ base: 'surface',
497
+ target: 'accent',
498
+ value: 10,
499
+ contrast: 'AA',
500
+ },
501
+
502
+ // Ensure the transparent overlay has at least 3:1 contrast
503
+ overlay: {
504
+ type: 'mix',
505
+ base: 'surface',
506
+ target: 'accent',
507
+ value: 5,
508
+ blend: 'transparent',
509
+ contrast: 3,
510
+ },
511
+ });
512
+ ```
513
+
514
+ ### High-Contrast Pairs
515
+
516
+ Both `value` and `contrast` support `[normal, highContrast]` pairs:
517
+
518
+ ```ts
519
+ theme.colors({
520
+ surface: { lightness: 95 },
521
+ accent: { lightness: 30 },
522
+
523
+ tint: {
524
+ type: 'mix',
525
+ base: 'surface',
526
+ target: 'accent',
527
+ value: [20, 40], // stronger mix in high-contrast mode
528
+ contrast: [3, 'AAA'], // stricter contrast in high-contrast mode
529
+ },
530
+ });
531
+ ```
532
+
533
+ ### Achromatic Colors
534
+
535
+ When mixing with achromatic colors (saturation near zero, e.g., white or black) in `okhsl` space, the hue comes from whichever color has saturation. This prevents meaningless hue artifacts and matches CSS `color-mix()` "missing component" behavior. For purely achromatic mixes, prefer `space: 'srgb'` where hue is irrelevant.
536
+
537
+ ### Mix Chaining
538
+
539
+ Mix colors can reference other mix colors, enabling multi-step derivations:
540
+
541
+ ```ts
542
+ theme.colors({
543
+ white: { lightness: 100, saturation: 0 },
544
+ black: { lightness: 0, saturation: 0 },
545
+ gray: { type: 'mix', base: 'white', target: 'black', value: 50, space: 'srgb' },
546
+ lightGray: { type: 'mix', base: 'white', target: 'gray', value: 50, space: 'srgb' },
547
+ });
548
+ ```
549
+
550
+ Mix colors cannot reference shadow colors (same restriction as regular dependent colors).
551
+
552
+ ## Output Formats
553
+
554
+ Control the color format in exports with the `format` option:
555
+
556
+ ```ts
557
+ // Default: OKHSL
558
+ theme.tokens(); // → 'okhsl(280 60% 97%)'
559
+
560
+ // RGB (modern space syntax, rounded integers)
561
+ theme.tokens({ format: 'rgb' }); // → 'rgb(244 240 250)'
562
+
563
+ // HSL (modern space syntax)
564
+ theme.tokens({ format: 'hsl' }); // → 'hsl(270.5 45.2% 95.8%)'
565
+
566
+ // OKLCH
567
+ theme.tokens({ format: 'oklch' }); // → 'oklch(0.965 0.0123 280)'
568
+ ```
569
+
570
+ The `format` option works on all export methods: `theme.tokens()`, `theme.tasty()`, `theme.json()`, `theme.css()`, `palette.tokens()`, `palette.tasty()`, `palette.json()`, `palette.css()`, and standalone `glaze.color().token()` / `.tasty()` / `.json()`.
571
+
572
+ Colors with `alpha < 1` (shadow colors, or regular colors with `opacity`) include an alpha component:
573
+
574
+ ```ts
575
+ // → 'oklch(0.15 0.009 282 / 0.1)'
576
+ // → 'rgb(34 28 42 / 0.1)'
577
+ ```
578
+
579
+ Available formats:
580
+
581
+ | Format | Output (alpha = 1) | Output (alpha < 1) | Notes |
582
+ |---|---|---|---|
583
+ | `'okhsl'` (default) | `okhsl(H S% L%)` | `okhsl(H S% L% / A)` | Native format, not a CSS function |
584
+ | `'rgb'` | `rgb(R G B)` | `rgb(R G B / A)` | Rounded integers, space syntax |
585
+ | `'hsl'` | `hsl(H S% L%)` | `hsl(H S% L% / A)` | Modern space syntax |
586
+ | `'oklch'` | `oklch(L C H)` | `oklch(L C H / A)` | OKLab-based LCH |
587
+
588
+ All numeric output strips trailing zeros for cleaner CSS (e.g., `95` not `95.0`).
589
+
590
+ ## Adaptation Modes
591
+
592
+ Modes control how colors adapt across schemes:
593
+
594
+ | Mode | Behavior |
595
+ |---|---|
596
+ | `'auto'` (default) | Full adaptation. Light ↔ dark inversion. High-contrast boost. |
597
+ | `'fixed'` | Color stays recognizable. Only safety corrections. For brand buttons, CTAs. |
598
+ | `'static'` | No adaptation. Same value in every scheme. |
599
+
600
+ ### How Relative Lightness Adapts
601
+
602
+ **`auto` mode** — relative lightness sign flips in dark scheme:
603
+
604
+ ```ts
605
+ // Light: surface L=97, text lightness='-52' → L=45 (dark text on light bg)
606
+ // Dark: surface inverts to L≈20 (Möbius curve), sign flips → L=20+52=72
607
+ // contrast solver may push further (light text on dark bg)
608
+ ```
609
+
610
+ **`fixed` mode** — lightness is mapped (not inverted), relative sign preserved:
611
+
612
+ ```ts
613
+ // Light: accent-fill L=52, accent-text lightness='+48' → L=100 (white on brand)
614
+ // Dark: accent-fill maps to L≈51.6, sign preserved → L≈99.6
615
+ ```
616
+
617
+ **`static` mode** — no adaptation, same value in every scheme.
618
+
619
+ ## Light Scheme Mapping
620
+
621
+ ### Lightness
622
+
623
+ Absolute lightness values (both root colors and dependent colors with absolute lightness) are mapped linearly within the configured `lightLightness` window:
624
+
625
+ ```ts
626
+ const [lo, hi] = lightLightness; // default: [10, 100]
627
+ const mappedL = (lightness * (hi - lo)) / 100 + lo;
628
+ ```
629
+
630
+ Both `auto` and `fixed` modes use the same linear formula. `static` mode and high-contrast variants bypass the mapping entirely (identity: `mappedL = l`).
631
+
632
+ | Color | Raw L | Mapped L (default [10, 100]) |
633
+ |---|---|---|
634
+ | surface (L=97) | 97 | 97.3 |
635
+ | accent-fill (L=52) | 52 | 56.8 |
636
+ | near-black (L=0) | 0 | 10 |
637
+
638
+ ## Dark Scheme Mapping
639
+
640
+ ### Lightness
641
+
642
+ **`auto`** — inverted with a Möbius transformation within the configured window:
643
+
644
+ ```ts
645
+ const [lo, hi] = darkLightness; // default: [15, 95]
646
+ const t = (100 - lightness) / 100;
647
+ const invertedL = lo + (hi - lo) * t / (t + darkCurve * (1 - t)); // darkCurve default: 0.5
648
+ ```
649
+
650
+ The `darkCurve` parameter (default `0.5`, range 0–1) controls how much the dark-mode inversion expands lightness deltas. Lower values produce stronger expansion; `1` gives linear (legacy) behavior. Accepts a `[normal, highContrast]` pair for separate HC tuning (e.g. `darkCurve: [0.5, 0.3]`); a single number applies to both. Unlike a power curve, the Möbius transformation provides **proportional expansion** — small and large deltas are scaled by similar ratios, preserving the visual hierarchy of the light theme.
651
+
652
+ **`fixed`** — mapped without inversion (not affected by `darkCurve`):
653
+
654
+ ```ts
655
+ const mappedL = (lightness * (hi - lo)) / 100 + lo;
656
+ ```
657
+
658
+ | Color | Light L | Auto (curve=0.5) | Auto (curve=1, linear) | Fixed (mapped) |
659
+ |---|---|---|---|---|
660
+ | surface (L=97) | 97 | 19.7 | 17.4 | 92.6 |
661
+ | accent-fill (L=52) | 52 | 66.9 | 53.4 | 56.6 |
662
+ | accent-text (L=100) | 100 | 15 | 15 | 95 |
663
+
664
+ In high-contrast variants, the `darkLightness` window is bypassed — auto uses the Möbius curve over the full [0, 100] range, and fixed uses identity (`L`). To use a different curve shape for HC, pass a `[normal, hc]` pair to `darkCurve` (e.g. `darkCurve: [0.5, 0.3]`).
665
+
666
+ ### Saturation
667
+
668
+ `darkDesaturation` reduces saturation for all colors in dark scheme:
669
+
670
+ ```ts
671
+ S_dark = S_light * (1 - darkDesaturation) // default: 0.1
672
+ ```
673
+
674
+ ## Inherited Themes (`extend`)
675
+
676
+ `extend` creates a new theme inheriting all color definitions, replacing the hue and/or saturation seed:
677
+
678
+ ```ts
679
+ const primary = glaze(280, 80);
680
+ primary.colors({ /* ... */ });
681
+
682
+ const danger = primary.extend({ hue: 23 });
683
+ const success = primary.extend({ hue: 157 });
684
+ const warning = primary.extend({ hue: 84 });
685
+ ```
686
+
687
+ Override individual colors (additive merge):
688
+
689
+ ```ts
690
+ const danger = primary.extend({
691
+ hue: 23,
692
+ colors: { 'accent-fill': { lightness: 48, mode: 'fixed' } },
693
+ });
694
+ ```
695
+
696
+ ## Palette Composition
697
+
698
+ Combine multiple themes into a single palette:
699
+
700
+ ```ts
701
+ const palette = glaze.palette({ primary, danger, success, warning });
702
+ ```
703
+
704
+ ### Prefix Behavior
705
+
706
+ Palette export methods (`tokens()`, `tasty()`, `css()`) default to `prefix: true` — all tokens are automatically prefixed with the theme name to avoid collisions:
707
+
708
+ ```ts
709
+ const tokens = palette.tokens();
710
+ // → {
711
+ // light: { 'primary-surface': 'okhsl(...)', 'danger-surface': 'okhsl(...)' },
712
+ // dark: { 'primary-surface': 'okhsl(...)', 'danger-surface': 'okhsl(...)' },
713
+ // }
714
+ ```
715
+
716
+ Custom prefix mapping:
717
+
718
+ ```ts
719
+ palette.tokens({ prefix: { primary: 'brand-', danger: 'error-' } });
720
+ ```
721
+
722
+ To disable prefixing entirely, pass `prefix: false` explicitly. Note that tokens with the same name will overwrite each other (last theme wins).
723
+
724
+ ### Primary Theme
725
+
726
+ Use the `primary` option to designate one theme as the primary. Its tokens are duplicated without prefix, providing convenient short aliases alongside the prefixed versions:
727
+
728
+ ```ts
729
+ const palette = glaze.palette({ primary, danger, success });
730
+ const tokens = palette.tokens({ primary: 'primary' });
731
+ // → {
732
+ // light: {
733
+ // 'primary-surface': 'okhsl(...)', // prefixed (all themes)
734
+ // 'danger-surface': 'okhsl(...)',
735
+ // 'success-surface': 'okhsl(...)',
736
+ // 'surface': 'okhsl(...)', // unprefixed alias (primary only)
737
+ // },
738
+ // }
739
+ ```
740
+
741
+ The `primary` option works on `tokens()`, `tasty()`, and `css()`. It combines with any prefix mode — when using a custom prefix map, primary tokens are still duplicated without prefix:
742
+
743
+ ```ts
744
+ palette.tokens({ prefix: { primary: 'p-', danger: 'd-' }, primary: 'primary' });
745
+ // → 'p-surface' + 'surface' (alias) + 'd-surface'
746
+ ```
747
+
748
+ An error is thrown if the primary name doesn't match any theme in the palette.
749
+
750
+ ### Tasty Export (for [Tasty](https://cube-ui-kit.vercel.app/?path=/docs/tasty-documentation--docs) style system)
751
+
752
+ The `tasty()` method exports tokens in the [Tasty](https://cube-ui-kit.vercel.app/?path=/docs/tasty-documentation--docs) style-to-state binding format — `#name` color token keys with state aliases (`''`, `@dark`, etc.):
753
+
754
+ ```ts
755
+ const tastyTokens = palette.tasty({ primary: 'primary' });
756
+ // → {
757
+ // '#primary-surface': { '': 'okhsl(...)', '@dark': 'okhsl(...)' },
758
+ // '#danger-surface': { '': 'okhsl(...)', '@dark': 'okhsl(...)' },
759
+ // '#surface': { '': 'okhsl(...)', '@dark': 'okhsl(...)' }, // alias
760
+ // }
761
+ ```
762
+
763
+ Apply as global styles to make color tokens available app-wide:
764
+
765
+ ```ts
766
+ import { useGlobalStyles } from '@cube-dev/ui-kit';
767
+
768
+ // In your root component
769
+ useGlobalStyles('body', tastyTokens);
770
+ ```
771
+
772
+ For zero-runtime builds, use `tastyStatic` to generate the CSS at build time:
773
+
774
+ ```ts
775
+ import { tastyStatic } from '@cube-dev/ui-kit';
776
+
777
+ tastyStatic('body', tastyTokens);
778
+ ```
779
+
780
+ Alternatively, register as a recipe via `configure()`:
781
+
782
+ ```ts
783
+ import { configure, tasty } from '@cube-dev/ui-kit';
784
+
785
+ configure({
786
+ recipes: {
787
+ 'all-themes': tastyTokens,
788
+ },
789
+ });
790
+
791
+ const Page = tasty({
792
+ styles: {
793
+ recipe: 'all-themes',
794
+ fill: '#primary-surface',
795
+ color: '#primary-text',
796
+ },
797
+ });
798
+ ```
799
+
800
+ Or spread directly into component styles:
801
+
802
+ ```ts
803
+ const Card = tasty({
804
+ styles: {
805
+ ...tastyTokens,
806
+ fill: '#primary-surface',
807
+ color: '#primary-text',
808
+ },
809
+ });
810
+ ```
811
+
812
+ Custom prefix mapping:
813
+
814
+ ```ts
815
+ palette.tasty({ prefix: { primary: 'brand-', danger: 'error-' } });
816
+ ```
817
+
818
+ Custom state aliases:
819
+
820
+ ```ts
821
+ palette.tasty({ states: { dark: '@dark', highContrast: '@hc' } });
822
+ ```
823
+
824
+ ### JSON Export (Framework-Agnostic)
825
+
826
+ JSON export groups by theme name (no prefix needed):
827
+
828
+ ```ts
829
+ const data = palette.json();
830
+ // → {
831
+ // primary: { surface: { light: 'okhsl(...)', dark: 'okhsl(...)' } },
832
+ // danger: { surface: { light: 'okhsl(...)', dark: 'okhsl(...)' } },
833
+ // }
834
+ ```
835
+
836
+ ### CSS Export
837
+
838
+ Export as CSS custom property declarations, grouped by scheme variant. Each variant is a string of `--name-color: value;` lines that you can wrap in your own selectors and media queries.
839
+
840
+ ```ts
841
+ const css = theme.css();
842
+ // css.light → "--surface-color: rgb(...);\n--text-color: rgb(...);"
843
+ // css.dark → "--surface-color: rgb(...);\n--text-color: rgb(...);"
844
+ // css.lightContrast → "--surface-color: rgb(...);\n--text-color: rgb(...);"
845
+ // css.darkContrast → "--surface-color: rgb(...);\n--text-color: rgb(...);"
846
+ ```
847
+
848
+ Use in a stylesheet:
849
+
850
+ ```ts
851
+ const css = palette.css({ primary: 'primary' });
852
+
853
+ const stylesheet = `
854
+ :root { ${css.light} }
855
+ @media (prefers-color-scheme: dark) {
856
+ :root { ${css.dark} }
857
+ }
858
+ `;
859
+ ```
860
+
861
+ Options:
862
+
863
+ | Option | Default | Description |
864
+ |---|---|---|
865
+ | `format` | `'rgb'` | Color format (`'rgb'`, `'hsl'`, `'okhsl'`, `'oklch'`) |
866
+ | `suffix` | `'-color'` | Suffix appended to each CSS property name |
867
+ | `prefix` | `true` (palette) | (palette only) `true` uses `"<themeName>-"`, or provide a custom map |
868
+ | `primary` | — | (palette only) Theme name to duplicate without prefix |
869
+
870
+ ```ts
871
+ // Custom suffix
872
+ theme.css({ suffix: '' });
873
+ // → "--surface: rgb(...);"
874
+
875
+ // Custom format
876
+ theme.css({ format: 'hsl' });
877
+ // → "--surface-color: hsl(...);"
878
+
879
+ // Palette with primary
880
+ palette.css({ primary: 'primary' });
881
+ // → "--primary-surface-color: rgb(...);\n--surface-color: rgb(...);\n--danger-surface-color: rgb(...);"
882
+ ```
883
+
884
+ ## Output Modes
885
+
886
+ Control which scheme variants appear in exports:
887
+
888
+ ```ts
889
+ // Light only
890
+ palette.tokens({ modes: { dark: false, highContrast: false } });
891
+ // → { light: { ... } }
892
+
893
+ // Light + dark (default)
894
+ palette.tokens({ modes: { highContrast: false } });
895
+ // → { light: { ... }, dark: { ... } }
896
+
897
+ // All four variants
898
+ palette.tokens({ modes: { dark: true, highContrast: true } });
899
+ // → { light: { ... }, dark: { ... }, lightContrast: { ... }, darkContrast: { ... } }
900
+ ```
901
+
902
+ The `modes` option works the same way on `tokens()`, `tasty()`, `json()`, and `css()`.
903
+
904
+ Resolution priority (highest first):
905
+
906
+ 1. `tokens({ modes })` / `tasty({ modes })` / `json({ modes })` / `css({ ... })` — per-call override
907
+ 2. `glaze.configure({ modes })` — global config
908
+ 3. Built-in default: `{ dark: true, highContrast: false }`
909
+
910
+ ## Configuration
911
+
912
+ ```ts
913
+ glaze.configure({
914
+ lightLightness: [10, 100], // Light scheme lightness window [lo, hi] (bypassed in HC)
915
+ darkLightness: [15, 95], // Dark scheme lightness window [lo, hi] (bypassed in HC)
916
+ darkDesaturation: 0.1, // Saturation reduction in dark scheme (0–1)
917
+ darkCurve: 0.5, // Möbius beta for dark auto-inversion (0–1); or [normal, hc] pair
918
+ states: {
919
+ dark: '@dark', // State alias for dark mode tokens
920
+ highContrast: '@high-contrast',
921
+ },
922
+ modes: {
923
+ dark: true, // Include dark variants in exports
924
+ highContrast: false, // Include high-contrast variants
925
+ },
926
+ shadowTuning: { // Default tuning for all shadow colors
927
+ alphaMax: 0.6,
928
+ bgHueBlend: 0.2,
929
+ },
930
+ });
931
+ ```
932
+
933
+ ## Color Definition Shape
934
+
935
+ `ColorDef` is a discriminated union of regular colors, shadow colors, and mix colors:
936
+
937
+ ```ts
938
+ type ColorDef = RegularColorDef | ShadowColorDef | MixColorDef;
939
+
940
+ interface RegularColorDef {
941
+ lightness?: HCPair<number | RelativeValue>;
942
+ saturation?: number;
943
+ hue?: number | RelativeValue;
944
+ base?: string;
945
+ contrast?: HCPair<MinContrast>;
946
+ mode?: 'auto' | 'fixed' | 'static';
947
+ opacity?: number; // fixed alpha (0–1)
948
+ }
949
+
950
+ interface ShadowColorDef {
951
+ type: 'shadow';
952
+ bg: string; // background color name (non-shadow)
953
+ fg?: string; // foreground color name (non-shadow)
954
+ intensity: HCPair<number>; // 0–100
955
+ tuning?: ShadowTuning;
956
+ }
957
+
958
+ interface MixColorDef {
959
+ type: 'mix';
960
+ base: string; // "from" color name
961
+ target: string; // "to" color name
962
+ value: HCPair<number>; // 0–100 (mix ratio or opacity)
963
+ blend?: 'opaque' | 'transparent'; // default: 'opaque'
964
+ space?: 'okhsl' | 'srgb'; // default: 'okhsl'
965
+ contrast?: HCPair<MinContrast>;
966
+ }
967
+ ```
968
+
969
+ A root color must have absolute `lightness` (a number). A dependent color must have `base`. Relative `lightness` (a string) requires `base`. Shadow colors use `type: 'shadow'` and must reference a non-shadow `bg` color. Mix colors use `type: 'mix'` and must reference two non-shadow colors.
970
+
971
+ ## Validation
972
+
973
+ | Condition | Behavior |
974
+ |---|---|
975
+ | `contrast` without `base` | Validation error |
976
+ | Relative `lightness` without `base` | Validation error |
977
+ | `lightness` resolves outside 0–100 | Clamp silently |
978
+ | `saturation` outside 0–1 | Clamp silently |
979
+ | Circular `base` references | Validation error |
980
+ | `base` references non-existent name | Validation error |
981
+ | Shadow `bg` references non-existent color | Validation error |
982
+ | Shadow `fg` references non-existent color | Validation error |
983
+ | Shadow `bg` references another shadow color | Validation error |
984
+ | Shadow `fg` references another shadow color | Validation error |
985
+ | Regular color `base` references a shadow color | Validation error |
986
+ | Shadow `intensity` outside 0–100 | Clamp silently |
987
+ | `contrast` + `opacity` combined | Warning |
988
+ | Mix `base` references non-existent color | Validation error |
989
+ | Mix `target` references non-existent color | Validation error |
990
+ | Mix `base` references a shadow color | Validation error |
991
+ | Mix `target` references a shadow color | Validation error |
992
+ | Mix `value` outside 0–100 | Clamp silently |
993
+ | Circular references involving mix colors | Validation error |
994
+
995
+ ## Advanced: Color Math Utilities
996
+
997
+ Glaze re-exports its internal color math for advanced use:
998
+
999
+ ```ts
1000
+ import {
1001
+ okhslToLinearSrgb,
1002
+ okhslToSrgb,
1003
+ okhslToOklab,
1004
+ srgbToOkhsl,
1005
+ parseHex,
1006
+ relativeLuminanceFromLinearRgb,
1007
+ contrastRatioFromLuminance,
1008
+ formatOkhsl,
1009
+ formatRgb,
1010
+ formatHsl,
1011
+ formatOklch,
1012
+ findLightnessForContrast,
1013
+ resolveMinContrast,
1014
+ } from '@tenphi/glaze';
1015
+ ```
1016
+
1017
+ ## Full Example
1018
+
1019
+ ```ts
1020
+ import { glaze } from '@tenphi/glaze';
1021
+
1022
+ const primary = glaze(280, 80);
1023
+
1024
+ primary.colors({
1025
+ surface: { lightness: 97, saturation: 0.75 },
1026
+ text: { base: 'surface', lightness: '-52', contrast: 'AAA' },
1027
+ border: { base: 'surface', lightness: ['-7', '-20'], contrast: 'AA-large' },
1028
+ bg: { lightness: 97, saturation: 0.75 },
1029
+ icon: { lightness: 60, saturation: 0.94 },
1030
+ 'accent-fill': { lightness: 52, mode: 'fixed' },
1031
+ 'accent-text': { base: 'accent-fill', lightness: '+48', contrast: 'AA', mode: 'fixed' },
1032
+ disabled: { lightness: 81, saturation: 0.4 },
1033
+
1034
+ // Shadow colors — computed alpha, automatic dark-mode adaptation
1035
+ 'shadow-sm': { type: 'shadow', bg: 'surface', fg: 'text', intensity: 5 },
1036
+ 'shadow-md': { type: 'shadow', bg: 'surface', fg: 'text', intensity: 10 },
1037
+ 'shadow-lg': { type: 'shadow', bg: 'surface', fg: 'text', intensity: 20 },
1038
+
1039
+ // Mix colors — hover overlays and tints
1040
+ 'hover': { type: 'mix', base: 'surface', target: 'accent-fill', value: 8, blend: 'transparent' },
1041
+ 'tint': { type: 'mix', base: 'surface', target: 'accent-fill', value: 20 },
1042
+
1043
+ // Fixed-alpha overlay
1044
+ overlay: { lightness: 0, opacity: 0.5 },
1045
+ });
1046
+
1047
+ const danger = primary.extend({ hue: 23 });
1048
+ const success = primary.extend({ hue: 157 });
1049
+ const warning = primary.extend({ hue: 84 });
1050
+ const note = primary.extend({ hue: 302 });
1051
+
1052
+ const palette = glaze.palette({ primary, danger, success, warning, note });
1053
+
1054
+ // Export as flat token map grouped by variant (prefix defaults to true)
1055
+ const tokens = palette.tokens({ primary: 'primary' });
1056
+ // tokens.light → { 'primary-surface': '...', 'surface': '...', 'danger-surface': '...' }
1057
+
1058
+ // Export as tasty style-to-state bindings (for Tasty style system)
1059
+ const tastyTokens = palette.tasty({ primary: 'primary' });
1060
+
1061
+ // Export as CSS custom properties (rgb format by default)
1062
+ const css = palette.css({ primary: 'primary' });
1063
+ // css.light → "--primary-surface-color: rgb(...);\n--surface-color: rgb(...);\n--danger-surface-color: rgb(...);"
1064
+
1065
+ // Standalone shadow computation
1066
+ const v = glaze.shadow({ bg: '#f0eef5', fg: '#1a1a2e', intensity: 10 });
1067
+ const shadowCss = glaze.format(v, 'oklch');
1068
+ // → 'oklch(0.15 0.014 280 / 0.1)'
1069
+
1070
+ // Save and restore a theme
1071
+ const snapshot = primary.export();
1072
+ const restored = glaze.from(snapshot);
1073
+
1074
+ // Create from an existing brand color
1075
+ const brand = glaze.fromHex('#7a4dbf');
1076
+ brand.colors({ surface: { lightness: 97 }, text: { base: 'surface', lightness: '-52' } });
1077
+ ```
1078
+
1079
+ ## API Reference
1080
+
1081
+ ### Theme Creation
1082
+
1083
+ | Method | Description |
1084
+ |---|---|
1085
+ | `glaze(hue, saturation?)` | Create a theme from hue (0–360) and saturation (0–100) |
1086
+ | `glaze({ hue, saturation })` | Create a theme from an options object |
1087
+ | `glaze.from(data)` | Create a theme from an exported configuration |
1088
+ | `glaze.fromHex(hex)` | Create a theme from a hex color (`#rgb` or `#rrggbb`) |
1089
+ | `glaze.fromRgb(r, g, b)` | Create a theme from RGB values (0–255) |
1090
+ | `glaze.color(input)` | Create a standalone color token |
1091
+ | `glaze.shadow(input)` | Compute a standalone shadow color (returns `ResolvedColorVariant`) |
1092
+ | `glaze.format(variant, format?)` | Format any `ResolvedColorVariant` as a CSS string |
1093
+
1094
+ ### Theme Methods
1095
+
1096
+ | Method | Description |
1097
+ |---|---|
1098
+ | `theme.colors(defs)` | Add/replace colors (additive merge) |
1099
+ | `theme.color(name)` | Get a color definition |
1100
+ | `theme.color(name, def)` | Set a single color definition |
1101
+ | `theme.remove(names)` | Remove one or more colors |
1102
+ | `theme.has(name)` | Check if a color is defined |
1103
+ | `theme.list()` | List all defined color names |
1104
+ | `theme.reset()` | Clear all color definitions |
1105
+ | `theme.export()` | Export configuration as JSON-safe object |
1106
+ | `theme.extend(options)` | Create a child theme |
1107
+ | `theme.resolve()` | Resolve all colors |
1108
+ | `theme.tokens(options?)` | Export as flat token map grouped by variant |
1109
+ | `theme.tasty(options?)` | Export as [Tasty](https://cube-ui-kit.vercel.app/?path=/docs/tasty-documentation--docs) style-to-state bindings |
1110
+ | `theme.json(options?)` | Export as plain JSON |
1111
+ | `theme.css(options?)` | Export as CSS custom property declarations |
1112
+
1113
+ ### Global Configuration
1114
+
1115
+ | Method | Description |
1116
+ |---|---|
1117
+ | `glaze.configure(config)` | Set global configuration |
1118
+ | `glaze.palette(themes)` | Compose themes into a palette |
1119
+ | `glaze.getConfig()` | Get current global config |
1120
+ | `glaze.resetConfig()` | Reset to defaults |
1121
+
1122
+ ## License
1123
+
1124
+ [MIT](LICENSE)