-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtypes.ts
More file actions
1555 lines (1422 loc) · 55.1 KB
/
Copy pathtypes.ts
File metadata and controls
1555 lines (1422 loc) · 55.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/**
* Glaze type definitions.
*/
import type { ApcaPreset, ContrastPreset } from './contrast-solver';
// ============================================================================
// Value types
// ============================================================================
/** A value or [normal, high-contrast] pair. */
export type HCPair<T> = T | [T, T];
/** Bare WCAG contrast target: a ratio number or a named preset. */
export type MinContrast = number | ContrastPreset;
/**
* A contrast floor with a pluggable metric.
*
* - `number` / `ContrastPreset`: a WCAG ratio (bare form).
* - `{ wcag }`: WCAG ratio or preset, optionally an HC pair.
* - `{ apca }`: APCA Lc target (absolute value or preset), optionally an HC pair.
*
* 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] }`, `{ apca: ['content', 'body'] }`).
*/
export type ContrastSpec =
| number
| ContrastPreset
| { wcag: HCPair<number | ContrastPreset> }
| { apca: HCPair<number | ApcaPreset> };
// ============================================================================
// Color role
// ============================================================================
/**
* The semantic role a color plays against its base, used to fix APCA contrast
* polarity (which side is the foreground vs the background). WCAG is
* symmetric, so role never changes WCAG results.
*/
export type Role = 'text' | 'surface' | 'border';
/**
* Any string accepted as a `role`. Canonical values plus aliases normalized by
* `normalizeRole` (see `roles.ts`): `surface` (bg/background/fill/canvas/
* paper/layer), `text` (fg/foreground/content/ink/label/stroke), `border`
* (divider/outline/separator/hairline/rule).
*/
export type RoleInput =
| Role
| 'bg'
| 'background'
| 'fill'
| 'canvas'
| 'paper'
| 'layer'
| 'fg'
| 'foreground'
| 'content'
| 'ink'
| 'label'
| 'stroke'
| 'divider'
| 'outline'
| 'separator'
| 'hairline'
| 'rule';
export type AdaptationMode = 'auto' | 'fixed' | 'static';
/** A signed relative offset string, e.g. '+20' or '-15.5'. */
export type RelativeValue = `+${number}` | `-${number}`;
/**
* Force a color to a tone extreme:
* - `'max'`: the highest tone in the active scheme range/window.
* - `'min'`: the lowest tone.
*
* Under `mode: 'auto'` the extreme inverts in the dark scheme (so `'max'`
* tracks the inversion and becomes the darkest tone). No `base` required.
*/
export type ExtremeValue = 'max' | 'min';
/**
* A tone value as authored on a color.
* - Number: absolute tone (0–100).
* - `'+N'` / `'-N'`: relative to the base's tone (requires `base`).
* - `'max'` / `'min'`: forced to the scheme's tone extreme (no base needed).
*/
export type ToneValue = number | RelativeValue | ExtremeValue;
/** Color format for output. */
export type GlazeColorFormat = 'okhsl' | 'okhst' | 'rgb' | 'hsl' | 'oklch';
/**
* Controls which scheme variants are generated in the export.
* Light is always included (it's the default).
*/
export interface GlazeOutputModes {
/** Include dark scheme variants. Default: true. */
dark?: boolean;
/**
* Include high-contrast variants (both light-HC and dark-HC). Default: false.
*
* Independent of `contrastLevel` — the level positions the normal variants
* while these stay the true high-contrast resolution, so the two compose. The
* exception is a global level of 100, where the normal variants already *are*
* the high-contrast ones: the tier would duplicate them and is dropped.
* @see GlazeConfig.contrastLevel
*/
highContrast?: boolean;
}
// ============================================================================
// Color definitions
// ============================================================================
/** Hex color string for DX hints. Runtime validation in `parseHex()`. */
export type HexColor = `#${string}`;
/** Direct OKHSL color input. */
export interface OkhslColor {
h: number;
s: number;
l: number;
}
/**
* Direct OKHST color input — OKHSL with the lightness axis replaced by the
* contrast-uniform tone axis. `h`: 0–360, `s`: 0–1, `t`: 0–1 (tone).
*/
export interface OkhstColor {
h: number;
s: number;
t: number;
}
/** sRGB components in 0–255 (value-shorthand object form). */
export interface RgbColor {
r: number;
g: number;
b: number;
}
/** OKLCh components matching CSS `oklch(L C H)` (L/C: 0–1, H: degrees). */
export interface OklchColor {
l: number;
c: number;
h: number;
}
export interface RegularColorDef {
/**
* Seed this color from a literal color instead of from the theme.
*
* Accepts the same values as `glaze.color()` — hex, `rgb()`, `hsl()`,
* `okhsl()`, `okhst()`, `oklch()`, or a value object. It supplies three
* things at once: `hue`, `tone`, and — unlike every other color in a theme —
* an **absolute saturation** rather than a factor of the seed. That last
* part is the point: a color can now be more saturated than its theme, so
* honoring a brand color no longer means re-seeding the whole theme to reach
* it.
*
* The **light, normal-contrast** variant reproduces the value exactly
* (a local `lightTone: false`, matching the value-shorthand form of
* `glaze.color()`). Dark and high-contrast variants adapt as usual — they
* are where readability outranks fidelity, and a color pinned across every
* scheme would defeat both. A `contrast` floor still applies everywhere and
* still only moves the color when the authored value misses it.
*
* Sibling fields override what the value supplied, so
* `{ from: '#2F5BFF', hue: 300 }` keeps its saturation and tone but rotates
* the hue.
*/
from?: GlazeColorValue;
/**
* Tone value (0–100, contrast-uniform — see `docs/okhst.md`).
* - Number: absolute tone.
* - String ('+N' / '-N'): relative to base color's tone (requires `base`).
* - `'max'` / `'min'`: force to the scheme's tone extreme (no base needed).
*
* Defaults to the tone of `from` when that is set.
*/
tone?: HCPair<ToneValue>;
/**
* Saturation factor applied to the seed saturation (0–1, default: 1).
*
* With `from`, an explicit value here is still a factor of the seed and
* overrides the absolute saturation the color carried.
*/
saturation?: number;
/**
* Hue override for this color.
* - Number: absolute hue (0–360).
* - String ('+N' / '-N'): relative to the theme seed hue.
*/
hue?: number | RelativeValue;
/**
* Dark-scheme hue override. Applies to the `dark` and `darkContrast`
* variants; falls back to `hue` when omitted.
* - Number: absolute hue (0–360).
* - String ('+N' / '-N'): relative to the theme's **dark** seed hue
* (`GlazeTheme.darkHue`, itself defaulting to the light seed hue).
*
* Ignored under `mode: 'static'`, which pins one value across all schemes.
*/
darkHue?: number | RelativeValue;
/**
* Dark-scheme saturation factor (0–1) applied to the dark seed
* saturation. Applies to the `dark` and `darkContrast` variants; falls
* back to `saturation` when omitted.
*
* When set, the global `darkDesaturation` reduction is **not** applied on
* top — the resulting saturation is taken literally. Ignored under
* `mode: 'static'`.
*/
darkSaturation?: number;
/** Name of another color in the same theme (dependent color). */
base?: string;
/**
* Contrast floor against the base color. A bare number/preset is WCAG;
* use `{ wcag }` / `{ apca }` to pick the metric. Accepts an HC pair.
*/
contrast?: HCPair<ContrastSpec>;
/** Adaptation mode. Default: 'auto'. */
mode?: AdaptationMode;
/**
* Whether to flip out-of-bounds results to the opposite side instead of
* clamping to the extreme. Affects both:
* - relative `tone`: when `base ± delta` exceeds `[0, 100]`, mirror the
* delta to the other side of the base. If the mirrored target is also
* out of range, keep the original delta and clamp on the authored side.
* - `contrast`: when the requested direction can't meet the floor, try the
* opposite side (same as the global `autoFlip`).
*
* Defaults to the global `autoFlip` config (default `true`). Set `false`
* to clamp instead.
*/
autoFlip?: boolean;
/**
* Fixed opacity (0–1).
* Output includes alpha in the CSS value.
* Does not affect contrast resolution — a semi-transparent color
* has no fixed perceived tone, so `contrast` and `opacity`
* should not be combined (a console.warn is emitted).
*/
opacity?: number;
/**
* Per-color override for the hue-independent "safe" chroma limit used in
* OKHSL↔sRGB conversions (luminance, contrast solving, output formatting).
* Falls through to the per-theme / per-token `pastel` override when omitted.
* @see GlazeConfigOverride.pastel
*/
pastel?: boolean;
/**
* Semantic role against `base`: how this color is used. Fixes APCA contrast
* polarity (the argument order in `apcaContrast`). WCAG is symmetric so it
* never affects WCAG results.
*
* Resolution: explicit `role` wins; else inferred from the color name when
* `inferRole` is enabled (default); else the opposite of the base's role;
* else defaults to `'text'` (foreground).
*/
role?: RoleInput;
/**
* Whether this color is inherited by child themes created via `extend()`.
* Default: true. Set to false to make this color local to the current theme.
*/
inherit?: boolean;
}
/** Shadow tuning knobs. All values use the 0–1 scale (OKHSL). */
export interface ShadowTuning {
/** Fraction of fg saturation kept in pigment (0-1). Default: 0.18. */
saturationFactor?: number;
/** Upper clamp on pigment saturation (0-1). Default: 0.25. */
maxSaturation?: number;
/** Multiplier for bg lightness → pigment lightness. Default: 0.25. */
lightnessFactor?: number;
/** [min, max] clamp for pigment lightness (0-1). Default: [0.05, 0.20]. */
lightnessBounds?: [number, number];
/**
* Target minimum gap between pigment lightness and bg lightness (0-1).
* Default: 0.05.
*/
minGapTarget?: number;
/** Max alpha (0-1). Reached at intensity=100 with max contrast. Default: 1.0. */
alphaMax?: number;
/**
* Blend weight (0-1) pulling pigment hue toward bg hue.
* 0 = pure fg hue, 1 = pure bg hue. Default: 0.2.
*/
bgHueBlend?: number;
}
export interface ShadowColorDef {
type: 'shadow';
/**
* Background color name — the surface the shadow sits on.
* Must reference a non-shadow color in the same theme.
*/
bg: string;
/**
* Foreground color name for tinting and intensity modulation.
* Must reference a non-shadow color in the same theme.
* Omit for achromatic shadow at full user-specified intensity.
*/
fg?: string;
/**
* Shadow intensity, 0-100.
* Supports [normal, highContrast] pair.
*/
intensity: HCPair<number>;
/** Override default tuning. Merged field-by-field with global `shadowTuning`. */
tuning?: ShadowTuning;
/**
* Per-color override for the hue-independent "safe" chroma limit used in
* OKHSL↔sRGB conversions (luminance, contrast solving, output formatting).
* Falls through to the per-theme / per-token `pastel` override when omitted.
* @see GlazeConfigOverride.pastel
*/
pastel?: boolean;
/**
* Whether this color is inherited by child themes created via `extend()`.
* Default: true. Set to false to make this color local to the current theme.
*/
inherit?: boolean;
}
export interface MixColorDef {
type: 'mix';
/** Background/base color name — the "from" color. */
base: string;
/** Target color name — the "to" color to mix toward. */
target: string;
/**
* Mix ratio 0–100 (0 = pure base, 100 = pure target).
* In 'transparent' blend mode, this controls the opacity of the target.
* Supports [normal, highContrast] pair.
*/
value: HCPair<number>;
/**
* Blending mode. Default: 'opaque'.
* - 'opaque': produces a solid color by interpolating base and target.
* - 'transparent': produces the target color with alpha = value/100.
*/
blend?: 'opaque' | 'transparent';
/**
* Interpolation color space for opaque blending. Default: 'okhsl'.
* - 'okhsl': perceptually uniform, consistent with Glaze's internal model.
* - 'srgb': linear sRGB interpolation, matches browser compositing.
*
* Ignored for 'transparent' blend (always composites in linear sRGB).
*/
space?: 'okhsl' | 'srgb';
/**
* Minimum contrast between the base and the resulting color.
* In 'opaque' mode, adjusts the mix ratio to meet contrast.
* In 'transparent' mode, adjusts opacity to meet contrast against the composite.
* A bare number/preset is WCAG; use `{ wcag }` / `{ apca }` to pick the
* metric. Supports [normal, highContrast] pair.
*/
contrast?: HCPair<ContrastSpec>;
/**
* Per-color override for the hue-independent "safe" chroma limit used in
* OKHSL↔sRGB conversions (luminance, contrast solving, output formatting).
* Falls through to the per-theme / per-token `pastel` override when omitted.
* @see GlazeConfigOverride.pastel
*/
pastel?: boolean;
/**
* Semantic role of the mixed result against `base`. Same semantics as
* `RegularColorDef.role` (fixes APCA polarity). Resolution and defaults
* are identical.
*/
role?: RoleInput;
/**
* Whether this color is inherited by child themes created via `extend()`.
* Default: true. Set to false to make this color local to the current theme.
*/
inherit?: boolean;
}
export type ColorDef = RegularColorDef | ShadowColorDef | MixColorDef;
export type ColorMap = Record<string, ColorDef>;
// ============================================================================
// Resolved internal types
// ============================================================================
/**
* Resolved color for a single scheme variant.
*
* Stored in OKHST: `h` / `s` are OKHSL hue/saturation, `t` is the canonical
* contrast-uniform tone (0–1, reference eps). Convert to OKHSL lightness via
* `variantToOkhsl` at the rendering / luminance edges.
*/
export interface ResolvedColorVariant {
/** OKHSL hue (0–360). */
h: number;
/** OKHSL saturation (0–1). */
s: number;
/** Canonical tone (0–1, reference eps). */
t: number;
/** Opacity (0–1). Default: 1. */
alpha: number;
/**
* Effective `pastel` flag used while resolving this variant (author def or
* config fallback). Carried on the variant so output formatting matches the
* gamut mapping applied during resolution.
*/
pastel?: boolean;
}
/** Fully resolved color across all scheme variants. */
export interface ResolvedColor {
name: string;
light: ResolvedColorVariant;
dark: ResolvedColorVariant;
lightContrast: ResolvedColorVariant;
darkContrast: ResolvedColorVariant;
/** Adaptation mode. Present only for regular colors, omitted for shadows. */
mode?: AdaptationMode;
}
// ============================================================================
// Configuration
// ============================================================================
/**
* A scheme tone window.
* - `[lo, hi]`: OKHSL-lightness endpoints (0–100) the authored tone is
* remapped into, using the reference eps `0.05`. The common form.
* - `{ lo, hi, eps }`: same, with an explicit render curvature `eps`
* (advanced — most palettes never need this).
* - `false`: disable clamping (full range `[0, 100]` at the reference eps).
* This removes the *boundaries*, not the tone curve.
*/
export type ToneWindow =
| false
| [number, number]
| { lo: number; hi: number; eps: number };
export interface GlazeConfig {
/** Light scheme tone window — `[lo, hi]` (default `[10, 100]`), `{ lo, hi, eps }` for advanced eps tuning, or `false` to disable clamping. */
lightTone?: ToneWindow;
/** Dark scheme tone window — `[lo, hi]` (default `[15, 95]`), `{ lo, hi, eps }`, or `false` to disable clamping. */
darkTone?: ToneWindow;
/** Saturation reduction factor for dark scheme (0–1). Default: 0.1. */
darkDesaturation?: number;
/**
* State alias names for Tasty token export. Default to media-query states
* (`'@media(prefers-color-scheme: dark)'` / `'@media(prefers-contrast: more)'`)
* so tokens react to the OS preference without registering custom states.
*/
states?: {
dark?: string;
highContrast?: string;
};
/**
* Which scheme variants to include in exports. Defaults: `dark: true`,
* `highContrast: false`.
*/
modes?: GlazeOutputModes;
/** Default tuning for all shadow colors. Per-color tuning merges field-by-field. */
shadowTuning?: ShadowTuning;
/**
* Manual contrast level — a 0–100 slider from normal contrast (`0`) to high
* contrast (`100`), or `'auto'` (the default) to take the authored normal
* entries as-is.
*
* With a number, Glaze resolves the `light` / `dark` variants *at* that level.
* That is all it does: `lightContrast` / `darkContrast` stay the true
* high-contrast resolution at every level, and `modes.highContrast` alone
* decides whether they are emitted. Level `0` therefore reproduces `'auto'`
* bit for bit. At `100` the normal variants *are* the high-contrast ones, so a
* separate tier would duplicate them and a global level of 100 drops it.
*
* The level interpolates all three things that make high contrast differ:
* authored `[normal, highContrast]` pairs, the tone-window widening, and
* the `AA → AAA` / APCA `+15 Lc` escalation. Contrast floors are re-solved
* at the interpolated target, so every level is a solution rather than an
* approximation, and a color keeps to one side of its base as the level moves.
*
* Pass `'auto'` to leave manual mode; `configure()` never clears a field by
* omission. See `docs/api.md` for the ramp's edge cases (un-interpolable
* pairs, side changes) and the export-freeze rule.
*
* @default 'auto'
*/
contrastLevel?: number | 'auto';
/**
* Automatically flip tone direction when contrast can't be met.
*
* When enabled (default `true`), the solver searches the requested
* tone direction first. If that direction can't reach the target,
* it tries the opposite direction and uses it when it passes. If neither
* side passes, the tone is pinned to the requested-direction
* extreme and a warning is emitted.
*
* Set to `false` for strict "no flip" behavior. The opposite
* direction is never considered: if the requested direction can't
* meet the target, the tone is pinned to its extreme (never
* falls back to the originally requested tone).
*/
autoFlip?: boolean;
/**
* If true (default), infer a color's `role` from its name when no explicit
* `role` is set. Set to `false` to opt out of name-based inference (the
* base-opposite and default-foreground fallbacks still apply).
* @default true
*/
inferRole?: boolean;
}
export interface GlazeConfigResolved {
lightTone: ToneWindow;
darkTone: ToneWindow;
darkDesaturation: number;
states: {
dark: string;
highContrast: string;
};
modes: Required<GlazeOutputModes>;
shadowTuning?: ShadowTuning;
autoFlip: boolean;
/**
* Manual contrast level (0–100) for the normal variants, or `'auto'` to take
* the authored normal entries as-is.
* @see GlazeConfig.contrastLevel
*/
contrastLevel: number | 'auto';
/**
* Instance-level pastel default (`def.pastel ?? config.pastel`).
* Not set via `glaze.configure()` — only via per-theme / per-token
* `GlazeConfigOverride` (default `false`).
*/
pastel: boolean;
inferRole: boolean;
}
/**
* Per-instance config override for `glaze.color()` and `glaze()` themes.
* Fields that are set take priority over the live global config. Fields
* that are omitted fall through to the live global at resolve time
* (`pastel` is instance-only and defaults to `false` when omitted).
*
* `false` for a tone window disables clamping (full range at reference eps).
*/
export interface GlazeConfigOverride {
/** Light scheme tone window, or `false` to disable clamping. */
lightTone?: ToneWindow;
/** Dark scheme tone window, or `false` to disable clamping. */
darkTone?: ToneWindow;
/** Saturation reduction factor for dark scheme (0–1). */
darkDesaturation?: number;
/** Whether to auto-flip tone when contrast can't be met. */
autoFlip?: boolean;
/**
* Manual contrast level (0–100) for this instance, or `'auto'` to opt out of a
* global level and take the authored normal entries as-is. Either way the
* instance keeps its high-contrast tier; a per-instance level never changes
* which modes are emitted.
*
* Only an instance-authored level is frozen into `.export()` snapshots — a
* level inherited from the global config is treated as a live preference and
* re-read at restore time.
*
* @see GlazeConfig.contrastLevel
*/
contrastLevel?: number | 'auto';
/**
* Instance-level pastel default for colors that omit per-color `pastel`.
* Not available on `glaze.configure()` — set here or per-color.
* @default false
*/
pastel?: boolean;
/**
* If true, infer a color's `role` from its name when no explicit `role` is
* set. Falls through to the live global at resolve time when omitted.
*/
inferRole?: boolean;
/**
* Shadow tuning defaults. Only meaningful for themes; harmless on
* standalone color tokens.
*/
shadowTuning?: ShadowTuning;
}
// ============================================================================
// Serialization
// ============================================================================
/**
* Current authoring-export schema version. Bump when the export shape
* changes in a non-compatible way. Written on every `.export()` snapshot.
*/
export const GLAZE_EXPORT_VERSION = 1 as const;
/** Literal type of {@link GLAZE_EXPORT_VERSION}. */
export type GlazeExportVersion = typeof GLAZE_EXPORT_VERSION;
/** Discriminator for authoring export snapshots. */
export type GlazeExportKind = 'theme' | 'color' | 'palette';
/** Serialized theme configuration (no resolved values). */
export interface GlazeThemeExport {
/** Snapshot kind. Always written by `theme.export()`; optional on legacy hand-written configs. */
kind?: 'theme';
/** Schema version. Always written by `theme.export()`; optional on legacy configs. */
version?: number;
hue: number;
saturation: number;
/** Dark-scheme seed hue (0–360). Omitted when the theme reuses `hue`. */
darkHue?: number;
/** Dark-scheme seed saturation (0–100). Omitted when the theme reuses `saturation`. */
darkSaturation?: number;
colors: ColorMap;
/**
* Effective config freeze from `.export()` —
* `getConfig() ∪ instance local ∪ exportArg`. May be sparse on legacy
* snapshots (omitted fields fall through to the live global at restore).
*/
config?: GlazeConfigOverride;
}
// ============================================================================
// Standalone shadow
// ============================================================================
/** Input for `glaze.shadow()` standalone factory. */
export interface GlazeShadowInput {
/**
* Background color — accepts any `GlazeColorValue` form: hex
* (`#rgb` / `#rrggbb` / `#rrggbbaa`), `rgb()` / `hsl()` / `okhsl()`
* / `oklch()` strings, or literal objects (`{ r, g, b }`, `{ h, s, l }`,
* `{ l, c, h }`). Alpha components are dropped with a warning.
*/
bg: GlazeColorValue;
/**
* Foreground color for tinting + intensity modulation. Accepts the
* same forms as `bg`.
*/
fg?: GlazeColorValue;
/** Intensity 0-100. */
intensity: number;
tuning?: ShadowTuning;
}
// ============================================================================
// Standalone color token
// ============================================================================
/** Input for the structured `glaze.color()` overload. */
export interface GlazeColorInput {
hue: number;
saturation: number;
tone: HCPair<number | ExtremeValue>;
saturationFactor?: number;
/**
* Dark-scheme hue. Number is absolute (0–360); `'+N'`/`'-N'` is relative
* to the dark seed hue, which defaults to `hue`. Falls back to `hue`.
*/
darkHue?: number | RelativeValue;
/** Dark-scheme seed saturation (0–100). Defaults to `saturation`. */
darkSaturation?: number;
/** Dark-scheme multiplier on the dark seed (0–1). Defaults to `saturationFactor`. */
darkSaturationFactor?: number;
mode?: AdaptationMode;
/** Flip out-of-bounds results instead of clamping. Default: global `autoFlip`. */
autoFlip?: boolean;
/**
* Fixed opacity (0–1). Output includes alpha in the CSS value.
* Combining with `contrast` is not recommended (perceived tone
* becomes unpredictable) — a `console.warn` is emitted in that case.
*/
opacity?: number;
/**
* Optional dependency on another color. Same semantics as
* `GlazeColorOverrides.base` — `contrast` and relative `tone`
* anchor to the base per scheme.
*/
base?: GlazeColorToken | GlazeColorValue;
/**
* Contrast floor against `base`. Requires `base` to be set. A bare
* number/preset is WCAG; use `{ wcag }` / `{ apca }` to pick the metric.
*/
contrast?: HCPair<ContrastSpec>;
/**
* Optional human-readable name for the token. Used in error and
* warning messages (otherwise an internal name like `"value"` is
* used). Does not affect output keys.
*/
name?: string;
/**
* Per-color override for the hue-independent "safe" chroma limit used in
* OKHSL↔sRGB conversions (luminance, contrast solving, output formatting).
* Falls through to the per-theme / per-token `pastel` override when omitted.
* @see GlazeConfigOverride.pastel
*/
pastel?: boolean;
/**
* Semantic role against `base` / the literal seed: how this token is used.
* Fixes APCA contrast polarity. Same resolution chain as
* `RegularColorDef.role` (explicit → name inference → opposite of base →
* `'text'`). For standalone tokens the name is internal, so set `role`
* explicitly or rely on the base-opposite / foreground default.
*/
role?: RoleInput;
}
/**
* Any single-color input form accepted by the value-shorthand
* overload of `glaze.color()`.
*
* Strings cover hex (`#rgb` / `#rrggbb` / `#rrggbbaa`, alpha dropped
* with a warning) and the four CSS color functions Glaze itself emits:
* `rgb()`, `hsl()`, `okhsl()`, `oklch()` (alpha components also dropped
* with a warning).
*
* Literal object forms:
* - `{ h, s, l }` — OKHSL (h: 0–360, s/l: 0–1). Passing 0–100 for `s`/`l`
* throws with a hint to use the structured form.
* - `{ h, s, t }` — OKHST (h: 0–360, s/t: 0–1). Tone in 0–1.
* - `{ r, g, b }` — sRGB 0–255.
* - `{ l, c, h }` — OKLCh (L/C: 0–1, H: degrees), same as `oklch()` strings.
*/
export type GlazeColorValue =
| string
| OkhslColor
| OkhstColor
| RgbColor
| OklchColor;
/** Color overrides for the `from` and value-shorthand inputs. */
export interface GlazeColorOverrides {
/**
* Override hue. Number is absolute (0–360); `'+N'`/`'-N'` is relative
* to the extracted (or overridden) seed hue — same semantics as
* `RegularColorDef.hue`.
*/
hue?: number | RelativeValue;
/** Override seed saturation (0–100). Default: extracted from value. */
saturation?: number;
/**
* Dark-scheme hue. Number is absolute (0–360); `'+N'`/`'-N'` is relative
* to the dark seed hue, which defaults to the (possibly overridden) seed
* hue. Falls back to `hue` when omitted.
*/
darkHue?: number | RelativeValue;
/**
* Dark-scheme seed saturation (0–100). Defaults to `saturation`. When set,
* the global `darkDesaturation` reduction is not applied on top.
*/
darkSaturation?: number;
/**
* Dark-scheme multiplier on the dark seed (0–1). Defaults to
* `saturationFactor`. When set, the global `darkDesaturation` reduction is
* not applied on top.
*/
darkSaturationFactor?: number;
/**
* Override tone. Number is absolute (0–100, contrast-uniform); `'+N'`/`'-N'`
* is relative to the literal seed (the value passed to `glaze.color()`);
* `'max'` / `'min'` force to the scheme's tone extreme.
* Supports HCPair for high-contrast.
*/
tone?: HCPair<ToneValue>;
/** Saturation multiplier on the seed (0–1). Default: 1. */
saturationFactor?: number;
/**
* Adaptation mode. Defaults to `'auto'` for every input form, so
* colors automatically adapt between light and dark like an ordinary
* theme color. Value-shorthand inputs (strings and literal objects)
* preserve light tone via a local `lightTone: false` default; other
* omitted config fields fall through to the live global at resolve
* time. Structured `{ hue, saturation, tone }` form also falls
* through for both tone windows unless overridden.
*
* Pass `'fixed'` explicitly to opt back into the linear, non-
* inverting mapping; pass `'static'` to pin the same tone
* across every variant.
*/
mode?: AdaptationMode;
/**
* Flip out-of-bounds results (relative `tone` overshoot / unmet
* `contrast`) to the opposite side instead of clamping. Defaults to
* the global `autoFlip`.
*/
autoFlip?: boolean;
/**
* Contrast floor. By default solved against the literal seed
* (the value itself); when `base` is set, solved against the base's
* resolved variant per scheme. Same shape as `RegularColorDef.contrast`
* (bare number/preset = WCAG; `{ wcag }` / `{ apca }` to pick the metric).
*/
contrast?: HCPair<ContrastSpec>;
/**
* Optional dependency on another color. Accepts either a
* `GlazeColorToken` (returned by another `glaze.color()`) or a raw
* `GlazeColorValue` (hex / CSS strings / `{ r, g, b }` / `{ h, s, l }` / …),
* which is automatically wrapped in `glaze.color(value)`.
*
* When set:
* - `contrast` is solved against the base's resolved variant
* per-scheme (light / dark / lightContrast / darkContrast).
* - Relative `tone: '+N'` / `'-N'` is anchored to the base's
* tone per-scheme (matches theme behavior for dependent colors).
* - Relative `hue: '+N'` / `'-N'` still anchors to the seed (the
* value passed to `glaze.color()`), not the base.
* - When the base was created via the structured form (with explicit
* `hue`/`saturation`/`tone`), it is resolved at full range
* (`lightTone: false`) for the linking math — ensuring the
* contrast/tone anchor matches the input tone, not the
* windowed output. The base's own `.resolve()` output is unaffected.
*
* The base token's `.resolve()` is called lazily on first resolve and
* its result is captured by reference; later mutations to the base's
* defining call don't apply (matches existing token snapshot semantics).
*/
base?: GlazeColorToken | GlazeColorValue;
/**
* Fixed opacity (0–1). Output includes alpha in the CSS value.
* Combining with `contrast` is not recommended (perceived tone
* becomes unpredictable) — a `console.warn` is emitted in that case.
*/
opacity?: number;
/**
* Optional human-readable name for the token. Used in error and
* warning messages (otherwise an internal name like `"value"` is
* used). Does not affect output keys.
*/
name?: string;
/**
* Per-color override for the hue-independent "safe" chroma limit used in
* OKHSL↔sRGB conversions (luminance, contrast solving, output formatting).
* Falls through to the per-theme / per-token `pastel` override when omitted.
* @see GlazeConfigOverride.pastel
*/
pastel?: boolean;
/**
* Semantic role against `base` / the literal seed: how this token is used.
* Fixes APCA contrast polarity. Same resolution chain as
* `RegularColorDef.role`.
*/
role?: RoleInput;
}
/**
* Object input for `glaze.color()` that carries a raw color value plus
* optional color overrides in the same object.
*
* ```ts
* glaze.color({ from: '#1a1a2e', base: bg, contrast: 'AA' })
* glaze.color({ from: { r: 38, g: 252, b: 178 }, tone: '+10' })
* ```
*/
export interface GlazeFromInput extends GlazeColorOverrides {
/** The source color value. Accepts the same forms as a bare `GlazeColorValue`. */
from: GlazeColorValue;
}
/** Options for `GlazeColorToken.css()`. */
export interface GlazeColorCssOptions {
/**
* Custom property base name (without leading `--`). Required.
* Becomes the variable identifier in the output, e.g.
* `name: 'brand'` → `--brand-color: …`.
*/
name: string;
/** Output color format. Default: 'oklch'. */
format?: GlazeColorFormat;
/**
* Suffix appended to the name. Default: '-color' (matches
* `theme.css` default).
*/
suffix?: string;
/**
* Emit hue as a separate custom property, referenced via `var()`.
* Requires `format: 'oklch'` and a pastel token. oklch + all-pastel only.
*/
splitHue?: boolean;
}
/** Return type for `glaze.color()`. */
export interface GlazeColorToken {
/** Resolve the color across all scheme variants. */
resolve(): ResolvedColor;
/** Export as a flat token map (no color name key). */
token(options?: GlazeTokenOptions): Record<string, string>;
/**
* Export as a tasty style-to-state binding (no color name key).
* Uses `#name` keys and state aliases (`''`, `'@media(prefers-color-scheme: dark)'`, etc.).
* @see https://tasty.style/docs
*/
tasty(options?: GlazeTokenOptions): Record<string, string>;
/** Export as a flat JSON map (no color name key). */
json(options?: GlazeJsonOptions): Record<string, string>;
/** Export as CSS custom property declarations grouped by scheme variant. */
css(options: GlazeColorCssOptions): GlazeCssResult;
/**
* Export as W3C DTCG color tokens (one per scheme variant, no color name
* key). Each entry is a full `{ $type: 'color', $value }` token.
* @see https://www.designtokens.org/
*/
dtcg(options?: GlazeDtcgOptions): GlazeColorDtcgResult;
/**
* Export as a single W3C DTCG Resolver-Module document for this color,
* keyed by `name` across all scheme variants. `name` is required.
* @see https://www.designtokens.org/
*/
dtcgResolver(
options: GlazeColorDtcgResolverOptions,
): GlazeDtcgResolverDocument;
/**
* Export as a Tailwind CSS v4 `@theme` block (light baseline) plus dark /
* high-contrast overrides. Returns a single ready-to-paste CSS string.
* `name` is required (forms `--color-<name>`).
* @see https://tailwindcss.com/docs/theme
*/
tailwind(options: GlazeColorTailwindOptions): string;
/**
* Serialize the token as a JSON-safe object. Captures the original
* input value, overrides, and config so it can be rehydrated via
* `glaze.colorFrom(...)`. `base` is recursively serialized.
* Optional `override` is merged over the instance local at export time.
*/
export(override?: GlazeConfigOverride): GlazeColorTokenExport;
}
/**
* JSON-safe serialization of a `glaze.color()` token. Pass to
* `glaze.colorFrom(...)` to rehydrate.
*/
export interface GlazeColorTokenExport {
/** Snapshot kind. Always written by `token.export()`; optional on legacy snapshots. */
kind?: 'color';
/** Schema version. Always written by `token.export()`; optional on legacy snapshots. */
version?: number;
/**
* Discriminator for the source overload that created the token.
* - `'value'`: created via `glaze.color(value)` or `glaze.color({ from, ...overrides })`.
* - `'structured'`: created via `glaze.color({ hue, saturation, ... })`.
*/
form: 'value' | 'structured';
/** Original input. For `form: 'value'` this is the raw `GlazeColorValue`; for `form: 'structured'` this is the structured input. */
input: GlazeColorValue | GlazeColorInputExport;
/**
* Overrides recorded at creation time. `base` is recursively
* serialized. Only present for `form: 'value'`.
*/
overrides?: GlazeColorOverridesExport;
/**
* Effective config freeze from `.export()` — `getConfig() ∪ local ∪
* exportArg` at call time. Used by `glaze.colorFrom()` to pin
* deterministic behavior across later `configure()` calls.
*/
config?: GlazeConfigOverride;
}
/**
* JSON-safe serialization of a `glaze.palette()` composition.
* Pass to `glaze.paletteFrom(...)` to rehydrate.
*/
export interface GlazePaletteExport {
/** Snapshot kind. Always written by `palette.export()`. */
kind?: 'palette';
/** Schema version. Always written by `palette.export()`. */
version?: number;
/** Per-theme authoring snapshots keyed by theme name. */
themes: Record<string, GlazeThemeExport>;
/** Primary theme name, if set on the palette. */
primary?: string;
}
/**