@tenphi/glaze 0.6.3 → 0.8.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.d.cts CHANGED
@@ -242,6 +242,12 @@ interface GlazeConfig {
242
242
  darkLightness?: [number, number];
243
243
  /** Saturation reduction factor for dark scheme (0–1). Default: 0.1. */
244
244
  darkDesaturation?: number;
245
+ /**
246
+ * Power-curve exponent for dark auto-inversion (0–1).
247
+ * Lower values expand subtle near-white distinctions in dark mode.
248
+ * Set to 1 for linear (legacy) behavior. Default: 0.5.
249
+ */
250
+ darkCurve?: number;
245
251
  /** State alias names for token export. */
246
252
  states?: {
247
253
  dark?: string;
@@ -256,6 +262,7 @@ interface GlazeConfigResolved {
256
262
  lightLightness: [number, number];
257
263
  darkLightness: [number, number];
258
264
  darkDesaturation: number;
265
+ darkCurve: number;
259
266
  states: {
260
267
  dark: string;
261
268
  highContrast: string;
@@ -385,33 +392,54 @@ interface GlazeCssResult {
385
392
  lightContrast: string;
386
393
  darkContrast: string;
387
394
  }
395
+ /** Options shared by palette `tokens()`, `tasty()`, and `css()` exports. */
396
+ interface GlazePaletteExportOptions {
397
+ /**
398
+ * Prefix mode. `true` uses `"<themeName>-"`, or provide a custom map.
399
+ * Defaults to `true` for palette export methods.
400
+ * Set to `false` explicitly to disable prefixing (last-write-wins on collisions).
401
+ */
402
+ prefix?: boolean | Record<string, string>;
403
+ /**
404
+ * Name of the primary theme. The primary theme's tokens are duplicated
405
+ * without prefix, providing convenient short aliases alongside the
406
+ * prefixed versions.
407
+ *
408
+ * @example
409
+ * ```ts
410
+ * palette.tokens({ primary: 'brand' })
411
+ * // → { light: { 'brand-surface': '...', 'surface': '...', 'accent-surface': '...' } }
412
+ * ```
413
+ */
414
+ primary?: string;
415
+ }
388
416
  interface GlazePalette {
389
417
  /**
390
418
  * Export all themes as a flat token map grouped by scheme variant.
419
+ * Prefix defaults to `true` — all tokens are prefixed with the theme name.
420
+ * Use `primary` to duplicate one theme's tokens without prefix.
391
421
  *
392
422
  * ```ts
393
- * palette.tokens({ prefix: true })
394
- * // → { light: { 'primary-surface': 'okhsl(...)' }, dark: { 'primary-surface': 'okhsl(...)' } }
423
+ * palette.tokens({ primary: 'brand' })
424
+ * // → { light: { 'brand-surface': '...', 'surface': '...', 'accent-surface': '...' } }
395
425
  * ```
396
426
  */
397
- tokens(options?: GlazeJsonOptions & {
398
- prefix?: boolean | Record<string, string>;
399
- }): Record<string, Record<string, string>>;
427
+ tokens(options?: GlazeJsonOptions & GlazePaletteExportOptions): Record<string, Record<string, string>>;
400
428
  /**
401
429
  * Export all themes as tasty style-to-state bindings.
402
430
  * Uses `#name` color token keys and state aliases (`''`, `@dark`, etc.).
403
- * Spread into component styles or register as a recipe via `configure({ recipes })`.
431
+ * Prefix defaults to `true`. Use `primary` to duplicate one theme without prefix.
404
432
  * @see https://cube-ui-kit.vercel.app/?path=/docs/tasty-documentation--docs
405
433
  */
406
- tasty(options?: GlazeTokenOptions): Record<string, Record<string, string>>;
407
- /** Export all themes as plain JSON. */
434
+ tasty(options?: GlazeTokenOptions & {
435
+ primary?: string;
436
+ }): Record<string, Record<string, string>>;
437
+ /** Export all themes as plain JSON grouped by theme name. */
408
438
  json(options?: GlazeJsonOptions & {
409
439
  prefix?: boolean | Record<string, string>;
410
440
  }): Record<string, Record<string, Record<string, string>>>;
411
441
  /** Export all themes as CSS custom property declarations. */
412
- css(options?: GlazeCssOptions & {
413
- prefix?: boolean | Record<string, string>;
414
- }): GlazeCssResult;
442
+ css(options?: GlazeCssOptions & GlazePaletteExportOptions): GlazeCssResult;
415
443
  }
416
444
  //#endregion
417
445
  //#region src/glaze.d.ts
@@ -433,16 +461,14 @@ declare function glaze(hueOrOptions: number | {
433
461
  declare namespace glaze {
434
462
  var configure: (config: GlazeConfig) => void;
435
463
  var palette: (themes: PaletteInput) => {
436
- tokens(options?: GlazeJsonOptions & {
437
- prefix?: boolean | Record<string, string>;
464
+ tokens(options?: GlazeJsonOptions & GlazePaletteExportOptions): Record<string, Record<string, string>>;
465
+ tasty(options?: GlazeTokenOptions & {
466
+ primary?: string;
438
467
  }): Record<string, Record<string, string>>;
439
- tasty(options?: GlazeTokenOptions): Record<string, Record<string, string>>;
440
468
  json(options?: GlazeJsonOptions & {
441
469
  prefix?: boolean | Record<string, string>;
442
470
  }): Record<string, Record<string, Record<string, string>>>;
443
- css(options?: GlazeCssOptions & {
444
- prefix?: boolean | Record<string, string>;
445
- }): GlazeCssResult;
471
+ css(options?: GlazeCssOptions & GlazePaletteExportOptions): GlazeCssResult;
446
472
  };
447
473
  var from: (data: GlazeThemeExport) => GlazeTheme;
448
474
  var color: (input: GlazeColorInput) => GlazeColorToken;
@@ -522,5 +548,5 @@ declare function formatHsl(h: number, s: number, l: number): string;
522
548
  */
523
549
  declare function formatOklch(h: number, s: number, l: number): string;
524
550
  //#endregion
525
- export { type AdaptationMode, type ColorDef, type ColorMap, type ContrastPreset, type FindLightnessForContrastOptions, type FindLightnessForContrastResult, type FindValueForMixContrastOptions, type FindValueForMixContrastResult, 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 MixColorDef, type OkhslColor, type RegularColorDef, type RelativeValue, type ResolvedColor, type ResolvedColorVariant, type ShadowColorDef, type ShadowTuning, contrastRatioFromLuminance, findLightnessForContrast, findValueForMixContrast, formatHsl, formatOkhsl, formatOklch, formatRgb, gamutClampedLuminance, glaze, okhslToLinearSrgb, okhslToOklab, okhslToSrgb, parseHex, relativeLuminanceFromLinearRgb, resolveMinContrast, srgbToOkhsl };
551
+ export { type AdaptationMode, type ColorDef, type ColorMap, type ContrastPreset, type FindLightnessForContrastOptions, type FindLightnessForContrastResult, type FindValueForMixContrastOptions, type FindValueForMixContrastResult, type GlazeColorFormat, type GlazeColorInput, type GlazeColorToken, type GlazeConfig, type GlazeCssOptions, type GlazeCssResult, type GlazeExtendOptions, type GlazeJsonOptions, type GlazeOutputModes, type GlazePalette, type GlazePaletteExportOptions, type GlazeShadowInput, type GlazeTheme, type GlazeThemeExport, type GlazeTokenOptions, type HCPair, type HexColor, type MinContrast, type MixColorDef, type OkhslColor, type RegularColorDef, type RelativeValue, type ResolvedColor, type ResolvedColorVariant, type ShadowColorDef, type ShadowTuning, contrastRatioFromLuminance, findLightnessForContrast, findValueForMixContrast, formatHsl, formatOkhsl, formatOklch, formatRgb, gamutClampedLuminance, glaze, okhslToLinearSrgb, okhslToOklab, okhslToSrgb, parseHex, relativeLuminanceFromLinearRgb, resolveMinContrast, srgbToOkhsl };
526
552
  //# sourceMappingURL=index.d.cts.map
package/dist/index.d.mts CHANGED
@@ -242,6 +242,12 @@ interface GlazeConfig {
242
242
  darkLightness?: [number, number];
243
243
  /** Saturation reduction factor for dark scheme (0–1). Default: 0.1. */
244
244
  darkDesaturation?: number;
245
+ /**
246
+ * Power-curve exponent for dark auto-inversion (0–1).
247
+ * Lower values expand subtle near-white distinctions in dark mode.
248
+ * Set to 1 for linear (legacy) behavior. Default: 0.5.
249
+ */
250
+ darkCurve?: number;
245
251
  /** State alias names for token export. */
246
252
  states?: {
247
253
  dark?: string;
@@ -256,6 +262,7 @@ interface GlazeConfigResolved {
256
262
  lightLightness: [number, number];
257
263
  darkLightness: [number, number];
258
264
  darkDesaturation: number;
265
+ darkCurve: number;
259
266
  states: {
260
267
  dark: string;
261
268
  highContrast: string;
@@ -385,33 +392,54 @@ interface GlazeCssResult {
385
392
  lightContrast: string;
386
393
  darkContrast: string;
387
394
  }
395
+ /** Options shared by palette `tokens()`, `tasty()`, and `css()` exports. */
396
+ interface GlazePaletteExportOptions {
397
+ /**
398
+ * Prefix mode. `true` uses `"<themeName>-"`, or provide a custom map.
399
+ * Defaults to `true` for palette export methods.
400
+ * Set to `false` explicitly to disable prefixing (last-write-wins on collisions).
401
+ */
402
+ prefix?: boolean | Record<string, string>;
403
+ /**
404
+ * Name of the primary theme. The primary theme's tokens are duplicated
405
+ * without prefix, providing convenient short aliases alongside the
406
+ * prefixed versions.
407
+ *
408
+ * @example
409
+ * ```ts
410
+ * palette.tokens({ primary: 'brand' })
411
+ * // → { light: { 'brand-surface': '...', 'surface': '...', 'accent-surface': '...' } }
412
+ * ```
413
+ */
414
+ primary?: string;
415
+ }
388
416
  interface GlazePalette {
389
417
  /**
390
418
  * Export all themes as a flat token map grouped by scheme variant.
419
+ * Prefix defaults to `true` — all tokens are prefixed with the theme name.
420
+ * Use `primary` to duplicate one theme's tokens without prefix.
391
421
  *
392
422
  * ```ts
393
- * palette.tokens({ prefix: true })
394
- * // → { light: { 'primary-surface': 'okhsl(...)' }, dark: { 'primary-surface': 'okhsl(...)' } }
423
+ * palette.tokens({ primary: 'brand' })
424
+ * // → { light: { 'brand-surface': '...', 'surface': '...', 'accent-surface': '...' } }
395
425
  * ```
396
426
  */
397
- tokens(options?: GlazeJsonOptions & {
398
- prefix?: boolean | Record<string, string>;
399
- }): Record<string, Record<string, string>>;
427
+ tokens(options?: GlazeJsonOptions & GlazePaletteExportOptions): Record<string, Record<string, string>>;
400
428
  /**
401
429
  * Export all themes as tasty style-to-state bindings.
402
430
  * Uses `#name` color token keys and state aliases (`''`, `@dark`, etc.).
403
- * Spread into component styles or register as a recipe via `configure({ recipes })`.
431
+ * Prefix defaults to `true`. Use `primary` to duplicate one theme without prefix.
404
432
  * @see https://cube-ui-kit.vercel.app/?path=/docs/tasty-documentation--docs
405
433
  */
406
- tasty(options?: GlazeTokenOptions): Record<string, Record<string, string>>;
407
- /** Export all themes as plain JSON. */
434
+ tasty(options?: GlazeTokenOptions & {
435
+ primary?: string;
436
+ }): Record<string, Record<string, string>>;
437
+ /** Export all themes as plain JSON grouped by theme name. */
408
438
  json(options?: GlazeJsonOptions & {
409
439
  prefix?: boolean | Record<string, string>;
410
440
  }): Record<string, Record<string, Record<string, string>>>;
411
441
  /** Export all themes as CSS custom property declarations. */
412
- css(options?: GlazeCssOptions & {
413
- prefix?: boolean | Record<string, string>;
414
- }): GlazeCssResult;
442
+ css(options?: GlazeCssOptions & GlazePaletteExportOptions): GlazeCssResult;
415
443
  }
416
444
  //#endregion
417
445
  //#region src/glaze.d.ts
@@ -433,16 +461,14 @@ declare function glaze(hueOrOptions: number | {
433
461
  declare namespace glaze {
434
462
  var configure: (config: GlazeConfig) => void;
435
463
  var palette: (themes: PaletteInput) => {
436
- tokens(options?: GlazeJsonOptions & {
437
- prefix?: boolean | Record<string, string>;
464
+ tokens(options?: GlazeJsonOptions & GlazePaletteExportOptions): Record<string, Record<string, string>>;
465
+ tasty(options?: GlazeTokenOptions & {
466
+ primary?: string;
438
467
  }): Record<string, Record<string, string>>;
439
- tasty(options?: GlazeTokenOptions): Record<string, Record<string, string>>;
440
468
  json(options?: GlazeJsonOptions & {
441
469
  prefix?: boolean | Record<string, string>;
442
470
  }): Record<string, Record<string, Record<string, string>>>;
443
- css(options?: GlazeCssOptions & {
444
- prefix?: boolean | Record<string, string>;
445
- }): GlazeCssResult;
471
+ css(options?: GlazeCssOptions & GlazePaletteExportOptions): GlazeCssResult;
446
472
  };
447
473
  var from: (data: GlazeThemeExport) => GlazeTheme;
448
474
  var color: (input: GlazeColorInput) => GlazeColorToken;
@@ -522,5 +548,5 @@ declare function formatHsl(h: number, s: number, l: number): string;
522
548
  */
523
549
  declare function formatOklch(h: number, s: number, l: number): string;
524
550
  //#endregion
525
- export { type AdaptationMode, type ColorDef, type ColorMap, type ContrastPreset, type FindLightnessForContrastOptions, type FindLightnessForContrastResult, type FindValueForMixContrastOptions, type FindValueForMixContrastResult, 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 MixColorDef, type OkhslColor, type RegularColorDef, type RelativeValue, type ResolvedColor, type ResolvedColorVariant, type ShadowColorDef, type ShadowTuning, contrastRatioFromLuminance, findLightnessForContrast, findValueForMixContrast, formatHsl, formatOkhsl, formatOklch, formatRgb, gamutClampedLuminance, glaze, okhslToLinearSrgb, okhslToOklab, okhslToSrgb, parseHex, relativeLuminanceFromLinearRgb, resolveMinContrast, srgbToOkhsl };
551
+ export { type AdaptationMode, type ColorDef, type ColorMap, type ContrastPreset, type FindLightnessForContrastOptions, type FindLightnessForContrastResult, type FindValueForMixContrastOptions, type FindValueForMixContrastResult, type GlazeColorFormat, type GlazeColorInput, type GlazeColorToken, type GlazeConfig, type GlazeCssOptions, type GlazeCssResult, type GlazeExtendOptions, type GlazeJsonOptions, type GlazeOutputModes, type GlazePalette, type GlazePaletteExportOptions, type GlazeShadowInput, type GlazeTheme, type GlazeThemeExport, type GlazeTokenOptions, type HCPair, type HexColor, type MinContrast, type MixColorDef, type OkhslColor, type RegularColorDef, type RelativeValue, type ResolvedColor, type ResolvedColorVariant, type ShadowColorDef, type ShadowTuning, contrastRatioFromLuminance, findLightnessForContrast, findValueForMixContrast, formatHsl, formatOkhsl, formatOklch, formatRgb, gamutClampedLuminance, glaze, okhslToLinearSrgb, okhslToOklab, okhslToSrgb, parseHex, relativeLuminanceFromLinearRgb, resolveMinContrast, srgbToOkhsl };
526
552
  //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs CHANGED
@@ -468,7 +468,7 @@ function formatOklch(h, s, l) {
468
468
  const C = Math.sqrt(a * a + b * b);
469
469
  let hh = Math.atan2(b, a) * (180 / Math.PI);
470
470
  hh = constrainAngle(hh);
471
- return `oklch(${fmt$1(L, 4)} ${fmt$1(C, 4)} ${fmt$1(hh, 1)})`;
471
+ return `oklch(${fmt$1(L, 4)} ${fmt$1(C, 4)} ${fmt$1(hh, 2)})`;
472
472
  }
473
473
 
474
474
  //#endregion
@@ -618,7 +618,7 @@ function coarseScan(h, s, lo, hi, yBase, target, epsilon, maxIter) {
618
618
  function findLightnessForContrast(options) {
619
619
  const { hue, saturation, preferredLightness, baseLinearRgb, contrast: contrastInput, lightnessRange = [0, 1], epsilon = 1e-4, maxIterations = 14 } = options;
620
620
  const target = resolveMinContrast(contrastInput);
621
- const searchTarget = target * 1.005;
621
+ const searchTarget = target * 1.007;
622
622
  const yBase = gamutClampedLuminance(baseLinearRgb);
623
623
  const crPref = contrastRatioFromLuminance(cachedLuminance(hue, saturation, preferredLightness), yBase);
624
624
  if (crPref >= searchTarget) return {
@@ -742,7 +742,7 @@ function searchMixBranch(lo, hi, yBase, target, epsilon, maxIter, preferred, lum
742
742
  function findValueForMixContrast(options) {
743
743
  const { preferredValue, baseLinearRgb, contrast: contrastInput, luminanceAtValue, epsilon = 1e-4, maxIterations = 20 } = options;
744
744
  const target = resolveMinContrast(contrastInput);
745
- const searchTarget = target * 1.005;
745
+ const searchTarget = target * 1.01;
746
746
  const yBase = gamutClampedLuminance(baseLinearRgb);
747
747
  const crPref = contrastRatioFromLuminance(luminanceAtValue(preferredValue), yBase);
748
748
  if (crPref >= searchTarget) return {
@@ -812,6 +812,7 @@ let globalConfig = {
812
812
  lightLightness: [10, 100],
813
813
  darkLightness: [15, 95],
814
814
  darkDesaturation: .1,
815
+ darkCurve: .5,
815
816
  states: {
816
817
  dark: "@dark",
817
818
  highContrast: "@high-contrast"
@@ -959,23 +960,42 @@ function topoSort(defs) {
959
960
  for (const name of Object.keys(defs)) visit(name);
960
961
  return result;
961
962
  }
962
- function mapLightnessLight(l, mode) {
963
- if (mode === "static") return l;
963
+ function mapLightnessLight(l, mode, isHighContrast) {
964
+ if (mode === "static" || isHighContrast) return l;
964
965
  const [lo, hi] = globalConfig.lightLightness;
965
966
  return l * (hi - lo) / 100 + lo;
966
967
  }
967
- function mapLightnessDark(l, mode) {
968
+ function mobiusCurve(t, beta) {
969
+ if (beta >= 1) return t;
970
+ return t / (t + beta * (1 - t));
971
+ }
972
+ function mapLightnessDark(l, mode, isHighContrast) {
968
973
  if (mode === "static") return l;
969
- const [lo, hi] = globalConfig.darkLightness;
970
- if (mode === "fixed") return l * (hi - lo) / 100 + lo;
971
- return (100 - l) * (hi - lo) / 100 + lo;
974
+ const beta = globalConfig.darkCurve;
975
+ if (isHighContrast) {
976
+ if (mode === "fixed") return l;
977
+ return 100 * mobiusCurve((100 - l) / 100, beta);
978
+ }
979
+ const [darkLo, darkHi] = globalConfig.darkLightness;
980
+ if (mode === "fixed") return l * (darkHi - darkLo) / 100 + darkLo;
981
+ const [lightLo, lightHi] = globalConfig.lightLightness;
982
+ const t = (lightHi - (l * (lightHi - lightLo) / 100 + lightLo)) / (lightHi - lightLo);
983
+ return darkLo + (darkHi - darkLo) * mobiusCurve(t, beta);
984
+ }
985
+ function lightMappedToDark(lightL, isHighContrast) {
986
+ const beta = globalConfig.darkCurve;
987
+ if (isHighContrast) return 100 * mobiusCurve((100 - lightL) / 100, beta);
988
+ const [lightLo, lightHi] = globalConfig.lightLightness;
989
+ const [darkLo, darkHi] = globalConfig.darkLightness;
990
+ const t = (lightHi - clamp(lightL, lightLo, lightHi)) / (lightHi - lightLo);
991
+ return darkLo + (darkHi - darkLo) * mobiusCurve(t, beta);
972
992
  }
973
993
  function mapSaturationDark(s, mode) {
974
994
  if (mode === "static") return s;
975
995
  return s * (1 - globalConfig.darkDesaturation);
976
996
  }
977
- function schemeLightnessRange(isDark, mode) {
978
- if (mode === "static") return [0, 1];
997
+ function schemeLightnessRange(isDark, mode, isHighContrast) {
998
+ if (mode === "static" || isHighContrast) return [0, 1];
979
999
  const [lo, hi] = isDark ? globalConfig.darkLightness : globalConfig.lightLightness;
980
1000
  return [lo / 100, hi / 100];
981
1001
  }
@@ -1035,26 +1055,26 @@ function resolveDependentColor(name, def, ctx, isHighContrast, isDark, effective
1035
1055
  else {
1036
1056
  const parsed = parseRelativeOrAbsolute(isHighContrast ? pairHC(rawLightness) : pairNormal(rawLightness));
1037
1057
  if (parsed.relative) {
1038
- let delta = parsed.value;
1039
- if (isDark && mode === "auto") delta = -delta;
1040
- preferredL = clamp(baseL + delta, 0, 100);
1041
- } else if (isDark) preferredL = mapLightnessDark(parsed.value, mode);
1042
- else preferredL = mapLightnessLight(parsed.value, mode);
1058
+ const delta = parsed.value;
1059
+ if (isDark && mode === "auto") preferredL = lightMappedToDark(clamp(getSchemeVariant(baseResolved, false, isHighContrast).l * 100 + delta, 0, 100), isHighContrast);
1060
+ else preferredL = clamp(baseL + delta, 0, 100);
1061
+ } else if (isDark) preferredL = mapLightnessDark(parsed.value, mode, isHighContrast);
1062
+ else preferredL = mapLightnessLight(parsed.value, mode, isHighContrast);
1043
1063
  }
1044
1064
  const rawContrast = def.contrast;
1045
1065
  if (rawContrast !== void 0) {
1046
1066
  const minCr = isHighContrast ? pairHC(rawContrast) : pairNormal(rawContrast);
1047
1067
  const effectiveSat = isDark ? mapSaturationDark(satFactor * ctx.saturation / 100, mode) : satFactor * ctx.saturation / 100;
1048
1068
  const baseLinearRgb = okhslToLinearSrgb(baseVariant.h, baseVariant.s, baseVariant.l);
1049
- const lightnessRange = schemeLightnessRange(isDark, mode);
1069
+ const windowRange = schemeLightnessRange(isDark, mode, isHighContrast);
1050
1070
  return {
1051
1071
  l: findLightnessForContrast({
1052
1072
  hue: effectiveHue,
1053
1073
  saturation: effectiveSat,
1054
- preferredLightness: clamp(preferredL / 100, lightnessRange[0], lightnessRange[1]),
1074
+ preferredLightness: clamp(preferredL / 100, windowRange[0], windowRange[1]),
1055
1075
  baseLinearRgb,
1056
1076
  contrast: minCr,
1057
- lightnessRange
1077
+ lightnessRange: [0, 1]
1058
1078
  }).lightness * 100,
1059
1079
  satFactor
1060
1080
  };
@@ -1091,13 +1111,13 @@ function resolveColorForScheme(name, def, ctx, isDark, isHighContrast) {
1091
1111
  let finalL;
1092
1112
  let finalSat;
1093
1113
  if (isDark && isRoot) {
1094
- finalL = mapLightnessDark(lightL, mode);
1114
+ finalL = mapLightnessDark(lightL, mode, isHighContrast);
1095
1115
  finalSat = mapSaturationDark(satFactor * ctx.saturation / 100, mode);
1096
1116
  } else if (isDark && !isRoot) {
1097
1117
  finalL = lightL;
1098
1118
  finalSat = mapSaturationDark(satFactor * ctx.saturation / 100, mode);
1099
1119
  } else if (isRoot) {
1100
- finalL = mapLightnessLight(lightL, mode);
1120
+ finalL = mapLightnessLight(lightL, mode, isHighContrast);
1101
1121
  finalSat = satFactor * ctx.saturation / 100;
1102
1122
  } else {
1103
1123
  finalL = lightL;
@@ -1419,26 +1439,40 @@ function createTheme(hue, saturation, initialColors) {
1419
1439
  }
1420
1440
  };
1421
1441
  }
1422
- function resolvePrefix(options, themeName) {
1423
- if (options?.prefix === true) return `${themeName}-`;
1424
- if (typeof options?.prefix === "object" && options.prefix !== null) return options.prefix[themeName] ?? `${themeName}-`;
1442
+ function resolvePrefix(options, themeName, defaultPrefix = false) {
1443
+ const prefix = options?.prefix ?? defaultPrefix;
1444
+ if (prefix === true) return `${themeName}-`;
1445
+ if (typeof prefix === "object" && prefix !== null) return prefix[themeName] ?? `${themeName}-`;
1425
1446
  return "";
1426
1447
  }
1448
+ function validatePrimaryTheme(primary, themes) {
1449
+ if (primary !== void 0 && !(primary in themes)) {
1450
+ const available = Object.keys(themes).join(", ");
1451
+ throw new Error(`glaze: primary theme "${primary}" not found in palette. Available: ${available}.`);
1452
+ }
1453
+ }
1427
1454
  function createPalette(themes) {
1428
1455
  return {
1429
1456
  tokens(options) {
1457
+ validatePrimaryTheme(options?.primary, themes);
1430
1458
  const modes = resolveModes(options?.modes);
1431
1459
  const allTokens = {};
1432
1460
  for (const [themeName, theme] of Object.entries(themes)) {
1433
- const tokens = buildFlatTokenMap(theme.resolve(), resolvePrefix(options, themeName), modes, options?.format);
1461
+ const resolved = theme.resolve();
1462
+ const tokens = buildFlatTokenMap(resolved, resolvePrefix(options, themeName, true), modes, options?.format);
1434
1463
  for (const variant of Object.keys(tokens)) {
1435
1464
  if (!allTokens[variant]) allTokens[variant] = {};
1436
1465
  Object.assign(allTokens[variant], tokens[variant]);
1437
1466
  }
1467
+ if (themeName === options?.primary) {
1468
+ const unprefixed = buildFlatTokenMap(resolved, "", modes, options?.format);
1469
+ for (const variant of Object.keys(unprefixed)) Object.assign(allTokens[variant], unprefixed[variant]);
1470
+ }
1438
1471
  }
1439
1472
  return allTokens;
1440
1473
  },
1441
1474
  tasty(options) {
1475
+ validatePrimaryTheme(options?.primary, themes);
1442
1476
  const states = {
1443
1477
  dark: options?.states?.dark ?? globalConfig.states.dark,
1444
1478
  highContrast: options?.states?.highContrast ?? globalConfig.states.highContrast
@@ -1446,8 +1480,13 @@ function createPalette(themes) {
1446
1480
  const modes = resolveModes(options?.modes);
1447
1481
  const allTokens = {};
1448
1482
  for (const [themeName, theme] of Object.entries(themes)) {
1449
- const tokens = buildTokenMap(theme.resolve(), resolvePrefix(options, themeName), states, modes, options?.format);
1483
+ const resolved = theme.resolve();
1484
+ const tokens = buildTokenMap(resolved, resolvePrefix(options, themeName, true), states, modes, options?.format);
1450
1485
  Object.assign(allTokens, tokens);
1486
+ if (themeName === options?.primary) {
1487
+ const unprefixed = buildTokenMap(resolved, "", states, modes, options?.format);
1488
+ Object.assign(allTokens, unprefixed);
1489
+ }
1451
1490
  }
1452
1491
  return allTokens;
1453
1492
  },
@@ -1458,6 +1497,7 @@ function createPalette(themes) {
1458
1497
  return result;
1459
1498
  },
1460
1499
  css(options) {
1500
+ validatePrimaryTheme(options?.primary, themes);
1461
1501
  const suffix = options?.suffix ?? "-color";
1462
1502
  const format = options?.format ?? "rgb";
1463
1503
  const allLines = {
@@ -1467,13 +1507,23 @@ function createPalette(themes) {
1467
1507
  darkContrast: []
1468
1508
  };
1469
1509
  for (const [themeName, theme] of Object.entries(themes)) {
1470
- const css = buildCssMap(theme.resolve(), resolvePrefix(options, themeName), suffix, format);
1510
+ const resolved = theme.resolve();
1511
+ const css = buildCssMap(resolved, resolvePrefix(options, themeName, true), suffix, format);
1471
1512
  for (const key of [
1472
1513
  "light",
1473
1514
  "dark",
1474
1515
  "lightContrast",
1475
1516
  "darkContrast"
1476
1517
  ]) if (css[key]) allLines[key].push(css[key]);
1518
+ if (themeName === options?.primary) {
1519
+ const unprefixed = buildCssMap(resolved, "", suffix, format);
1520
+ for (const key of [
1521
+ "light",
1522
+ "dark",
1523
+ "lightContrast",
1524
+ "darkContrast"
1525
+ ]) if (unprefixed[key]) allLines[key].push(unprefixed[key]);
1526
+ }
1477
1527
  }
1478
1528
  return {
1479
1529
  light: allLines.light.join("\n"),
@@ -1533,6 +1583,7 @@ glaze.configure = function configure(config) {
1533
1583
  lightLightness: config.lightLightness ?? globalConfig.lightLightness,
1534
1584
  darkLightness: config.darkLightness ?? globalConfig.darkLightness,
1535
1585
  darkDesaturation: config.darkDesaturation ?? globalConfig.darkDesaturation,
1586
+ darkCurve: config.darkCurve ?? globalConfig.darkCurve,
1536
1587
  states: {
1537
1588
  dark: config.states?.dark ?? globalConfig.states.dark,
1538
1589
  highContrast: config.states?.highContrast ?? globalConfig.states.highContrast
@@ -1632,6 +1683,7 @@ glaze.resetConfig = function resetConfig() {
1632
1683
  lightLightness: [10, 100],
1633
1684
  darkLightness: [15, 95],
1634
1685
  darkDesaturation: .1,
1686
+ darkCurve: .5,
1635
1687
  states: {
1636
1688
  dark: "@dark",
1637
1689
  highContrast: "@high-contrast"