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