@tenphi/glaze 0.0.0-snapshot.75f81fa → 0.0.0-snapshot.78261ef

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
@@ -133,6 +133,11 @@ interface RegularColorDef {
133
133
  * should not be combined (a console.warn is emitted).
134
134
  */
135
135
  opacity?: number;
136
+ /**
137
+ * Whether this color is inherited by child themes created via `extend()`.
138
+ * Default: true. Set to false to make this color local to the current theme.
139
+ */
140
+ inherit?: boolean;
136
141
  }
137
142
  /** Shadow tuning knobs. All values use the 0–1 scale (OKHSL). */
138
143
  interface ShadowTuning {
@@ -156,6 +161,12 @@ interface ShadowTuning {
156
161
  * 0 = pure fg hue, 1 = pure bg hue. Default: 0.2.
157
162
  */
158
163
  bgHueBlend?: number;
164
+ /**
165
+ * Power curve for dark-scheme shadow alpha (0-1). Default: 0.4.
166
+ * Lower values compress low/mid-intensity shadows more aggressively.
167
+ * 1.0 = no dampening (identity).
168
+ */
169
+ darkShadowCurve?: number;
159
170
  }
160
171
  interface ShadowColorDef {
161
172
  type: 'shadow';
@@ -177,6 +188,11 @@ interface ShadowColorDef {
177
188
  intensity: HCPair<number>;
178
189
  /** Override default tuning. Merged field-by-field with global `shadowTuning`. */
179
190
  tuning?: ShadowTuning;
191
+ /**
192
+ * Whether this color is inherited by child themes created via `extend()`.
193
+ * Default: true. Set to false to make this color local to the current theme.
194
+ */
195
+ inherit?: boolean;
180
196
  }
181
197
  interface MixColorDef {
182
198
  type: 'mix';
@@ -211,6 +227,11 @@ interface MixColorDef {
211
227
  * Supports [normal, highContrast] pair.
212
228
  */
213
229
  contrast?: HCPair<MinContrast>;
230
+ /**
231
+ * Whether this color is inherited by child themes created via `extend()`.
232
+ * Default: true. Set to false to make this color local to the current theme.
233
+ */
234
+ inherit?: boolean;
214
235
  }
215
236
  type ColorDef = RegularColorDef | ShadowColorDef | MixColorDef;
216
237
  type ColorMap = Record<string, ColorDef>;
@@ -243,11 +264,12 @@ interface GlazeConfig {
243
264
  /** Saturation reduction factor for dark scheme (0–1). Default: 0.1. */
244
265
  darkDesaturation?: number;
245
266
  /**
246
- * Power-curve exponent for dark auto-inversion (0–1).
267
+ * Möbius beta for dark auto-inversion (0–1).
247
268
  * Lower values expand subtle near-white distinctions in dark mode.
248
269
  * Set to 1 for linear (legacy) behavior. Default: 0.5.
270
+ * Accepts [normal, highContrast] pair for separate HC tuning.
249
271
  */
250
- darkCurve?: number;
272
+ darkCurve?: HCPair<number>;
251
273
  /** State alias names for token export. */
252
274
  states?: {
253
275
  dark?: string;
@@ -262,7 +284,7 @@ interface GlazeConfigResolved {
262
284
  lightLightness: [number, number];
263
285
  darkLightness: [number, number];
264
286
  darkDesaturation: number;
265
- darkCurve: number;
287
+ darkCurve: HCPair<number>;
266
288
  states: {
267
289
  dark: string;
268
290
  highContrast: string;
@@ -285,6 +307,8 @@ interface GlazeShadowInput {
285
307
  /** Intensity 0-100. */
286
308
  intensity: number;
287
309
  tuning?: ShadowTuning;
310
+ /** Whether to apply dark-scheme dampening. Default: false. */
311
+ dark?: boolean;
288
312
  }
289
313
  /** Input for `glaze.color()` standalone factory. */
290
314
  interface GlazeColorInput {
@@ -392,35 +416,47 @@ interface GlazeCssResult {
392
416
  lightContrast: string;
393
417
  darkContrast: string;
394
418
  }
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>;
419
+ /** Options for `glaze.palette()` creation. */
420
+ interface GlazePaletteOptions {
403
421
  /**
404
422
  * Name of the primary theme. The primary theme's tokens are duplicated
405
- * without prefix, providing convenient short aliases alongside the
406
- * prefixed versions.
423
+ * without prefix in all exports, providing convenient short aliases
424
+ * alongside the prefixed versions. Can be overridden per-export.
407
425
  *
408
426
  * @example
409
427
  * ```ts
410
- * palette.tokens({ primary: 'brand' })
428
+ * const palette = glaze.palette({ brand, accent }, { primary: 'brand' });
429
+ * palette.tokens()
411
430
  * // → { light: { 'brand-surface': '...', 'surface': '...', 'accent-surface': '...' } }
412
431
  * ```
413
432
  */
414
433
  primary?: string;
415
434
  }
435
+ /** Options shared by palette `tokens()`, `tasty()`, and `css()` exports. */
436
+ interface GlazePaletteExportOptions {
437
+ /**
438
+ * Prefix mode. `true` uses `"<themeName>-"`, or provide a custom map.
439
+ * Defaults to `true` for palette export methods.
440
+ * Set to `false` explicitly to disable prefixing. Colliding keys
441
+ * produce a console.warn and the first-written value wins.
442
+ */
443
+ prefix?: boolean | Record<string, string>;
444
+ /**
445
+ * Override the palette-level primary theme for this export.
446
+ * Pass a theme name to set/change the primary, or `false` to disable it.
447
+ * When omitted, inherits the palette-level `primary`.
448
+ */
449
+ primary?: string | false;
450
+ }
416
451
  interface GlazePalette {
417
452
  /**
418
453
  * Export all themes as a flat token map grouped by scheme variant.
419
454
  * Prefix defaults to `true` — all tokens are prefixed with the theme name.
420
- * Use `primary` to duplicate one theme's tokens without prefix.
455
+ * Inherits the palette-level `primary`; override per-call or pass `false` to disable.
421
456
  *
422
457
  * ```ts
423
- * palette.tokens({ primary: 'brand' })
458
+ * const palette = glaze.palette({ brand, accent }, { primary: 'brand' });
459
+ * palette.tokens()
424
460
  * // → { light: { 'brand-surface': '...', 'surface': '...', 'accent-surface': '...' } }
425
461
  * ```
426
462
  */
@@ -428,12 +464,10 @@ interface GlazePalette {
428
464
  /**
429
465
  * Export all themes as tasty style-to-state bindings.
430
466
  * Uses `#name` color token keys and state aliases (`''`, `@dark`, etc.).
431
- * Prefix defaults to `true`. Use `primary` to duplicate one theme without prefix.
467
+ * Prefix defaults to `true`. Inherits the palette-level `primary`.
432
468
  * @see https://cube-ui-kit.vercel.app/?path=/docs/tasty-documentation--docs
433
469
  */
434
- tasty(options?: GlazeTokenOptions & {
435
- primary?: string;
436
- }): Record<string, Record<string, string>>;
470
+ tasty(options?: GlazeTokenOptions & GlazePaletteExportOptions): Record<string, Record<string, string>>;
437
471
  /** Export all themes as plain JSON grouped by theme name. */
438
472
  json(options?: GlazeJsonOptions & {
439
473
  prefix?: boolean | Record<string, string>;
@@ -460,11 +494,9 @@ declare function glaze(hueOrOptions: number | {
460
494
  }, saturation?: number): GlazeTheme;
461
495
  declare namespace glaze {
462
496
  var configure: (config: GlazeConfig) => void;
463
- var palette: (themes: PaletteInput) => {
497
+ var palette: (themes: PaletteInput, options?: GlazePaletteOptions) => {
464
498
  tokens(options?: GlazeJsonOptions & GlazePaletteExportOptions): Record<string, Record<string, string>>;
465
- tasty(options?: GlazeTokenOptions & {
466
- primary?: string;
467
- }): Record<string, Record<string, string>>;
499
+ tasty(options?: GlazeTokenOptions & GlazePaletteExportOptions): Record<string, Record<string, string>>;
468
500
  json(options?: GlazeJsonOptions & {
469
501
  prefix?: boolean | Record<string, string>;
470
502
  }): Record<string, Record<string, Record<string, string>>>;
@@ -548,5 +580,5 @@ declare function formatHsl(h: number, s: number, l: number): string;
548
580
  */
549
581
  declare function formatOklch(h: number, s: number, l: number): string;
550
582
  //#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 };
583
+ 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
584
  //# sourceMappingURL=index.d.cts.map
package/dist/index.d.mts CHANGED
@@ -133,6 +133,11 @@ interface RegularColorDef {
133
133
  * should not be combined (a console.warn is emitted).
134
134
  */
135
135
  opacity?: number;
136
+ /**
137
+ * Whether this color is inherited by child themes created via `extend()`.
138
+ * Default: true. Set to false to make this color local to the current theme.
139
+ */
140
+ inherit?: boolean;
136
141
  }
137
142
  /** Shadow tuning knobs. All values use the 0–1 scale (OKHSL). */
138
143
  interface ShadowTuning {
@@ -156,6 +161,12 @@ interface ShadowTuning {
156
161
  * 0 = pure fg hue, 1 = pure bg hue. Default: 0.2.
157
162
  */
158
163
  bgHueBlend?: number;
164
+ /**
165
+ * Power curve for dark-scheme shadow alpha (0-1). Default: 0.4.
166
+ * Lower values compress low/mid-intensity shadows more aggressively.
167
+ * 1.0 = no dampening (identity).
168
+ */
169
+ darkShadowCurve?: number;
159
170
  }
160
171
  interface ShadowColorDef {
161
172
  type: 'shadow';
@@ -177,6 +188,11 @@ interface ShadowColorDef {
177
188
  intensity: HCPair<number>;
178
189
  /** Override default tuning. Merged field-by-field with global `shadowTuning`. */
179
190
  tuning?: ShadowTuning;
191
+ /**
192
+ * Whether this color is inherited by child themes created via `extend()`.
193
+ * Default: true. Set to false to make this color local to the current theme.
194
+ */
195
+ inherit?: boolean;
180
196
  }
181
197
  interface MixColorDef {
182
198
  type: 'mix';
@@ -211,6 +227,11 @@ interface MixColorDef {
211
227
  * Supports [normal, highContrast] pair.
212
228
  */
213
229
  contrast?: HCPair<MinContrast>;
230
+ /**
231
+ * Whether this color is inherited by child themes created via `extend()`.
232
+ * Default: true. Set to false to make this color local to the current theme.
233
+ */
234
+ inherit?: boolean;
214
235
  }
215
236
  type ColorDef = RegularColorDef | ShadowColorDef | MixColorDef;
216
237
  type ColorMap = Record<string, ColorDef>;
@@ -243,11 +264,12 @@ interface GlazeConfig {
243
264
  /** Saturation reduction factor for dark scheme (0–1). Default: 0.1. */
244
265
  darkDesaturation?: number;
245
266
  /**
246
- * Power-curve exponent for dark auto-inversion (0–1).
267
+ * Möbius beta for dark auto-inversion (0–1).
247
268
  * Lower values expand subtle near-white distinctions in dark mode.
248
269
  * Set to 1 for linear (legacy) behavior. Default: 0.5.
270
+ * Accepts [normal, highContrast] pair for separate HC tuning.
249
271
  */
250
- darkCurve?: number;
272
+ darkCurve?: HCPair<number>;
251
273
  /** State alias names for token export. */
252
274
  states?: {
253
275
  dark?: string;
@@ -262,7 +284,7 @@ interface GlazeConfigResolved {
262
284
  lightLightness: [number, number];
263
285
  darkLightness: [number, number];
264
286
  darkDesaturation: number;
265
- darkCurve: number;
287
+ darkCurve: HCPair<number>;
266
288
  states: {
267
289
  dark: string;
268
290
  highContrast: string;
@@ -285,6 +307,8 @@ interface GlazeShadowInput {
285
307
  /** Intensity 0-100. */
286
308
  intensity: number;
287
309
  tuning?: ShadowTuning;
310
+ /** Whether to apply dark-scheme dampening. Default: false. */
311
+ dark?: boolean;
288
312
  }
289
313
  /** Input for `glaze.color()` standalone factory. */
290
314
  interface GlazeColorInput {
@@ -392,35 +416,47 @@ interface GlazeCssResult {
392
416
  lightContrast: string;
393
417
  darkContrast: string;
394
418
  }
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>;
419
+ /** Options for `glaze.palette()` creation. */
420
+ interface GlazePaletteOptions {
403
421
  /**
404
422
  * Name of the primary theme. The primary theme's tokens are duplicated
405
- * without prefix, providing convenient short aliases alongside the
406
- * prefixed versions.
423
+ * without prefix in all exports, providing convenient short aliases
424
+ * alongside the prefixed versions. Can be overridden per-export.
407
425
  *
408
426
  * @example
409
427
  * ```ts
410
- * palette.tokens({ primary: 'brand' })
428
+ * const palette = glaze.palette({ brand, accent }, { primary: 'brand' });
429
+ * palette.tokens()
411
430
  * // → { light: { 'brand-surface': '...', 'surface': '...', 'accent-surface': '...' } }
412
431
  * ```
413
432
  */
414
433
  primary?: string;
415
434
  }
435
+ /** Options shared by palette `tokens()`, `tasty()`, and `css()` exports. */
436
+ interface GlazePaletteExportOptions {
437
+ /**
438
+ * Prefix mode. `true` uses `"<themeName>-"`, or provide a custom map.
439
+ * Defaults to `true` for palette export methods.
440
+ * Set to `false` explicitly to disable prefixing. Colliding keys
441
+ * produce a console.warn and the first-written value wins.
442
+ */
443
+ prefix?: boolean | Record<string, string>;
444
+ /**
445
+ * Override the palette-level primary theme for this export.
446
+ * Pass a theme name to set/change the primary, or `false` to disable it.
447
+ * When omitted, inherits the palette-level `primary`.
448
+ */
449
+ primary?: string | false;
450
+ }
416
451
  interface GlazePalette {
417
452
  /**
418
453
  * Export all themes as a flat token map grouped by scheme variant.
419
454
  * Prefix defaults to `true` — all tokens are prefixed with the theme name.
420
- * Use `primary` to duplicate one theme's tokens without prefix.
455
+ * Inherits the palette-level `primary`; override per-call or pass `false` to disable.
421
456
  *
422
457
  * ```ts
423
- * palette.tokens({ primary: 'brand' })
458
+ * const palette = glaze.palette({ brand, accent }, { primary: 'brand' });
459
+ * palette.tokens()
424
460
  * // → { light: { 'brand-surface': '...', 'surface': '...', 'accent-surface': '...' } }
425
461
  * ```
426
462
  */
@@ -428,12 +464,10 @@ interface GlazePalette {
428
464
  /**
429
465
  * Export all themes as tasty style-to-state bindings.
430
466
  * Uses `#name` color token keys and state aliases (`''`, `@dark`, etc.).
431
- * Prefix defaults to `true`. Use `primary` to duplicate one theme without prefix.
467
+ * Prefix defaults to `true`. Inherits the palette-level `primary`.
432
468
  * @see https://cube-ui-kit.vercel.app/?path=/docs/tasty-documentation--docs
433
469
  */
434
- tasty(options?: GlazeTokenOptions & {
435
- primary?: string;
436
- }): Record<string, Record<string, string>>;
470
+ tasty(options?: GlazeTokenOptions & GlazePaletteExportOptions): Record<string, Record<string, string>>;
437
471
  /** Export all themes as plain JSON grouped by theme name. */
438
472
  json(options?: GlazeJsonOptions & {
439
473
  prefix?: boolean | Record<string, string>;
@@ -460,11 +494,9 @@ declare function glaze(hueOrOptions: number | {
460
494
  }, saturation?: number): GlazeTheme;
461
495
  declare namespace glaze {
462
496
  var configure: (config: GlazeConfig) => void;
463
- var palette: (themes: PaletteInput) => {
497
+ var palette: (themes: PaletteInput, options?: GlazePaletteOptions) => {
464
498
  tokens(options?: GlazeJsonOptions & GlazePaletteExportOptions): Record<string, Record<string, string>>;
465
- tasty(options?: GlazeTokenOptions & {
466
- primary?: string;
467
- }): Record<string, Record<string, string>>;
499
+ tasty(options?: GlazeTokenOptions & GlazePaletteExportOptions): Record<string, Record<string, string>>;
468
500
  json(options?: GlazeJsonOptions & {
469
501
  prefix?: boolean | Record<string, string>;
470
502
  }): Record<string, Record<string, Record<string, string>>>;
@@ -548,5 +580,5 @@ declare function formatHsl(h: number, s: number, l: number): string;
548
580
  */
549
581
  declare function formatOklch(h: number, s: number, l: number): string;
550
582
  //#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 };
583
+ 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
584
  //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs CHANGED
@@ -841,7 +841,8 @@ const DEFAULT_SHADOW_TUNING = {
841
841
  lightnessBounds: [.05, .2],
842
842
  minGapTarget: .05,
843
843
  alphaMax: 1,
844
- bgHueBlend: .2
844
+ bgHueBlend: .2,
845
+ darkShadowCurve: .4
845
846
  };
846
847
  function resolveShadowTuning(perColor) {
847
848
  return {
@@ -975,7 +976,7 @@ function mobiusCurve(t, beta) {
975
976
  }
976
977
  function mapLightnessDark(l, mode, isHighContrast) {
977
978
  if (mode === "static") return l;
978
- const beta = globalConfig.darkCurve;
979
+ const beta = isHighContrast ? pairHC(globalConfig.darkCurve) : pairNormal(globalConfig.darkCurve);
979
980
  const [darkLo, darkHi] = lightnessWindow(isHighContrast, "dark");
980
981
  if (mode === "fixed") return l * (darkHi - darkLo) / 100 + darkLo;
981
982
  const [lightLo, lightHi] = lightnessWindow(isHighContrast, "light");
@@ -983,7 +984,7 @@ function mapLightnessDark(l, mode, isHighContrast) {
983
984
  return darkLo + (darkHi - darkLo) * mobiusCurve(t, beta);
984
985
  }
985
986
  function lightMappedToDark(lightL, isHighContrast) {
986
- const beta = globalConfig.darkCurve;
987
+ const beta = isHighContrast ? pairHC(globalConfig.darkCurve) : pairNormal(globalConfig.darkCurve);
987
988
  const [lightLo, lightHi] = lightnessWindow(isHighContrast, "light");
988
989
  const [darkLo, darkHi] = lightnessWindow(isHighContrast, "dark");
989
990
  const t = (lightHi - clamp(lightL, lightLo, lightHi)) / (lightHi - lightLo);
@@ -1135,7 +1136,13 @@ function resolveShadowForScheme(def, ctx, isDark, isHighContrast) {
1135
1136
  if (def.fg) fgVariant = getSchemeVariant(ctx.resolved.get(def.fg), isDark, isHighContrast);
1136
1137
  const intensity = isHighContrast ? pairHC(def.intensity) : pairNormal(def.intensity);
1137
1138
  const tuning = resolveShadowTuning(def.tuning);
1138
- return computeShadow(bgVariant, fgVariant, intensity, tuning);
1139
+ const result = computeShadow(bgVariant, fgVariant, intensity, tuning);
1140
+ if (isDark && tuning.darkShadowCurve < 1 && result.alpha > 0) {
1141
+ const normalized = result.alpha / tuning.alphaMax;
1142
+ const exponent = 1 / tuning.darkShadowCurve;
1143
+ result.alpha = tuning.alphaMax * Math.pow(normalized, exponent);
1144
+ }
1145
+ return result;
1139
1146
  }
1140
1147
  function variantToLinearRgb(v) {
1141
1148
  return okhslToLinearSrgb(v.h, v.s, v.l);
@@ -1413,10 +1420,14 @@ function createTheme(hue, saturation, initialColors) {
1413
1420
  };
1414
1421
  },
1415
1422
  extend(options) {
1416
- return createTheme(options.hue ?? hue, options.saturation ?? saturation, options.colors ? {
1417
- ...colorDefs,
1423
+ const newHue = options.hue ?? hue;
1424
+ const newSat = options.saturation ?? saturation;
1425
+ const inheritedColors = {};
1426
+ for (const [name, def] of Object.entries(colorDefs)) if (def.inherit !== false) inheritedColors[name] = def;
1427
+ return createTheme(newHue, newSat, options.colors ? {
1428
+ ...inheritedColors,
1418
1429
  ...options.colors
1419
- } : { ...colorDefs });
1430
+ } : { ...inheritedColors });
1420
1431
  },
1421
1432
  resolve() {
1422
1433
  return resolveAllColors(hue, saturation, colorDefs);
@@ -1450,40 +1461,74 @@ function validatePrimaryTheme(primary, themes) {
1450
1461
  throw new Error(`glaze: primary theme "${primary}" not found in palette. Available: ${available}.`);
1451
1462
  }
1452
1463
  }
1453
- function createPalette(themes) {
1464
+ /**
1465
+ * Resolve the effective primary for an export call.
1466
+ * `false` disables, a string overrides, `undefined` inherits from palette.
1467
+ */
1468
+ function resolveEffectivePrimary(exportPrimary, palettePrimary) {
1469
+ if (exportPrimary === false) return void 0;
1470
+ return exportPrimary ?? palettePrimary;
1471
+ }
1472
+ /**
1473
+ * Filter a resolved color map, skipping keys already in `seen`.
1474
+ * Warns on collision and keeps the first-written value (first-write-wins).
1475
+ * Returns a new map containing only non-colliding entries.
1476
+ */
1477
+ function filterCollisions(resolved, prefix, seen, themeName, isPrimary) {
1478
+ const filtered = /* @__PURE__ */ new Map();
1479
+ const label = isPrimary ? `${themeName} (primary)` : themeName;
1480
+ for (const [name, color] of resolved) {
1481
+ const key = `${prefix}${name}`;
1482
+ if (seen.has(key)) {
1483
+ console.warn(`glaze: token "${key}" from theme "${label}" collides with theme "${seen.get(key)}" — skipping.`);
1484
+ continue;
1485
+ }
1486
+ seen.set(key, label);
1487
+ filtered.set(name, color);
1488
+ }
1489
+ return filtered;
1490
+ }
1491
+ function createPalette(themes, paletteOptions) {
1492
+ validatePrimaryTheme(paletteOptions?.primary, themes);
1454
1493
  return {
1455
1494
  tokens(options) {
1456
- validatePrimaryTheme(options?.primary, themes);
1495
+ const effectivePrimary = resolveEffectivePrimary(options?.primary, paletteOptions?.primary);
1496
+ if (options?.primary !== void 0) validatePrimaryTheme(effectivePrimary, themes);
1457
1497
  const modes = resolveModes(options?.modes);
1458
1498
  const allTokens = {};
1499
+ const seen = /* @__PURE__ */ new Map();
1459
1500
  for (const [themeName, theme] of Object.entries(themes)) {
1460
1501
  const resolved = theme.resolve();
1461
- const tokens = buildFlatTokenMap(resolved, resolvePrefix(options, themeName, true), modes, options?.format);
1502
+ const prefix = resolvePrefix(options, themeName, true);
1503
+ const tokens = buildFlatTokenMap(filterCollisions(resolved, prefix, seen, themeName), prefix, modes, options?.format);
1462
1504
  for (const variant of Object.keys(tokens)) {
1463
1505
  if (!allTokens[variant]) allTokens[variant] = {};
1464
1506
  Object.assign(allTokens[variant], tokens[variant]);
1465
1507
  }
1466
- if (themeName === options?.primary) {
1467
- const unprefixed = buildFlatTokenMap(resolved, "", modes, options?.format);
1508
+ if (themeName === effectivePrimary) {
1509
+ const unprefixed = buildFlatTokenMap(filterCollisions(resolved, "", seen, themeName, true), "", modes, options?.format);
1468
1510
  for (const variant of Object.keys(unprefixed)) Object.assign(allTokens[variant], unprefixed[variant]);
1469
1511
  }
1470
1512
  }
1471
1513
  return allTokens;
1472
1514
  },
1473
1515
  tasty(options) {
1474
- validatePrimaryTheme(options?.primary, themes);
1516
+ const effectivePrimary = resolveEffectivePrimary(options?.primary, paletteOptions?.primary);
1517
+ if (options?.primary !== void 0) validatePrimaryTheme(effectivePrimary, themes);
1475
1518
  const states = {
1476
1519
  dark: options?.states?.dark ?? globalConfig.states.dark,
1477
1520
  highContrast: options?.states?.highContrast ?? globalConfig.states.highContrast
1478
1521
  };
1479
1522
  const modes = resolveModes(options?.modes);
1480
1523
  const allTokens = {};
1524
+ const seen = /* @__PURE__ */ new Map();
1481
1525
  for (const [themeName, theme] of Object.entries(themes)) {
1482
1526
  const resolved = theme.resolve();
1483
- const tokens = buildTokenMap(resolved, resolvePrefix(options, themeName, true), states, modes, options?.format);
1527
+ const prefix = resolvePrefix(options, themeName, true);
1528
+ const tokens = buildTokenMap(filterCollisions(resolved, prefix, seen, themeName), prefix, states, modes, options?.format);
1484
1529
  Object.assign(allTokens, tokens);
1485
- if (themeName === options?.primary) {
1486
- const unprefixed = buildTokenMap(resolved, "", states, modes, options?.format);
1530
+ if (themeName === effectivePrimary) {
1531
+ const unprefixed = buildTokenMap(filterCollisions(resolved, "", seen, themeName, true), "", states, modes, options?.format);
1487
1532
  Object.assign(allTokens, unprefixed);
1488
1533
  }
1489
1534
  }
@@ -1496,7 +1541,8 @@ function createPalette(themes) {
1496
1541
  return result;
1497
1542
  },
1498
1543
  css(options) {
1499
- validatePrimaryTheme(options?.primary, themes);
1544
+ const effectivePrimary = resolveEffectivePrimary(options?.primary, paletteOptions?.primary);
1545
+ if (options?.primary !== void 0) validatePrimaryTheme(effectivePrimary, themes);
1500
1546
  const suffix = options?.suffix ?? "-color";
1501
1547
  const format = options?.format ?? "rgb";
1502
1548
  const allLines = {
@@ -1505,17 +1551,19 @@ function createPalette(themes) {
1505
1551
  lightContrast: [],
1506
1552
  darkContrast: []
1507
1553
  };
1554
+ const seen = /* @__PURE__ */ new Map();
1508
1555
  for (const [themeName, theme] of Object.entries(themes)) {
1509
1556
  const resolved = theme.resolve();
1510
- const css = buildCssMap(resolved, resolvePrefix(options, themeName, true), suffix, format);
1557
+ const prefix = resolvePrefix(options, themeName, true);
1558
+ const css = buildCssMap(filterCollisions(resolved, prefix, seen, themeName), prefix, suffix, format);
1511
1559
  for (const key of [
1512
1560
  "light",
1513
1561
  "dark",
1514
1562
  "lightContrast",
1515
1563
  "darkContrast"
1516
1564
  ]) if (css[key]) allLines[key].push(css[key]);
1517
- if (themeName === options?.primary) {
1518
- const unprefixed = buildCssMap(resolved, "", suffix, format);
1565
+ if (themeName === effectivePrimary) {
1566
+ const unprefixed = buildCssMap(filterCollisions(resolved, "", seen, themeName, true), "", suffix, format);
1519
1567
  for (const key of [
1520
1568
  "light",
1521
1569
  "dark",
@@ -1597,8 +1645,8 @@ glaze.configure = function configure(config) {
1597
1645
  /**
1598
1646
  * Compose multiple themes into a palette.
1599
1647
  */
1600
- glaze.palette = function palette(themes) {
1601
- return createPalette(themes);
1648
+ glaze.palette = function palette(themes, options) {
1649
+ return createPalette(themes, options);
1602
1650
  };
1603
1651
  /**
1604
1652
  * Create a theme from a serialized export.
@@ -1619,13 +1667,19 @@ glaze.shadow = function shadow(input) {
1619
1667
  const bg = parseOkhslInput(input.bg);
1620
1668
  const fg = input.fg ? parseOkhslInput(input.fg) : void 0;
1621
1669
  const tuning = resolveShadowTuning(input.tuning);
1622
- return computeShadow({
1670
+ const result = computeShadow({
1623
1671
  ...bg,
1624
1672
  alpha: 1
1625
1673
  }, fg ? {
1626
1674
  ...fg,
1627
1675
  alpha: 1
1628
1676
  } : void 0, input.intensity, tuning);
1677
+ if (input.dark && tuning.darkShadowCurve < 1 && result.alpha > 0) {
1678
+ const normalized = result.alpha / tuning.alphaMax;
1679
+ const exponent = 1 / tuning.darkShadowCurve;
1680
+ result.alpha = tuning.alphaMax * Math.pow(normalized, exponent);
1681
+ }
1682
+ return result;
1629
1683
  };
1630
1684
  /**
1631
1685
  * Format a resolved color variant as a CSS string.