@tenphi/glaze 0.3.0 → 0.5.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
@@ -439,23 +439,26 @@ function parseHex(hex) {
439
439
  }
440
440
  return null;
441
441
  }
442
+ function fmt$1(value, decimals) {
443
+ return parseFloat(value.toFixed(decimals)).toString();
444
+ }
442
445
  /**
443
446
  * Format OKHSL values as a CSS `okhsl(H S% L%)` string.
444
447
  * h: 0–360, s: 0–100, l: 0–100 (percentage scale for s and l).
445
448
  */
446
449
  function formatOkhsl(h, s, l) {
447
- return `okhsl(${h.toFixed(1)} ${s.toFixed(1)}% ${l.toFixed(1)}%)`;
450
+ return `okhsl(${fmt$1(h, 1)} ${fmt$1(s, 1)}% ${fmt$1(l, 1)}%)`;
448
451
  }
449
452
  /**
450
- * Format OKHSL values as a CSS `rgb(R, G, B)` string with fractional 0–255 values.
453
+ * Format OKHSL values as a CSS `rgb(R G B)` string with rounded integer values.
451
454
  * h: 0–360, s: 0–100, l: 0–100 (percentage scale for s and l).
452
455
  */
453
456
  function formatRgb(h, s, l) {
454
457
  const [r, g, b] = okhslToSrgb(h, s / 100, l / 100);
455
- return `rgb(${(r * 255).toFixed(3)}, ${(g * 255).toFixed(3)}, ${(b * 255).toFixed(3)})`;
458
+ return `rgb(${Math.round(r * 255)} ${Math.round(g * 255)} ${Math.round(b * 255)})`;
456
459
  }
457
460
  /**
458
- * Format OKHSL values as a CSS `hsl(H, S%, L%)` string.
461
+ * Format OKHSL values as a CSS `hsl(H S% L%)` string.
459
462
  * h: 0–360, s: 0–100, l: 0–100 (percentage scale for s and l).
460
463
  */
461
464
  function formatHsl(h, s, l) {
@@ -472,10 +475,10 @@ function formatHsl(h, s, l) {
472
475
  else if (max === g) hh = ((b - r) / delta + 2) * 60;
473
476
  else hh = ((r - g) / delta + 4) * 60;
474
477
  }
475
- return `hsl(${hh.toFixed(1)}, ${(ss * 100).toFixed(1)}%, ${(ll * 100).toFixed(1)}%)`;
478
+ return `hsl(${fmt$1(hh, 1)} ${fmt$1(ss * 100, 1)}% ${fmt$1(ll * 100, 1)}%)`;
476
479
  }
477
480
  /**
478
- * Format OKHSL values as a CSS `oklch(L% C H)` string.
481
+ * Format OKHSL values as a CSS `oklch(L C H)` string.
479
482
  * h: 0–360, s: 0–100, l: 0–100 (percentage scale for s and l).
480
483
  */
481
484
  function formatOklch(h, s, l) {
@@ -483,7 +486,7 @@ function formatOklch(h, s, l) {
483
486
  const C = Math.sqrt(a * a + b * b);
484
487
  let hh = Math.atan2(b, a) * (180 / Math.PI);
485
488
  hh = constrainAngle(hh);
486
- return `oklch(${(L * 100).toFixed(1)}% ${C.toFixed(4)} ${hh.toFixed(1)})`;
489
+ return `oklch(${fmt$1(L, 4)} ${fmt$1(C, 4)} ${fmt$1(hh, 1)})`;
487
490
  }
488
491
 
489
492
  //#endregion
@@ -709,14 +712,70 @@ function pairNormal(p) {
709
712
  function pairHC(p) {
710
713
  return Array.isArray(p) ? p[1] : p;
711
714
  }
715
+ function isShadowDef(def) {
716
+ return def.type === "shadow";
717
+ }
718
+ const DEFAULT_SHADOW_TUNING = {
719
+ saturationFactor: .18,
720
+ maxSaturation: .25,
721
+ lightnessFactor: .25,
722
+ lightnessBounds: [.05, .2],
723
+ minGapTarget: .05,
724
+ alphaMax: .6,
725
+ bgHueBlend: .2
726
+ };
727
+ function resolveShadowTuning(perColor) {
728
+ return {
729
+ ...DEFAULT_SHADOW_TUNING,
730
+ ...globalConfig.shadowTuning,
731
+ ...perColor,
732
+ lightnessBounds: perColor?.lightnessBounds ?? globalConfig.shadowTuning?.lightnessBounds ?? DEFAULT_SHADOW_TUNING.lightnessBounds
733
+ };
734
+ }
735
+ function circularLerp(a, b, t) {
736
+ let diff = b - a;
737
+ if (diff > 180) diff -= 360;
738
+ else if (diff < -180) diff += 360;
739
+ return ((a + diff * t) % 360 + 360) % 360;
740
+ }
741
+ function computeShadow(bg, fg, intensity, tuning) {
742
+ const EPSILON = 1e-6;
743
+ const clampedIntensity = clamp(intensity, 0, 100);
744
+ const contrastWeight = fg ? Math.abs(bg.l - fg.l) : 1;
745
+ const deltaL = clampedIntensity / 100 * contrastWeight;
746
+ const h = fg ? circularLerp(fg.h, bg.h, tuning.bgHueBlend) : bg.h;
747
+ const s = fg ? Math.min(fg.s * tuning.saturationFactor, tuning.maxSaturation) : 0;
748
+ let lSh = clamp(bg.l * tuning.lightnessFactor, tuning.lightnessBounds[0], tuning.lightnessBounds[1]);
749
+ lSh = Math.max(Math.min(lSh, bg.l - tuning.minGapTarget), 0);
750
+ const t = deltaL / Math.max(bg.l - lSh, EPSILON);
751
+ const alpha = tuning.alphaMax * Math.tanh(t / tuning.alphaMax);
752
+ return {
753
+ h,
754
+ s,
755
+ l: lSh,
756
+ alpha
757
+ };
758
+ }
712
759
  function validateColorDefs(defs) {
713
760
  const names = new Set(Object.keys(defs));
714
761
  for (const [name, def] of Object.entries(defs)) {
715
- if (def.contrast !== void 0 && !def.base) throw new Error(`glaze: color "${name}" has "contrast" without "base".`);
716
- if (def.lightness !== void 0 && !isAbsoluteLightness(def.lightness) && !def.base) throw new Error(`glaze: color "${name}" has relative "lightness" without "base".`);
717
- if (isAbsoluteLightness(def.lightness) && def.base !== void 0) console.warn(`glaze: color "${name}" has absolute "lightness" and "base". Absolute lightness takes precedence.`);
718
- if (def.base && !names.has(def.base)) throw new Error(`glaze: color "${name}" references non-existent base "${def.base}".`);
719
- if (!isAbsoluteLightness(def.lightness) && def.base === void 0) throw new Error(`glaze: color "${name}" must have either absolute "lightness" (root) or "base" (dependent).`);
762
+ if (isShadowDef(def)) {
763
+ if (!names.has(def.bg)) throw new Error(`glaze: shadow "${name}" references non-existent bg "${def.bg}".`);
764
+ if (isShadowDef(defs[def.bg])) throw new Error(`glaze: shadow "${name}" bg "${def.bg}" references another shadow color.`);
765
+ if (def.fg !== void 0) {
766
+ if (!names.has(def.fg)) throw new Error(`glaze: shadow "${name}" references non-existent fg "${def.fg}".`);
767
+ if (isShadowDef(defs[def.fg])) throw new Error(`glaze: shadow "${name}" fg "${def.fg}" references another shadow color.`);
768
+ }
769
+ continue;
770
+ }
771
+ const regDef = def;
772
+ if (regDef.contrast !== void 0 && !regDef.base) throw new Error(`glaze: color "${name}" has "contrast" without "base".`);
773
+ if (regDef.lightness !== void 0 && !isAbsoluteLightness(regDef.lightness) && !regDef.base) throw new Error(`glaze: color "${name}" has relative "lightness" without "base".`);
774
+ if (isAbsoluteLightness(regDef.lightness) && regDef.base !== void 0) console.warn(`glaze: color "${name}" has absolute "lightness" and "base". Absolute lightness takes precedence.`);
775
+ if (regDef.base && !names.has(regDef.base)) throw new Error(`glaze: color "${name}" references non-existent base "${regDef.base}".`);
776
+ if (regDef.base && isShadowDef(defs[regDef.base])) throw new Error(`glaze: color "${name}" base "${regDef.base}" references a shadow color.`);
777
+ if (!isAbsoluteLightness(regDef.lightness) && regDef.base === void 0) throw new Error(`glaze: color "${name}" must have either absolute "lightness" (root) or "base" (dependent).`);
778
+ if (regDef.contrast !== void 0 && regDef.opacity !== void 0) console.warn(`glaze: color "${name}" has both "contrast" and "opacity". Opacity makes perceived lightness unpredictable.`);
720
779
  }
721
780
  const visited = /* @__PURE__ */ new Set();
722
781
  const inStack = /* @__PURE__ */ new Set();
@@ -725,7 +784,13 @@ function validateColorDefs(defs) {
725
784
  if (visited.has(name)) return;
726
785
  inStack.add(name);
727
786
  const def = defs[name];
728
- if (def.base && !isAbsoluteLightness(def.lightness)) dfs(def.base);
787
+ if (isShadowDef(def)) {
788
+ dfs(def.bg);
789
+ if (def.fg) dfs(def.fg);
790
+ } else {
791
+ const regDef = def;
792
+ if (regDef.base && !isAbsoluteLightness(regDef.lightness)) dfs(regDef.base);
793
+ }
729
794
  inStack.delete(name);
730
795
  visited.add(name);
731
796
  }
@@ -738,7 +803,13 @@ function topoSort(defs) {
738
803
  if (visited.has(name)) return;
739
804
  visited.add(name);
740
805
  const def = defs[name];
741
- if (def.base && !isAbsoluteLightness(def.lightness)) visit(def.base);
806
+ if (isShadowDef(def)) {
807
+ visit(def.bg);
808
+ if (def.fg) visit(def.fg);
809
+ } else {
810
+ const regDef = def;
811
+ if (regDef.base && !isAbsoluteLightness(regDef.lightness)) visit(regDef.base);
812
+ }
742
813
  result.push(name);
743
814
  }
744
815
  for (const name of Object.keys(defs)) visit(name);
@@ -802,11 +873,8 @@ function resolveDependentColor(name, def, ctx, isHighContrast, isDark, effective
802
873
  if (!baseResolved) throw new Error(`glaze: base "${baseName}" not yet resolved for "${name}".`);
803
874
  const mode = def.mode ?? "auto";
804
875
  const satFactor = clamp(def.saturation ?? 1, 0, 1);
805
- let baseL;
806
- if (isDark && isHighContrast) baseL = baseResolved.darkContrast.l * 100;
807
- else if (isDark) baseL = baseResolved.dark.l * 100;
808
- else if (isHighContrast) baseL = baseResolved.lightContrast.l * 100;
809
- else baseL = baseResolved.light.l * 100;
876
+ const baseVariant = getSchemeVariant(baseResolved, isDark, isHighContrast);
877
+ const baseL = baseVariant.l * 100;
810
878
  let preferredL;
811
879
  const rawLightness = def.lightness;
812
880
  if (rawLightness === void 0) preferredL = baseL;
@@ -823,27 +891,7 @@ function resolveDependentColor(name, def, ctx, isHighContrast, isDark, effective
823
891
  if (rawContrast !== void 0) {
824
892
  const minCr = isHighContrast ? pairHC(rawContrast) : pairNormal(rawContrast);
825
893
  const effectiveSat = isDark ? mapSaturationDark(satFactor * ctx.saturation / 100, mode) : satFactor * ctx.saturation / 100;
826
- let baseH;
827
- let baseS;
828
- let baseLNorm;
829
- if (isDark && isHighContrast) {
830
- baseH = baseResolved.darkContrast.h;
831
- baseS = baseResolved.darkContrast.s;
832
- baseLNorm = baseResolved.darkContrast.l;
833
- } else if (isDark) {
834
- baseH = baseResolved.dark.h;
835
- baseS = baseResolved.dark.s;
836
- baseLNorm = baseResolved.dark.l;
837
- } else if (isHighContrast) {
838
- baseH = baseResolved.lightContrast.h;
839
- baseS = baseResolved.lightContrast.s;
840
- baseLNorm = baseResolved.lightContrast.l;
841
- } else {
842
- baseH = baseResolved.light.h;
843
- baseS = baseResolved.light.s;
844
- baseLNorm = baseResolved.light.l;
845
- }
846
- const baseLinearRgb = okhslToLinearSrgb(baseH, baseS, baseLNorm);
894
+ const baseLinearRgb = okhslToLinearSrgb(baseVariant.h, baseVariant.s, baseVariant.l);
847
895
  return {
848
896
  l: findLightnessForContrast({
849
897
  hue: effectiveHue,
@@ -860,18 +908,26 @@ function resolveDependentColor(name, def, ctx, isHighContrast, isDark, effective
860
908
  satFactor
861
909
  };
862
910
  }
911
+ function getSchemeVariant(color, isDark, isHighContrast) {
912
+ if (isDark && isHighContrast) return color.darkContrast;
913
+ if (isDark) return color.dark;
914
+ if (isHighContrast) return color.lightContrast;
915
+ return color.light;
916
+ }
863
917
  function resolveColorForScheme(name, def, ctx, isDark, isHighContrast) {
864
- const mode = def.mode ?? "auto";
865
- const isRoot = isAbsoluteLightness(def.lightness) && !def.base;
866
- const effectiveHue = resolveEffectiveHue(ctx.hue, def.hue);
918
+ if (isShadowDef(def)) return resolveShadowForScheme(def, ctx, isDark, isHighContrast);
919
+ const regDef = def;
920
+ const mode = regDef.mode ?? "auto";
921
+ const isRoot = isAbsoluteLightness(regDef.lightness) && !regDef.base;
922
+ const effectiveHue = resolveEffectiveHue(ctx.hue, regDef.hue);
867
923
  let lightL;
868
924
  let satFactor;
869
925
  if (isRoot) {
870
- const root = resolveRootColor(name, def, ctx, isHighContrast);
926
+ const root = resolveRootColor(name, regDef, ctx, isHighContrast);
871
927
  lightL = root.lightL;
872
928
  satFactor = root.satFactor;
873
929
  } else {
874
- const dep = resolveDependentColor(name, def, ctx, isHighContrast, isDark, effectiveHue);
930
+ const dep = resolveDependentColor(name, regDef, ctx, isHighContrast, isDark, effectiveHue);
875
931
  lightL = dep.l;
876
932
  satFactor = dep.satFactor;
877
933
  }
@@ -890,9 +946,18 @@ function resolveColorForScheme(name, def, ctx, isDark, isHighContrast) {
890
946
  return {
891
947
  h: effectiveHue,
892
948
  s: clamp(finalSat, 0, 1),
893
- l: clamp(finalL / 100, 0, 1)
949
+ l: clamp(finalL / 100, 0, 1),
950
+ alpha: regDef.opacity ?? 1
894
951
  };
895
952
  }
953
+ function resolveShadowForScheme(def, ctx, isDark, isHighContrast) {
954
+ const bgVariant = getSchemeVariant(ctx.resolved.get(def.bg), isDark, isHighContrast);
955
+ let fgVariant;
956
+ if (def.fg) fgVariant = getSchemeVariant(ctx.resolved.get(def.fg), isDark, isHighContrast);
957
+ const intensity = isHighContrast ? pairHC(def.intensity) : pairNormal(def.intensity);
958
+ const tuning = resolveShadowTuning(def.tuning);
959
+ return computeShadow(bgVariant, fgVariant, intensity, tuning);
960
+ }
896
961
  function resolveAllColors(hue, saturation, defs) {
897
962
  validateColorDefs(defs);
898
963
  const order = topoSort(defs);
@@ -902,6 +967,9 @@ function resolveAllColors(hue, saturation, defs) {
902
967
  defs,
903
968
  resolved: /* @__PURE__ */ new Map()
904
969
  };
970
+ function defMode(def) {
971
+ return isShadowDef(def) ? void 0 : def.mode ?? "auto";
972
+ }
905
973
  const lightMap = /* @__PURE__ */ new Map();
906
974
  for (const name of order) {
907
975
  const variant = resolveColorForScheme(name, defs[name], ctx, false, false);
@@ -912,7 +980,7 @@ function resolveAllColors(hue, saturation, defs) {
912
980
  dark: variant,
913
981
  lightContrast: variant,
914
982
  darkContrast: variant,
915
- mode: defs[name].mode ?? "auto"
983
+ mode: defMode(defs[name])
916
984
  });
917
985
  }
918
986
  const lightHCMap = /* @__PURE__ */ new Map();
@@ -935,7 +1003,7 @@ function resolveAllColors(hue, saturation, defs) {
935
1003
  dark: lightMap.get(name),
936
1004
  lightContrast: lightHCMap.get(name),
937
1005
  darkContrast: lightHCMap.get(name),
938
- mode: defs[name].mode ?? "auto"
1006
+ mode: defMode(defs[name])
939
1007
  });
940
1008
  for (const name of order) {
941
1009
  const variant = resolveColorForScheme(name, defs[name], ctx, true, false);
@@ -965,7 +1033,7 @@ function resolveAllColors(hue, saturation, defs) {
965
1033
  dark: darkMap.get(name),
966
1034
  lightContrast: lightHCMap.get(name),
967
1035
  darkContrast: darkHCMap.get(name),
968
- mode: defs[name].mode ?? "auto"
1036
+ mode: defMode(defs[name])
969
1037
  });
970
1038
  return result;
971
1039
  }
@@ -975,8 +1043,14 @@ const formatters = {
975
1043
  hsl: formatHsl,
976
1044
  oklch: formatOklch
977
1045
  };
1046
+ function fmt(value, decimals) {
1047
+ return parseFloat(value.toFixed(decimals)).toString();
1048
+ }
978
1049
  function formatVariant(v, format = "okhsl") {
979
- return formatters[format](v.h, v.s * 100, v.l * 100);
1050
+ const base = formatters[format](v.h, v.s * 100, v.l * 100);
1051
+ if (v.alpha >= 1) return base;
1052
+ const closing = base.lastIndexOf(")");
1053
+ return `${base.slice(0, closing)} / ${fmt(v.alpha, 4)})`;
980
1054
  }
981
1055
  function resolveModes(override) {
982
1056
  return {
@@ -996,6 +1070,20 @@ function buildTokenMap(resolved, prefix, states, modes, format = "okhsl") {
996
1070
  }
997
1071
  return tokens;
998
1072
  }
1073
+ function buildFlatTokenMap(resolved, prefix, modes, format = "okhsl") {
1074
+ const result = { light: {} };
1075
+ if (modes.dark) result.dark = {};
1076
+ if (modes.highContrast) result.lightContrast = {};
1077
+ if (modes.dark && modes.highContrast) result.darkContrast = {};
1078
+ for (const [name, color] of resolved) {
1079
+ const key = `${prefix}${name}`;
1080
+ result.light[key] = formatVariant(color.light, format);
1081
+ if (modes.dark) result.dark[key] = formatVariant(color.dark, format);
1082
+ if (modes.highContrast) result.lightContrast[key] = formatVariant(color.lightContrast, format);
1083
+ if (modes.dark && modes.highContrast) result.darkContrast[key] = formatVariant(color.darkContrast, format);
1084
+ }
1085
+ return result;
1086
+ }
999
1087
  function buildJsonMap(resolved, modes, format = "okhsl") {
1000
1088
  const result = {};
1001
1089
  for (const [name, color] of resolved) {
@@ -1077,6 +1165,9 @@ function createTheme(hue, saturation, initialColors) {
1077
1165
  return resolveAllColors(hue, saturation, colorDefs);
1078
1166
  },
1079
1167
  tokens(options) {
1168
+ return buildFlatTokenMap(resolveAllColors(hue, saturation, colorDefs), "", resolveModes(options?.modes), options?.format);
1169
+ },
1170
+ tasty(options) {
1080
1171
  return buildTokenMap(resolveAllColors(hue, saturation, colorDefs), "", {
1081
1172
  dark: options?.states?.dark ?? globalConfig.states.dark,
1082
1173
  highContrast: options?.states?.highContrast ?? globalConfig.states.highContrast
@@ -1090,9 +1181,26 @@ function createTheme(hue, saturation, initialColors) {
1090
1181
  }
1091
1182
  };
1092
1183
  }
1184
+ function resolvePrefix(options, themeName) {
1185
+ if (options?.prefix === true) return `${themeName}-`;
1186
+ if (typeof options?.prefix === "object" && options.prefix !== null) return options.prefix[themeName] ?? `${themeName}-`;
1187
+ return "";
1188
+ }
1093
1189
  function createPalette(themes) {
1094
1190
  return {
1095
1191
  tokens(options) {
1192
+ const modes = resolveModes(options?.modes);
1193
+ const allTokens = {};
1194
+ for (const [themeName, theme] of Object.entries(themes)) {
1195
+ const tokens = buildFlatTokenMap(theme.resolve(), resolvePrefix(options, themeName), modes, options?.format);
1196
+ for (const variant of Object.keys(tokens)) {
1197
+ if (!allTokens[variant]) allTokens[variant] = {};
1198
+ Object.assign(allTokens[variant], tokens[variant]);
1199
+ }
1200
+ }
1201
+ return allTokens;
1202
+ },
1203
+ tasty(options) {
1096
1204
  const states = {
1097
1205
  dark: options?.states?.dark ?? globalConfig.states.dark,
1098
1206
  highContrast: options?.states?.highContrast ?? globalConfig.states.highContrast
@@ -1100,11 +1208,7 @@ function createPalette(themes) {
1100
1208
  const modes = resolveModes(options?.modes);
1101
1209
  const allTokens = {};
1102
1210
  for (const [themeName, theme] of Object.entries(themes)) {
1103
- const resolved = theme.resolve();
1104
- let prefix = "";
1105
- if (options?.prefix === true) prefix = `${themeName}-`;
1106
- else if (typeof options?.prefix === "object" && options.prefix !== null) prefix = options.prefix[themeName] ?? `${themeName}-`;
1107
- const tokens = buildTokenMap(resolved, prefix, states, modes, options?.format);
1211
+ const tokens = buildTokenMap(theme.resolve(), resolvePrefix(options, themeName), states, modes, options?.format);
1108
1212
  Object.assign(allTokens, tokens);
1109
1213
  }
1110
1214
  return allTokens;
@@ -1125,11 +1229,7 @@ function createPalette(themes) {
1125
1229
  darkContrast: []
1126
1230
  };
1127
1231
  for (const [themeName, theme] of Object.entries(themes)) {
1128
- const resolved = theme.resolve();
1129
- let prefix = "";
1130
- if (options?.prefix === true) prefix = `${themeName}-`;
1131
- else if (typeof options?.prefix === "object" && options.prefix !== null) prefix = options.prefix[themeName] ?? `${themeName}-`;
1132
- const css = buildCssMap(resolved, prefix, suffix, format);
1232
+ const css = buildCssMap(theme.resolve(), resolvePrefix(options, themeName), suffix, format);
1133
1233
  for (const key of [
1134
1234
  "light",
1135
1235
  "dark",
@@ -1162,6 +1262,12 @@ function createColorToken(input) {
1162
1262
  highContrast: options?.states?.highContrast ?? globalConfig.states.highContrast
1163
1263
  }, resolveModes(options?.modes), options?.format)["#__color__"];
1164
1264
  },
1265
+ tasty(options) {
1266
+ return buildTokenMap(resolveAllColors(input.hue, input.saturation, defs), "", {
1267
+ dark: options?.states?.dark ?? globalConfig.states.dark,
1268
+ highContrast: options?.states?.highContrast ?? globalConfig.states.highContrast
1269
+ }, resolveModes(options?.modes), options?.format)["#__color__"];
1270
+ },
1165
1271
  json(options) {
1166
1272
  return buildJsonMap(resolveAllColors(input.hue, input.saturation, defs), resolveModes(options?.modes), options?.format)["__color__"];
1167
1273
  }
@@ -1195,7 +1301,8 @@ glaze.configure = function configure(config) {
1195
1301
  modes: {
1196
1302
  dark: config.modes?.dark ?? globalConfig.modes.dark,
1197
1303
  highContrast: config.modes?.highContrast ?? globalConfig.modes.highContrast
1198
- }
1304
+ },
1305
+ shadowTuning: config.shadowTuning ?? globalConfig.shadowTuning
1199
1306
  };
1200
1307
  };
1201
1308
  /**
@@ -1217,6 +1324,40 @@ glaze.color = function color(input) {
1217
1324
  return createColorToken(input);
1218
1325
  };
1219
1326
  /**
1327
+ * Compute a shadow color from a bg/fg pair and intensity.
1328
+ */
1329
+ glaze.shadow = function shadow(input) {
1330
+ const bg = parseOkhslInput(input.bg);
1331
+ const fg = input.fg ? parseOkhslInput(input.fg) : void 0;
1332
+ const tuning = resolveShadowTuning(input.tuning);
1333
+ return computeShadow({
1334
+ ...bg,
1335
+ alpha: 1
1336
+ }, fg ? {
1337
+ ...fg,
1338
+ alpha: 1
1339
+ } : void 0, input.intensity, tuning);
1340
+ };
1341
+ /**
1342
+ * Format a resolved color variant as a CSS string.
1343
+ */
1344
+ glaze.format = function format(variant, colorFormat) {
1345
+ return formatVariant(variant, colorFormat);
1346
+ };
1347
+ function parseOkhslInput(input) {
1348
+ if (typeof input === "string") {
1349
+ const rgb = parseHex(input);
1350
+ if (!rgb) throw new Error(`glaze: invalid hex color "${input}".`);
1351
+ const [h, s, l] = srgbToOkhsl(rgb);
1352
+ return {
1353
+ h,
1354
+ s,
1355
+ l
1356
+ };
1357
+ }
1358
+ return input;
1359
+ }
1360
+ /**
1220
1361
  * Create a theme from a hex color string.
1221
1362
  * Extracts hue and saturation from the color.
1222
1363
  */