@tenphi/glaze 0.0.0-snapshot.8233469 → 0.0.0-snapshot.84edd5e

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
@@ -881,52 +881,6 @@ function isPaletteExport(data) {
881
881
  return hasPaletteShape(data);
882
882
  }
883
883
 
884
- //#endregion
885
- //#region src/format-guard.ts
886
- const NON_NATIVE_FORMATS = new Set(["okhsl", "okhst"]);
887
- /**
888
- * Throw when a non-native Glaze color space is requested for an export that
889
- * emits raw CSS or non-Tasty token maps.
890
- */
891
- function assertNativeFormat(format, method) {
892
- if (format !== void 0 && NON_NATIVE_FORMATS.has(format)) throw new Error(`glaze: ${format} output is only supported by tasty() (not a native CSS color space). Use tasty({ format: '${format}' }) or pick a native format (oklch|hsl|rgb) for ${method}().`);
893
- }
894
- const SCHEME_FIELDS = [
895
- {
896
- field: "light",
897
- modes: () => true
898
- },
899
- {
900
- field: "dark",
901
- modes: (m) => m.dark
902
- },
903
- {
904
- field: "lightContrast",
905
- modes: (m) => m.highContrast
906
- },
907
- {
908
- field: "darkContrast",
909
- modes: (m) => m.dark && m.highContrast
910
- }
911
- ];
912
- /**
913
- * Throw when `splitHue` is enabled but any exported color is not pastel.
914
- * Hue rotation is only clip-free when chroma is bounded by the hue-independent
915
- * safe chroma (`computeSafeChromaOKLCH`).
916
- */
917
- function assertAllPastel(resolved, modes) {
918
- const nonPastel = [];
919
- for (const [name, color] of resolved) for (const { field, modes: active } of SCHEME_FIELDS) {
920
- if (!active(modes)) continue;
921
- if (color[field].pastel !== true) {
922
- if (!nonPastel.includes(name)) nonPastel.push(name);
923
- break;
924
- }
925
- }
926
- if (nonPastel.length === 0) return;
927
- throw new Error(`glaze: splitHue requires every color to be pastel (hue rotation is only clip-free when chroma is bounded by the hue-independent safe chroma). Non-pastel: ${nonPastel.join(", ")}. Set pastel: true (per-theme, per-token, or per-color) or drop splitHue.`);
928
- }
929
-
930
884
  //#endregion
931
885
  //#region src/hc-pair.ts
932
886
  function pairNormal(p) {
@@ -1038,7 +992,6 @@ function parseToneValueAt(p, f) {
1038
992
  if (!Array.isArray(p)) return parseToneValue(p);
1039
993
  if (f <= 0) return parseToneValue(p[0]);
1040
994
  if (f >= 1) return parseToneValue(p[1]);
1041
- if (p[0] === p[1]) return parseToneValue(p[0]);
1042
995
  const normal = parseToneValue(p[0]);
1043
996
  const hc = parseToneValue(p[1]);
1044
997
  if (normal.kind === hc.kind && normal.kind !== "extreme") return {
@@ -1259,6 +1212,263 @@ function schemeToneRange(isDark, mode, isHighContrast, config) {
1259
1212
  return [clamp(toTone(win.lo / 100) / 100, 0, 1), clamp(toTone(win.hi / 100) / 100, 0, 1)];
1260
1213
  }
1261
1214
 
1215
+ //#endregion
1216
+ //#region src/color-value.ts
1217
+ /**
1218
+ * Parsing and validation for `GlazeColorValue` — the literal color forms.
1219
+ *
1220
+ * Hex, the CSS color functions Glaze emits, and the four value-object shapes all
1221
+ * land here and come out as OKHSL. Split out of `color-token.ts` so both
1222
+ * consumers can reach it without an import cycle: standalone `glaze.color()`
1223
+ * tokens build on it, and the resolver needs it for a theme color's `from`. It
1224
+ * depends on nothing but the color math, which is what keeps that true.
1225
+ */
1226
+ /**
1227
+ * Matches the CSS color functions Glaze itself emits (`rgb()`, `hsl()`,
1228
+ * `okhsl()`, `oklch()`) plus their legacy alpha aliases (`rgba()`, `hsla()`).
1229
+ *
1230
+ * Only bare numeric components are supported. Named colors (`red`),
1231
+ * relative-color syntax (`from <color> ...`), and angle units other
1232
+ * than bare degrees (`deg` is the only suffix tolerated by `parseFloat`)
1233
+ * are out of scope.
1234
+ */
1235
+ const COLOR_FN_RE = /^(rgba?|hsla?|okhsl|okhst|oklch)\(\s*([^)]*)\s*\)$/i;
1236
+ function parseNumberOrPercent(raw, percentScale) {
1237
+ if (raw.endsWith("%")) return parseFloat(raw) / 100 * percentScale;
1238
+ return parseFloat(raw);
1239
+ }
1240
+ /**
1241
+ * Split the body of a CSS color function into its components and detect
1242
+ * whether an alpha channel was present.
1243
+ *
1244
+ * Handles both modern slash syntax (`R G B / A` or `R, G, B / A`) and
1245
+ * legacy comma syntax (`R, G, B, A`). The alpha value itself is discarded
1246
+ * by the caller — standalone Glaze colors have no opacity field.
1247
+ */
1248
+ function splitColorBody(body) {
1249
+ const slashIdx = body.indexOf("/");
1250
+ if (slashIdx !== -1) return {
1251
+ components: body.slice(0, slashIdx).trim().split(/[\s,]+/).filter(Boolean),
1252
+ hadAlpha: body.slice(slashIdx + 1).trim().length > 0
1253
+ };
1254
+ const components = body.split(/[\s,]+/).filter(Boolean);
1255
+ if (components.length === 4) {
1256
+ components.pop();
1257
+ return {
1258
+ components,
1259
+ hadAlpha: true
1260
+ };
1261
+ }
1262
+ return {
1263
+ components,
1264
+ hadAlpha: false
1265
+ };
1266
+ }
1267
+ function warnDroppedAlpha(input) {
1268
+ console.warn(`glaze: alpha component dropped from "${input}" (standalone color has no opacity field).`);
1269
+ }
1270
+ function parseColorString(input) {
1271
+ if (input.startsWith("#")) {
1272
+ const parsed = parseHexAlpha(input);
1273
+ if (!parsed) throw new Error(`glaze: invalid hex color "${input}".`);
1274
+ if (parsed.alpha !== void 0) warnDroppedAlpha(input);
1275
+ const [h, s, l] = srgbToOkhsl(parsed.rgb);
1276
+ return {
1277
+ h,
1278
+ s,
1279
+ l
1280
+ };
1281
+ }
1282
+ const m = input.match(COLOR_FN_RE);
1283
+ if (!m) throw new Error(`glaze: unsupported color string "${input}".`);
1284
+ const fn = m[1].toLowerCase();
1285
+ const { components, hadAlpha } = splitColorBody(m[2].trim());
1286
+ if (hadAlpha) warnDroppedAlpha(input);
1287
+ if (components.length !== 3) throw new Error(`glaze: expected 3 components in "${input}".`);
1288
+ switch (fn) {
1289
+ case "rgb":
1290
+ case "rgba": {
1291
+ const [h, s, l] = srgbToOkhsl([
1292
+ parseNumberOrPercent(components[0], 255) / 255,
1293
+ parseNumberOrPercent(components[1], 255) / 255,
1294
+ parseNumberOrPercent(components[2], 255) / 255
1295
+ ]);
1296
+ return {
1297
+ h,
1298
+ s,
1299
+ l
1300
+ };
1301
+ }
1302
+ case "hsl":
1303
+ case "hsla": {
1304
+ const [oh, os, ol] = srgbToOkhsl(hslToSrgb(parseFloat(components[0]), parseNumberOrPercent(components[1], 1), parseNumberOrPercent(components[2], 1)));
1305
+ return {
1306
+ h: oh,
1307
+ s: os,
1308
+ l: ol
1309
+ };
1310
+ }
1311
+ case "okhsl": return {
1312
+ h: parseFloat(components[0]),
1313
+ s: parseNumberOrPercent(components[1], 1),
1314
+ l: parseNumberOrPercent(components[2], 1)
1315
+ };
1316
+ case "okhst": return okhstToOkhsl({
1317
+ h: parseFloat(components[0]),
1318
+ s: parseNumberOrPercent(components[1], 1),
1319
+ t: parseNumberOrPercent(components[2], 1)
1320
+ });
1321
+ case "oklch": {
1322
+ const L = parseNumberOrPercent(components[0], 1);
1323
+ const C = parseNumberOrPercent(components[1], .4);
1324
+ const hRad = parseFloat(components[2]) * Math.PI / 180;
1325
+ const [h, s, l] = oklabToOkhsl([
1326
+ L,
1327
+ C * Math.cos(hRad),
1328
+ C * Math.sin(hRad)
1329
+ ]);
1330
+ return {
1331
+ h,
1332
+ s,
1333
+ l
1334
+ };
1335
+ }
1336
+ }
1337
+ throw new Error(`glaze: unsupported color function "${fn}".`);
1338
+ }
1339
+ /**
1340
+ * Validate a user-supplied `OkhslColor`. Catches the common 0-100 vs 0-1
1341
+ * confusion (the structured form uses 0-100, OKHSL objects use 0-1).
1342
+ */
1343
+ function validateOkhslColor(value) {
1344
+ const { h, s, l } = value;
1345
+ if (!Number.isFinite(h) || !Number.isFinite(s) || !Number.isFinite(l)) throw new Error("glaze.color: OkhslColor h/s/l must be finite numbers.");
1346
+ if (s > 1.5 || l > 1.5) throw new Error("glaze.color: OkhslColor s/l must be in 0–1 range. Did you mean the structured form { hue, saturation, tone } (which uses 0–100)?");
1347
+ }
1348
+ /** Validate a user-supplied `{ r, g, b }` object in 0–255. */
1349
+ function validateRgbColor(value) {
1350
+ for (const key of [
1351
+ "r",
1352
+ "g",
1353
+ "b"
1354
+ ]) {
1355
+ const n = value[key];
1356
+ if (!Number.isFinite(n) || n < 0 || n > 255) throw new Error(`glaze.color: RgbColor ${key} must be a finite number in 0–255 (got ${n}).`);
1357
+ }
1358
+ }
1359
+ /** Validate a user-supplied `{ l, c, h }` OKLCh object. */
1360
+ function validateOklchColor(value) {
1361
+ const { l, c, h } = value;
1362
+ if (!Number.isFinite(l) || !Number.isFinite(c) || !Number.isFinite(h)) throw new Error("glaze.color: OklchColor l/c/h must be finite numbers.");
1363
+ if (l > 1.5 || c > 1.5) throw new Error("glaze.color: OklchColor l/c must be in 0–1 range (matching oklch() strings).");
1364
+ }
1365
+ function oklchComponentsToOkhsl(l, c, hDeg) {
1366
+ const hRad = hDeg * Math.PI / 180;
1367
+ const [h, s, outL] = oklabToOkhsl([
1368
+ l,
1369
+ c * Math.cos(hRad),
1370
+ c * Math.sin(hRad)
1371
+ ]);
1372
+ return {
1373
+ h,
1374
+ s,
1375
+ l: outL
1376
+ };
1377
+ }
1378
+ function isRgbColorObject(value) {
1379
+ return "r" in value && "g" in value && "b" in value;
1380
+ }
1381
+ function isOklchColorObject(value) {
1382
+ return "c" in value && "l" in value && "h" in value;
1383
+ }
1384
+ function isOkhstColorObject(value) {
1385
+ return "t" in value && "h" in value && "s" in value;
1386
+ }
1387
+ /** Validate a user-supplied `{ h, s, t }` OKHST object (s/t in 0–1). */
1388
+ function validateOkhstColor(value) {
1389
+ const { h, s, t } = value;
1390
+ if (!Number.isFinite(h) || !Number.isFinite(s) || !Number.isFinite(t)) throw new Error("glaze.color: OkhstColor h/s/t must be finite numbers.");
1391
+ if (s > 1.5 || t > 1.5) throw new Error("glaze.color: OkhstColor s/t must be in 0–1 range. Did you mean the structured form { hue, saturation, tone } (which uses 0–100)?");
1392
+ }
1393
+ /**
1394
+ * Extract an OKHSL color from any `GlazeColorValue` form. Also used by
1395
+ * `glaze.shadow()` so all shadow inputs (hex, color functions, OKHSL,
1396
+ * literal objects) go through one parser.
1397
+ */
1398
+ function extractOkhslFromValue(value) {
1399
+ if (typeof value === "string") return parseColorString(value);
1400
+ if (Array.isArray(value)) throw new Error("glaze.color: RGB tuple [r, g, b] is no longer supported — use { r, g, b } instead.");
1401
+ if (isRgbColorObject(value)) {
1402
+ validateRgbColor(value);
1403
+ const [h, s, l] = srgbToOkhsl([
1404
+ value.r / 255,
1405
+ value.g / 255,
1406
+ value.b / 255
1407
+ ]);
1408
+ return {
1409
+ h,
1410
+ s,
1411
+ l
1412
+ };
1413
+ }
1414
+ if (isOklchColorObject(value)) {
1415
+ validateOklchColor(value);
1416
+ return oklchComponentsToOkhsl(value.l, value.c, value.h);
1417
+ }
1418
+ if (isOkhstColorObject(value)) {
1419
+ validateOkhstColor(value);
1420
+ return okhstToOkhsl(value);
1421
+ }
1422
+ validateOkhslColor(value);
1423
+ return value;
1424
+ }
1425
+
1426
+ //#endregion
1427
+ //#region src/format-guard.ts
1428
+ const NON_NATIVE_FORMATS = new Set(["okhsl", "okhst"]);
1429
+ /**
1430
+ * Throw when a non-native Glaze color space is requested for an export that
1431
+ * emits raw CSS or non-Tasty token maps.
1432
+ */
1433
+ function assertNativeFormat(format, method) {
1434
+ if (format !== void 0 && NON_NATIVE_FORMATS.has(format)) throw new Error(`glaze: ${format} output is only supported by tasty() (not a native CSS color space). Use tasty({ format: '${format}' }) or pick a native format (oklch|hsl|rgb) for ${method}().`);
1435
+ }
1436
+ const SCHEME_FIELDS = [
1437
+ {
1438
+ field: "light",
1439
+ modes: () => true
1440
+ },
1441
+ {
1442
+ field: "dark",
1443
+ modes: (m) => m.dark
1444
+ },
1445
+ {
1446
+ field: "lightContrast",
1447
+ modes: (m) => m.highContrast
1448
+ },
1449
+ {
1450
+ field: "darkContrast",
1451
+ modes: (m) => m.dark && m.highContrast
1452
+ }
1453
+ ];
1454
+ /**
1455
+ * Throw when `splitHue` is enabled but any exported color is not pastel.
1456
+ * Hue rotation is only clip-free when chroma is bounded by the hue-independent
1457
+ * safe chroma (`computeSafeChromaOKLCH`).
1458
+ */
1459
+ function assertAllPastel(resolved, modes) {
1460
+ const nonPastel = [];
1461
+ for (const [name, color] of resolved) for (const { field, modes: active } of SCHEME_FIELDS) {
1462
+ if (!active(modes)) continue;
1463
+ if (color[field].pastel !== true) {
1464
+ if (!nonPastel.includes(name)) nonPastel.push(name);
1465
+ break;
1466
+ }
1467
+ }
1468
+ if (nonPastel.length === 0) return;
1469
+ throw new Error(`glaze: splitHue requires every color to be pastel (hue rotation is only clip-free when chroma is bounded by the hue-independent safe chroma). Non-pastel: ${nonPastel.join(", ")}. Set pastel: true (per-theme, per-token, or per-color) or drop splitHue.`);
1470
+ }
1471
+
1262
1472
  //#endregion
1263
1473
  //#region src/contrast-solver.ts
1264
1474
  /**
@@ -1897,6 +2107,16 @@ function computeShadow(bg, fg, intensity, tuning) {
1897
2107
  * its base / bg / fg / target dependencies.
1898
2108
  */
1899
2109
  /**
2110
+ * Does this color sit at an absolute tone — either authored, or carried by a
2111
+ * `from` value? That is the test for "root" (placed on its own) as opposed to
2112
+ * "dependent" (placed relative to a base), and it is what lets a bare
2113
+ * `{ from: '#2F5BFF' }` stand as a complete color definition.
2114
+ */
2115
+ function hasAbsoluteTone(def) {
2116
+ if (def.tone === void 0) return def.from !== void 0;
2117
+ return isAbsoluteTone(def.tone);
2118
+ }
2119
+ /**
1900
2120
  * Reject a `contrast` pair whose two entries measure in different metrics.
1901
2121
  *
1902
2122
  * A WCAG ratio and an APCA Lc are different scales, so `[4.5, { apca: 75 }]`
@@ -1939,7 +2159,7 @@ function validateColorDefs(defs, externalBases) {
1939
2159
  if (regDef.tone !== void 0 && !isAbsoluteTone(regDef.tone) && !regDef.base) throw new Error(`glaze: color "${name}" has relative "tone" without "base".`);
1940
2160
  if (regDef.base && !allNames.has(regDef.base)) throw new Error(`glaze: color "${name}" references non-existent base "${regDef.base}".`);
1941
2161
  if (regDef.base && localNames.has(regDef.base) && isShadowDef(defs[regDef.base])) throw new Error(`glaze: color "${name}" base "${regDef.base}" references a shadow color.`);
1942
- if (!isAbsoluteTone(regDef.tone) && regDef.base === void 0) throw new Error(`glaze: color "${name}" must have either absolute "tone" (root) or "base" (dependent).`);
2162
+ if (!hasAbsoluteTone(regDef) && regDef.base === void 0) throw new Error(`glaze: color "${name}" must have either absolute "tone" (root), "from" (a literal color), or "base" (dependent).`);
1943
2163
  if (regDef.contrast !== void 0 && regDef.opacity !== void 0) console.warn(`glaze: color "${name}" has both "contrast" and "opacity". Opacity makes perceived tone unpredictable.`);
1944
2164
  }
1945
2165
  const visited = /* @__PURE__ */ new Set();
@@ -2155,8 +2375,7 @@ function resolveContrastSpec(spec, isHighContrast, config, polarity) {
2155
2375
  */
2156
2376
  function passTone(tone, isHighContrast, config) {
2157
2377
  const f = contrastFraction(config);
2158
- if (f === void 0 || isHighContrast) return parseToneValue(isHighContrast ? pairHC(tone) : pairNormal(tone));
2159
- return parseToneValueAt(tone, f);
2378
+ return f === void 0 || isHighContrast ? parseToneValue(isHighContrast ? pairHC(tone) : pairNormal(tone)) : parseToneValueAt(tone, f);
2160
2379
  }
2161
2380
  /** The authored numeric pair for this pass (shadow `intensity`, mix `value`). */
2162
2381
  function passNumber(p, isHighContrast, config) {
@@ -2190,11 +2409,52 @@ function applyToneFlip(delta, baseTone, flip) {
2190
2409
  * result is clamped to `[0, 100]` only — crossing the dark tone window is
2191
2410
  * intended, since the author asked for the extreme.
2192
2411
  */
2193
- function extremeDarkTone(authorTone, mode, isHighContrast, baseResolved, baseDarkTone, config) {
2194
- const delta = mapToneForScheme(authorTone, mode, false, isHighContrast, config) - getSchemeVariant(baseResolved, false, isHighContrast).t * 100;
2195
- return clamp(baseDarkTone + (mode === "auto" ? -delta : delta), 0, 100);
2412
+ function extremeDarkTone(authorTone, mode, isHighContrast, baseResolved, config) {
2413
+ const extremeLightTone = mapToneForScheme(authorTone, mode, false, isHighContrast, config);
2414
+ const lightBase = getSchemeVariant(baseResolved, false, isHighContrast);
2415
+ const darkBase = getSchemeVariant(baseResolved, true, isHighContrast);
2416
+ const shift = extremeLightTone - lightBase.t * 100;
2417
+ return clamp(darkBase.t * 100 + (mode === "auto" ? -shift : shift), 0, 100);
2418
+ }
2419
+ /**
2420
+ * Hue, absolute saturation (0–1) and tone (0–100) read off a color def's `from`.
2421
+ *
2422
+ * Memoized on the def object: a single resolve pass reads it once per scheme per
2423
+ * color, and the parse is pure, so the cache is a `WeakMap` keyed by the def
2424
+ * itself rather than a per-pass structure that would have to be threaded around.
2425
+ */
2426
+ const fromSeeds = /* @__PURE__ */ new WeakMap();
2427
+ function fromSeed(def) {
2428
+ if (def.from === void 0) return void 0;
2429
+ const cached = fromSeeds.get(def);
2430
+ if (cached) return cached;
2431
+ const { h, s, l } = extractOkhslFromValue(def.from);
2432
+ const seed = {
2433
+ hue: h,
2434
+ saturation: s,
2435
+ tone: toTone(l)
2436
+ };
2437
+ fromSeeds.set(def, seed);
2438
+ return seed;
2439
+ }
2440
+ /**
2441
+ * The config this color resolves against.
2442
+ *
2443
+ * A `from` color carries a literal value, so its light variant has to survive
2444
+ * the light tone window intact — hence a local `lightTone: false`, the same
2445
+ * default the value-shorthand form of `glaze.color()` applies. Only the light
2446
+ * window is dropped: dark and high contrast keep theirs, because that is where
2447
+ * the color is expected to adapt.
2448
+ */
2449
+ function configForColor(def, config) {
2450
+ if (def.from === void 0) return config;
2451
+ return {
2452
+ ...config,
2453
+ lightTone: false
2454
+ };
2196
2455
  }
2197
2456
  function resolveRootColor(def, isHighContrast, config) {
2457
+ if (def.tone === void 0) return clamp(fromSeed(def).tone, 0, 100);
2198
2458
  return clamp(passTone(def.tone, isHighContrast, config).value, 0, 100);
2199
2459
  }
2200
2460
  /**
@@ -2213,16 +2473,19 @@ function resolveRootColor(def, isHighContrast, config) {
2213
2473
  function resolveChannels(def, ctx, isDark) {
2214
2474
  const mode = def.mode ?? "auto";
2215
2475
  const satFactor = clamp(def.saturation ?? 1, 0, 1);
2476
+ const seed = fromSeed(def);
2477
+ const fromHue = seed !== void 0 && def.hue === void 0 ? seed.hue : void 0;
2478
+ const fromSaturation = seed !== void 0 && def.saturation === void 0 ? seed.saturation : void 0;
2216
2479
  if (!isDark || mode === "static") return {
2217
- hue: resolveEffectiveHue(ctx.hue, def.hue),
2218
- saturation: clamp(satFactor * ctx.saturation / 100, 0, 1)
2480
+ hue: fromHue ?? resolveEffectiveHue(ctx.hue, def.hue),
2481
+ saturation: fromSaturation ?? clamp(satFactor * ctx.saturation / 100, 0, 1)
2219
2482
  };
2220
2483
  const darkSeedSaturation = ctx.darkSaturation ?? ctx.saturation;
2221
2484
  const darkFactor = clamp(def.darkSaturation ?? satFactor, 0, 1);
2222
2485
  const explicitDark = def.darkSaturation !== void 0 || ctx.darkSaturation !== void 0;
2223
- const raw = darkFactor * darkSeedSaturation / 100;
2486
+ const raw = fromSaturation !== void 0 && def.darkSaturation === void 0 ? fromSaturation : darkFactor * darkSeedSaturation / 100;
2224
2487
  return {
2225
- hue: resolveEffectiveHue(ctx.darkHue ?? ctx.hue, def.darkHue ?? def.hue),
2488
+ hue: fromHue !== void 0 && def.darkHue === void 0 ? fromHue : resolveEffectiveHue(ctx.darkHue ?? ctx.hue, def.darkHue ?? def.hue),
2226
2489
  saturation: clamp(explicitDark ? raw : mapSaturationDark(raw, mode, ctx.config), 0, 1)
2227
2490
  };
2228
2491
  }
@@ -2233,63 +2496,59 @@ function resolveDependentColor(name, def, ctx, isHighContrast, isDark, channels,
2233
2496
  const mode = def.mode ?? "auto";
2234
2497
  const flip = def.autoFlip ?? ctx.config.autoFlip;
2235
2498
  const pastel = effectivePastel;
2499
+ const config = configForColor(def, ctx.config);
2236
2500
  const baseVariant = getSchemeVariant(baseResolved, isDark, isHighContrast);
2237
2501
  const baseTone = baseVariant.t * 100;
2238
2502
  let preferredTone;
2239
2503
  let isExtreme = false;
2240
- const rawTone = def.tone;
2504
+ const seed = fromSeed(def);
2505
+ const rawTone = def.tone ?? (seed !== void 0 ? seed.tone : void 0);
2241
2506
  if (rawTone === void 0) preferredTone = baseTone;
2242
2507
  else {
2243
- const parsed = passTone(rawTone, isHighContrast, ctx.config);
2508
+ const parsed = passTone(rawTone, isHighContrast, config);
2244
2509
  if (parsed.kind === "relative") if (isDark && mode === "auto") {
2245
2510
  const baseLightTone = getSchemeVariant(baseResolved, false, isHighContrast).t * 100;
2246
- preferredTone = mapToneForScheme(clamp(baseLightTone + applyToneFlip(parsed.value, baseLightTone, flip), 0, 100), "auto", true, isHighContrast, ctx.config);
2511
+ preferredTone = mapToneForScheme(clamp(baseLightTone + applyToneFlip(parsed.value, baseLightTone, flip), 0, 100), "auto", true, isHighContrast, config);
2247
2512
  } else preferredTone = clamp(baseTone + applyToneFlip(parsed.value, baseTone, flip), 0, 100);
2248
- else if (parsed.kind === "extreme" && isDark && mode !== "static") {
2249
- isExtreme = true;
2250
- preferredTone = extremeDarkTone(parsed.value, mode, isHighContrast, baseResolved, baseTone, ctx.config);
2251
- } else {
2513
+ else {
2252
2514
  isExtreme = parsed.kind === "extreme";
2253
- preferredTone = mapToneForScheme(parsed.value, mode, isDark, isHighContrast, ctx.config);
2515
+ if (isExtreme && isDark && mode !== "static") preferredTone = extremeDarkTone(parsed.value, mode, isHighContrast, baseResolved, config);
2516
+ else preferredTone = mapToneForScheme(parsed.value, mode, isDark, isHighContrast, config);
2254
2517
  }
2255
2518
  }
2256
2519
  const rawContrast = def.contrast;
2257
2520
  if (rawContrast !== void 0) {
2258
- const resolvedContrast = resolveContrastSpec(rawContrast, isHighContrast, ctx.config, polarity);
2521
+ const resolvedContrast = resolveContrastSpec(rawContrast, isHighContrast, config, polarity);
2259
2522
  const baseOkhsl = toOkhslVariant(baseVariant);
2260
2523
  const baseLinearRgb = okhslToLinearSrgb(baseOkhsl.h, baseOkhsl.s, baseOkhsl.l, baseVariant.pastel ?? ctx.config.pastel);
2261
- const toneRange = schemeToneRange(isDark, mode, isHighContrast, ctx.config);
2524
+ const preferredRange = isExtreme ? [0, 1] : schemeToneRange(isDark, mode, isHighContrast, config);
2262
2525
  let initialDirection;
2263
2526
  if (preferredTone < baseTone) initialDirection = "darker";
2264
2527
  else if (preferredTone > baseTone) initialDirection = "lighter";
2265
- const seedTone = isExtreme ? clamp(preferredTone / 100, 0, 1) : clamp(preferredTone / 100, toneRange[0], toneRange[1]);
2528
+ const solve = {
2529
+ hue: channels.hue,
2530
+ saturation: channels.saturation,
2531
+ preferredTone: clamp(preferredTone / 100, preferredRange[0], preferredRange[1]),
2532
+ baseLinearRgb,
2533
+ toneRange: [0, 1],
2534
+ flip,
2535
+ pastel
2536
+ };
2266
2537
  const level = contrastFraction(ctx.config);
2267
2538
  let preferInitial = false;
2268
2539
  if (level !== void 0 && level > 0 && level < 1) {
2269
2540
  initialDirection = findToneForContrast({
2270
- hue: channels.hue,
2271
- saturation: channels.saturation,
2272
- preferredTone: seedTone,
2273
- baseLinearRgb,
2541
+ ...solve,
2274
2542
  contrast: resolveContrastForLevel(rawContrast, level < PAIR_SWITCH ? 0 : 100, polarity),
2275
- toneRange: [0, 1],
2276
- initialDirection,
2277
- flip,
2278
- pastel
2543
+ initialDirection
2279
2544
  }).tone * 100 < baseTone ? "darker" : "lighter";
2280
2545
  preferInitial = true;
2281
2546
  }
2282
2547
  const result = findToneForContrast({
2283
- hue: channels.hue,
2284
- saturation: channels.saturation,
2285
- preferredTone: seedTone,
2286
- baseLinearRgb,
2548
+ ...solve,
2287
2549
  contrast: resolvedContrast,
2288
- toneRange: [0, 1],
2289
2550
  initialDirection,
2290
- flip,
2291
- preferInitial,
2292
- pastel
2551
+ preferInitial
2293
2552
  });
2294
2553
  if (!result.met) warnContrastUnmet(name, isDark, isHighContrast, resolvedContrast, result.contrast);
2295
2554
  return result.tone * 100;
@@ -2301,11 +2560,12 @@ function resolveColorForScheme(name, def, ctx, isDark, isHighContrast) {
2301
2560
  if (isMixDef(def)) return resolveMixForScheme(name, def, ctx, isDark, isHighContrast);
2302
2561
  const regDef = def;
2303
2562
  const mode = regDef.mode ?? "auto";
2304
- const isRoot = isAbsoluteTone(regDef.tone) && !regDef.base;
2563
+ const isRoot = hasAbsoluteTone(regDef) && !regDef.base;
2305
2564
  const channels = resolveChannels(regDef, ctx, isDark);
2306
2565
  const polarity = roleToPolarity(resolveRole(name, def, ctx));
2307
2566
  const pastel = regDef.pastel ?? ctx.config.pastel;
2308
- const finalTone = isRoot ? mapToneForScheme(resolveRootColor(regDef, isHighContrast, ctx.config), mode, isDark, isHighContrast, ctx.config) : resolveDependentColor(name, regDef, ctx, isHighContrast, isDark, channels, polarity, pastel);
2567
+ const config = configForColor(regDef, ctx.config);
2568
+ const finalTone = isRoot ? mapToneForScheme(resolveRootColor(regDef, isHighContrast, config), mode, isDark, isHighContrast, config) : resolveDependentColor(name, regDef, ctx, isHighContrast, isDark, channels, polarity, pastel);
2309
2569
  return {
2310
2570
  h: channels.hue,
2311
2571
  s: channels.saturation,
@@ -2479,7 +2739,7 @@ function verifyContrastDrift(order, defs, result, config) {
2479
2739
  const base = result.get(regDef.base);
2480
2740
  if (!color || !base) continue;
2481
2741
  const polarity = roleToPolarity(resolveRoleInMap(name, def, defs, config.inferRole, roles));
2482
- const allSchemes = [
2742
+ const schemes = [
2483
2743
  {
2484
2744
  isDark: false,
2485
2745
  isHighContrast: false,
@@ -2501,8 +2761,9 @@ function verifyContrastDrift(order, defs, result, config) {
2501
2761
  field: "darkContrast"
2502
2762
  }
2503
2763
  ];
2504
- const schemes = contrastFraction(config) !== void 0 ? allSchemes.filter((s) => !s.isHighContrast) : allSchemes;
2764
+ const manual = contrastFraction(config) !== void 0;
2505
2765
  for (const s of schemes) {
2766
+ if (manual && s.isHighContrast) continue;
2506
2767
  const spec = resolveContrastSpec(regDef.contrast, s.isHighContrast, config, polarity);
2507
2768
  const cVariant = color[s.field];
2508
2769
  const bVariant = base[s.field];
@@ -2565,7 +2826,9 @@ function resolveAllColors(seed, defs, config, externalBases) {
2565
2826
  *
2566
2827
  * Hue is scheme-independent unless the theme or a color authors a dark hue,
2567
2828
  * so declarations are collected per scheme (`'light'` / `'dark'`) and the dark
2568
- * set is emitted only when it actually differs — see `darkHueDeclarations`.
2829
+ * set is emitted only when it actually differs — see `buildHueDeclarations`.
2830
+ * A color that authors a hue in *either* scheme gets its own var in both, so
2831
+ * the shared `var()` reference stays valid.
2569
2832
  */
2570
2833
  const ACHROMATIC_EPSILON = 1e-6;
2571
2834
  function cssProp(prefix, name, suffix) {
@@ -2587,23 +2850,38 @@ function themeHuePlan(name, def, variant, ctx, scheme) {
2587
2850
  const regDef = def;
2588
2851
  const baseHueVar = `var(--${ctx.baseName}-hue)`;
2589
2852
  const prop = cssProp(ctx.prefix, name, "-hue");
2590
- if ((regDef.mode ?? "auto") === "static" && schemeSeedHue(ctx, "dark") !== ctx.seedHue) {
2591
- const pinned = resolveEffectiveHue(ctx.seedHue, regDef.hue);
2853
+ if ((regDef.mode ?? "auto") === "static" && schemeSeedHue(ctx, "dark") !== ctx.seedHue) return {
2854
+ hueVar: `var(${prop})`,
2855
+ inline: false,
2856
+ declarations: [{
2857
+ prop,
2858
+ value: String(variant.h)
2859
+ }]
2860
+ };
2861
+ if (regDef.hue === void 0 && regDef.darkHue === void 0 && regDef.from === void 0) return {
2862
+ hueVar: baseHueVar,
2863
+ inline: false,
2864
+ declarations: []
2865
+ };
2866
+ const authored = scheme === "dark" ? regDef.darkHue ?? regDef.hue : regDef.hue;
2867
+ if (authored === void 0) {
2868
+ if (regDef.from !== void 0) return {
2869
+ hueVar: `var(${prop})`,
2870
+ inline: false,
2871
+ declarations: [{
2872
+ prop,
2873
+ value: String(variant.h)
2874
+ }]
2875
+ };
2592
2876
  return {
2593
2877
  hueVar: `var(${prop})`,
2594
2878
  inline: false,
2595
2879
  declarations: [{
2596
2880
  prop,
2597
- value: String(pinned)
2881
+ value: baseHueVar
2598
2882
  }]
2599
2883
  };
2600
2884
  }
2601
- const authored = scheme === "dark" ? regDef.darkHue ?? regDef.hue : regDef.hue;
2602
- if (authored === void 0) return {
2603
- hueVar: baseHueVar,
2604
- inline: false,
2605
- declarations: []
2606
- };
2607
2885
  const parsed = parseRelativeOrAbsolute(authored);
2608
2886
  if (parsed.relative) {
2609
2887
  const sign = parsed.value >= 0 ? "+" : "-";
@@ -2650,7 +2928,7 @@ function buildHuePlan(name, def, variant, ctx, scheme = "light") {
2650
2928
  return themeHuePlan(name, def, variant, ctx, scheme);
2651
2929
  }
2652
2930
  /** Collect unique hue declarations across all colors (theme + per-color). */
2653
- function collectHueDeclarations(resolved, ctx, scheme = "light") {
2931
+ function collectHueDeclarations(resolved, ctx, scheme) {
2654
2932
  if (ctx.emitDeclarations === false) return [];
2655
2933
  const seen = /* @__PURE__ */ new Set();
2656
2934
  const out = [];
@@ -2671,21 +2949,24 @@ function collectHueDeclarations(resolved, ctx, scheme = "light") {
2671
2949
  return out;
2672
2950
  }
2673
2951
  /**
2674
- * Dark-scheme hue declarations, or `[]` when dark resolves to the same hues as
2675
- * light (the common case — no `darkHue` anywhere).
2952
+ * Hue declarations for both schemes. `dark` is empty when it resolves to the
2953
+ * same hues as light — the common case, with no `darkHue` anywhere.
2676
2954
  *
2677
- * When they differ, the **whole** set is returned rather than only the entries
2678
- * whose text changed. A relative declaration reads
2955
+ * When they differ, the **whole** dark set is returned rather than only the
2956
+ * entries whose text changed. A relative declaration reads
2679
2957
  * `calc(var(--theme-hue) + N)` in both schemes even though its resolved value
2680
2958
  * moves with the seed, and custom properties substitute `var()` at
2681
2959
  * computed-value time on the declaring element — so re-declaring
2682
2960
  * `--{baseName}-hue` alone would leave inherited per-color vars still holding
2683
2961
  * the light-substituted value.
2684
2962
  */
2685
- function darkHueDeclarations(resolved, ctx) {
2686
- const dark = collectHueDeclarations(resolved, ctx, "dark");
2963
+ function buildHueDeclarations(resolved, ctx) {
2687
2964
  const light = collectHueDeclarations(resolved, ctx, "light");
2688
- return dark.length === light.length && dark.every((decl, i) => decl.prop === light[i].prop && decl.value === light[i].value) ? [] : dark;
2965
+ const dark = collectHueDeclarations(resolved, ctx, "dark");
2966
+ return {
2967
+ light,
2968
+ dark: dark.length === light.length && dark.every((decl, i) => decl.prop === light[i].prop && decl.value === light[i].value) ? [] : dark
2969
+ };
2689
2970
  }
2690
2971
  function buildHuePlans(resolved, ctx) {
2691
2972
  const plans = /* @__PURE__ */ new Map();
@@ -2752,30 +3033,32 @@ function formatColorValue(v, format, pastel, huePlan) {
2752
3033
  /**
2753
3034
  * Resolve the effective output modes for an export.
2754
3035
  *
2755
- * A global manual `contrastLevel` resolves the contrast preference into the
2756
- * normal variants and mirrors them into the high-contrast slots, so a separate
2757
- * high-contrast tier would only repeat them — it defaults off. An explicit
2758
- * `modes.highContrast` still wins (explicit input beats an automatic default
2759
- * everywhere else in Glaze too), and emits values identical to the normal ones.
3036
+ * A global manual `contrastLevel` turns off high-contrast output outright: the
3037
+ * level *is* the contrast preference, so a separate high-contrast tier has no
3038
+ * meaning alongside it. `modes.highContrast` therefore reads as "emit a separate
3039
+ * high-contrast set **when** contrast is automatic" — it goes inert under a
3040
+ * manual level rather than fighting it, and silently, since switching a
3041
+ * preference from auto to manual is normal use and not a mistake to report.
2760
3042
  *
2761
3043
  * A level set on a single theme or token does not change which modes are
2762
3044
  * emitted: sibling themes in a palette may still have a real high-contrast
2763
- * tier, and the manual one correctly reports its own blended values there.
3045
+ * tier, and the manual one correctly reports its own resolved values there —
3046
+ * the alternative would drop its colors from that tier entirely.
2764
3047
  */
2765
3048
  function resolveModes(override) {
2766
3049
  const cfg = getConfig();
2767
- const manual = contrastFraction(cfg) !== void 0;
2768
3050
  return {
2769
3051
  dark: override?.dark ?? cfg.modes.dark,
2770
- highContrast: override?.highContrast ?? (manual ? false : cfg.modes.highContrast)
3052
+ highContrast: contrastFraction(cfg) !== void 0 ? false : override?.highContrast ?? cfg.modes.highContrast
2771
3053
  };
2772
3054
  }
2773
3055
  function buildTokenMap(resolved, prefix, states, modes, format = "oklch", pastel = false, channelCtx) {
2774
3056
  const tokens = {};
2775
3057
  const huePlans = channelCtx !== void 0 && format === "oklch" ? buildHuePlans(resolved, channelCtx) : void 0;
2776
3058
  if (huePlans !== void 0 && channelCtx !== void 0) {
2777
- for (const decl of collectHueDeclarations(resolved, channelCtx)) tokens[`$${decl.prop.slice(2)}`] = { "": decl.value };
2778
- if (modes.dark) for (const decl of darkHueDeclarations(resolved, channelCtx)) {
3059
+ const hueDecls = buildHueDeclarations(resolved, channelCtx);
3060
+ for (const decl of hueDecls.light) tokens[`$${decl.prop.slice(2)}`] = { "": decl.value };
3061
+ if (modes.dark) for (const decl of hueDecls.dark) {
2779
3062
  const key = `$${decl.prop.slice(2)}`;
2780
3063
  tokens[key] = {
2781
3064
  ...tokens[key],
@@ -2832,8 +3115,9 @@ function buildCssMap(resolved, prefix, suffix, format, pastel = false, channelCt
2832
3115
  };
2833
3116
  const huePlans = channelCtx !== void 0 && format === "oklch" ? buildHuePlans(resolved, channelCtx) : void 0;
2834
3117
  if (huePlans !== void 0 && channelCtx !== void 0) {
2835
- for (const decl of collectHueDeclarations(resolved, channelCtx)) lines.light.push(`${decl.prop}: ${decl.value};`);
2836
- for (const decl of darkHueDeclarations(resolved, channelCtx)) {
3118
+ const hueDecls = buildHueDeclarations(resolved, channelCtx);
3119
+ for (const decl of hueDecls.light) lines.light.push(`${decl.prop}: ${decl.value};`);
3120
+ for (const decl of hueDecls.dark) {
2837
3121
  lines.dark.push(`${decl.prop}: ${decl.value};`);
2838
3122
  lines.darkContrast.push(`${decl.prop}: ${decl.value};`);
2839
3123
  }
@@ -3079,173 +3363,6 @@ function sparseValueFormLocal(userOverride) {
3079
3363
  };
3080
3364
  }
3081
3365
  /**
3082
- * Matches the CSS color functions Glaze itself emits (`rgb()`, `hsl()`,
3083
- * `okhsl()`, `oklch()`) plus their legacy alpha aliases (`rgba()`, `hsla()`).
3084
- *
3085
- * Only bare numeric components are supported. Named colors (`red`),
3086
- * relative-color syntax (`from <color> ...`), and angle units other
3087
- * than bare degrees (`deg` is the only suffix tolerated by `parseFloat`)
3088
- * are out of scope.
3089
- */
3090
- const COLOR_FN_RE = /^(rgba?|hsla?|okhsl|okhst|oklch)\(\s*([^)]*)\s*\)$/i;
3091
- function parseNumberOrPercent(raw, percentScale) {
3092
- if (raw.endsWith("%")) return parseFloat(raw) / 100 * percentScale;
3093
- return parseFloat(raw);
3094
- }
3095
- /**
3096
- * Split the body of a CSS color function into its components and detect
3097
- * whether an alpha channel was present.
3098
- *
3099
- * Handles both modern slash syntax (`R G B / A` or `R, G, B / A`) and
3100
- * legacy comma syntax (`R, G, B, A`). The alpha value itself is discarded
3101
- * by the caller — standalone Glaze colors have no opacity field.
3102
- */
3103
- function splitColorBody(body) {
3104
- const slashIdx = body.indexOf("/");
3105
- if (slashIdx !== -1) return {
3106
- components: body.slice(0, slashIdx).trim().split(/[\s,]+/).filter(Boolean),
3107
- hadAlpha: body.slice(slashIdx + 1).trim().length > 0
3108
- };
3109
- const components = body.split(/[\s,]+/).filter(Boolean);
3110
- if (components.length === 4) {
3111
- components.pop();
3112
- return {
3113
- components,
3114
- hadAlpha: true
3115
- };
3116
- }
3117
- return {
3118
- components,
3119
- hadAlpha: false
3120
- };
3121
- }
3122
- function warnDroppedAlpha(input) {
3123
- console.warn(`glaze: alpha component dropped from "${input}" (standalone color has no opacity field).`);
3124
- }
3125
- function parseColorString(input) {
3126
- if (input.startsWith("#")) {
3127
- const parsed = parseHexAlpha(input);
3128
- if (!parsed) throw new Error(`glaze: invalid hex color "${input}".`);
3129
- if (parsed.alpha !== void 0) warnDroppedAlpha(input);
3130
- const [h, s, l] = srgbToOkhsl(parsed.rgb);
3131
- return {
3132
- h,
3133
- s,
3134
- l
3135
- };
3136
- }
3137
- const m = input.match(COLOR_FN_RE);
3138
- if (!m) throw new Error(`glaze: unsupported color string "${input}".`);
3139
- const fn = m[1].toLowerCase();
3140
- const { components, hadAlpha } = splitColorBody(m[2].trim());
3141
- if (hadAlpha) warnDroppedAlpha(input);
3142
- if (components.length !== 3) throw new Error(`glaze: expected 3 components in "${input}".`);
3143
- switch (fn) {
3144
- case "rgb":
3145
- case "rgba": {
3146
- const [h, s, l] = srgbToOkhsl([
3147
- parseNumberOrPercent(components[0], 255) / 255,
3148
- parseNumberOrPercent(components[1], 255) / 255,
3149
- parseNumberOrPercent(components[2], 255) / 255
3150
- ]);
3151
- return {
3152
- h,
3153
- s,
3154
- l
3155
- };
3156
- }
3157
- case "hsl":
3158
- case "hsla": {
3159
- const [oh, os, ol] = srgbToOkhsl(hslToSrgb(parseFloat(components[0]), parseNumberOrPercent(components[1], 1), parseNumberOrPercent(components[2], 1)));
3160
- return {
3161
- h: oh,
3162
- s: os,
3163
- l: ol
3164
- };
3165
- }
3166
- case "okhsl": return {
3167
- h: parseFloat(components[0]),
3168
- s: parseNumberOrPercent(components[1], 1),
3169
- l: parseNumberOrPercent(components[2], 1)
3170
- };
3171
- case "okhst": return okhstToOkhsl({
3172
- h: parseFloat(components[0]),
3173
- s: parseNumberOrPercent(components[1], 1),
3174
- t: parseNumberOrPercent(components[2], 1)
3175
- });
3176
- case "oklch": {
3177
- const L = parseNumberOrPercent(components[0], 1);
3178
- const C = parseNumberOrPercent(components[1], .4);
3179
- const hRad = parseFloat(components[2]) * Math.PI / 180;
3180
- const [h, s, l] = oklabToOkhsl([
3181
- L,
3182
- C * Math.cos(hRad),
3183
- C * Math.sin(hRad)
3184
- ]);
3185
- return {
3186
- h,
3187
- s,
3188
- l
3189
- };
3190
- }
3191
- }
3192
- throw new Error(`glaze: unsupported color function "${fn}".`);
3193
- }
3194
- /**
3195
- * Validate a user-supplied `OkhslColor`. Catches the common 0-100 vs 0-1
3196
- * confusion (the structured form uses 0-100, OKHSL objects use 0-1).
3197
- */
3198
- function validateOkhslColor(value) {
3199
- const { h, s, l } = value;
3200
- if (!Number.isFinite(h) || !Number.isFinite(s) || !Number.isFinite(l)) throw new Error("glaze.color: OkhslColor h/s/l must be finite numbers.");
3201
- if (s > 1.5 || l > 1.5) throw new Error("glaze.color: OkhslColor s/l must be in 0–1 range. Did you mean the structured form { hue, saturation, tone } (which uses 0–100)?");
3202
- }
3203
- /** Validate a user-supplied `{ r, g, b }` object in 0–255. */
3204
- function validateRgbColor(value) {
3205
- for (const key of [
3206
- "r",
3207
- "g",
3208
- "b"
3209
- ]) {
3210
- const n = value[key];
3211
- if (!Number.isFinite(n) || n < 0 || n > 255) throw new Error(`glaze.color: RgbColor ${key} must be a finite number in 0–255 (got ${n}).`);
3212
- }
3213
- }
3214
- /** Validate a user-supplied `{ l, c, h }` OKLCh object. */
3215
- function validateOklchColor(value) {
3216
- const { l, c, h } = value;
3217
- if (!Number.isFinite(l) || !Number.isFinite(c) || !Number.isFinite(h)) throw new Error("glaze.color: OklchColor l/c/h must be finite numbers.");
3218
- if (l > 1.5 || c > 1.5) throw new Error("glaze.color: OklchColor l/c must be in 0–1 range (matching oklch() strings).");
3219
- }
3220
- function oklchComponentsToOkhsl(l, c, hDeg) {
3221
- const hRad = hDeg * Math.PI / 180;
3222
- const [h, s, outL] = oklabToOkhsl([
3223
- l,
3224
- c * Math.cos(hRad),
3225
- c * Math.sin(hRad)
3226
- ]);
3227
- return {
3228
- h,
3229
- s,
3230
- l: outL
3231
- };
3232
- }
3233
- function isRgbColorObject(value) {
3234
- return "r" in value && "g" in value && "b" in value;
3235
- }
3236
- function isOklchColorObject(value) {
3237
- return "c" in value && "l" in value && "h" in value;
3238
- }
3239
- function isOkhstColorObject(value) {
3240
- return "t" in value && "h" in value && "s" in value;
3241
- }
3242
- /** Validate a user-supplied `{ h, s, t }` OKHST object (s/t in 0–1). */
3243
- function validateOkhstColor(value) {
3244
- const { h, s, t } = value;
3245
- if (!Number.isFinite(h) || !Number.isFinite(s) || !Number.isFinite(t)) throw new Error("glaze.color: OkhstColor h/s/t must be finite numbers.");
3246
- if (s > 1.5 || t > 1.5) throw new Error("glaze.color: OkhstColor s/t must be in 0–1 range. Did you mean the structured form { hue, saturation, tone } (which uses 0–100)?");
3247
- }
3248
- /**
3249
3366
  * Validate a user-supplied `opacity` override on `glaze.color()`.
3250
3367
  * Must be a finite number in `0..=1`.
3251
3368
  */
@@ -3295,38 +3412,6 @@ function validateStandaloneName(name) {
3295
3412
  }
3296
3413
  }
3297
3414
  /**
3298
- * Extract an OKHSL color from any `GlazeColorValue` form. Also used by
3299
- * `glaze.shadow()` so all shadow inputs (hex, color functions, OKHSL,
3300
- * literal objects) go through one parser.
3301
- */
3302
- function extractOkhslFromValue(value) {
3303
- if (typeof value === "string") return parseColorString(value);
3304
- if (Array.isArray(value)) throw new Error("glaze.color: RGB tuple [r, g, b] is no longer supported — use { r, g, b } instead.");
3305
- if (isRgbColorObject(value)) {
3306
- validateRgbColor(value);
3307
- const [h, s, l] = srgbToOkhsl([
3308
- value.r / 255,
3309
- value.g / 255,
3310
- value.b / 255
3311
- ]);
3312
- return {
3313
- h,
3314
- s,
3315
- l
3316
- };
3317
- }
3318
- if (isOklchColorObject(value)) {
3319
- validateOklchColor(value);
3320
- return oklchComponentsToOkhsl(value.l, value.c, value.h);
3321
- }
3322
- if (isOkhstColorObject(value)) {
3323
- validateOkhstColor(value);
3324
- return okhstToOkhsl(value);
3325
- }
3326
- validateOkhslColor(value);
3327
- return value;
3328
- }
3329
- /**
3330
3415
  * Resolve `'max'` / `'min'` to their literal author tones (100 / 0).
3331
3416
  *
3332
3417
  * The hidden `STANDALONE_SEED` anchor exists only so `contrast` has something