@tenphi/glaze 0.8.0 → 0.9.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
@@ -243,11 +243,12 @@ interface GlazeConfig {
243
243
  /** Saturation reduction factor for dark scheme (0–1). Default: 0.1. */
244
244
  darkDesaturation?: number;
245
245
  /**
246
- * Power-curve exponent for dark auto-inversion (0–1).
246
+ * Möbius beta for dark auto-inversion (0–1).
247
247
  * Lower values expand subtle near-white distinctions in dark mode.
248
248
  * Set to 1 for linear (legacy) behavior. Default: 0.5.
249
+ * Accepts [normal, highContrast] pair for separate HC tuning.
249
250
  */
250
- darkCurve?: number;
251
+ darkCurve?: HCPair<number>;
251
252
  /** State alias names for token export. */
252
253
  states?: {
253
254
  dark?: string;
@@ -262,7 +263,7 @@ interface GlazeConfigResolved {
262
263
  lightLightness: [number, number];
263
264
  darkLightness: [number, number];
264
265
  darkDesaturation: number;
265
- darkCurve: number;
266
+ darkCurve: HCPair<number>;
266
267
  states: {
267
268
  dark: string;
268
269
  highContrast: string;
@@ -392,35 +393,47 @@ interface GlazeCssResult {
392
393
  lightContrast: string;
393
394
  darkContrast: string;
394
395
  }
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>;
396
+ /** Options for `glaze.palette()` creation. */
397
+ interface GlazePaletteOptions {
403
398
  /**
404
399
  * Name of the primary theme. The primary theme's tokens are duplicated
405
- * without prefix, providing convenient short aliases alongside the
406
- * prefixed versions.
400
+ * without prefix in all exports, providing convenient short aliases
401
+ * alongside the prefixed versions. Can be overridden per-export.
407
402
  *
408
403
  * @example
409
404
  * ```ts
410
- * palette.tokens({ primary: 'brand' })
405
+ * const palette = glaze.palette({ brand, accent }, { primary: 'brand' });
406
+ * palette.tokens()
411
407
  * // → { light: { 'brand-surface': '...', 'surface': '...', 'accent-surface': '...' } }
412
408
  * ```
413
409
  */
414
410
  primary?: string;
415
411
  }
412
+ /** Options shared by palette `tokens()`, `tasty()`, and `css()` exports. */
413
+ interface GlazePaletteExportOptions {
414
+ /**
415
+ * Prefix mode. `true` uses `"<themeName>-"`, or provide a custom map.
416
+ * Defaults to `true` for palette export methods.
417
+ * Set to `false` explicitly to disable prefixing. Colliding keys
418
+ * produce a console.warn and the first-written value wins.
419
+ */
420
+ prefix?: boolean | Record<string, string>;
421
+ /**
422
+ * Override the palette-level primary theme for this export.
423
+ * Pass a theme name to set/change the primary, or `false` to disable it.
424
+ * When omitted, inherits the palette-level `primary`.
425
+ */
426
+ primary?: string | false;
427
+ }
416
428
  interface GlazePalette {
417
429
  /**
418
430
  * Export all themes as a flat token map grouped by scheme variant.
419
431
  * Prefix defaults to `true` — all tokens are prefixed with the theme name.
420
- * Use `primary` to duplicate one theme's tokens without prefix.
432
+ * Inherits the palette-level `primary`; override per-call or pass `false` to disable.
421
433
  *
422
434
  * ```ts
423
- * palette.tokens({ primary: 'brand' })
435
+ * const palette = glaze.palette({ brand, accent }, { primary: 'brand' });
436
+ * palette.tokens()
424
437
  * // → { light: { 'brand-surface': '...', 'surface': '...', 'accent-surface': '...' } }
425
438
  * ```
426
439
  */
@@ -428,12 +441,10 @@ interface GlazePalette {
428
441
  /**
429
442
  * Export all themes as tasty style-to-state bindings.
430
443
  * Uses `#name` color token keys and state aliases (`''`, `@dark`, etc.).
431
- * Prefix defaults to `true`. Use `primary` to duplicate one theme without prefix.
444
+ * Prefix defaults to `true`. Inherits the palette-level `primary`.
432
445
  * @see https://cube-ui-kit.vercel.app/?path=/docs/tasty-documentation--docs
433
446
  */
434
- tasty(options?: GlazeTokenOptions & {
435
- primary?: string;
436
- }): Record<string, Record<string, string>>;
447
+ tasty(options?: GlazeTokenOptions & GlazePaletteExportOptions): Record<string, Record<string, string>>;
437
448
  /** Export all themes as plain JSON grouped by theme name. */
438
449
  json(options?: GlazeJsonOptions & {
439
450
  prefix?: boolean | Record<string, string>;
@@ -460,11 +471,9 @@ declare function glaze(hueOrOptions: number | {
460
471
  }, saturation?: number): GlazeTheme;
461
472
  declare namespace glaze {
462
473
  var configure: (config: GlazeConfig) => void;
463
- var palette: (themes: PaletteInput) => {
474
+ var palette: (themes: PaletteInput, options?: GlazePaletteOptions) => {
464
475
  tokens(options?: GlazeJsonOptions & GlazePaletteExportOptions): Record<string, Record<string, string>>;
465
- tasty(options?: GlazeTokenOptions & {
466
- primary?: string;
467
- }): Record<string, Record<string, string>>;
476
+ tasty(options?: GlazeTokenOptions & GlazePaletteExportOptions): Record<string, Record<string, string>>;
468
477
  json(options?: GlazeJsonOptions & {
469
478
  prefix?: boolean | Record<string, string>;
470
479
  }): Record<string, Record<string, Record<string, string>>>;
@@ -548,5 +557,5 @@ declare function formatHsl(h: number, s: number, l: number): string;
548
557
  */
549
558
  declare function formatOklch(h: number, s: number, l: number): string;
550
559
  //#endregion
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 };
560
+ 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 GlazePaletteOptions, 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 };
552
561
  //# sourceMappingURL=index.d.cts.map
package/dist/index.d.mts CHANGED
@@ -243,11 +243,12 @@ interface GlazeConfig {
243
243
  /** Saturation reduction factor for dark scheme (0–1). Default: 0.1. */
244
244
  darkDesaturation?: number;
245
245
  /**
246
- * Power-curve exponent for dark auto-inversion (0–1).
246
+ * Möbius beta for dark auto-inversion (0–1).
247
247
  * Lower values expand subtle near-white distinctions in dark mode.
248
248
  * Set to 1 for linear (legacy) behavior. Default: 0.5.
249
+ * Accepts [normal, highContrast] pair for separate HC tuning.
249
250
  */
250
- darkCurve?: number;
251
+ darkCurve?: HCPair<number>;
251
252
  /** State alias names for token export. */
252
253
  states?: {
253
254
  dark?: string;
@@ -262,7 +263,7 @@ interface GlazeConfigResolved {
262
263
  lightLightness: [number, number];
263
264
  darkLightness: [number, number];
264
265
  darkDesaturation: number;
265
- darkCurve: number;
266
+ darkCurve: HCPair<number>;
266
267
  states: {
267
268
  dark: string;
268
269
  highContrast: string;
@@ -392,35 +393,47 @@ interface GlazeCssResult {
392
393
  lightContrast: string;
393
394
  darkContrast: string;
394
395
  }
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>;
396
+ /** Options for `glaze.palette()` creation. */
397
+ interface GlazePaletteOptions {
403
398
  /**
404
399
  * Name of the primary theme. The primary theme's tokens are duplicated
405
- * without prefix, providing convenient short aliases alongside the
406
- * prefixed versions.
400
+ * without prefix in all exports, providing convenient short aliases
401
+ * alongside the prefixed versions. Can be overridden per-export.
407
402
  *
408
403
  * @example
409
404
  * ```ts
410
- * palette.tokens({ primary: 'brand' })
405
+ * const palette = glaze.palette({ brand, accent }, { primary: 'brand' });
406
+ * palette.tokens()
411
407
  * // → { light: { 'brand-surface': '...', 'surface': '...', 'accent-surface': '...' } }
412
408
  * ```
413
409
  */
414
410
  primary?: string;
415
411
  }
412
+ /** Options shared by palette `tokens()`, `tasty()`, and `css()` exports. */
413
+ interface GlazePaletteExportOptions {
414
+ /**
415
+ * Prefix mode. `true` uses `"<themeName>-"`, or provide a custom map.
416
+ * Defaults to `true` for palette export methods.
417
+ * Set to `false` explicitly to disable prefixing. Colliding keys
418
+ * produce a console.warn and the first-written value wins.
419
+ */
420
+ prefix?: boolean | Record<string, string>;
421
+ /**
422
+ * Override the palette-level primary theme for this export.
423
+ * Pass a theme name to set/change the primary, or `false` to disable it.
424
+ * When omitted, inherits the palette-level `primary`.
425
+ */
426
+ primary?: string | false;
427
+ }
416
428
  interface GlazePalette {
417
429
  /**
418
430
  * Export all themes as a flat token map grouped by scheme variant.
419
431
  * Prefix defaults to `true` — all tokens are prefixed with the theme name.
420
- * Use `primary` to duplicate one theme's tokens without prefix.
432
+ * Inherits the palette-level `primary`; override per-call or pass `false` to disable.
421
433
  *
422
434
  * ```ts
423
- * palette.tokens({ primary: 'brand' })
435
+ * const palette = glaze.palette({ brand, accent }, { primary: 'brand' });
436
+ * palette.tokens()
424
437
  * // → { light: { 'brand-surface': '...', 'surface': '...', 'accent-surface': '...' } }
425
438
  * ```
426
439
  */
@@ -428,12 +441,10 @@ interface GlazePalette {
428
441
  /**
429
442
  * Export all themes as tasty style-to-state bindings.
430
443
  * Uses `#name` color token keys and state aliases (`''`, `@dark`, etc.).
431
- * Prefix defaults to `true`. Use `primary` to duplicate one theme without prefix.
444
+ * Prefix defaults to `true`. Inherits the palette-level `primary`.
432
445
  * @see https://cube-ui-kit.vercel.app/?path=/docs/tasty-documentation--docs
433
446
  */
434
- tasty(options?: GlazeTokenOptions & {
435
- primary?: string;
436
- }): Record<string, Record<string, string>>;
447
+ tasty(options?: GlazeTokenOptions & GlazePaletteExportOptions): Record<string, Record<string, string>>;
437
448
  /** Export all themes as plain JSON grouped by theme name. */
438
449
  json(options?: GlazeJsonOptions & {
439
450
  prefix?: boolean | Record<string, string>;
@@ -460,11 +471,9 @@ declare function glaze(hueOrOptions: number | {
460
471
  }, saturation?: number): GlazeTheme;
461
472
  declare namespace glaze {
462
473
  var configure: (config: GlazeConfig) => void;
463
- var palette: (themes: PaletteInput) => {
474
+ var palette: (themes: PaletteInput, options?: GlazePaletteOptions) => {
464
475
  tokens(options?: GlazeJsonOptions & GlazePaletteExportOptions): Record<string, Record<string, string>>;
465
- tasty(options?: GlazeTokenOptions & {
466
- primary?: string;
467
- }): Record<string, Record<string, string>>;
476
+ tasty(options?: GlazeTokenOptions & GlazePaletteExportOptions): Record<string, Record<string, string>>;
468
477
  json(options?: GlazeJsonOptions & {
469
478
  prefix?: boolean | Record<string, string>;
470
479
  }): Record<string, Record<string, Record<string, string>>>;
@@ -548,5 +557,5 @@ declare function formatHsl(h: number, s: number, l: number): string;
548
557
  */
549
558
  declare function formatOklch(h: number, s: number, l: number): string;
550
559
  //#endregion
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 };
560
+ 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 GlazePaletteOptions, 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 };
552
561
  //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs CHANGED
@@ -960,9 +960,13 @@ function topoSort(defs) {
960
960
  for (const name of Object.keys(defs)) visit(name);
961
961
  return result;
962
962
  }
963
+ function lightnessWindow(isHighContrast, kind) {
964
+ if (isHighContrast) return [0, 100];
965
+ return kind === "dark" ? globalConfig.darkLightness : globalConfig.lightLightness;
966
+ }
963
967
  function mapLightnessLight(l, mode, isHighContrast) {
964
- if (mode === "static" || isHighContrast) return l;
965
- const [lo, hi] = globalConfig.lightLightness;
968
+ if (mode === "static") return l;
969
+ const [lo, hi] = lightnessWindow(isHighContrast, "light");
966
970
  return l * (hi - lo) / 100 + lo;
967
971
  }
968
972
  function mobiusCurve(t, beta) {
@@ -971,22 +975,17 @@ function mobiusCurve(t, beta) {
971
975
  }
972
976
  function mapLightnessDark(l, mode, isHighContrast) {
973
977
  if (mode === "static") return l;
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;
978
+ const beta = isHighContrast ? pairHC(globalConfig.darkCurve) : pairNormal(globalConfig.darkCurve);
979
+ const [darkLo, darkHi] = lightnessWindow(isHighContrast, "dark");
980
980
  if (mode === "fixed") return l * (darkHi - darkLo) / 100 + darkLo;
981
- const [lightLo, lightHi] = globalConfig.lightLightness;
981
+ const [lightLo, lightHi] = lightnessWindow(isHighContrast, "light");
982
982
  const t = (lightHi - (l * (lightHi - lightLo) / 100 + lightLo)) / (lightHi - lightLo);
983
983
  return darkLo + (darkHi - darkLo) * mobiusCurve(t, beta);
984
984
  }
985
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;
986
+ const beta = isHighContrast ? pairHC(globalConfig.darkCurve) : pairNormal(globalConfig.darkCurve);
987
+ const [lightLo, lightHi] = lightnessWindow(isHighContrast, "light");
988
+ const [darkLo, darkHi] = lightnessWindow(isHighContrast, "dark");
990
989
  const t = (lightHi - clamp(lightL, lightLo, lightHi)) / (lightHi - lightLo);
991
990
  return darkLo + (darkHi - darkLo) * mobiusCurve(t, beta);
992
991
  }
@@ -995,8 +994,8 @@ function mapSaturationDark(s, mode) {
995
994
  return s * (1 - globalConfig.darkDesaturation);
996
995
  }
997
996
  function schemeLightnessRange(isDark, mode, isHighContrast) {
998
- if (mode === "static" || isHighContrast) return [0, 1];
999
- const [lo, hi] = isDark ? globalConfig.darkLightness : globalConfig.lightLightness;
997
+ if (mode === "static") return [0, 1];
998
+ const [lo, hi] = lightnessWindow(isHighContrast, isDark ? "dark" : "light");
1000
999
  return [lo / 100, hi / 100];
1001
1000
  }
1002
1001
  function clamp(v, min, max) {
@@ -1451,40 +1450,74 @@ function validatePrimaryTheme(primary, themes) {
1451
1450
  throw new Error(`glaze: primary theme "${primary}" not found in palette. Available: ${available}.`);
1452
1451
  }
1453
1452
  }
1454
- function createPalette(themes) {
1453
+ /**
1454
+ * Resolve the effective primary for an export call.
1455
+ * `false` disables, a string overrides, `undefined` inherits from palette.
1456
+ */
1457
+ function resolveEffectivePrimary(exportPrimary, palettePrimary) {
1458
+ if (exportPrimary === false) return void 0;
1459
+ return exportPrimary ?? palettePrimary;
1460
+ }
1461
+ /**
1462
+ * Filter a resolved color map, skipping keys already in `seen`.
1463
+ * Warns on collision and keeps the first-written value (first-write-wins).
1464
+ * Returns a new map containing only non-colliding entries.
1465
+ */
1466
+ function filterCollisions(resolved, prefix, seen, themeName, isPrimary) {
1467
+ const filtered = /* @__PURE__ */ new Map();
1468
+ const label = isPrimary ? `${themeName} (primary)` : themeName;
1469
+ for (const [name, color] of resolved) {
1470
+ const key = `${prefix}${name}`;
1471
+ if (seen.has(key)) {
1472
+ console.warn(`glaze: token "${key}" from theme "${label}" collides with theme "${seen.get(key)}" — skipping.`);
1473
+ continue;
1474
+ }
1475
+ seen.set(key, label);
1476
+ filtered.set(name, color);
1477
+ }
1478
+ return filtered;
1479
+ }
1480
+ function createPalette(themes, paletteOptions) {
1481
+ validatePrimaryTheme(paletteOptions?.primary, themes);
1455
1482
  return {
1456
1483
  tokens(options) {
1457
- validatePrimaryTheme(options?.primary, themes);
1484
+ const effectivePrimary = resolveEffectivePrimary(options?.primary, paletteOptions?.primary);
1485
+ if (options?.primary !== void 0) validatePrimaryTheme(effectivePrimary, themes);
1458
1486
  const modes = resolveModes(options?.modes);
1459
1487
  const allTokens = {};
1488
+ const seen = /* @__PURE__ */ new Map();
1460
1489
  for (const [themeName, theme] of Object.entries(themes)) {
1461
1490
  const resolved = theme.resolve();
1462
- const tokens = buildFlatTokenMap(resolved, resolvePrefix(options, themeName, true), modes, options?.format);
1491
+ const prefix = resolvePrefix(options, themeName, true);
1492
+ const tokens = buildFlatTokenMap(filterCollisions(resolved, prefix, seen, themeName), prefix, modes, options?.format);
1463
1493
  for (const variant of Object.keys(tokens)) {
1464
1494
  if (!allTokens[variant]) allTokens[variant] = {};
1465
1495
  Object.assign(allTokens[variant], tokens[variant]);
1466
1496
  }
1467
- if (themeName === options?.primary) {
1468
- const unprefixed = buildFlatTokenMap(resolved, "", modes, options?.format);
1497
+ if (themeName === effectivePrimary) {
1498
+ const unprefixed = buildFlatTokenMap(filterCollisions(resolved, "", seen, themeName, true), "", modes, options?.format);
1469
1499
  for (const variant of Object.keys(unprefixed)) Object.assign(allTokens[variant], unprefixed[variant]);
1470
1500
  }
1471
1501
  }
1472
1502
  return allTokens;
1473
1503
  },
1474
1504
  tasty(options) {
1475
- validatePrimaryTheme(options?.primary, themes);
1505
+ const effectivePrimary = resolveEffectivePrimary(options?.primary, paletteOptions?.primary);
1506
+ if (options?.primary !== void 0) validatePrimaryTheme(effectivePrimary, themes);
1476
1507
  const states = {
1477
1508
  dark: options?.states?.dark ?? globalConfig.states.dark,
1478
1509
  highContrast: options?.states?.highContrast ?? globalConfig.states.highContrast
1479
1510
  };
1480
1511
  const modes = resolveModes(options?.modes);
1481
1512
  const allTokens = {};
1513
+ const seen = /* @__PURE__ */ new Map();
1482
1514
  for (const [themeName, theme] of Object.entries(themes)) {
1483
1515
  const resolved = theme.resolve();
1484
- const tokens = buildTokenMap(resolved, resolvePrefix(options, themeName, true), states, modes, options?.format);
1516
+ const prefix = resolvePrefix(options, themeName, true);
1517
+ const tokens = buildTokenMap(filterCollisions(resolved, prefix, seen, themeName), prefix, states, modes, options?.format);
1485
1518
  Object.assign(allTokens, tokens);
1486
- if (themeName === options?.primary) {
1487
- const unprefixed = buildTokenMap(resolved, "", states, modes, options?.format);
1519
+ if (themeName === effectivePrimary) {
1520
+ const unprefixed = buildTokenMap(filterCollisions(resolved, "", seen, themeName, true), "", states, modes, options?.format);
1488
1521
  Object.assign(allTokens, unprefixed);
1489
1522
  }
1490
1523
  }
@@ -1497,7 +1530,8 @@ function createPalette(themes) {
1497
1530
  return result;
1498
1531
  },
1499
1532
  css(options) {
1500
- validatePrimaryTheme(options?.primary, themes);
1533
+ const effectivePrimary = resolveEffectivePrimary(options?.primary, paletteOptions?.primary);
1534
+ if (options?.primary !== void 0) validatePrimaryTheme(effectivePrimary, themes);
1501
1535
  const suffix = options?.suffix ?? "-color";
1502
1536
  const format = options?.format ?? "rgb";
1503
1537
  const allLines = {
@@ -1506,17 +1540,19 @@ function createPalette(themes) {
1506
1540
  lightContrast: [],
1507
1541
  darkContrast: []
1508
1542
  };
1543
+ const seen = /* @__PURE__ */ new Map();
1509
1544
  for (const [themeName, theme] of Object.entries(themes)) {
1510
1545
  const resolved = theme.resolve();
1511
- const css = buildCssMap(resolved, resolvePrefix(options, themeName, true), suffix, format);
1546
+ const prefix = resolvePrefix(options, themeName, true);
1547
+ const css = buildCssMap(filterCollisions(resolved, prefix, seen, themeName), prefix, suffix, format);
1512
1548
  for (const key of [
1513
1549
  "light",
1514
1550
  "dark",
1515
1551
  "lightContrast",
1516
1552
  "darkContrast"
1517
1553
  ]) if (css[key]) allLines[key].push(css[key]);
1518
- if (themeName === options?.primary) {
1519
- const unprefixed = buildCssMap(resolved, "", suffix, format);
1554
+ if (themeName === effectivePrimary) {
1555
+ const unprefixed = buildCssMap(filterCollisions(resolved, "", seen, themeName, true), "", suffix, format);
1520
1556
  for (const key of [
1521
1557
  "light",
1522
1558
  "dark",
@@ -1598,8 +1634,8 @@ glaze.configure = function configure(config) {
1598
1634
  /**
1599
1635
  * Compose multiple themes into a palette.
1600
1636
  */
1601
- glaze.palette = function palette(themes) {
1602
- return createPalette(themes);
1637
+ glaze.palette = function palette(themes, options) {
1638
+ return createPalette(themes, options);
1603
1639
  };
1604
1640
  /**
1605
1641
  * Create a theme from a serialized export.