@tenphi/glaze 0.0.0-snapshot.c84faa6 → 0.0.0-snapshot.d38eee5

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/dist/index.d.cts CHANGED
@@ -1,55 +1,176 @@
1
1
  //#region src/contrast-solver.d.ts
2
+ type LinearRgb = [number, number, number];
3
+ type ContrastPreset = 'AA' | 'AAA' | 'AA-large' | 'AAA-large';
4
+ type MinContrast$1 = number | ContrastPreset;
2
5
  /**
3
- * OKHSL Contrast Solver
6
+ * Named APCA Lc floor presets (APCA Bronze Simple Mode conformance levels),
7
+ * independent of role. Use them anywhere an APCA target is accepted.
4
8
  *
5
- * Finds the closest OKHSL lightness that satisfies a WCAG 2 contrast target
6
- * against a base color. Used by glaze when resolving dependent colors
7
- * with `contrast`.
9
+ * | Preset | Lc | Use case |
10
+ * | ------------- | --- | ---------------------------------------------------- |
11
+ * | `'preferred'` | 90 | Preferred body / column text |
12
+ * | `'body'` | 75 | Minimum body / column text |
13
+ * | `'content'` | 60 | Readable non-body content (~WCAG AA 4.5:1) |
14
+ * | `'large'` | 45 | Large/bold headlines; fine icons/outlines (~3:1) |
15
+ * | `'non-text'` | 30 | Solid icons/controls; placeholder/disabled text |
16
+ * | `'min'` | 15 | Dividers/decorative; APCA "point of invisibility" |
8
17
  */
9
- type ContrastPreset = 'AA' | 'AAA' | 'AA-large' | 'AAA-large';
10
- type MinContrast$1 = number | ContrastPreset;
11
- interface FindLightnessForContrastOptions {
18
+ type ApcaPreset = 'preferred' | 'body' | 'content' | 'large' | 'non-text' | 'min';
19
+ /**
20
+ * Resolve an APCA target — a raw Lc number (kept as-is) or an `ApcaPreset`
21
+ * keyword mapped to its Lc value. The magnitude is forced non-negative.
22
+ */
23
+ declare function resolveApcaTarget(value: number | ApcaPreset): number;
24
+ /** Metric + numeric target after resolving a `ContrastSpec` for a mode. */
25
+ interface ResolvedContrast {
26
+ metric: 'wcag' | 'apca';
27
+ /** WCAG ratio (>= 1) or APCA Lc magnitude (0–106). */
28
+ target: number;
29
+ /**
30
+ * APCA argument order: which side the resolved (candidate) color plays
31
+ * against the base. `'fg'` (default) → `apcaContrast(yCandidate, yBase)`;
32
+ * `'bg'` → `apcaContrast(yBase, yCandidate)`. Always `'fg'` for WCAG
33
+ * (symmetric, ignored).
34
+ */
35
+ polarity?: 'fg' | 'bg';
36
+ }
37
+ declare function resolveMinContrast(value: MinContrast$1): number;
38
+ /**
39
+ * Resolve a `ContrastSpec` (already selected from any outer HC pair) for a
40
+ * given mode into `{ metric, target }`. Handles the inner metric HC pair and
41
+ * preset resolution. `polarity` is passed through to the result for the APCA
42
+ * branch (it controls argument order in the solver); WCAG ignores it.
43
+ */
44
+ declare function resolveContrastForMode(spec: ContrastSpec, isHighContrast: boolean, polarity?: 'fg' | 'bg'): ResolvedContrast;
45
+ /**
46
+ * APCA lightness contrast (Lc), signed: positive for dark text on light bg,
47
+ * negative for light text on dark bg. Inputs are screen luminances (0–1).
48
+ */
49
+ declare function apcaContrast(yText: number, yBg: number): number;
50
+ interface FindToneForContrastOptions {
12
51
  /** Hue of the candidate color (0–360). */
13
52
  hue: number;
14
53
  /** Saturation of the candidate color (0–1). */
15
54
  saturation: number;
16
- /** Preferred lightness of the candidate (0–1). */
17
- preferredLightness: number;
18
- /** Base/reference color as linear sRGB (channels may be outside 0–1 before clamp). */
19
- baseLinearRgb: [number, number, number];
20
- /** WCAG contrast ratio target floor. */
21
- contrast: MinContrast$1;
22
- /** Search bounds for lightness. Default: [0, 1]. */
23
- lightnessRange?: [number, number];
55
+ /** Preferred tone of the candidate (0–1). */
56
+ preferredTone: number;
57
+ /** Base/reference color as linear sRGB. */
58
+ baseLinearRgb: LinearRgb;
59
+ /** Resolved contrast floor (metric + target). */
60
+ contrast: ResolvedContrast;
61
+ /** Search bounds for tone. Default: [0, 1]. */
62
+ toneRange?: [number, number];
24
63
  /** Convergence threshold. Default: 1e-4. */
25
64
  epsilon?: number;
26
- /** Maximum binary-search iterations per branch. Default: 14. */
65
+ /** Maximum binary-search iterations per branch. Default: 18. */
27
66
  maxIterations?: number;
67
+ /** Preferred search direction before auto-flip is considered. */
68
+ initialDirection?: 'lighter' | 'darker';
69
+ /** Auto-flip tone direction when contrast can't be met. Default: false. */
70
+ flip?: boolean;
71
+ /** Use the hue-independent "safe" chroma boundary. Default: false. */
72
+ pastel?: boolean;
28
73
  }
29
- interface FindLightnessForContrastResult {
30
- /** Chosen lightness in 0–1. */
31
- lightness: number;
32
- /** Achieved WCAG contrast ratio. */
74
+ interface FindToneForContrastResult {
75
+ /** Chosen tone in 0–1. */
76
+ tone: number;
77
+ /** Achieved score (WCAG ratio or APCA Lc magnitude). */
33
78
  contrast: number;
34
79
  /** Whether the target was reached. */
35
80
  met: boolean;
36
81
  /** Which branch was selected. */
37
82
  branch: 'lighter' | 'darker' | 'preferred';
83
+ /** Whether the result auto-flipped to the opposite direction. */
84
+ flipped?: boolean;
85
+ }
86
+ /**
87
+ * Find the tone that satisfies a contrast floor against a base color,
88
+ * staying as close to `preferredTone` as possible.
89
+ */
90
+ declare function findToneForContrast(options: FindToneForContrastOptions): FindToneForContrastResult;
91
+ interface FindValueForMixContrastOptions {
92
+ /** Preferred mix parameter (0–1). */
93
+ preferredValue: number;
94
+ /** Base color as linear sRGB. */
95
+ baseLinearRgb: LinearRgb;
96
+ /** Target color as linear sRGB. */
97
+ targetLinearRgb: LinearRgb;
98
+ /** Resolved contrast floor (metric + target). */
99
+ contrast: ResolvedContrast;
100
+ /** Compute the luminance of the mixed color at parameter t. */
101
+ luminanceAtValue: (t: number) => number;
102
+ /** Convergence threshold. Default: 1e-4. */
103
+ epsilon?: number;
104
+ /** Maximum binary-search iterations per branch. Default: 20. */
105
+ maxIterations?: number;
106
+ /** Auto-flip mix direction when contrast can't be met. Default: false. */
107
+ flip?: boolean;
108
+ }
109
+ interface FindValueForMixContrastResult {
110
+ value: number;
111
+ contrast: number;
112
+ met: boolean;
113
+ flipped?: boolean;
38
114
  }
39
- declare function resolveMinContrast(value: MinContrast$1): number;
40
115
  /**
41
- * Find the OKHSL lightness that satisfies a WCAG 2 contrast target
42
- * against a base color, staying as close to `preferredLightness` as possible.
116
+ * Find the mix parameter (ratio or opacity) that satisfies a contrast floor
117
+ * against a base color, staying as close to `preferredValue` as possible.
43
118
  */
44
- declare function findLightnessForContrast(options: FindLightnessForContrastOptions): FindLightnessForContrastResult;
119
+ declare function findValueForMixContrast(options: FindValueForMixContrastOptions): FindValueForMixContrastResult;
45
120
  //#endregion
46
121
  //#region src/types.d.ts
47
122
  /** A value or [normal, high-contrast] pair. */
48
123
  type HCPair<T> = T | [T, T];
124
+ /** Bare WCAG contrast target: a ratio number or a named preset. */
49
125
  type MinContrast = number | ContrastPreset;
126
+ /**
127
+ * A contrast floor with a pluggable metric.
128
+ *
129
+ * - `number` / `ContrastPreset`: a WCAG ratio (bare form).
130
+ * - `{ wcag }`: WCAG ratio or preset, optionally an HC pair.
131
+ * - `{ apca }`: APCA Lc target (absolute value or preset), optionally an HC pair.
132
+ *
133
+ * The `[normal, highContrast]` pair may live at the outer level
134
+ * (`[4.5, 7]`, `[{ wcag: 4.5 }, { wcag: 7 }]`) or inside the metric
135
+ * (`{ wcag: [4.5, 7] }`, `{ apca: [45, 60] }`, `{ apca: ['content', 'body'] }`).
136
+ */
137
+ type ContrastSpec = number | ContrastPreset | {
138
+ wcag: HCPair<number | ContrastPreset>;
139
+ } | {
140
+ apca: HCPair<number | ApcaPreset>;
141
+ };
142
+ /**
143
+ * The semantic role a color plays against its base, used to fix APCA contrast
144
+ * polarity (which side is the foreground vs the background). WCAG is
145
+ * symmetric, so role never changes WCAG results.
146
+ */
147
+ type Role = 'text' | 'surface' | 'border';
148
+ /**
149
+ * Any string accepted as a `role`. Canonical values plus aliases normalized by
150
+ * `normalizeRole` (see `roles.ts`): `surface` (bg/background/fill/canvas/
151
+ * paper/layer), `text` (fg/foreground/content/ink/label/stroke), `border`
152
+ * (divider/outline/separator/hairline/rule).
153
+ */
154
+ type RoleInput = Role | 'bg' | 'background' | 'fill' | 'canvas' | 'paper' | 'layer' | 'fg' | 'foreground' | 'content' | 'ink' | 'label' | 'stroke' | 'divider' | 'outline' | 'separator' | 'hairline' | 'rule';
50
155
  type AdaptationMode = 'auto' | 'fixed' | 'static';
51
156
  /** A signed relative offset string, e.g. '+20' or '-15.5'. */
52
157
  type RelativeValue = `+${number}` | `-${number}`;
158
+ /**
159
+ * Force a color to a tone extreme:
160
+ * - `'max'`: the highest tone in the active scheme range/window.
161
+ * - `'min'`: the lowest tone.
162
+ *
163
+ * Under `mode: 'auto'` the extreme inverts in the dark scheme (so `'max'`
164
+ * tracks the inversion and becomes the darkest tone). No `base` required.
165
+ */
166
+ type ExtremeValue = 'max' | 'min';
167
+ /**
168
+ * A tone value as authored on a color.
169
+ * - Number: absolute tone (0–100).
170
+ * - `'+N'` / `'-N'`: relative to the base's tone (requires `base`).
171
+ * - `'max'` / `'min'`: forced to the scheme's tone extreme (no base needed).
172
+ */
173
+ type ToneValue = number | RelativeValue | ExtremeValue;
53
174
  /** Color format for output. */
54
175
  type GlazeColorFormat = 'okhsl' | 'rgb' | 'hsl' | 'oklch';
55
176
  /**
@@ -70,13 +191,35 @@ interface OkhslColor {
70
191
  s: number;
71
192
  l: number;
72
193
  }
194
+ /**
195
+ * Direct OKHST color input — OKHSL with the lightness axis replaced by the
196
+ * contrast-uniform tone axis. `h`: 0–360, `s`: 0–1, `t`: 0–1 (tone).
197
+ */
198
+ interface OkhstColor {
199
+ h: number;
200
+ s: number;
201
+ t: number;
202
+ }
203
+ /** sRGB components in 0–255 (value-shorthand object form). */
204
+ interface RgbColor {
205
+ r: number;
206
+ g: number;
207
+ b: number;
208
+ }
209
+ /** OKLCh components matching CSS `oklch(L C H)` (L/C: 0–1, H: degrees). */
210
+ interface OklchColor {
211
+ l: number;
212
+ c: number;
213
+ h: number;
214
+ }
73
215
  interface RegularColorDef {
74
216
  /**
75
- * Lightness value (0–100).
76
- * - Number: absolute lightness.
77
- * - String ('+N' / '-N'): relative to base color's lightness (requires `base`).
217
+ * Tone value (0–100, contrast-uniform — see `docs/okhst.md`).
218
+ * - Number: absolute tone.
219
+ * - String ('+N' / '-N'): relative to base color's tone (requires `base`).
220
+ * - `'max'` / `'min'`: force to the scheme's tone extreme (no base needed).
78
221
  */
79
- lightness?: HCPair<number | RelativeValue>;
222
+ tone?: HCPair<ToneValue>;
80
223
  /** Saturation factor applied to the seed saturation (0–1, default: 1). */
81
224
  saturation?: number;
82
225
  /**
@@ -87,18 +230,55 @@ interface RegularColorDef {
87
230
  hue?: number | RelativeValue;
88
231
  /** Name of another color in the same theme (dependent color). */
89
232
  base?: string;
90
- /** WCAG contrast ratio floor against the base color. */
91
- contrast?: HCPair<MinContrast>;
233
+ /**
234
+ * Contrast floor against the base color. A bare number/preset is WCAG;
235
+ * use `{ wcag }` / `{ apca }` to pick the metric. Accepts an HC pair.
236
+ */
237
+ contrast?: HCPair<ContrastSpec>;
92
238
  /** Adaptation mode. Default: 'auto'. */
93
239
  mode?: AdaptationMode;
240
+ /**
241
+ * Whether to flip out-of-bounds results to the opposite side instead of
242
+ * clamping to the extreme. Affects both:
243
+ * - relative `tone`: when `base ± delta` exceeds `[0, 100]`, mirror the
244
+ * delta to the other side of the base.
245
+ * - `contrast`: when the requested direction can't meet the floor, try the
246
+ * opposite side (same as the global `autoFlip`).
247
+ *
248
+ * Defaults to the global `autoFlip` config (default `true`). Set `false`
249
+ * to clamp instead.
250
+ */
251
+ flip?: boolean;
94
252
  /**
95
253
  * Fixed opacity (0–1).
96
254
  * Output includes alpha in the CSS value.
97
255
  * Does not affect contrast resolution — a semi-transparent color
98
- * has no fixed perceived lightness, so `contrast` and `opacity`
256
+ * has no fixed perceived tone, so `contrast` and `opacity`
99
257
  * should not be combined (a console.warn is emitted).
100
258
  */
101
259
  opacity?: number;
260
+ /**
261
+ * Per-color override for the hue-independent "safe" chroma limit used in
262
+ * OKHSL↔sRGB conversions (luminance, contrast solving, output formatting).
263
+ * Falls through to the global / per-theme `pastel` config when omitted.
264
+ * @see GlazeConfig.pastel
265
+ */
266
+ pastel?: boolean;
267
+ /**
268
+ * Semantic role against `base`: how this color is used. Fixes APCA contrast
269
+ * polarity (the argument order in `apcaContrast`). WCAG is symmetric so it
270
+ * never affects WCAG results.
271
+ *
272
+ * Resolution: explicit `role` wins; else inferred from the color name when
273
+ * `inferRole` is enabled (default); else the opposite of the base's role;
274
+ * else defaults to `'text'` (foreground).
275
+ */
276
+ role?: RoleInput;
277
+ /**
278
+ * Whether this color is inherited by child themes created via `extend()`.
279
+ * Default: true. Set to false to make this color local to the current theme.
280
+ */
281
+ inherit?: boolean;
102
282
  }
103
283
  /** Shadow tuning knobs. All values use the 0–1 scale (OKHSL). */
104
284
  interface ShadowTuning {
@@ -143,19 +323,96 @@ interface ShadowColorDef {
143
323
  intensity: HCPair<number>;
144
324
  /** Override default tuning. Merged field-by-field with global `shadowTuning`. */
145
325
  tuning?: ShadowTuning;
326
+ /**
327
+ * Per-color override for the hue-independent "safe" chroma limit used in
328
+ * OKHSL↔sRGB conversions (luminance, contrast solving, output formatting).
329
+ * Falls through to the global / per-theme `pastel` config when omitted.
330
+ * @see GlazeConfig.pastel
331
+ */
332
+ pastel?: boolean;
333
+ /**
334
+ * Whether this color is inherited by child themes created via `extend()`.
335
+ * Default: true. Set to false to make this color local to the current theme.
336
+ */
337
+ inherit?: boolean;
146
338
  }
147
- type ColorDef = RegularColorDef | ShadowColorDef;
339
+ interface MixColorDef {
340
+ type: 'mix';
341
+ /** Background/base color name — the "from" color. */
342
+ base: string;
343
+ /** Target color name — the "to" color to mix toward. */
344
+ target: string;
345
+ /**
346
+ * Mix ratio 0–100 (0 = pure base, 100 = pure target).
347
+ * In 'transparent' blend mode, this controls the opacity of the target.
348
+ * Supports [normal, highContrast] pair.
349
+ */
350
+ value: HCPair<number>;
351
+ /**
352
+ * Blending mode. Default: 'opaque'.
353
+ * - 'opaque': produces a solid color by interpolating base and target.
354
+ * - 'transparent': produces the target color with alpha = value/100.
355
+ */
356
+ blend?: 'opaque' | 'transparent';
357
+ /**
358
+ * Interpolation color space for opaque blending. Default: 'okhsl'.
359
+ * - 'okhsl': perceptually uniform, consistent with Glaze's internal model.
360
+ * - 'srgb': linear sRGB interpolation, matches browser compositing.
361
+ *
362
+ * Ignored for 'transparent' blend (always composites in linear sRGB).
363
+ */
364
+ space?: 'okhsl' | 'srgb';
365
+ /**
366
+ * Minimum contrast between the base and the resulting color.
367
+ * In 'opaque' mode, adjusts the mix ratio to meet contrast.
368
+ * In 'transparent' mode, adjusts opacity to meet contrast against the composite.
369
+ * A bare number/preset is WCAG; use `{ wcag }` / `{ apca }` to pick the
370
+ * metric. Supports [normal, highContrast] pair.
371
+ */
372
+ contrast?: HCPair<ContrastSpec>;
373
+ /**
374
+ * Per-color override for the hue-independent "safe" chroma limit used in
375
+ * OKHSL↔sRGB conversions (luminance, contrast solving, output formatting).
376
+ * Falls through to the global / per-theme `pastel` config when omitted.
377
+ * @see GlazeConfig.pastel
378
+ */
379
+ pastel?: boolean;
380
+ /**
381
+ * Semantic role of the mixed result against `base`. Same semantics as
382
+ * `RegularColorDef.role` (fixes APCA polarity). Resolution and defaults
383
+ * are identical.
384
+ */
385
+ role?: RoleInput;
386
+ /**
387
+ * Whether this color is inherited by child themes created via `extend()`.
388
+ * Default: true. Set to false to make this color local to the current theme.
389
+ */
390
+ inherit?: boolean;
391
+ }
392
+ type ColorDef = RegularColorDef | ShadowColorDef | MixColorDef;
148
393
  type ColorMap = Record<string, ColorDef>;
149
- /** Resolved color for a single scheme variant. */
394
+ /**
395
+ * Resolved color for a single scheme variant.
396
+ *
397
+ * Stored in OKHST: `h` / `s` are OKHSL hue/saturation, `t` is the canonical
398
+ * contrast-uniform tone (0–1, reference eps). Convert to OKHSL lightness via
399
+ * `variantToOkhsl` at the rendering / luminance edges.
400
+ */
150
401
  interface ResolvedColorVariant {
151
402
  /** OKHSL hue (0–360). */
152
403
  h: number;
153
404
  /** OKHSL saturation (0–1). */
154
405
  s: number;
155
- /** OKHSL lightness (0–1). */
156
- l: number;
406
+ /** Canonical tone (0–1, reference eps). */
407
+ t: number;
157
408
  /** Opacity (0–1). Default: 1. */
158
409
  alpha: number;
410
+ /**
411
+ * Effective `pastel` flag used while resolving this variant (author def or
412
+ * config fallback). Carried on the variant so output formatting matches the
413
+ * gamut mapping applied during resolution.
414
+ */
415
+ pastel?: boolean;
159
416
  }
160
417
  /** Fully resolved color across all scheme variants. */
161
418
  interface ResolvedColor {
@@ -167,11 +424,25 @@ interface ResolvedColor {
167
424
  /** Adaptation mode. Present only for regular colors, omitted for shadows. */
168
425
  mode?: AdaptationMode;
169
426
  }
427
+ /**
428
+ * A scheme tone window.
429
+ * - `[lo, hi]`: OKHSL-lightness endpoints (0–100) the authored tone is
430
+ * remapped into, using the reference eps `0.05`. The common form.
431
+ * - `{ lo, hi, eps }`: same, with an explicit render curvature `eps`
432
+ * (advanced — most palettes never need this).
433
+ * - `false`: disable clamping (full range `[0, 100]` at the reference eps).
434
+ * This removes the *boundaries*, not the tone curve.
435
+ */
436
+ type ToneWindow = false | [number, number] | {
437
+ lo: number;
438
+ hi: number;
439
+ eps: number;
440
+ };
170
441
  interface GlazeConfig {
171
- /** Light scheme lightness window [lo, hi]. Default: [10, 100]. */
172
- lightLightness?: [number, number];
173
- /** Dark scheme lightness window [lo, hi]. Default: [15, 95]. */
174
- darkLightness?: [number, number];
442
+ /** Light scheme tone window — `[lo, hi]` (default `[10, 100]`), `{ lo, hi, eps }` for advanced eps tuning, or `false` to disable clamping. */
443
+ lightTone?: ToneWindow;
444
+ /** Dark scheme tone window — `[lo, hi]` (default `[15, 95]`), `{ lo, hi, eps }`, or `false` to disable clamping. */
445
+ darkTone?: ToneWindow;
175
446
  /** Saturation reduction factor for dark scheme (0–1). Default: 0.1. */
176
447
  darkDesaturation?: number;
177
448
  /** State alias names for token export. */
@@ -183,10 +454,39 @@ interface GlazeConfig {
183
454
  modes?: GlazeOutputModes;
184
455
  /** Default tuning for all shadow colors. Per-color tuning merges field-by-field. */
185
456
  shadowTuning?: ShadowTuning;
457
+ /**
458
+ * Automatically flip tone direction when contrast can't be met.
459
+ *
460
+ * When enabled (default `true`), the solver searches the requested
461
+ * tone direction first. If that direction can't reach the target,
462
+ * it tries the opposite direction and uses it when it passes. If neither
463
+ * side passes, the tone is pinned to the requested-direction
464
+ * extreme and a warning is emitted.
465
+ *
466
+ * Set to `false` for strict "no flip" behavior. The opposite
467
+ * direction is never considered: if the requested direction can't
468
+ * meet the target, the tone is pinned to its extreme (never
469
+ * falls back to the originally requested tone).
470
+ */
471
+ autoFlip?: boolean;
472
+ /**
473
+ * If true, uses a hue-independent "safe" chroma limit across all colors
474
+ * so that scaling saturation never exceeds the sRGB boundary at any hue
475
+ * for the given lightness.
476
+ * @default false
477
+ */
478
+ pastel?: boolean;
479
+ /**
480
+ * If true (default), infer a color's `role` from its name when no explicit
481
+ * `role` is set. Set to `false` to opt out of name-based inference (the
482
+ * base-opposite and default-foreground fallbacks still apply).
483
+ * @default true
484
+ */
485
+ inferRole?: boolean;
186
486
  }
187
487
  interface GlazeConfigResolved {
188
- lightLightness: [number, number];
189
- darkLightness: [number, number];
488
+ lightTone: ToneWindow;
489
+ darkTone: ToneWindow;
190
490
  darkDesaturation: number;
191
491
  states: {
192
492
  dark: string;
@@ -194,30 +494,258 @@ interface GlazeConfigResolved {
194
494
  };
195
495
  modes: Required<GlazeOutputModes>;
196
496
  shadowTuning?: ShadowTuning;
497
+ autoFlip: boolean;
498
+ pastel: boolean;
499
+ inferRole: boolean;
500
+ }
501
+ /**
502
+ * Per-instance config override for `glaze.color()` and `glaze()` themes.
503
+ * Fields that are set take priority over the live global config. Fields
504
+ * that are omitted fall through to the live global at resolve time.
505
+ *
506
+ * `false` for a tone window disables clamping (full range at reference eps).
507
+ */
508
+ interface GlazeConfigOverride {
509
+ /** Light scheme tone window, or `false` to disable clamping. */
510
+ lightTone?: ToneWindow;
511
+ /** Dark scheme tone window, or `false` to disable clamping. */
512
+ darkTone?: ToneWindow;
513
+ /** Saturation reduction factor for dark scheme (0–1). */
514
+ darkDesaturation?: number;
515
+ /** Whether to auto-flip tone when contrast can't be met. */
516
+ autoFlip?: boolean;
517
+ /**
518
+ * If true, uses a hue-independent "safe" chroma limit across all colors
519
+ * so that scaling saturation never exceeds the sRGB boundary at any hue
520
+ * for the given lightness.
521
+ */
522
+ pastel?: boolean;
523
+ /**
524
+ * If true, infer a color's `role` from its name when no explicit `role` is
525
+ * set. Falls through to the live global at resolve time when omitted.
526
+ */
527
+ inferRole?: boolean;
528
+ /**
529
+ * Shadow tuning defaults. Only meaningful for themes; harmless on
530
+ * standalone color tokens.
531
+ */
532
+ shadowTuning?: ShadowTuning;
197
533
  }
198
534
  /** Serialized theme configuration (no resolved values). */
199
535
  interface GlazeThemeExport {
200
536
  hue: number;
201
537
  saturation: number;
202
538
  colors: ColorMap;
539
+ /** Per-theme config override, if any. */
540
+ config?: GlazeConfigOverride;
203
541
  }
204
542
  /** Input for `glaze.shadow()` standalone factory. */
205
543
  interface GlazeShadowInput {
206
- /** Background color — hex string or OKHSL { h, s (0-1), l (0-1) }. */
207
- bg: HexColor | OkhslColor;
208
- /** Foreground color for tinting + intensity modulation. */
209
- fg?: HexColor | OkhslColor;
544
+ /**
545
+ * Background color — accepts any `GlazeColorValue` form: hex
546
+ * (`#rgb` / `#rrggbb` / `#rrggbbaa`), `rgb()` / `hsl()` / `okhsl()`
547
+ * / `oklch()` strings, or literal objects (`{ r, g, b }`, `{ h, s, l }`,
548
+ * `{ l, c, h }`). Alpha components are dropped with a warning.
549
+ */
550
+ bg: GlazeColorValue;
551
+ /**
552
+ * Foreground color for tinting + intensity modulation. Accepts the
553
+ * same forms as `bg`.
554
+ */
555
+ fg?: GlazeColorValue;
210
556
  /** Intensity 0-100. */
211
557
  intensity: number;
212
558
  tuning?: ShadowTuning;
213
559
  }
214
- /** Input for `glaze.color()` standalone factory. */
560
+ /** Input for the structured `glaze.color()` overload. */
215
561
  interface GlazeColorInput {
216
562
  hue: number;
217
563
  saturation: number;
218
- lightness: HCPair<number>;
564
+ tone: HCPair<number | ExtremeValue>;
565
+ saturationFactor?: number;
566
+ mode?: AdaptationMode;
567
+ /** Flip out-of-bounds results instead of clamping. Default: global `autoFlip`. */
568
+ flip?: boolean;
569
+ /**
570
+ * Fixed opacity (0–1). Output includes alpha in the CSS value.
571
+ * Combining with `contrast` is not recommended (perceived tone
572
+ * becomes unpredictable) — a `console.warn` is emitted in that case.
573
+ */
574
+ opacity?: number;
575
+ /**
576
+ * Optional dependency on another color. Same semantics as
577
+ * `GlazeColorOverrides.base` — `contrast` and relative `tone`
578
+ * anchor to the base per scheme.
579
+ */
580
+ base?: GlazeColorToken | GlazeColorValue;
581
+ /**
582
+ * Contrast floor against `base`. Requires `base` to be set. A bare
583
+ * number/preset is WCAG; use `{ wcag }` / `{ apca }` to pick the metric.
584
+ */
585
+ contrast?: HCPair<ContrastSpec>;
586
+ /**
587
+ * Optional human-readable name for the token. Used in error and
588
+ * warning messages (otherwise an internal name like `"value"` is
589
+ * used). Does not affect output keys.
590
+ */
591
+ name?: string;
592
+ /**
593
+ * Per-color override for the hue-independent "safe" chroma limit used in
594
+ * OKHSL↔sRGB conversions (luminance, contrast solving, output formatting).
595
+ * Falls through to the global / per-theme `pastel` config when omitted.
596
+ * @see GlazeConfig.pastel
597
+ */
598
+ pastel?: boolean;
599
+ /**
600
+ * Semantic role against `base` / the literal seed: how this token is used.
601
+ * Fixes APCA contrast polarity. Same resolution chain as
602
+ * `RegularColorDef.role` (explicit → name inference → opposite of base →
603
+ * `'text'`). For standalone tokens the name is internal, so set `role`
604
+ * explicitly or rely on the base-opposite / foreground default.
605
+ */
606
+ role?: RoleInput;
607
+ }
608
+ /**
609
+ * Any single-color input form accepted by the value-shorthand
610
+ * overload of `glaze.color()`.
611
+ *
612
+ * Strings cover hex (`#rgb` / `#rrggbb` / `#rrggbbaa`, alpha dropped
613
+ * with a warning) and the four CSS color functions Glaze itself emits:
614
+ * `rgb()`, `hsl()`, `okhsl()`, `oklch()` (alpha components also dropped
615
+ * with a warning).
616
+ *
617
+ * Literal object forms:
618
+ * - `{ h, s, l }` — OKHSL (h: 0–360, s/l: 0–1). Passing 0–100 for `s`/`l`
619
+ * throws with a hint to use the structured form.
620
+ * - `{ h, s, t }` — OKHST (h: 0–360, s/t: 0–1). Tone in 0–1.
621
+ * - `{ r, g, b }` — sRGB 0–255.
622
+ * - `{ l, c, h }` — OKLCh (L/C: 0–1, H: degrees), same as `oklch()` strings.
623
+ */
624
+ type GlazeColorValue = string | OkhslColor | OkhstColor | RgbColor | OklchColor;
625
+ /** Color overrides for the `from` and value-shorthand inputs. */
626
+ interface GlazeColorOverrides {
627
+ /**
628
+ * Override hue. Number is absolute (0–360); `'+N'`/`'-N'` is relative
629
+ * to the extracted (or overridden) seed hue — same semantics as
630
+ * `RegularColorDef.hue`.
631
+ */
632
+ hue?: number | RelativeValue;
633
+ /** Override seed saturation (0–100). Default: extracted from value. */
634
+ saturation?: number;
635
+ /**
636
+ * Override tone. Number is absolute (0–100, contrast-uniform); `'+N'`/`'-N'`
637
+ * is relative to the literal seed (the value passed to `glaze.color()`);
638
+ * `'max'` / `'min'` force to the scheme's tone extreme.
639
+ * Supports HCPair for high-contrast.
640
+ */
641
+ tone?: HCPair<ToneValue>;
642
+ /** Saturation multiplier on the seed (0–1). Default: 1. */
219
643
  saturationFactor?: number;
644
+ /**
645
+ * Adaptation mode. Defaults to `'auto'` for every input form, so
646
+ * colors automatically adapt between light and dark like an ordinary
647
+ * theme color. All value-shorthand inputs (strings and literal objects)
648
+ * preserve light tone (`lightTone: false`) and snapshot
649
+ * `globalConfig.darkTone` on the dark side. Only the structured
650
+ * `{ hue, saturation, tone }` form also snapshots
651
+ * `globalConfig.lightTone`.
652
+ *
653
+ * Pass `'fixed'` explicitly to opt back into the linear, non-
654
+ * inverting mapping; pass `'static'` to pin the same tone
655
+ * across every variant.
656
+ */
220
657
  mode?: AdaptationMode;
658
+ /**
659
+ * Flip out-of-bounds results (relative `tone` overshoot / unmet
660
+ * `contrast`) to the opposite side instead of clamping. Defaults to
661
+ * the global `autoFlip`.
662
+ */
663
+ flip?: boolean;
664
+ /**
665
+ * Contrast floor. By default solved against the literal seed
666
+ * (the value itself); when `base` is set, solved against the base's
667
+ * resolved variant per scheme. Same shape as `RegularColorDef.contrast`
668
+ * (bare number/preset = WCAG; `{ wcag }` / `{ apca }` to pick the metric).
669
+ */
670
+ contrast?: HCPair<ContrastSpec>;
671
+ /**
672
+ * Optional dependency on another color. Accepts either a
673
+ * `GlazeColorToken` (returned by another `glaze.color()`) or a raw
674
+ * `GlazeColorValue` (hex / CSS strings / `{ r, g, b }` / `{ h, s, l }` / …),
675
+ * which is automatically wrapped in `glaze.color(value)`.
676
+ *
677
+ * When set:
678
+ * - `contrast` is solved against the base's resolved variant
679
+ * per-scheme (light / dark / lightContrast / darkContrast).
680
+ * - Relative `tone: '+N'` / `'-N'` is anchored to the base's
681
+ * tone per-scheme (matches theme behavior for dependent colors).
682
+ * - Relative `hue: '+N'` / `'-N'` still anchors to the seed (the
683
+ * value passed to `glaze.color()`), not the base.
684
+ * - When the base was created via the structured form (with explicit
685
+ * `hue`/`saturation`/`tone`), it is resolved at full range
686
+ * (`lightTone: false`) for the linking math — ensuring the
687
+ * contrast/tone anchor matches the input tone, not the
688
+ * windowed output. The base's own `.resolve()` output is unaffected.
689
+ *
690
+ * The base token's `.resolve()` is called lazily on first resolve and
691
+ * its result is captured by reference; later mutations to the base's
692
+ * defining call don't apply (matches existing token snapshot semantics).
693
+ */
694
+ base?: GlazeColorToken | GlazeColorValue;
695
+ /**
696
+ * Fixed opacity (0–1). Output includes alpha in the CSS value.
697
+ * Combining with `contrast` is not recommended (perceived tone
698
+ * becomes unpredictable) — a `console.warn` is emitted in that case.
699
+ */
700
+ opacity?: number;
701
+ /**
702
+ * Optional human-readable name for the token. Used in error and
703
+ * warning messages (otherwise an internal name like `"value"` is
704
+ * used). Does not affect output keys.
705
+ */
706
+ name?: string;
707
+ /**
708
+ * Per-color override for the hue-independent "safe" chroma limit used in
709
+ * OKHSL↔sRGB conversions (luminance, contrast solving, output formatting).
710
+ * Falls through to the global / per-theme `pastel` config when omitted.
711
+ * @see GlazeConfig.pastel
712
+ */
713
+ pastel?: boolean;
714
+ /**
715
+ * Semantic role against `base` / the literal seed: how this token is used.
716
+ * Fixes APCA contrast polarity. Same resolution chain as
717
+ * `RegularColorDef.role`.
718
+ */
719
+ role?: RoleInput;
720
+ }
721
+ /**
722
+ * Object input for `glaze.color()` that carries a raw color value plus
723
+ * optional color overrides in the same object.
724
+ *
725
+ * ```ts
726
+ * glaze.color({ from: '#1a1a2e', base: bg, contrast: 'AA' })
727
+ * glaze.color({ from: { r: 38, g: 252, b: 178 }, tone: '+10' })
728
+ * ```
729
+ */
730
+ interface GlazeFromInput extends GlazeColorOverrides {
731
+ /** The source color value. Accepts the same forms as a bare `GlazeColorValue`. */
732
+ from: GlazeColorValue;
733
+ }
734
+ /** Options for `GlazeColorToken.css()`. */
735
+ interface GlazeColorCssOptions {
736
+ /**
737
+ * Custom property base name (without leading `--`). Required.
738
+ * Becomes the variable identifier in the output, e.g.
739
+ * `name: 'brand'` → `--brand-color: …`.
740
+ */
741
+ name: string;
742
+ /** Output color format. Default: 'rgb' (matches `theme.css` default). */
743
+ format?: GlazeColorFormat;
744
+ /**
745
+ * Suffix appended to the name. Default: '-color' (matches
746
+ * `theme.css` default).
747
+ */
748
+ suffix?: string;
221
749
  }
222
750
  /** Return type for `glaze.color()`. */
223
751
  interface GlazeColorToken {
@@ -228,17 +756,91 @@ interface GlazeColorToken {
228
756
  /**
229
757
  * Export as a tasty style-to-state binding (no color name key).
230
758
  * Uses `#name` keys and state aliases (`''`, `@dark`, etc.).
231
- * @see https://cube-ui-kit.vercel.app/?path=/docs/tasty-documentation--docs
759
+ * @see https://tasty.style/docs
232
760
  */
233
761
  tasty(options?: GlazeTokenOptions): Record<string, string>;
234
762
  /** Export as a flat JSON map (no color name key). */
235
763
  json(options?: GlazeJsonOptions): Record<string, string>;
764
+ /** Export as CSS custom property declarations grouped by scheme variant. */
765
+ css(options: GlazeColorCssOptions): GlazeCssResult;
766
+ /**
767
+ * Serialize the token as a JSON-safe object. Captures the original
768
+ * input value, overrides, and config so it can be rehydrated via
769
+ * `glaze.colorFrom(...)`. `base` is recursively serialized.
770
+ */
771
+ export(): GlazeColorTokenExport;
772
+ }
773
+ /**
774
+ * JSON-safe serialization of a `glaze.color()` token. Pass to
775
+ * `glaze.colorFrom(...)` to rehydrate.
776
+ */
777
+ interface GlazeColorTokenExport {
778
+ /**
779
+ * Discriminator for the source overload that created the token.
780
+ * - `'value'`: created via `glaze.color(value)` or `glaze.color({ from, ...overrides })`.
781
+ * - `'structured'`: created via `glaze.color({ hue, saturation, ... })`.
782
+ */
783
+ form: 'value' | 'structured';
784
+ /** Original input. For `form: 'value'` this is the raw `GlazeColorValue`; for `form: 'structured'` this is the structured input. */
785
+ input: GlazeColorValue | GlazeColorInputExport;
786
+ /**
787
+ * Overrides recorded at creation time. `base` is recursively
788
+ * serialized. Only present for `form: 'value'`.
789
+ */
790
+ overrides?: GlazeColorOverridesExport;
791
+ /**
792
+ * Effective config snapshot at creation time — captures the merged
793
+ * result of the global config + any per-call override at the moment
794
+ * the token was created. Only fields that differ from their
795
+ * post-merge defaults are present. Used by `glaze.colorFrom()` to
796
+ * reproduce deterministic behavior across `configure()` calls.
797
+ */
798
+ config?: GlazeConfigOverride;
799
+ }
800
+ /**
801
+ * Serializable shape of a structured `glaze.color({...})` input.
802
+ * Differs from `GlazeColorInput` only in that `base` is replaced by an
803
+ * `export` instead of a token reference.
804
+ */
805
+ interface GlazeColorInputExport {
806
+ hue: number;
807
+ saturation: number;
808
+ tone: HCPair<number | ExtremeValue>;
809
+ saturationFactor?: number;
810
+ mode?: AdaptationMode;
811
+ flip?: boolean;
812
+ opacity?: number;
813
+ base?: GlazeColorTokenExport | GlazeColorValue;
814
+ contrast?: HCPair<ContrastSpec>;
815
+ name?: string;
816
+ pastel?: boolean;
817
+ role?: RoleInput;
818
+ }
819
+ /**
820
+ * Serializable shape of `GlazeColorOverrides`. `base` is replaced by
821
+ * its export (or left as a `GlazeColorValue` if it was originally a value).
822
+ */
823
+ interface GlazeColorOverridesExport {
824
+ hue?: number | RelativeValue;
825
+ saturation?: number;
826
+ tone?: HCPair<ToneValue>;
827
+ saturationFactor?: number;
828
+ mode?: AdaptationMode;
829
+ flip?: boolean;
830
+ contrast?: HCPair<ContrastSpec>;
831
+ base?: GlazeColorTokenExport | GlazeColorValue;
832
+ opacity?: number;
833
+ name?: string;
834
+ pastel?: boolean;
835
+ role?: RoleInput;
236
836
  }
237
837
  interface GlazeTheme {
238
838
  /** The hue seed (0–360). */
239
839
  readonly hue: number;
240
840
  /** The saturation seed (0–100). */
241
841
  readonly saturation: number;
842
+ /** The effective config for this theme. */
843
+ getConfig(): GlazeConfigResolved;
242
844
  /** Add/replace colors (additive merge with existing definitions). */
243
845
  colors(defs: ColorMap): void;
244
846
  /** Get a color definition by name. */
@@ -272,7 +874,7 @@ interface GlazeTheme {
272
874
  * Export as tasty style-to-state bindings.
273
875
  * Uses `#name` color token keys and state aliases (`''`, `@dark`, etc.).
274
876
  * Spread into component styles or register as a recipe via `configure({ recipes })`.
275
- * @see https://cube-ui-kit.vercel.app/?path=/docs/tasty-documentation--docs
877
+ * @see https://tasty.style/docs
276
878
  */
277
879
  tasty(options?: GlazeTokenOptions): Record<string, Record<string, string>>;
278
880
  /** Export as plain JSON. */
@@ -284,6 +886,8 @@ interface GlazeExtendOptions {
284
886
  hue?: number;
285
887
  saturation?: number;
286
888
  colors?: ColorMap;
889
+ /** Config override for the child theme. Merged with the parent's override. */
890
+ config?: GlazeConfigOverride;
287
891
  }
288
892
  interface GlazeTokenOptions {
289
893
  /** Prefix mode. `true` uses "<themeName>-", or provide a custom map. */
@@ -317,33 +921,64 @@ interface GlazeCssResult {
317
921
  lightContrast: string;
318
922
  darkContrast: string;
319
923
  }
924
+ /** Options for `glaze.palette()` creation. */
925
+ interface GlazePaletteOptions {
926
+ /**
927
+ * Name of the primary theme. The primary theme's tokens are duplicated
928
+ * without prefix in all exports, providing convenient short aliases
929
+ * alongside the prefixed versions. Can be overridden per-export.
930
+ *
931
+ * @example
932
+ * ```ts
933
+ * const palette = glaze.palette({ brand, accent }, { primary: 'brand' });
934
+ * palette.tokens()
935
+ * // → { light: { 'brand-surface': '...', 'surface': '...', 'accent-surface': '...' } }
936
+ * ```
937
+ */
938
+ primary?: string;
939
+ }
940
+ /** Options shared by palette `tokens()`, `tasty()`, and `css()` exports. */
941
+ interface GlazePaletteExportOptions {
942
+ /**
943
+ * Prefix mode. `true` uses `"<themeName>-"`, or provide a custom map.
944
+ * Defaults to `true` for palette export methods.
945
+ * Set to `false` explicitly to disable prefixing. Colliding keys
946
+ * produce a console.warn and the first-written value wins.
947
+ */
948
+ prefix?: boolean | Record<string, string>;
949
+ /**
950
+ * Override the palette-level primary theme for this export.
951
+ * Pass a theme name to set/change the primary, or `false` to disable it.
952
+ * When omitted, inherits the palette-level `primary`.
953
+ */
954
+ primary?: string | false;
955
+ }
320
956
  interface GlazePalette {
321
957
  /**
322
958
  * Export all themes as a flat token map grouped by scheme variant.
959
+ * Prefix defaults to `true` — all tokens are prefixed with the theme name.
960
+ * Inherits the palette-level `primary`; override per-call or pass `false` to disable.
323
961
  *
324
962
  * ```ts
325
- * palette.tokens({ prefix: true })
326
- * // → { light: { 'primary-surface': 'okhsl(...)' }, dark: { 'primary-surface': 'okhsl(...)' } }
963
+ * const palette = glaze.palette({ brand, accent }, { primary: 'brand' });
964
+ * palette.tokens()
965
+ * // → { light: { 'brand-surface': '...', 'surface': '...', 'accent-surface': '...' } }
327
966
  * ```
328
967
  */
329
- tokens(options?: GlazeJsonOptions & {
330
- prefix?: boolean | Record<string, string>;
331
- }): Record<string, Record<string, string>>;
968
+ tokens(options?: GlazeJsonOptions & GlazePaletteExportOptions): Record<string, Record<string, string>>;
332
969
  /**
333
970
  * Export all themes as tasty style-to-state bindings.
334
971
  * Uses `#name` color token keys and state aliases (`''`, `@dark`, etc.).
335
- * Spread into component styles or register as a recipe via `configure({ recipes })`.
336
- * @see https://cube-ui-kit.vercel.app/?path=/docs/tasty-documentation--docs
972
+ * Prefix defaults to `true`. Inherits the palette-level `primary`.
973
+ * @see https://tasty.style/docs
337
974
  */
338
- tasty(options?: GlazeTokenOptions): Record<string, Record<string, string>>;
339
- /** Export all themes as plain JSON. */
975
+ tasty(options?: GlazeTokenOptions & GlazePaletteExportOptions): Record<string, Record<string, string>>;
976
+ /** Export all themes as plain JSON grouped by theme name. */
340
977
  json(options?: GlazeJsonOptions & {
341
978
  prefix?: boolean | Record<string, string>;
342
979
  }): Record<string, Record<string, Record<string, string>>>;
343
980
  /** Export all themes as CSS custom property declarations. */
344
- css(options?: GlazeCssOptions & {
345
- prefix?: boolean | Record<string, string>;
346
- }): GlazeCssResult;
981
+ css(options?: GlazeCssOptions & GlazePaletteExportOptions): GlazeCssResult;
347
982
  }
348
983
  //#endregion
349
984
  //#region src/glaze.d.ts
@@ -351,54 +986,155 @@ type PaletteInput = Record<string, GlazeTheme>;
351
986
  /**
352
987
  * Create a single-hue glaze theme.
353
988
  *
989
+ * An optional `config` override can be supplied to customize the resolve
990
+ * behavior for this theme (tone windows, etc.). The
991
+ * override is **merged over the live global config at resolve time** —
992
+ * the theme still reacts to later `configure()` calls for fields it
993
+ * didn't override.
994
+ *
354
995
  * @example
355
996
  * ```ts
356
- * const primary = glaze({ hue: 280, saturation: 80 });
357
- * // or shorthand:
358
997
  * const primary = glaze(280, 80);
998
+ * // or shorthand:
999
+ * const primary = glaze({ hue: 280, saturation: 80 });
1000
+ * // with config override:
1001
+ * const raw = glaze(280, 80, { lightTone: false });
359
1002
  * ```
360
1003
  */
361
1004
  declare function glaze(hueOrOptions: number | {
362
1005
  hue: number;
363
1006
  saturation: number;
364
- }, saturation?: number): GlazeTheme;
1007
+ }, saturation?: number, config?: GlazeConfigOverride): GlazeTheme;
365
1008
  declare namespace glaze {
366
1009
  var configure: (config: GlazeConfig) => void;
367
- var palette: (themes: PaletteInput) => {
368
- tokens(options?: GlazeJsonOptions & {
369
- prefix?: boolean | Record<string, string>;
370
- }): Record<string, Record<string, string>>;
371
- tasty(options?: GlazeTokenOptions): Record<string, Record<string, string>>;
372
- json(options?: GlazeJsonOptions & {
373
- prefix?: boolean | Record<string, string>;
374
- }): Record<string, Record<string, Record<string, string>>>;
375
- css(options?: GlazeCssOptions & {
376
- prefix?: boolean | Record<string, string>;
377
- }): GlazeCssResult;
378
- };
1010
+ var palette: (themes: PaletteInput, options?: GlazePaletteOptions) => GlazePalette;
379
1011
  var from: (data: GlazeThemeExport) => GlazeTheme;
380
- var color: (input: GlazeColorInput) => GlazeColorToken;
1012
+ var color: (input: GlazeFromInput | GlazeColorInput | GlazeColorValue, config?: GlazeConfigOverride) => GlazeColorToken;
381
1013
  var shadow: (input: GlazeShadowInput) => ResolvedColorVariant;
382
- var format: (variant: ResolvedColorVariant, colorFormat?: GlazeColorFormat) => string;
1014
+ var format: (variant: ResolvedColorVariant, colorFormat?: GlazeColorFormat, pastel?: boolean) => string;
383
1015
  var fromHex: (hex: string) => GlazeTheme;
384
1016
  var fromRgb: (r: number, g: number, b: number) => GlazeTheme;
1017
+ var colorFrom: (data: GlazeColorTokenExport) => GlazeColorToken;
385
1018
  var getConfig: () => GlazeConfigResolved;
386
1019
  var resetConfig: () => void;
387
1020
  }
388
1021
  //#endregion
1022
+ //#region src/roles.d.ts
1023
+ /**
1024
+ * Normalize a `RoleInput` (canonical value or alias) into a canonical `Role`.
1025
+ * Returns `undefined` for unrecognized strings so callers can fall through to
1026
+ * the next step of the resolution chain.
1027
+ */
1028
+ declare function normalizeRole(input: RoleInput | undefined): Role | undefined;
1029
+ /**
1030
+ * Infer a `Role` from a color name by matching its tokens against the role
1031
+ * keyword sets. When multiple tokens match, the **last** recognized token
1032
+ * wins (so `button-text` → `text`, `input-bg` → `surface`, `card-border` →
1033
+ * `border`). Returns `undefined` when no token matches.
1034
+ */
1035
+ declare function inferRoleFromName(name: string): Role | undefined;
1036
+ /** APCA argument order: which side the resolved color plays. */
1037
+ type Polarity = 'fg' | 'bg';
1038
+ /**
1039
+ * Map a role to its APCA polarity. `text` and `border` are foreground spots
1040
+ * against their base (the candidate is the text argument); `surface` is the
1041
+ * background (the base is the text argument).
1042
+ */
1043
+ declare function roleToPolarity(role: Role): Polarity;
1044
+ /**
1045
+ * The opposite role of `role`, used when a color with no explicit role and no
1046
+ * inferable name depends on a base: the dependent color plays the opposite
1047
+ * role of its base. `surface` ↔ `text`; `border` is treated as a foreground
1048
+ * spot, so its opposite is `surface`.
1049
+ */
1050
+ declare function oppositeRole(role: Role): Role;
1051
+ //#endregion
1052
+ //#region src/okhst.d.ts
1053
+ /**
1054
+ * Reference eps for the OKHST color space. WCAG 2 contrast is
1055
+ * `(Y_hi + 0.05) / (Y_lo + 0.05)`, so an eps of `0.05` makes equal tone
1056
+ * steps yield equal WCAG contrast. This is the canonical eps used by
1057
+ * `okhst()` input, `{ h, s, t }` input, stored `ResolvedColorVariant.t`,
1058
+ * relative `tone` offsets, and the contrast solver.
1059
+ */
1060
+ declare const REF_EPS = 0.05;
1061
+ /**
1062
+ * Map a luminance `Y` (0–1) to tone (0–100) at the given eps.
1063
+ * `toneFromY(0) === 0` and `toneFromY(1) === 100` for any eps.
1064
+ */
1065
+ declare function toneFromY(y: number, eps?: number): number;
1066
+ /** Map a tone (0–100) back to luminance (0–1). Inverse of {@link toneFromY}. */
1067
+ declare function yFromTone(t: number, eps?: number): number;
1068
+ /** OKHSL lightness (0–1) -> tone (0–100). */
1069
+ declare function toTone(l: number, eps?: number): number;
1070
+ /** Tone (0–100) -> OKHSL lightness (0–1). Inverse of {@link toTone}. */
1071
+ declare function fromTone(t: number, eps?: number): number;
1072
+ /** Convert OKHST `{ h, s, t }` (t in 0–1) to OKHSL `{ h, s, l }`. */
1073
+ declare function okhstToOkhsl(c: {
1074
+ h: number;
1075
+ s: number;
1076
+ t: number;
1077
+ }): {
1078
+ h: number;
1079
+ s: number;
1080
+ l: number;
1081
+ };
1082
+ /** Convert OKHSL `{ h, s, l }` to OKHST `{ h, s, t }` (t in 0–1). */
1083
+ declare function okhslToOkhst(c: {
1084
+ h: number;
1085
+ s: number;
1086
+ l: number;
1087
+ }): {
1088
+ h: number;
1089
+ s: number;
1090
+ t: number;
1091
+ };
1092
+ /**
1093
+ * Edge adapter: a resolved variant stores canonical tone `t` (0–1). Convert
1094
+ * it to the OKHSL `{ h, s, l }` the formatters and luminance pipeline expect.
1095
+ */
1096
+ declare function variantToOkhsl(v: {
1097
+ h: number;
1098
+ s: number;
1099
+ t: number;
1100
+ }): {
1101
+ h: number;
1102
+ s: number;
1103
+ l: number;
1104
+ };
1105
+ //#endregion
389
1106
  //#region src/okhsl-color-math.d.ts
390
1107
  /**
391
1108
  * OKHSL color math primitives for the glaze theme generator.
392
1109
  *
393
- * Provides bidirectional OKHSL ↔ sRGB conversion, relative luminance
394
- * computation for WCAG 2 contrast calculations, and multi-format output
395
- * (okhsl, rgb, hsl, oklch).
1110
+ * Provides bidirectional OKHSL ↔ sRGB conversion, luminance computation
1111
+ * for both contrast metrics (WCAG 2 relative luminance and APCA screen
1112
+ * luminance `Ys`), and multi-format output (okhsl, rgb, hsl, oklch).
1113
+ */
1114
+ type Vec3 = [number, number, number];
1115
+ /**
1116
+ * OKHSL toe function: maps OKLab lightness L to perceptual lightness l.
1117
+ * Exported for the OKHST tone transfers in `okhst.ts`.
396
1118
  */
1119
+ /**
1120
+ * OKHSL lightness of the gamut cusp for a hue — the lightness where the
1121
+ * realizable chroma peaks. Reuses the same `find_cusp` OKHSL already runs for
1122
+ * its `s` normalization (no new color math); the OKLab cusp lightness is run
1123
+ * through the OKHSL `toe` and clamped to `[0.001, 0.999]` so divisions that
1124
+ * key off it stay safe. Cached per (rounded) hue.
1125
+ *
1126
+ * @param h Hue, 0–360.
1127
+ */
1128
+ declare function cuspLightness(h: number): number;
1129
+ /**
1130
+ * Convert OKHSL (h: 0–360, s: 0–1, l: 0–1) to OKLab [L, a, b].
1131
+ */
1132
+ declare function okhslToOklab(h: number, s: number, l: number, pastel?: boolean): [number, number, number];
397
1133
  /**
398
1134
  * Convert OKHSL (h: 0–360, s: 0–1, l: 0–1) to linear sRGB.
399
1135
  * Channels may exceed [0, 1] near gamut boundaries — caller must clamp if needed.
400
1136
  */
401
- declare function okhslToLinearSrgb(h: number, s: number, l: number): [number, number, number];
1137
+ declare function okhslToLinearSrgb(h: number, s: number, l: number, pastel?: boolean): [number, number, number];
402
1138
  /**
403
1139
  * Compute relative luminance Y from linear sRGB channels.
404
1140
  * Per WCAG 2: Y = 0.2126·R + 0.7152·G + 0.0722·B
@@ -411,41 +1147,70 @@ declare function contrastRatioFromLuminance(yA: number, yB: number): number;
411
1147
  /**
412
1148
  * Convert OKHSL to gamma-encoded sRGB (clamped to 0–1).
413
1149
  */
414
- declare function okhslToSrgb(h: number, s: number, l: number): [number, number, number];
1150
+ declare function okhslToSrgb(h: number, s: number, l: number, pastel?: boolean): [number, number, number];
415
1151
  /**
416
- * Convert OKHSL (h: 0–360, s: 0–1, l: 0–1) to OKLab [L, a, b].
1152
+ * Compute WCAG 2 relative luminance from linear sRGB, matching the browser
1153
+ * rendering pipeline: gamma-encode, clamp to sRGB gamut [0,1], then linearize.
1154
+ * This avoids over/under-estimating luminance for out-of-gamut OKHSL colors.
1155
+ */
1156
+ declare function gamutClampedLuminance(linearRgb: [number, number, number]): number;
1157
+ /**
1158
+ * Convert OKLab to OKHSL.
1159
+ * Input: [L, a, b] where L: 0–1, a/b: roughly -0.5 to 0.5.
1160
+ * Returns [h, s, l] where h: 0–360, s: 0–1, l: 0–1.
417
1161
  */
418
- declare function okhslToOklab(h: number, s: number, l: number): [number, number, number];
1162
+ declare const oklabToOkhsl: (lab: Vec3, pastel?: boolean) => Vec3;
419
1163
  /**
420
1164
  * Convert gamma-encoded sRGB (0–1 per channel) to OKHSL.
421
1165
  * Returns [h, s, l] where h: 0–360, s: 0–1, l: 0–1.
422
1166
  */
423
- declare function srgbToOkhsl(rgb: [number, number, number]): [number, number, number];
1167
+ declare function srgbToOkhsl(rgb: [number, number, number], pastel?: boolean): [number, number, number];
1168
+ /**
1169
+ * Convert CSS HSL (sRGB-based) to gamma-encoded sRGB [r, g, b] in 0–1 range.
1170
+ * h: 0–360, s: 0–1, l: 0–1.
1171
+ *
1172
+ * Note: CSS HSL is not the same as OKHSL — it's HSL in the sRGB color space.
1173
+ * Use this when parsing `hsl(...)` strings before passing to `srgbToOkhsl`.
1174
+ */
1175
+ declare function hslToSrgb(h: number, s: number, l: number): [number, number, number];
424
1176
  /**
425
1177
  * Parse a hex color string (#rgb or #rrggbb) to sRGB [r, g, b] in 0–1 range.
426
1178
  * Returns null if the string is not a valid hex color.
1179
+ *
1180
+ * For 8-digit hex (`#rrggbbaa`) and 4-digit hex (`#rgba`) with alpha,
1181
+ * use {@link parseHexAlpha}.
427
1182
  */
428
1183
  declare function parseHex(hex: string): [number, number, number] | null;
1184
+ /**
1185
+ * Parse a hex color string (#rgb, #rrggbb, #rgba, or #rrggbbaa) to
1186
+ * sRGB [r, g, b] in 0–1 range plus an optional alpha (0–1).
1187
+ * Returns null if the string is not a valid hex color.
1188
+ */
1189
+ declare function parseHexAlpha(hex: string): {
1190
+ rgb: [number, number, number];
1191
+ alpha?: number;
1192
+ } | null;
429
1193
  /**
430
1194
  * Format OKHSL values as a CSS `okhsl(H S% L%)` string.
431
1195
  * h: 0–360, s: 0–100, l: 0–100 (percentage scale for s and l).
432
1196
  */
433
- declare function formatOkhsl(h: number, s: number, l: number): string;
1197
+ declare function formatOkhsl(h: number, s: number, l: number, pastel?: boolean): string;
434
1198
  /**
435
- * Format OKHSL values as a CSS `rgb(R G B)` string with rounded integer values.
1199
+ * Format OKHSL values as a CSS `rgb(R G B)` string.
1200
+ * Uses 2 decimal places to avoid 8-bit quantization contrast loss.
436
1201
  * h: 0–360, s: 0–100, l: 0–100 (percentage scale for s and l).
437
1202
  */
438
- declare function formatRgb(h: number, s: number, l: number): string;
1203
+ declare function formatRgb(h: number, s: number, l: number, pastel?: boolean): string;
439
1204
  /**
440
1205
  * Format OKHSL values as a CSS `hsl(H S% L%)` string.
441
1206
  * h: 0–360, s: 0–100, l: 0–100 (percentage scale for s and l).
442
1207
  */
443
- declare function formatHsl(h: number, s: number, l: number): string;
1208
+ declare function formatHsl(h: number, s: number, l: number, pastel?: boolean): string;
444
1209
  /**
445
1210
  * Format OKHSL values as a CSS `oklch(L C H)` string.
446
1211
  * h: 0–360, s: 0–100, l: 0–100 (percentage scale for s and l).
447
1212
  */
448
- declare function formatOklch(h: number, s: number, l: number): string;
1213
+ declare function formatOklch(h: number, s: number, l: number, pastel?: boolean): string;
449
1214
  //#endregion
450
- export { type AdaptationMode, type ColorDef, type ColorMap, type ContrastPreset, type FindLightnessForContrastOptions, type FindLightnessForContrastResult, type GlazeColorFormat, type GlazeColorInput, type GlazeColorToken, type GlazeConfig, type GlazeCssOptions, type GlazeCssResult, type GlazeExtendOptions, type GlazeJsonOptions, type GlazeOutputModes, type GlazePalette, type GlazeShadowInput, type GlazeTheme, type GlazeThemeExport, type GlazeTokenOptions, type HCPair, type HexColor, type MinContrast, type OkhslColor, type RegularColorDef, type RelativeValue, type ResolvedColor, type ResolvedColorVariant, type ShadowColorDef, type ShadowTuning, contrastRatioFromLuminance, findLightnessForContrast, formatHsl, formatOkhsl, formatOklch, formatRgb, glaze, okhslToLinearSrgb, okhslToOklab, okhslToSrgb, parseHex, relativeLuminanceFromLinearRgb, resolveMinContrast, srgbToOkhsl };
1215
+ export { type AdaptationMode, type ApcaPreset, type ColorDef, type ColorMap, type ContrastPreset, type ContrastSpec, type ExtremeValue, type FindToneForContrastOptions, type FindToneForContrastResult, type FindValueForMixContrastOptions, type FindValueForMixContrastResult, type GlazeColorCssOptions, type GlazeColorFormat, type GlazeColorInput, type GlazeColorInputExport, type GlazeColorOverrides, type GlazeColorOverridesExport, type GlazeColorToken, type GlazeColorTokenExport, type GlazeColorValue, type GlazeConfig, type GlazeConfigOverride, type GlazeConfigResolved, type GlazeCssOptions, type GlazeCssResult, type GlazeExtendOptions, type GlazeFromInput, type GlazeJsonOptions, type GlazeOutputModes, type GlazePalette, type GlazePaletteExportOptions, type GlazePaletteOptions, type GlazeShadowInput, type GlazeTheme, type GlazeThemeExport, type GlazeTokenOptions, type HCPair, type HexColor, type MinContrast, type MixColorDef, type OkhslColor, type OkhstColor, type OklchColor, type Polarity, REF_EPS, type RegularColorDef, type RelativeValue, type ResolvedColor, type ResolvedColorVariant, type ResolvedContrast, type RgbColor, type Role, type RoleInput, type ShadowColorDef, type ShadowTuning, type ToneValue, type ToneWindow, apcaContrast, contrastRatioFromLuminance, cuspLightness, findToneForContrast, findValueForMixContrast, formatHsl, formatOkhsl, formatOklch, formatRgb, fromTone, gamutClampedLuminance, glaze, hslToSrgb, inferRoleFromName, normalizeRole, okhslToLinearSrgb, okhslToOkhst, okhslToOklab, okhslToSrgb, okhstToOkhsl, oklabToOkhsl, oppositeRole, parseHex, parseHexAlpha, relativeLuminanceFromLinearRgb, resolveApcaTarget, resolveContrastForMode, resolveMinContrast, roleToPolarity, srgbToOkhsl, toTone, toneFromY, variantToOkhsl, yFromTone };
451
1216
  //# sourceMappingURL=index.d.cts.map