@tenphi/glaze 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -256,10 +256,48 @@ const getCs = (L, a, b, cusp) => {
256
256
  cMax
257
257
  ];
258
258
  };
259
+ const CYAN_A = Math.cos(199.8 * Math.PI / 180);
260
+ const CYAN_B = Math.sin(199.8 * Math.PI / 180);
261
+ const BLUE_A = Math.cos(267.4 * Math.PI / 180);
262
+ const BLUE_B = Math.sin(267.4 * Math.PI / 180);
263
+ let cyanCusp;
264
+ let blueCusp;
265
+ /**
266
+ * Computes the maximum safe OKLCH chroma that fits inside the sRGB gamut
267
+ * for all possible hues at a given OKLab lightness `L`.
268
+ */
269
+ function computeSafeChromaOKLCH(L) {
270
+ if (!cyanCusp) cyanCusp = findCuspOKLCH(CYAN_A, CYAN_B);
271
+ if (!blueCusp) blueCusp = findCuspOKLCH(BLUE_A, BLUE_B);
272
+ const c1 = findGamutIntersectionOKLCH(CYAN_A, CYAN_B, L, 1, L, cyanCusp);
273
+ const c2 = findGamutIntersectionOKLCH(BLUE_A, BLUE_B, L, 1, L, blueCusp);
274
+ return Math.min(c1, c2);
275
+ }
276
+ /** Per-hue cusp-lightness cache. The cusp is mode-independent, so keying on
277
+ * a rounded hue is safe and keeps the cache small. */
278
+ const cuspLightnessCache = /* @__PURE__ */ new Map();
279
+ /**
280
+ * OKHSL lightness of the gamut cusp for a hue — the lightness where the
281
+ * realizable chroma peaks. Reuses the same `find_cusp` OKHSL already runs for
282
+ * its `s` normalization (no new color math); the OKLab cusp lightness is run
283
+ * through the OKHSL `toe` and clamped to `[0.001, 0.999]` so divisions that
284
+ * key off it stay safe. Cached per (rounded) hue.
285
+ *
286
+ * @param h Hue, 0–360.
287
+ */
288
+ function cuspLightness(h) {
289
+ const key = Math.round(constrainAngle(h) * 100) / 100;
290
+ const cached = cuspLightnessCache.get(key);
291
+ if (cached !== void 0) return cached;
292
+ const hNorm = key / 360;
293
+ const lc = clampVal(toe(findCuspOKLCH(Math.cos(TAU * hNorm), Math.sin(TAU * hNorm))[0]), .001, .999);
294
+ cuspLightnessCache.set(key, lc);
295
+ return lc;
296
+ }
259
297
  /**
260
298
  * Convert OKHSL (h: 0–360, s: 0–1, l: 0–1) to OKLab [L, a, b].
261
299
  */
262
- function okhslToOklab(h, s, l) {
300
+ function okhslToOklab(h, s, l, pastel = false) {
263
301
  const L = toeInv(l);
264
302
  let a = 0;
265
303
  let b = 0;
@@ -267,24 +305,30 @@ function okhslToOklab(h, s, l) {
267
305
  if (L !== 0 && L !== 1 && s !== 0) {
268
306
  const a_ = Math.cos(TAU * hNorm);
269
307
  const b_ = Math.sin(TAU * hNorm);
270
- const [c0, cMid, cMax] = getCs(L, a_, b_, findCuspOKLCH(a_, b_));
271
- const mid = .8;
272
- const midInv = 1.25;
273
- let t, k0, k1, k2;
274
- if (s < mid) {
275
- t = midInv * s;
276
- k0 = 0;
277
- k1 = mid * c0;
278
- k2 = 1 - k1 / cMid;
308
+ if (pastel) {
309
+ const c = s * computeSafeChromaOKLCH(L);
310
+ a = c * a_;
311
+ b = c * b_;
279
312
  } else {
280
- t = 5 * (s - .8);
281
- k0 = cMid;
282
- k1 = .2 * cMid ** 2 * 1.25 ** 2 / c0;
283
- k2 = 1 - k1 / (cMax - cMid);
313
+ const [c0, cMid, cMax] = getCs(L, a_, b_, findCuspOKLCH(a_, b_));
314
+ const mid = .8;
315
+ const midInv = 1.25;
316
+ let t, k0, k1, k2;
317
+ if (s < mid) {
318
+ t = midInv * s;
319
+ k0 = 0;
320
+ k1 = mid * c0;
321
+ k2 = 1 - k1 / cMid;
322
+ } else {
323
+ t = 5 * (s - .8);
324
+ k0 = cMid;
325
+ k1 = .2 * cMid ** 2 * 1.25 ** 2 / c0;
326
+ k2 = 1 - k1 / (cMax - cMid);
327
+ }
328
+ const c = k0 + t * k1 / (1 - k2 * t);
329
+ a = c * a_;
330
+ b = c * b_;
284
331
  }
285
- const c = k0 + t * k1 / (1 - k2 * t);
286
- a = c * a_;
287
- b = c * b_;
288
332
  }
289
333
  return [
290
334
  L,
@@ -296,8 +340,8 @@ function okhslToOklab(h, s, l) {
296
340
  * Convert OKHSL (h: 0–360, s: 0–1, l: 0–1) to linear sRGB.
297
341
  * Channels may exceed [0, 1] near gamut boundaries — caller must clamp if needed.
298
342
  */
299
- function okhslToLinearSrgb(h, s, l) {
300
- return OKLabToLinearSRGB(okhslToOklab(h, s, l));
343
+ function okhslToLinearSrgb(h, s, l, pastel = false) {
344
+ return OKLabToLinearSRGB(okhslToOklab(h, s, l, pastel));
301
345
  }
302
346
  /**
303
347
  * Compute relative luminance Y from linear sRGB channels.
@@ -327,8 +371,8 @@ const sRGBGammaToLinear = (val) => {
327
371
  /**
328
372
  * Convert OKHSL to gamma-encoded sRGB (clamped to 0–1).
329
373
  */
330
- function okhslToSrgb(h, s, l) {
331
- const lin = okhslToLinearSrgb(h, s, l);
374
+ function okhslToSrgb(h, s, l, pastel = false) {
375
+ const lin = okhslToLinearSrgb(h, s, l, pastel);
332
376
  return [
333
377
  Math.max(0, Math.min(1, sRGBLinearToGamma(lin[0]))),
334
378
  Math.max(0, Math.min(1, sRGBLinearToGamma(lin[1]))),
@@ -370,7 +414,7 @@ const linearSrgbToOklab = (rgb) => {
370
414
  * Input: [L, a, b] where L: 0–1, a/b: roughly -0.5 to 0.5.
371
415
  * Returns [h, s, l] where h: 0–360, s: 0–1, l: 0–1.
372
416
  */
373
- const oklabToOkhsl = (lab) => {
417
+ const oklabToOkhsl = (lab, pastel = false) => {
374
418
  const L = lab[0];
375
419
  const a = lab[1];
376
420
  const b = lab[2];
@@ -390,19 +434,22 @@ const oklabToOkhsl = (lab) => {
390
434
  const b_ = b / C;
391
435
  let h = Math.atan2(b, a) * (180 / Math.PI);
392
436
  h = constrainAngle(h);
393
- const [c0, cMid, cMax] = getCs(L, a_, b_, findCuspOKLCH(a_, b_));
394
- const mid = .8;
395
- const midInv = 1.25;
396
437
  let s;
397
- if (C < cMid) {
398
- const k1 = mid * c0;
399
- s = C / (k1 + C * (1 - k1 / cMid)) / midInv;
400
- } else {
401
- const k0 = cMid;
402
- const k1 = .2 * cMid ** 2 * 1.25 ** 2 / c0;
403
- const k2 = 1 - k1 / (cMax - cMid);
404
- const cDiff = C - k0;
405
- s = mid + cDiff / (k1 + cDiff * k2) / 5;
438
+ if (pastel) s = C / computeSafeChromaOKLCH(L);
439
+ else {
440
+ const [c0, cMid, cMax] = getCs(L, a_, b_, findCuspOKLCH(a_, b_));
441
+ const mid = .8;
442
+ const midInv = 1.25;
443
+ if (C < cMid) {
444
+ const k1 = mid * c0;
445
+ s = C / (k1 + C * (1 - k1 / cMid)) / midInv;
446
+ } else {
447
+ const k0 = cMid;
448
+ const k1 = .2 * cMid ** 2 * 1.25 ** 2 / c0;
449
+ const k2 = 1 - k1 / (cMax - cMid);
450
+ const cDiff = C - k0;
451
+ s = mid + cDiff / (k1 + cDiff * k2) / 5;
452
+ }
406
453
  }
407
454
  const l = toe(L);
408
455
  return [
@@ -415,12 +462,12 @@ const oklabToOkhsl = (lab) => {
415
462
  * Convert gamma-encoded sRGB (0–1 per channel) to OKHSL.
416
463
  * Returns [h, s, l] where h: 0–360, s: 0–1, l: 0–1.
417
464
  */
418
- function srgbToOkhsl(rgb) {
465
+ function srgbToOkhsl(rgb, pastel = false) {
419
466
  return oklabToOkhsl(linearSrgbToOklab([
420
467
  sRGBGammaToLinear(rgb[0]),
421
468
  sRGBGammaToLinear(rgb[1]),
422
469
  sRGBGammaToLinear(rgb[2])
423
- ]));
470
+ ]), pastel);
424
471
  }
425
472
  /**
426
473
  * Convert CSS HSL (sRGB-based) to gamma-encoded sRGB [r, g, b] in 0–1 range.
@@ -535,24 +582,26 @@ function fmt$1(value, decimals) {
535
582
  * Format OKHSL values as a CSS `okhsl(H S% L%)` string.
536
583
  * h: 0–360, s: 0–100, l: 0–100 (percentage scale for s and l).
537
584
  */
538
- function formatOkhsl(h, s, l) {
539
- return `okhsl(${fmt$1(h, 2)} ${fmt$1(s, 2)}% ${fmt$1(l, 2)}%)`;
585
+ function formatOkhsl(h, s, l, pastel = false) {
586
+ let outS = s;
587
+ if (pastel) outS = oklabToOkhsl(okhslToOklab(h, s / 100, l / 100, true), false)[1] * 100;
588
+ return `okhsl(${fmt$1(h, 2)} ${fmt$1(outS, 2)}% ${fmt$1(l, 2)}%)`;
540
589
  }
541
590
  /**
542
591
  * Format OKHSL values as a CSS `rgb(R G B)` string.
543
592
  * Uses 2 decimal places to avoid 8-bit quantization contrast loss.
544
593
  * h: 0–360, s: 0–100, l: 0–100 (percentage scale for s and l).
545
594
  */
546
- function formatRgb(h, s, l) {
547
- const [r, g, b] = okhslToSrgb(h, s / 100, l / 100);
595
+ function formatRgb(h, s, l, pastel = false) {
596
+ const [r, g, b] = okhslToSrgb(h, s / 100, l / 100, pastel);
548
597
  return `rgb(${parseFloat((r * 255).toFixed(2))} ${parseFloat((g * 255).toFixed(2))} ${parseFloat((b * 255).toFixed(2))})`;
549
598
  }
550
599
  /**
551
600
  * Format OKHSL values as a CSS `hsl(H S% L%)` string.
552
601
  * h: 0–360, s: 0–100, l: 0–100 (percentage scale for s and l).
553
602
  */
554
- function formatHsl(h, s, l) {
555
- const [r, g, b] = okhslToSrgb(h, s / 100, l / 100);
603
+ function formatHsl(h, s, l, pastel = false) {
604
+ const [r, g, b] = okhslToSrgb(h, s / 100, l / 100, pastel);
556
605
  const max = Math.max(r, g, b);
557
606
  const min = Math.min(r, g, b);
558
607
  const delta = max - min;
@@ -571,8 +620,8 @@ function formatHsl(h, s, l) {
571
620
  * Format OKHSL values as a CSS `oklch(L C H)` string.
572
621
  * h: 0–360, s: 0–100, l: 0–100 (percentage scale for s and l).
573
622
  */
574
- function formatOklch(h, s, l) {
575
- const [L, a, b] = okhslToOklab(h, s / 100, l / 100);
623
+ function formatOklch(h, s, l, pastel = false) {
624
+ const [L, a, b] = okhslToOklab(h, s / 100, l / 100, pastel);
576
625
  const C = Math.sqrt(a * a + b * b);
577
626
  let hh = Math.atan2(b, a) * (180 / Math.PI);
578
627
  hh = constrainAngle(hh);
@@ -598,7 +647,6 @@ function defaultConfig() {
598
647
  eps: .05
599
648
  },
600
649
  darkDesaturation: .1,
601
- saturationTaper: .15,
602
650
  states: {
603
651
  dark: "@dark",
604
652
  highContrast: "@high-contrast"
@@ -607,7 +655,8 @@ function defaultConfig() {
607
655
  dark: true,
608
656
  highContrast: false
609
657
  },
610
- autoFlip: true
658
+ autoFlip: true,
659
+ pastel: false
611
660
  };
612
661
  }
613
662
  let globalConfig = defaultConfig();
@@ -637,7 +686,6 @@ function configure(config) {
637
686
  lightTone: config.lightTone ?? globalConfig.lightTone,
638
687
  darkTone: config.darkTone ?? globalConfig.darkTone,
639
688
  darkDesaturation: config.darkDesaturation ?? globalConfig.darkDesaturation,
640
- saturationTaper: config.saturationTaper ?? globalConfig.saturationTaper,
641
689
  states: {
642
690
  dark: config.states?.dark ?? globalConfig.states.dark,
643
691
  highContrast: config.states?.highContrast ?? globalConfig.states.highContrast
@@ -647,7 +695,8 @@ function configure(config) {
647
695
  highContrast: config.modes?.highContrast ?? globalConfig.modes.highContrast
648
696
  },
649
697
  shadowTuning: config.shadowTuning ?? globalConfig.shadowTuning,
650
- autoFlip: config.autoFlip ?? globalConfig.autoFlip
698
+ autoFlip: config.autoFlip ?? globalConfig.autoFlip,
699
+ pastel: config.pastel ?? globalConfig.pastel
651
700
  };
652
701
  }
653
702
  function resetConfig() {
@@ -666,11 +715,11 @@ function mergeConfig(base, override) {
666
715
  lightTone: override.lightTone !== void 0 ? override.lightTone : base.lightTone,
667
716
  darkTone: override.darkTone !== void 0 ? override.darkTone : base.darkTone,
668
717
  darkDesaturation: override.darkDesaturation ?? base.darkDesaturation,
669
- saturationTaper: override.saturationTaper ?? base.saturationTaper,
670
718
  states: base.states,
671
719
  modes: base.modes,
672
720
  shadowTuning: override.shadowTuning ?? base.shadowTuning,
673
- autoFlip: override.autoFlip ?? base.autoFlip
721
+ autoFlip: override.autoFlip ?? base.autoFlip,
722
+ pastel: override.pastel ?? base.pastel
674
723
  };
675
724
  }
676
725
 
@@ -763,8 +812,8 @@ function isAbsoluteTone(tone) {
763
812
  * - the `{ h, s, t }` <-> `{ h, s, l }` color-space converters,
764
813
  * - the resolved-variant edge adapter (`variantToOkhsl`),
765
814
  * - the per-scheme tone mapping that replaced the Möbius dark curve
766
- * (`mapToneForScheme`), the saturation reducers, and the solver's
767
- * scheme tone range.
815
+ * (`mapToneForScheme`), the dark desaturation reducer, and the solver's scheme
816
+ * tone range.
768
817
  *
769
818
  * See `docs/okhst.md` for the full specification and the calibrated
770
819
  * default constants.
@@ -907,33 +956,6 @@ function mapSaturationDark(s, mode, config) {
907
956
  if (mode === "static") return s;
908
957
  return s * (1 - config.darkDesaturation);
909
958
  }
910
- /** Smoothstep `0..1`. */
911
- function smoothstep(x) {
912
- const t = clamp(x, 0, 1);
913
- return t * t * (3 - 2 * t);
914
- }
915
- /** Fraction of the tone range over which the taper ramps in, per end. */
916
- const TAPER_REGION = .15;
917
- /**
918
- * Gently taper saturation toward the tone extremes, where in-gamut chroma
919
- * collapses and high saturation reads as noise. `taper` is the *strength*
920
- * (0–1): the maximum fraction of saturation removed at the very edges. The
921
- * rolloff ramps in smoothly over the outer {@link TAPER_REGION} of tone on
922
- * each end, so mid-tones are untouched and high-tone surfaces keep most of
923
- * their color. `taper = 0` disables the effect.
924
- *
925
- * @param s Saturation (0–1).
926
- * @param toneFinal Stored canonical tone (0–1).
927
- * @param taper Strength (0–1); default config is a gentle 0.15.
928
- */
929
- function saturationEnvelope(s, toneFinal, taper) {
930
- if (taper <= 0) return s;
931
- const t = clamp(toneFinal, 0, 1);
932
- const strength = clamp(taper, 0, 1);
933
- const edge = Math.min(t, 1 - t);
934
- if (edge >= TAPER_REGION) return s;
935
- return s * (1 - strength * (1 - smoothstep(edge / TAPER_REGION)));
936
- }
937
959
  /**
938
960
  * Tone search range (0–1) for the contrast solver in a given scheme.
939
961
  * `static` searches the full range; otherwise the scheme window's tone
@@ -1038,12 +1060,12 @@ const cacheOrder = [];
1038
1060
  * the metric's luminance basis. The metric is part of the cache key because
1039
1061
  * WCAG and APCA derive different luminances from the same color.
1040
1062
  */
1041
- function cachedLuminance(metric, h, s, t) {
1063
+ function cachedLuminance(metric, h, s, t, pastel) {
1042
1064
  const tRounded = Math.round(t * 1e4) / 1e4;
1043
- const key = `${metric}|${h}|${s}|${tRounded}`;
1065
+ const key = `${metric}|${h}|${s}|${tRounded}|${pastel}`;
1044
1066
  const cached = luminanceCache.get(key);
1045
1067
  if (cached !== void 0) return cached;
1046
- const y = metricLuminance(metric, okhslToLinearSrgb(h, s, fromTone(tRounded * 100, REF_EPS)));
1068
+ const y = metricLuminance(metric, okhslToLinearSrgb(h, s, fromTone(tRounded * 100, REF_EPS), pastel));
1047
1069
  if (luminanceCache.size >= CACHE_SIZE) {
1048
1070
  const evict = cacheOrder.shift();
1049
1071
  luminanceCache.delete(evict);
@@ -1174,14 +1196,11 @@ function solveNearestContrast(opts) {
1174
1196
  * staying as close to `preferredTone` as possible.
1175
1197
  */
1176
1198
  function findToneForContrast(options) {
1177
- const { hue, saturation, preferredTone, baseLinearRgb, contrast, toneRange = [0, 1], epsilon = 1e-4, maxIterations = 18 } = options;
1199
+ const { hue, saturation, preferredTone, baseLinearRgb, contrast, toneRange = [0, 1], epsilon = 1e-4, maxIterations = 18, pastel = false } = options;
1178
1200
  const { metric, target } = contrast;
1179
1201
  const searchTarget = metric === "wcag" ? target * 1.01 : target + .5;
1180
1202
  const yBase = metricLuminance(metric, baseLinearRgb);
1181
- const taper = options.saturationTaper ?? 0;
1182
- const lum = taper > 0 ? (t) => {
1183
- return metricLuminance(metric, okhslToLinearSrgb(hue, saturationEnvelope(saturation, t, taper), fromTone(t * 100, REF_EPS)));
1184
- } : (t) => cachedLuminance(metric, hue, saturation, t);
1203
+ const lum = (t) => cachedLuminance(metric, hue, saturation, t, pastel);
1185
1204
  const scorePref = metricScore(metric, lum(preferredTone), yBase);
1186
1205
  if (scorePref >= searchTarget) return {
1187
1206
  tone: preferredTone,
@@ -1585,7 +1604,7 @@ function resolveDependentColor(name, def, ctx, isHighContrast, isDark, effective
1585
1604
  const resolvedContrast = resolveContrastSpec(rawContrast, isHighContrast);
1586
1605
  const effectiveSat = isDark ? mapSaturationDark(satFactor * ctx.saturation / 100, mode, ctx.config) : satFactor * ctx.saturation / 100;
1587
1606
  const baseOkhsl = toOkhslVariant(baseVariant);
1588
- const baseLinearRgb = okhslToLinearSrgb(baseOkhsl.h, baseOkhsl.s, baseOkhsl.l);
1607
+ const baseLinearRgb = okhslToLinearSrgb(baseOkhsl.h, baseOkhsl.s, baseOkhsl.l, ctx.config.pastel);
1589
1608
  const toneRange = schemeToneRange(isDark, mode, isHighContrast, ctx.config);
1590
1609
  let initialDirection;
1591
1610
  if (preferredTone < baseTone) initialDirection = "darker";
@@ -1599,7 +1618,7 @@ function resolveDependentColor(name, def, ctx, isHighContrast, isDark, effective
1599
1618
  toneRange: [0, 1],
1600
1619
  initialDirection,
1601
1620
  flip,
1602
- saturationTaper: ctx.config.saturationTaper
1621
+ pastel: ctx.config.pastel
1603
1622
  });
1604
1623
  if (!result.met) warnContrastUnmet(name, isDark, isHighContrast, resolvedContrast, result.contrast);
1605
1624
  return {
@@ -1631,9 +1650,8 @@ function resolveColorForScheme(name, def, ctx, isDark, isHighContrast) {
1631
1650
  satFactor = dep.satFactor;
1632
1651
  }
1633
1652
  const baseSat = satFactor * ctx.saturation / 100;
1634
- let finalSat = isDark ? mapSaturationDark(baseSat, mode, ctx.config) : baseSat;
1653
+ const finalSat = isDark ? mapSaturationDark(baseSat, mode, ctx.config) : baseSat;
1635
1654
  const toneFraction = clamp(finalTone / 100, 0, 1);
1636
- finalSat = saturationEnvelope(finalSat, toneFraction, ctx.config.saturationTaper);
1637
1655
  return {
1638
1656
  h: effectiveHue,
1639
1657
  s: clamp(finalSat, 0, 1),
@@ -1649,8 +1667,8 @@ function resolveShadowForScheme(def, ctx, isDark, isHighContrast) {
1649
1667
  const tuning = resolveShadowTuning(def.tuning, ctx.config.shadowTuning);
1650
1668
  return toToneVariant(computeShadow(bgVariant, fgVariant, intensity, tuning));
1651
1669
  }
1652
- function okhslVariantToLinearRgb(v) {
1653
- return okhslToLinearSrgb(v.h, v.s, v.l);
1670
+ function okhslVariantToLinearRgb(v, pastel) {
1671
+ return okhslToLinearSrgb(v.h, v.s, v.l, pastel);
1654
1672
  }
1655
1673
  /**
1656
1674
  * Resolve hue for OKHSL mixing, handling achromatic colors.
@@ -1673,12 +1691,12 @@ function linearSrgbLerp(base, target, t) {
1673
1691
  base[2] + (target[2] - base[2]) * t
1674
1692
  ];
1675
1693
  }
1676
- function linearRgbToToneVariant(rgb) {
1694
+ function linearRgbToToneVariant(rgb, pastel) {
1677
1695
  const [h, s, l] = srgbToOkhsl([
1678
1696
  Math.max(0, Math.min(1, sRGBLinearToGamma(rgb[0]))),
1679
1697
  Math.max(0, Math.min(1, sRGBLinearToGamma(rgb[1]))),
1680
1698
  Math.max(0, Math.min(1, sRGBLinearToGamma(rgb[2])))
1681
- ]);
1699
+ ], pastel);
1682
1700
  return toToneVariant({
1683
1701
  h,
1684
1702
  s,
@@ -1694,15 +1712,15 @@ function resolveMixForScheme(def, ctx, isDark, isHighContrast) {
1694
1712
  let t = clamp(isHighContrast ? pairHC(def.value) : pairNormal(def.value), 0, 100) / 100;
1695
1713
  const blend = def.blend ?? "opaque";
1696
1714
  const space = def.space ?? "okhsl";
1697
- const baseLinear = okhslVariantToLinearRgb(baseVariant);
1698
- const targetLinear = okhslVariantToLinearRgb(targetVariant);
1715
+ const baseLinear = okhslVariantToLinearRgb(baseVariant, ctx.config.pastel);
1716
+ const targetLinear = okhslVariantToLinearRgb(targetVariant, ctx.config.pastel);
1699
1717
  if (def.contrast !== void 0) {
1700
1718
  const resolvedContrast = resolveContrastSpec(def.contrast, isHighContrast);
1701
1719
  const metric = resolvedContrast.metric;
1702
1720
  let luminanceAt;
1703
1721
  if (blend === "transparent" || space === "srgb") luminanceAt = (v) => metricLuminance(metric, linearSrgbLerp(baseLinear, targetLinear, v));
1704
1722
  else luminanceAt = (v) => {
1705
- return metricLuminance(metric, okhslToLinearSrgb(mixHue(baseVariant, targetVariant, v), baseVariant.s + (targetVariant.s - baseVariant.s) * v, baseVariant.l + (targetVariant.l - baseVariant.l) * v));
1723
+ return metricLuminance(metric, okhslToLinearSrgb(mixHue(baseVariant, targetVariant, v), baseVariant.s + (targetVariant.s - baseVariant.s) * v, baseVariant.l + (targetVariant.l - baseVariant.l) * v, ctx.config.pastel));
1706
1724
  };
1707
1725
  t = findValueForMixContrast({
1708
1726
  preferredValue: t,
@@ -1719,7 +1737,7 @@ function resolveMixForScheme(def, ctx, isDark, isHighContrast) {
1719
1737
  l: targetVariant.l,
1720
1738
  alpha: clamp(t, 0, 1)
1721
1739
  });
1722
- if (space === "srgb") return linearRgbToToneVariant(linearSrgbLerp(baseLinear, targetLinear, t));
1740
+ if (space === "srgb") return linearRgbToToneVariant(linearSrgbLerp(baseLinear, targetLinear, t), ctx.config.pastel);
1723
1741
  return toToneVariant({
1724
1742
  h: mixHue(baseVariant, targetVariant, t),
1725
1743
  s: clamp(baseVariant.s + (targetVariant.s - baseVariant.s) * t, 0, 1),
@@ -1871,9 +1889,9 @@ const formatters = {
1871
1889
  function fmt(value, decimals) {
1872
1890
  return parseFloat(value.toFixed(decimals)).toString();
1873
1891
  }
1874
- function formatVariant(v, format = "okhsl") {
1892
+ function formatVariant(v, format = "okhsl", pastel = false) {
1875
1893
  const { l } = variantToOkhsl(v);
1876
- const base = formatters[format](v.h, v.s * 100, l * 100);
1894
+ const base = formatters[format](v.h, v.s * 100, l * 100, pastel);
1877
1895
  if (v.alpha >= 1) return base;
1878
1896
  const closing = base.lastIndexOf(")");
1879
1897
  return `${base.slice(0, closing)} / ${fmt(v.alpha, 4)})`;
@@ -1885,44 +1903,44 @@ function resolveModes(override) {
1885
1903
  highContrast: override?.highContrast ?? cfg.modes.highContrast
1886
1904
  };
1887
1905
  }
1888
- function buildTokenMap(resolved, prefix, states, modes, format = "okhsl") {
1906
+ function buildTokenMap(resolved, prefix, states, modes, format = "okhsl", pastel = false) {
1889
1907
  const tokens = {};
1890
1908
  for (const [name, color] of resolved) {
1891
1909
  const key = `#${prefix}${name}`;
1892
- const entry = { "": formatVariant(color.light, format) };
1893
- if (modes.dark) entry[states.dark] = formatVariant(color.dark, format);
1894
- if (modes.highContrast) entry[states.highContrast] = formatVariant(color.lightContrast, format);
1895
- if (modes.dark && modes.highContrast) entry[`${states.dark} & ${states.highContrast}`] = formatVariant(color.darkContrast, format);
1910
+ const entry = { "": formatVariant(color.light, format, pastel) };
1911
+ if (modes.dark) entry[states.dark] = formatVariant(color.dark, format, pastel);
1912
+ if (modes.highContrast) entry[states.highContrast] = formatVariant(color.lightContrast, format, pastel);
1913
+ if (modes.dark && modes.highContrast) entry[`${states.dark} & ${states.highContrast}`] = formatVariant(color.darkContrast, format, pastel);
1896
1914
  tokens[key] = entry;
1897
1915
  }
1898
1916
  return tokens;
1899
1917
  }
1900
- function buildFlatTokenMap(resolved, prefix, modes, format = "okhsl") {
1918
+ function buildFlatTokenMap(resolved, prefix, modes, format = "okhsl", pastel = false) {
1901
1919
  const result = { light: {} };
1902
1920
  if (modes.dark) result.dark = {};
1903
1921
  if (modes.highContrast) result.lightContrast = {};
1904
1922
  if (modes.dark && modes.highContrast) result.darkContrast = {};
1905
1923
  for (const [name, color] of resolved) {
1906
1924
  const key = `${prefix}${name}`;
1907
- result.light[key] = formatVariant(color.light, format);
1908
- if (modes.dark) result.dark[key] = formatVariant(color.dark, format);
1909
- if (modes.highContrast) result.lightContrast[key] = formatVariant(color.lightContrast, format);
1910
- if (modes.dark && modes.highContrast) result.darkContrast[key] = formatVariant(color.darkContrast, format);
1925
+ result.light[key] = formatVariant(color.light, format, pastel);
1926
+ if (modes.dark) result.dark[key] = formatVariant(color.dark, format, pastel);
1927
+ if (modes.highContrast) result.lightContrast[key] = formatVariant(color.lightContrast, format, pastel);
1928
+ if (modes.dark && modes.highContrast) result.darkContrast[key] = formatVariant(color.darkContrast, format, pastel);
1911
1929
  }
1912
1930
  return result;
1913
1931
  }
1914
- function buildJsonMap(resolved, modes, format = "okhsl") {
1932
+ function buildJsonMap(resolved, modes, format = "okhsl", pastel = false) {
1915
1933
  const result = {};
1916
1934
  for (const [name, color] of resolved) {
1917
- const entry = { light: formatVariant(color.light, format) };
1918
- if (modes.dark) entry.dark = formatVariant(color.dark, format);
1919
- if (modes.highContrast) entry.lightContrast = formatVariant(color.lightContrast, format);
1920
- if (modes.dark && modes.highContrast) entry.darkContrast = formatVariant(color.darkContrast, format);
1935
+ const entry = { light: formatVariant(color.light, format, pastel) };
1936
+ if (modes.dark) entry.dark = formatVariant(color.dark, format, pastel);
1937
+ if (modes.highContrast) entry.lightContrast = formatVariant(color.lightContrast, format, pastel);
1938
+ if (modes.dark && modes.highContrast) entry.darkContrast = formatVariant(color.darkContrast, format, pastel);
1921
1939
  result[name] = entry;
1922
1940
  }
1923
1941
  return result;
1924
1942
  }
1925
- function buildCssMap(resolved, prefix, suffix, format) {
1943
+ function buildCssMap(resolved, prefix, suffix, format, pastel = false) {
1926
1944
  const lines = {
1927
1945
  light: [],
1928
1946
  dark: [],
@@ -1931,10 +1949,10 @@ function buildCssMap(resolved, prefix, suffix, format) {
1931
1949
  };
1932
1950
  for (const [name, color] of resolved) {
1933
1951
  const prop = `--${prefix}${name}${suffix}`;
1934
- lines.light.push(`${prop}: ${formatVariant(color.light, format)};`);
1935
- lines.dark.push(`${prop}: ${formatVariant(color.dark, format)};`);
1936
- lines.lightContrast.push(`${prop}: ${formatVariant(color.lightContrast, format)};`);
1937
- lines.darkContrast.push(`${prop}: ${formatVariant(color.darkContrast, format)};`);
1952
+ lines.light.push(`${prop}: ${formatVariant(color.light, format, pastel)};`);
1953
+ lines.dark.push(`${prop}: ${formatVariant(color.dark, format, pastel)};`);
1954
+ lines.lightContrast.push(`${prop}: ${formatVariant(color.lightContrast, format, pastel)};`);
1955
+ lines.darkContrast.push(`${prop}: ${formatVariant(color.darkContrast, format, pastel)};`);
1938
1956
  }
1939
1957
  return {
1940
1958
  light: lines.light.join("\n"),
@@ -1986,7 +2004,6 @@ function buildValueFormConfigOverride(userOverride) {
1986
2004
  lightTone: userOverride?.lightTone !== void 0 ? userOverride.lightTone : false,
1987
2005
  darkTone: userOverride?.darkTone !== void 0 ? userOverride.darkTone : cfg.darkTone,
1988
2006
  darkDesaturation: userOverride?.darkDesaturation ?? cfg.darkDesaturation,
1989
- saturationTaper: userOverride?.saturationTaper ?? cfg.saturationTaper,
1990
2007
  autoFlip: userOverride?.autoFlip ?? cfg.autoFlip,
1991
2008
  shadowTuning: userOverride?.shadowTuning ?? cfg.shadowTuning
1992
2009
  };
@@ -2003,7 +2020,6 @@ function buildStructuredConfigOverride(userOverride) {
2003
2020
  lightTone: userOverride?.lightTone !== void 0 ? userOverride.lightTone : cfg.lightTone,
2004
2021
  darkTone: userOverride?.darkTone !== void 0 ? userOverride.darkTone : cfg.darkTone,
2005
2022
  darkDesaturation: userOverride?.darkDesaturation ?? cfg.darkDesaturation,
2006
- saturationTaper: userOverride?.saturationTaper ?? cfg.saturationTaper,
2007
2023
  autoFlip: userOverride?.autoFlip ?? cfg.autoFlip,
2008
2024
  shadowTuning: userOverride?.shadowTuning ?? cfg.shadowTuning
2009
2025
  };
@@ -2319,7 +2335,7 @@ function createColorTokenFromDefs(seedHue, seedSaturation, defs, primary, effect
2319
2335
  };
2320
2336
  };
2321
2337
  const tokenLike = (options) => {
2322
- return buildTokenMap(resolveOnce(), "", resolveStates(options), resolveModes(options?.modes), options?.format)[`#${primary}`];
2338
+ return buildTokenMap(resolveOnce(), "", resolveStates(options), resolveModes(options?.modes), options?.format, effectiveConfig.pastel)[`#${primary}`];
2323
2339
  };
2324
2340
  return {
2325
2341
  resolve() {
@@ -2328,10 +2344,10 @@ function createColorTokenFromDefs(seedHue, seedSaturation, defs, primary, effect
2328
2344
  token: tokenLike,
2329
2345
  tasty: tokenLike,
2330
2346
  json(options) {
2331
- return buildJsonMap(resolveOnce(), resolveModes(options?.modes), options?.format)[primary];
2347
+ return buildJsonMap(resolveOnce(), resolveModes(options?.modes), options?.format, effectiveConfig.pastel)[primary];
2332
2348
  },
2333
2349
  css(options) {
2334
- return buildCssMap(new Map([[options.name, resolveOnce().get(primary)]]), "", options.suffix ?? "-color", options.format ?? "rgb");
2350
+ return buildCssMap(new Map([[options.name, resolveOnce().get(primary)]]), "", options.suffix ?? "-color", options.format ?? "rgb", effectiveConfig.pastel);
2335
2351
  },
2336
2352
  export: exportData
2337
2353
  };
@@ -2571,9 +2587,10 @@ function buildPaletteOutput(themes, paletteOptions, options, buildOne, merge, em
2571
2587
  const seen = /* @__PURE__ */ new Map();
2572
2588
  for (const [themeName, theme] of Object.entries(themes)) {
2573
2589
  const resolved = theme.resolve();
2590
+ const pastel = theme.getConfig().pastel;
2574
2591
  const prefix = resolvePrefix(options, themeName, true);
2575
- merge(acc, buildOne(filterCollisions(resolved, prefix, seen, themeName), prefix));
2576
- if (themeName === effectivePrimary) merge(acc, buildOne(filterCollisions(resolved, "", seen, themeName, true), ""));
2592
+ merge(acc, buildOne(filterCollisions(resolved, prefix, seen, themeName), prefix, pastel));
2593
+ if (themeName === effectivePrimary) merge(acc, buildOne(filterCollisions(resolved, "", seen, themeName, true), "", pastel));
2577
2594
  }
2578
2595
  return acc;
2579
2596
  }
@@ -2582,7 +2599,7 @@ function createPalette(themes, paletteOptions) {
2582
2599
  return {
2583
2600
  tokens(options) {
2584
2601
  const modes = resolveModes(options?.modes);
2585
- return buildPaletteOutput(themes, paletteOptions, options, (filtered, prefix) => buildFlatTokenMap(filtered, prefix, modes, options?.format), (acc, part) => {
2602
+ return buildPaletteOutput(themes, paletteOptions, options, (filtered, prefix, pastel) => buildFlatTokenMap(filtered, prefix, modes, options?.format, pastel), (acc, part) => {
2586
2603
  for (const variant of Object.keys(part)) {
2587
2604
  if (!acc[variant]) acc[variant] = {};
2588
2605
  Object.assign(acc[variant], part[variant]);
@@ -2596,18 +2613,18 @@ function createPalette(themes, paletteOptions) {
2596
2613
  highContrast: options?.states?.highContrast ?? cfg.states.highContrast
2597
2614
  };
2598
2615
  const modes = resolveModes(options?.modes);
2599
- return buildPaletteOutput(themes, paletteOptions, options, (filtered, prefix) => buildTokenMap(filtered, prefix, states, modes, options?.format), (acc, part) => Object.assign(acc, part), () => ({}));
2616
+ return buildPaletteOutput(themes, paletteOptions, options, (filtered, prefix, pastel) => buildTokenMap(filtered, prefix, states, modes, options?.format, pastel), (acc, part) => Object.assign(acc, part), () => ({}));
2600
2617
  },
2601
2618
  json(options) {
2602
2619
  const modes = resolveModes(options?.modes);
2603
2620
  const result = {};
2604
- for (const [themeName, theme] of Object.entries(themes)) result[themeName] = buildJsonMap(theme.resolve(), modes, options?.format);
2621
+ for (const [themeName, theme] of Object.entries(themes)) result[themeName] = buildJsonMap(theme.resolve(), modes, options?.format, theme.getConfig().pastel);
2605
2622
  return result;
2606
2623
  },
2607
2624
  css(options) {
2608
2625
  const suffix = options?.suffix ?? "-color";
2609
2626
  const format = options?.format ?? "rgb";
2610
- const lines = buildPaletteOutput(themes, paletteOptions, options, (filtered, prefix) => buildCssMap(filtered, prefix, suffix, format), (acc, part) => {
2627
+ const lines = buildPaletteOutput(themes, paletteOptions, options, (filtered, prefix, pastel) => buildCssMap(filtered, prefix, suffix, format, pastel), (acc, part) => {
2611
2628
  for (const key of [
2612
2629
  "light",
2613
2630
  "dark",
@@ -2674,6 +2691,9 @@ function createTheme(hue, saturation, initialColors, configOverride) {
2674
2691
  get saturation() {
2675
2692
  return saturation;
2676
2693
  },
2694
+ getConfig() {
2695
+ return getEffectiveConfig();
2696
+ },
2677
2697
  colors(defs) {
2678
2698
  colorDefs = {
2679
2699
  ...colorDefs,
@@ -2728,7 +2748,7 @@ function createTheme(hue, saturation, initialColors, configOverride) {
2728
2748
  },
2729
2749
  tokens(options) {
2730
2750
  const modes = resolveModes(options?.modes);
2731
- return buildFlatTokenMap(resolveCached(), "", modes, options?.format);
2751
+ return buildFlatTokenMap(resolveCached(), "", modes, options?.format, getEffectiveConfig().pastel);
2732
2752
  },
2733
2753
  tasty(options) {
2734
2754
  const cfg = getEffectiveConfig();
@@ -2737,14 +2757,14 @@ function createTheme(hue, saturation, initialColors, configOverride) {
2737
2757
  highContrast: options?.states?.highContrast ?? cfg.states.highContrast
2738
2758
  };
2739
2759
  const modes = resolveModes(options?.modes);
2740
- return buildTokenMap(resolveCached(), "", states, modes, options?.format);
2760
+ return buildTokenMap(resolveCached(), "", states, modes, options?.format, cfg.pastel);
2741
2761
  },
2742
2762
  json(options) {
2743
2763
  const modes = resolveModes(options?.modes);
2744
- return buildJsonMap(resolveCached(), modes, options?.format);
2764
+ return buildJsonMap(resolveCached(), modes, options?.format, getEffectiveConfig().pastel);
2745
2765
  },
2746
2766
  css(options) {
2747
- return buildCssMap(resolveCached(), "", options?.suffix ?? "-color", options?.format ?? "rgb");
2767
+ return buildCssMap(resolveCached(), "", options?.suffix ?? "-color", options?.format ?? "rgb", getEffectiveConfig().pastel);
2748
2768
  }
2749
2769
  };
2750
2770
  }
@@ -2767,7 +2787,7 @@ function createTheme(hue, saturation, initialColors, configOverride) {
2767
2787
  * Create a single-hue glaze theme.
2768
2788
  *
2769
2789
  * An optional `config` override can be supplied to customize the resolve
2770
- * behavior for this theme (tone windows, saturation taper, etc.). The
2790
+ * behavior for this theme (tone windows, etc.). The
2771
2791
  * override is **merged over the live global config at resolve time** —
2772
2792
  * the theme still reacts to later `configure()` calls for fields it
2773
2793
  * didn't override.
@@ -2827,7 +2847,7 @@ glaze.from = function from(data) {
2827
2847
  *
2828
2848
  * // Config override on any form
2829
2849
  * glaze.color('#26fcb2', { darkTone: false, autoFlip: false })
2830
- * glaze.color({ from: '#fff', base: bg }, { saturationTaper: 0 })
2850
+ * glaze.color({ from: '#fff', base: bg })
2831
2851
  * ```
2832
2852
  *
2833
2853
  * Defaults: every form defaults to `mode: 'auto'`. Value-shorthand forms
@@ -2881,8 +2901,8 @@ glaze.shadow = function shadow(input) {
2881
2901
  };
2882
2902
  };
2883
2903
  /** Format a resolved color variant as a CSS string. */
2884
- glaze.format = function format(variant, colorFormat) {
2885
- return formatVariant(variant, colorFormat);
2904
+ glaze.format = function format(variant, colorFormat, pastel) {
2905
+ return formatVariant(variant, colorFormat, pastel);
2886
2906
  };
2887
2907
  /**
2888
2908
  * Create a theme from a hex color string.
@@ -2936,5 +2956,5 @@ glaze.resetConfig = function resetConfig$1() {
2936
2956
  };
2937
2957
 
2938
2958
  //#endregion
2939
- export { REF_EPS, apcaContrast, contrastRatioFromLuminance, findToneForContrast, findValueForMixContrast, formatHsl, formatOkhsl, formatOklch, formatRgb, fromTone, gamutClampedLuminance, glaze, hslToSrgb, okhslToLinearSrgb, okhslToOkhst, okhslToOklab, okhslToSrgb, okhstToOkhsl, oklabToOkhsl, parseHex, parseHexAlpha, relativeLuminanceFromLinearRgb, resolveContrastForMode, resolveMinContrast, srgbToOkhsl, toTone, toneFromY, variantToOkhsl, yFromTone };
2959
+ export { REF_EPS, apcaContrast, contrastRatioFromLuminance, cuspLightness, findToneForContrast, findValueForMixContrast, formatHsl, formatOkhsl, formatOklch, formatRgb, fromTone, gamutClampedLuminance, glaze, hslToSrgb, okhslToLinearSrgb, okhslToOkhst, okhslToOklab, okhslToSrgb, okhstToOkhsl, oklabToOkhsl, parseHex, parseHexAlpha, relativeLuminanceFromLinearRgb, resolveContrastForMode, resolveMinContrast, srgbToOkhsl, toTone, toneFromY, variantToOkhsl, yFromTone };
2940
2960
  //# sourceMappingURL=index.mjs.map