@epilot/spark-ui 0.11.1 → 0.12.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.
Files changed (139) hide show
  1. package/dist/components/accordion.cjs +1 -1
  2. package/dist/components/accordion.js +1 -1
  3. package/dist/components/attachment.d.cts +1 -1
  4. package/dist/components/attachment.d.ts +1 -1
  5. package/dist/components/avatar.d.cts +1 -1
  6. package/dist/components/avatar.d.ts +1 -1
  7. package/dist/components/badge.d.cts +1 -1
  8. package/dist/components/badge.d.ts +1 -1
  9. package/dist/components/bar-chart.js +1 -1
  10. package/dist/components/box.cjs +3 -2
  11. package/dist/components/box.cjs.map +1 -1
  12. package/dist/components/box.d.cts +8 -1
  13. package/dist/components/box.d.ts +8 -1
  14. package/dist/components/box.js +3 -2
  15. package/dist/components/box.js.map +1 -1
  16. package/dist/components/bubble.d.cts +1 -1
  17. package/dist/components/bubble.d.ts +1 -1
  18. package/dist/components/button-group.d.cts +1 -1
  19. package/dist/components/button-group.d.ts +1 -1
  20. package/dist/components/button.d.cts +3 -3
  21. package/dist/components/button.d.ts +3 -3
  22. package/dist/components/calendar.cjs +2 -2
  23. package/dist/components/calendar.cjs.map +1 -1
  24. package/dist/components/calendar.js +2 -2
  25. package/dist/components/calendar.js.map +1 -1
  26. package/dist/components/callout.cjs +1 -1
  27. package/dist/components/callout.cjs.map +1 -1
  28. package/dist/components/callout.d.cts +2 -2
  29. package/dist/components/callout.d.ts +2 -2
  30. package/dist/components/callout.js +1 -1
  31. package/dist/components/callout.js.map +1 -1
  32. package/dist/components/card.cjs +3 -2
  33. package/dist/components/card.cjs.map +1 -1
  34. package/dist/components/card.d.cts +7 -1
  35. package/dist/components/card.d.ts +7 -1
  36. package/dist/components/card.js +3 -2
  37. package/dist/components/card.js.map +1 -1
  38. package/dist/components/checkbox.d.cts +1 -1
  39. package/dist/components/checkbox.d.ts +1 -1
  40. package/dist/components/field.d.cts +1 -1
  41. package/dist/components/field.d.ts +1 -1
  42. package/dist/components/icon.cjs +1 -1
  43. package/dist/components/icon.js +1 -1
  44. package/dist/components/info-block.d.cts +1 -1
  45. package/dist/components/info-block.d.ts +1 -1
  46. package/dist/components/input-group.cjs +1 -1
  47. package/dist/components/input-group.cjs.map +1 -1
  48. package/dist/components/input-group.js +1 -1
  49. package/dist/components/input-group.js.map +1 -1
  50. package/dist/components/input-otp.cjs +1 -1
  51. package/dist/components/input-otp.js +1 -1
  52. package/dist/components/input.cjs +1 -1
  53. package/dist/components/input.d.cts +1 -1
  54. package/dist/components/input.d.ts +1 -1
  55. package/dist/components/input.js +1 -1
  56. package/dist/components/line-chart.cjs +1 -1
  57. package/dist/components/line-chart.js +2 -2
  58. package/dist/components/list.d.cts +1 -1
  59. package/dist/components/list.d.ts +1 -1
  60. package/dist/components/page-header.d.cts +1 -1
  61. package/dist/components/page-header.d.ts +1 -1
  62. package/dist/components/pill.cjs +20 -13
  63. package/dist/components/pill.cjs.map +1 -1
  64. package/dist/components/pill.d.cts +1 -1
  65. package/dist/components/pill.d.ts +1 -1
  66. package/dist/components/pill.js +20 -13
  67. package/dist/components/pill.js.map +1 -1
  68. package/dist/components/power-history-chart.cjs +1 -1
  69. package/dist/components/power-history-chart.js +2 -2
  70. package/dist/components/progress.cjs +1 -1
  71. package/dist/components/progress.cjs.map +1 -1
  72. package/dist/components/progress.js +1 -1
  73. package/dist/components/progress.js.map +1 -1
  74. package/dist/components/radio-card-group.cjs.map +1 -1
  75. package/dist/components/radio-card-group.js.map +1 -1
  76. package/dist/components/radio.d.cts +1 -1
  77. package/dist/components/radio.d.ts +1 -1
  78. package/dist/components/schedule-list.cjs +10 -7
  79. package/dist/components/schedule-list.cjs.map +1 -1
  80. package/dist/components/schedule-list.js +10 -7
  81. package/dist/components/schedule-list.js.map +1 -1
  82. package/dist/components/segmented-control.d.cts +1 -1
  83. package/dist/components/segmented-control.d.ts +1 -1
  84. package/dist/components/select.cjs +1 -1
  85. package/dist/components/select.d.cts +1 -1
  86. package/dist/components/select.d.ts +1 -1
  87. package/dist/components/select.js +1 -1
  88. package/dist/components/solar-forecast-chart.cjs +1 -1
  89. package/dist/components/solar-forecast-chart.js +2 -2
  90. package/dist/components/switch.d.cts +1 -1
  91. package/dist/components/switch.d.ts +1 -1
  92. package/dist/components/textarea.cjs +1 -1
  93. package/dist/components/textarea.d.cts +1 -1
  94. package/dist/components/textarea.d.ts +1 -1
  95. package/dist/components/textarea.js +1 -1
  96. package/dist/components/theme.cjs +18 -6
  97. package/dist/components/theme.cjs.map +1 -1
  98. package/dist/components/theme.d.cts +16 -2
  99. package/dist/components/theme.d.ts +16 -2
  100. package/dist/components/theme.js +19 -7
  101. package/dist/components/theme.js.map +1 -1
  102. package/dist/{generate-radix-colors-Bw-RYaV4.d.ts → generate-radix-colors-BY4Wjsm7.d.cts} +4 -2
  103. package/dist/{generate-radix-colors-Bw-RYaV4.d.cts → generate-radix-colors-BY4Wjsm7.d.ts} +4 -2
  104. package/dist/icons/material-symbol.cjs +1 -1
  105. package/dist/icons/material-symbol.cjs.map +1 -1
  106. package/dist/icons/material-symbol.js +1 -1
  107. package/dist/icons/material-symbol.js.map +1 -1
  108. package/dist/lib/accent-theme.cjs +367 -16
  109. package/dist/lib/accent-theme.cjs.map +1 -1
  110. package/dist/lib/accent-theme.d.cts +75 -6
  111. package/dist/lib/accent-theme.d.ts +75 -6
  112. package/dist/lib/accent-theme.js +365 -18
  113. package/dist/lib/accent-theme.js.map +1 -1
  114. package/dist/lib/container-chrome.cjs +9 -7
  115. package/dist/lib/container-chrome.cjs.map +1 -1
  116. package/dist/lib/container-chrome.d.cts +4 -3
  117. package/dist/lib/container-chrome.d.ts +4 -3
  118. package/dist/lib/container-chrome.js +9 -8
  119. package/dist/lib/container-chrome.js.map +1 -1
  120. package/dist/lib/generate-radix-colors.cjs +7 -0
  121. package/dist/lib/generate-radix-colors.cjs.map +1 -1
  122. package/dist/lib/generate-radix-colors.d.cts +2 -2
  123. package/dist/lib/generate-radix-colors.d.ts +2 -2
  124. package/dist/lib/generate-radix-colors.js +6 -1
  125. package/dist/lib/generate-radix-colors.js.map +1 -1
  126. package/dist/lib/input-box-variants.d.cts +1 -1
  127. package/dist/lib/input-box-variants.d.ts +1 -1
  128. package/dist/lib/theme-types.cjs +19 -0
  129. package/dist/lib/theme-types.cjs.map +1 -0
  130. package/dist/lib/theme-types.d.cts +2 -2
  131. package/dist/lib/theme-types.d.ts +2 -2
  132. package/dist/lib/theme-types.js +17 -1
  133. package/dist/lib/theme-types.js.map +1 -0
  134. package/dist/styles/material-symbol.css +3 -0
  135. package/dist/styles/spark-default.css +148 -0
  136. package/dist/styles/utilities.css +9 -8
  137. package/dist/{theme-types-1yB0d8mp.d.ts → theme-types-Bn5E15-P.d.cts} +12 -3
  138. package/dist/{theme-types-1yB0d8mp.d.cts → theme-types-Bn5E15-P.d.ts} +12 -3
  139. package/package.json +113 -55
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const require_chunk = require("../chunk-DY84GLCh.cjs");
4
4
  const require_lib_generate_radix_colors = require("./generate-radix-colors.cjs");
5
+ const require_lib_theme_types = require("./theme-types.cjs");
5
6
  let _radix_ui_colors = require("@radix-ui/colors");
6
7
  _radix_ui_colors = require_chunk.__toESM(_radix_ui_colors, 1);
7
8
  let colorjs_io = require("colorjs.io");
@@ -51,22 +52,45 @@ function appearanceForBackground(background) {
51
52
  /** deltaE2000 ≈ 2 is the just-noticeable bar — a good solve lands far inside it. */
52
53
  const ALPHA_SOLVE_TOLERANCE = 2;
53
54
  /**
54
- * Replace alpha steps the solver could not actually solve with their opaque solid.
55
+ * OKLCH chroma below which a color reads as neutral rather than tinted — shared by every
56
+ * "is this canvas/accent actually chromatic" check (getMatchingNeutral, resolveAutoNeutral,
57
+ * accentScaleForCanvas).
58
+ */
59
+ const CHROMATIC_THRESHOLD = .04;
60
+ /**
61
+ * An alpha step past this needs to cover the canvas almost completely to reproduce its
62
+ * target — at that point it isn't a tint anymore, and the opaque solid is both cheaper and
63
+ * more predictable. Only applied to the accent ramp's canvas-tinted low steps (gap #12a,
64
+ * ask 2 — specifically the steps `tintLowStepsFromCanvas` retargets; steps 6-12 are by-design
65
+ * high-alpha ink/solid tokens even on plain white — capping *those* would be a regression, not
66
+ * a fix). Same shape as the deltaE check below, widened from "could not solve" to "solved, but
67
+ * not usefully".
68
+ */
69
+ const ALPHA_CAP = .5;
70
+ /**
71
+ * Replace alpha steps the solver could not actually solve — or, within `capAlphaSteps` leading
72
+ * steps, solved only at a near-opaque alpha — with their opaque solid.
55
73
  *
56
74
  * Each `aN` is solved so that compositing it over the background reproduces step N
57
75
  * (`target = bg·(1-α) + fg·α`). From a saturated canvas a desaturated ink target is
58
76
  * unreachable within alpha bounds, and Radix's solver **degrades silently** rather than
59
77
  * failing — on `#FFD400` it returns `#0000ff39` for slate a11, which composites to
60
- * 1.66:1 body text where the solid step is a correct 9.03:1.
78
+ * 1.66:1 body text where the solid step is a correct 9.03:1. Separately, even a solve that
79
+ * *succeeds* can need alpha past `ALPHA_CAP` to get there — technically correct only over
80
+ * that exact background, and no longer a usable "tint" anywhere else (gap #12a, ask 2).
81
+ *
82
+ * `space` must match whatever space `alpha` was actually solved in — `getAlphaColorP3` solves
83
+ * per-channel in P3, and verifying that against an sRGB composite rejects perfectly good
84
+ * solves (they were never trying to reproduce the sRGB composite in the first place).
61
85
  *
62
- * So verify the solve and fall back to the opaque value where it doesn't hold. Only ever
63
- * called for a custom background; on white/black every step solves exactly (deltaE ~0),
86
+ * So verify the solve and fall back to the opaque value where either check fails. Only ever
87
+ * called for a custom background; on white/black every step solves exactly at low alpha,
64
88
  * so the default output — and portal parity — is untouched either way.
65
89
  */
66
- function opaqueWhereUnsolvable(background, solid, alpha) {
90
+ function opaqueWhereUnsolvable(background, solid, alpha, capAlphaSteps = 0, space = "srgb") {
67
91
  let bg;
68
92
  try {
69
- bg = new colorjs_io.default(background).to("srgb").coords;
93
+ bg = new colorjs_io.default(background).to(space).coords;
70
94
  } catch {
71
95
  return alpha;
72
96
  }
@@ -75,9 +99,10 @@ function opaqueWhereUnsolvable(background, solid, alpha) {
75
99
  if (!target) return step;
76
100
  try {
77
101
  const ink = new colorjs_io.default(step);
78
- const fg = ink.to("srgb").coords;
102
+ const fg = ink.to(space).coords;
79
103
  const a = ink.alpha;
80
- return new colorjs_io.default("srgb", [
104
+ if (i < capAlphaSteps && a > ALPHA_CAP) return target;
105
+ return new colorjs_io.default(space, [
81
106
  0,
82
107
  1,
83
108
  2
@@ -87,21 +112,139 @@ function opaqueWhereUnsolvable(background, solid, alpha) {
87
112
  }
88
113
  });
89
114
  }
115
+ /**
116
+ * Weight (0–1) each of the accent scale's first 5 steps is pulled toward the canvas's own
117
+ * hue/chroma; step 6 and up (borders, solid, text) stay the true accent untouched.
118
+ */
119
+ const CANVAS_TINT_WEIGHTS = [
120
+ 1,
121
+ .8,
122
+ .6,
123
+ .4,
124
+ .2
125
+ ];
126
+ /**
127
+ * Pull the accent scale's low steps toward the canvas's own color (gap #12a, ask 1): Radix's
128
+ * generator sets every step's hue/chroma from the accent itself and only re-curves lightness
129
+ * against the background, so on a chromatic canvas the low steps (app/component background
130
+ * territory) stay a flat wash of the accent instead of a tint of the page — right where the
131
+ * now-canvas-tinted neutral scale (gap #12) visibly disagrees with it. Step 9 (the solid,
132
+ * brand-defining step) and everything above step 5 is left exactly as Radix generated it.
133
+ *
134
+ * Mixed in OKLab (rectangular a/b), not OKLCH (polar hue + chroma): a circular hue
135
+ * interpolation between two far-apart hues (a blue accent on a yellow-green canvas, say) has
136
+ * to sweep through whatever unrelated hues sit on the "shorter" arc between them — a vivid
137
+ * green/teal/cyan that belongs to neither endpoint. Linearly mixing a/b instead moves along
138
+ * the straight line between the two points, which naturally desaturates toward neutral as the
139
+ * two colors pull in more different directions, rather than swinging through a third, unrelated
140
+ * hue. Lightness (shared between OKLab and OKLCH) is untouched either way — Radix's own
141
+ * background-aware curve already owns it.
142
+ *
143
+ * Takes/returns `Color` objects rather than strings so callers can reuse the same tinted
144
+ * coordinates for both the sRGB and wide-gamut representations without a lossy round trip.
145
+ */
146
+ function tintLowStepsFromCanvas(solidScale, bg) {
147
+ const [, bgA, bgB] = bg.to("oklab").coords;
148
+ return solidScale.map((step, i) => {
149
+ const weight = CANVAS_TINT_WEIGHTS[i];
150
+ if (!weight) return step;
151
+ const [lightness, a, b] = step.to("oklab").coords;
152
+ return new colorjs_io.default("oklab", [
153
+ lightness ?? 0,
154
+ (a ?? 0) * (1 - weight) + (bgA ?? 0) * weight,
155
+ (b ?? 0) * (1 - weight) + (bgB ?? 0) * weight
156
+ ]);
157
+ });
158
+ }
159
+ /** Mirrors generate-radix-colors.tsx's private toOklchString (not exported by the vendored file). */
160
+ function toOklchWideGamutString(color) {
161
+ const L = +((color.coords[0] ?? 0) * 100).toFixed(1);
162
+ return color.to("oklch").toString({ precision: 4 }).replace(/(\S+)(.+)/, `oklch(${L}%$2`);
163
+ }
164
+ /** Same fixed alpha the vendored generator uses for `accentSurface` — a stylistic choice, not a solve. */
165
+ function surfaceAlphaFor(appearance) {
166
+ return appearance === "light" ? .8 : .5;
167
+ }
168
+ /**
169
+ * Canvas-tinted low steps for the accent scale, or the untouched scale (and `tinted: false`)
170
+ * when the canvas isn't chromatic enough to bother (gap #12a, ask 1). Only indices
171
+ * `0..CANVAS_TINT_WEIGHTS.length-1` are ever replaced — steps 6-12 come back byte-identical to
172
+ * what Radix generated, in both representations (no lossy sRGB round trip for the untouched
173
+ * wide-gamut steps).
174
+ *
175
+ * `tinted` tells the caller whether to scope `opaqueWhereUnsolvable`'s alpha cap in at all —
176
+ * the cap only makes sense for steps this function actually retargeted; a caller that applied
177
+ * it unconditionally on any custom `background` would wrongly force perfectly fine low-alpha
178
+ * steps opaque on an *achromatic* custom background (an explicit `#000000`/`#f5f5f5`, say),
179
+ * where nothing here ever ran.
180
+ *
181
+ * Re-solves alpha for the tinted indices too: the generator's own `accentScaleAlpha` was
182
+ * solved against the *pre-tint* solid, so once that solid moves the old alpha value no longer
183
+ * reproduces it — left alone, every tinted step would fail `opaqueWhereUnsolvable`'s deltaE
184
+ * check and go opaque, defeating the point (the `soft`-card fill this exists for would never
185
+ * actually be translucent on a tinted canvas). Reuses Radix's own alpha solver so the result
186
+ * matches what the generator itself would have produced had it been given this target directly.
187
+ *
188
+ * `accentSurface` (the `surface` card variant's fill) gets the same treatment: the generator
189
+ * computes it as a fixed-alpha composite of step 2 — index 1, always tinted — over the
190
+ * background, so recomputing it from the *tinted* step 2 at that same fixed alpha (0.8 light,
191
+ * 0.5 dark; not a solve) keeps it in sync with `soft`'s fill instead of staying the old,
192
+ * untinted wash (review-findings.md #13).
193
+ */
194
+ function accentScaleForCanvas(scale, background, appearance) {
195
+ try {
196
+ const bg = new colorjs_io.default(background).to("oklch");
197
+ const [, bgChroma] = bg.coords;
198
+ if (!bgChroma || bgChroma < CHROMATIC_THRESHOLD) return {
199
+ ...scale,
200
+ tinted: false
201
+ };
202
+ const tinted = tintLowStepsFromCanvas(scale.accentScale.map((hex) => new colorjs_io.default(hex).to("oklch")), bg);
203
+ const accentScale = scale.accentScale.map((original, i) => CANVAS_TINT_WEIGHTS[i] ? tinted[i].to("srgb").toString({ format: "hex" }) : original);
204
+ const accentScaleWideGamut = scale.accentScaleWideGamut.map((original, i) => CANVAS_TINT_WEIGHTS[i] ? toOklchWideGamutString(tinted[i]) : original);
205
+ const accentScaleAlpha = scale.accentScaleAlpha.map((original, i) => CANVAS_TINT_WEIGHTS[i] ? require_lib_generate_radix_colors.getAlphaColorSrgb(accentScale[i], scale.background) : original);
206
+ const accentScaleAlphaWideGamut = scale.accentScaleAlphaWideGamut.map((original, i) => CANVAS_TINT_WEIGHTS[i] ? require_lib_generate_radix_colors.getAlphaColorP3(accentScaleWideGamut[i], scale.background) : original);
207
+ const surfaceAlpha = surfaceAlphaFor(appearance);
208
+ return {
209
+ accentScale,
210
+ accentScaleWideGamut,
211
+ accentScaleAlpha,
212
+ accentScaleAlphaWideGamut,
213
+ accentSurface: require_lib_generate_radix_colors.getAlphaColorSrgb(accentScale[1], scale.background, surfaceAlpha),
214
+ accentSurfaceWideGamut: require_lib_generate_radix_colors.getAlphaColorP3(accentScaleWideGamut[1], scale.background, surfaceAlpha),
215
+ tinted: true
216
+ };
217
+ } catch {
218
+ return {
219
+ ...scale,
220
+ tinted: false
221
+ };
222
+ }
223
+ }
90
224
  /** Generate the Radix-style accent scale for one appearance from a single accent color. */
91
225
  function generateAccentScale(accent, appearance, opts) {
92
226
  const background = opts?.background ?? DEFAULTS[appearance].background;
93
227
  const scale = require_lib_generate_radix_colors.generateRadixColors({
94
228
  appearance,
95
229
  accent,
96
- gray: opts?.neutral ? NEUTRAL_BASE[opts.neutral] : DEFAULTS[appearance].gray,
230
+ gray: opts?.neutral ? require_lib_theme_types.isSparkNeutralFamily(opts.neutral) ? NEUTRAL_BASE[opts.neutral] : opts.neutral : DEFAULTS[appearance].gray,
97
231
  background
98
232
  });
233
+ const { accentScale, accentScaleWideGamut, accentScaleAlpha: canvasAccentScaleAlpha, accentScaleAlphaWideGamut: canvasAccentScaleAlphaWideGamut, accentSurface, accentSurfaceWideGamut, tinted } = opts?.background ? accentScaleForCanvas(scale, background, appearance) : {
234
+ ...scale,
235
+ tinted: false
236
+ };
237
+ const capAlphaSteps = tinted ? CANVAS_TINT_WEIGHTS.length : 0;
99
238
  const solved = opts?.background ? {
100
239
  ...scale,
101
- accentScaleAlpha: opaqueWhereUnsolvable(background, scale.accentScale, scale.accentScaleAlpha),
102
- accentScaleAlphaWideGamut: opaqueWhereUnsolvable(background, scale.accentScaleWideGamut, scale.accentScaleAlphaWideGamut),
240
+ accentScale,
241
+ accentScaleWideGamut,
242
+ accentSurface,
243
+ accentSurfaceWideGamut,
244
+ accentScaleAlpha: opaqueWhereUnsolvable(background, accentScale, canvasAccentScaleAlpha, capAlphaSteps),
245
+ accentScaleAlphaWideGamut: opaqueWhereUnsolvable(background, accentScaleWideGamut, canvasAccentScaleAlphaWideGamut, capAlphaSteps, "p3"),
103
246
  grayScaleAlpha: opaqueWhereUnsolvable(background, scale.grayScale, scale.grayScaleAlpha),
104
- grayScaleAlphaWideGamut: opaqueWhereUnsolvable(background, scale.grayScaleWideGamut, scale.grayScaleAlphaWideGamut)
247
+ grayScaleAlphaWideGamut: opaqueWhereUnsolvable(background, scale.grayScaleWideGamut, scale.grayScaleAlphaWideGamut, 0, "p3")
105
248
  } : scale;
106
249
  return {
107
250
  ...solved,
@@ -119,15 +262,28 @@ function readableContrast(step9Hex, native, min) {
119
262
  Math.max(.08 * (chroma ?? 0), .04),
120
263
  hue ?? 0
121
264
  ]).to("srgb").toString({ format: "hex" });
265
+ const ratioOf = (hex) => new colorjs_io.default(hex).contrast(step9, "WCAG21");
122
266
  const candidates = [
123
267
  native,
124
268
  tinted(.25),
125
269
  tinted(.15)
126
270
  ];
127
- const ratios = candidates.map((c) => new colorjs_io.default(c).contrast(step9, "WCAG21"));
128
- const passing = ratios.findIndex((r) => r >= min);
271
+ const passing = candidates.map(ratioOf).findIndex((r) => r >= min);
129
272
  if (passing !== -1) return candidates[passing];
130
- return candidates[ratios.indexOf(Math.max(...ratios))];
273
+ const lightness9 = step9.to("oklch").coords[0] ?? .5;
274
+ const toward = ratioOf(tinted(0)) >= ratioOf(tinted(1)) ? 0 : 1;
275
+ let fails = lightness9;
276
+ let passes = toward;
277
+ for (let i = 0; i < 16; i++) {
278
+ const mid = (fails + passes) / 2;
279
+ if (ratioOf(tinted(mid)) >= min) passes = mid;
280
+ else fails = mid;
281
+ }
282
+ const found = tinted(passes);
283
+ if (ratioOf(found) >= min) return found;
284
+ const all = [...candidates, tinted(toward)];
285
+ const allRatios = all.map(ratioOf);
286
+ return all[allRatios.indexOf(Math.max(...allRatios))];
131
287
  }
132
288
  /** sRGB `--spark-*` declarations for a generated accent scale. */
133
289
  function accentSparkVars(scale) {
@@ -158,7 +314,7 @@ function getMatchingNeutral(accent) {
158
314
  if (byName) return byName;
159
315
  try {
160
316
  const [, chroma, hueRaw] = new colorjs_io.default(accent).to("oklch").coords;
161
- if (!chroma || chroma < .04) return "gray";
317
+ if (!chroma || chroma < CHROMATIC_THRESHOLD) return "gray";
162
318
  const hue = ((hueRaw ?? 0) % 360 + 360) % 360;
163
319
  if (hue >= 300 || hue < 35) return "mauve";
164
320
  if (hue < 115) return "sand";
@@ -169,6 +325,180 @@ function getMatchingNeutral(accent) {
169
325
  return "slate";
170
326
  }
171
327
  }
328
+ /**
329
+ * Fill steps (Radix roles 1-5), measured from the surface toward the *scrim* — the pole
330
+ * opposite the ink. Magnitudes are unchanged from the original ink-directed ramp; only
331
+ * the direction flips, so a fill and the text on it move apart instead of together.
332
+ */
333
+ const INVERTED_FILL_RAMP = [
334
+ .06,
335
+ .12,
336
+ .2,
337
+ .3,
338
+ .4
339
+ ];
340
+ /** Strongest border step (8, the hover/active edge). 6 is solved per accent; 7 splits them. */
341
+ const INVERTED_BORDER_TOP = .82;
342
+ /**
343
+ * Ink steps (9-12), measured from the surface toward the ink. Step 9 (the solid fill)
344
+ * lands on the ink itself; 10 eases back for hover; 11-12 are full-strength ink.
345
+ */
346
+ const INVERTED_INK_RAMP = [
347
+ 1,
348
+ .88,
349
+ 1,
350
+ 1
351
+ ];
352
+ /**
353
+ * Lowest alpha at which the ink, laid over the surface, is 3:1 distinguishable from it
354
+ * (WCAG 1.4.11) — the floor for a border that has to read as an edge at all.
355
+ *
356
+ * Solved rather than hardcoded because it moves with the accent (0.57-0.72 across the
357
+ * curated set). Contrast against the surface rises monotonically with `t`, so bisection
358
+ * finds the boundary.
359
+ *
360
+ * Targets 1% above the floor rather than landing on it: the step ships as `#rrggbbaa`, and
361
+ * 8-bit rounding on any of the four channels can drop an exact solve back under 3:1.
362
+ */
363
+ function solveBorderFloor(surface, ink) {
364
+ const target = MIN_GRAPHIC_CONTRAST * 1.01;
365
+ const ratioAt = (t) => new colorjs_io.default(colorjs_io.default.mix(surface, ink, t, { space: "srgb" })).contrast(surface, "WCAG21");
366
+ let fails = 0;
367
+ let passes = 1;
368
+ for (let i = 0; i < 16; i++) {
369
+ const mid = (fails + passes) / 2;
370
+ if (ratioAt(mid) >= target) passes = mid;
371
+ else fails = mid;
372
+ }
373
+ return passes;
374
+ }
375
+ /** OKLCH string in the wide-gamut format the generator emits (`oklch(96.1% 0.0155 254.2)`). */
376
+ function oklchString(color) {
377
+ const oklch = color.to("oklch");
378
+ const lightness = +((oklch.coords[0] ?? 0) * 100).toFixed(1);
379
+ return oklch.toString({ precision: 4 }).replace(/(\S+)(.+)/, `oklch(${lightness}%$2`);
380
+ }
381
+ /**
382
+ * Re-cast a scale for use ON its own solid (step-9) fill — the `inverted` knob.
383
+ *
384
+ * A solid accent control on a solid accent surface is invisible: both paint `--spark-9`.
385
+ * Inverting swaps that pair — step 9 and its contrast ink trade places — so the solid fill
386
+ * and its text are contrast-safe **by construction, for any accent**: `accentContrast` is
387
+ * already the WCAG-checked readable ink for step 9 (`readableContrast`).
388
+ *
389
+ * The non-solid styles need a second idea, because the ink is also the text colour. A fill
390
+ * blended surface→ink moves the background *toward* the foreground, so ink-on-fill contrast
391
+ * falls monotonically with `t` — and measured across the curated set, the highest `t` that
392
+ * still clears 4.5:1 (0.174) sits below the lowest `t` that is 3:1 distinguishable from the
393
+ * surface (0.543). On that line no fill can be both legible and visible, on any accent.
394
+ *
395
+ * So fills blend the other way, toward the **scrim** — the lightness pole opposite the ink,
396
+ * carrying the surface's own hue. Contrast against the ink now *rises* with `t`, which puts
397
+ * every non-solid style at or above the solid pair's ratio for free. Borders keep stepping
398
+ * toward the ink (a scrim-side border caps at ~2:1 on light accents like orange, so it can
399
+ * never clear 1.4.11), starting at the solved 3:1 floor.
400
+ *
401
+ * Appearance-stable on purpose. The generator pins step 9 to the accent itself, so the
402
+ * surface is identical in light and dark — which makes the whole inverted scale identical
403
+ * too, and `[data-inverted]` needs no `.dark` twin.
404
+ *
405
+ * Two things this is deliberately NOT:
406
+ * - **A positional mirror** (`1↔12`). It leaves the solid fill only 2.04:1 from the card in
407
+ * dark and 2.23:1 on a light accent like orange — under the 3:1 graphical floor, i.e. still
408
+ * invisible, which is the bug this exists to fix.
409
+ * - **A regenerated on-surface ramp.** Feeding the surface back in as `background` produces
410
+ * off-hue overlays (a warm wash on a blue card) and ink at 3.35:1.
411
+ *
412
+ * Values are literals, not `var(--spark-N)` aliases: a scale can't re-point to itself
413
+ * (`--spark-1: var(--spark-12)` next to `--spark-12: var(--spark-1)` is a cycle, and CSS
414
+ * discards both).
415
+ */
416
+ function invertAccentScale(scale) {
417
+ const surface = scale.accentScale[8];
418
+ const ink = scale.accentContrast;
419
+ const surfaceColor = new colorjs_io.default(surface);
420
+ const inkColor = new colorjs_io.default(ink);
421
+ const [surfaceLightness, surfaceChroma, surfaceHue] = surfaceColor.to("oklch").coords;
422
+ const scrimColor = new colorjs_io.default("oklch", [
423
+ (inkColor.to("oklch").coords[0] ?? 0) > (surfaceLightness ?? 0) ? .12 : .98,
424
+ Math.max(.08 * (surfaceChroma ?? 0), .02),
425
+ surfaceHue ?? 0
426
+ ]);
427
+ const borderFloor = solveBorderFloor(surfaceColor, inkColor);
428
+ const ramp = [
429
+ ...INVERTED_FILL_RAMP.map((t) => ({
430
+ toward: scrimColor,
431
+ t
432
+ })),
433
+ {
434
+ toward: inkColor,
435
+ t: borderFloor
436
+ },
437
+ {
438
+ toward: inkColor,
439
+ t: (borderFloor + INVERTED_BORDER_TOP) / 2
440
+ },
441
+ {
442
+ toward: inkColor,
443
+ t: INVERTED_BORDER_TOP
444
+ },
445
+ ...INVERTED_INK_RAMP.map((t) => ({
446
+ toward: inkColor,
447
+ t
448
+ }))
449
+ ];
450
+ const blend = ({ toward, t }) => new colorjs_io.default(colorjs_io.default.mix(surfaceColor, toward, t, { space: "srgb" }));
451
+ const steps = ramp.map((s) => blend(s).to("srgb").toString({ format: "hex" }));
452
+ const stepsWideGamut = ramp.map((s) => oklchString(blend(s)));
453
+ const alpha = ramp.map(({ toward, t }) => {
454
+ const translucent = toward.clone();
455
+ translucent.alpha = t;
456
+ return translucent.to("srgb").toString({ format: "hex" });
457
+ });
458
+ const alphaWideGamut = ramp.map(({ toward, t }) => {
459
+ const translucent = toward.clone().to("p3");
460
+ translucent.alpha = t;
461
+ return translucent.toString({ precision: 4 }).replace("color(p3 ", "color(display-p3 ");
462
+ });
463
+ return {
464
+ ...scale,
465
+ accentScale: steps,
466
+ accentScaleWideGamut: stepsWideGamut,
467
+ accentScaleAlpha: alpha,
468
+ accentScaleAlphaWideGamut: alphaWideGamut,
469
+ accentContrast: surface,
470
+ accentOnSolid: surface,
471
+ accentSurface: steps[1],
472
+ accentSurfaceWideGamut: stepsWideGamut[1]
473
+ };
474
+ }
475
+ /**
476
+ * `neutralColor="auto"` resolution: prefer the canvas's own tint when it has one — a
477
+ * chromatic `background` seeds a visibly warmer/cooler ramp than bucketing it into one
478
+ * of the 6 named families would (see gap #12). Falls back to accent-matched bucketing
479
+ * when there's no tinted canvas to pull from.
480
+ */
481
+ function resolveAutoNeutral(accent, background) {
482
+ if (background) try {
483
+ const [, chroma] = new colorjs_io.default(background).to("oklch").coords;
484
+ if (chroma && chroma >= CHROMATIC_THRESHOLD) return background;
485
+ } catch {}
486
+ return getMatchingNeutral(accent);
487
+ }
488
+ /**
489
+ * Whether a string parses as a color at all — for validating a custom `neutralColor`
490
+ * before it reaches the generator. An unparseable seed (e.g. a typo'd family name like
491
+ * `"slat"`) would otherwise throw deep inside `generateRadixColors`, taking the whole
492
+ * accent scale down with it via the caller's shared try/catch.
493
+ */
494
+ function isParsableColor(value) {
495
+ try {
496
+ new colorjs_io.default(value).to("oklch");
497
+ return true;
498
+ } catch {
499
+ return false;
500
+ }
501
+ }
172
502
  /** The 27 tokens every scale family declares, under an arbitrary prefix. */
173
503
  function scaleVars(prefix, steps, alpha, contrast, onSolid, surface) {
174
504
  return [
@@ -189,6 +519,23 @@ function sparkVars(steps, alpha, contrast, onSolid, surface, background) {
189
519
  ];
190
520
  }
191
521
  /**
522
+ * The same `--neutral-*` tokens `neutralSparkVars` emits, pointed back at a static Radix
523
+ * family instead of a generated scale — the counterpart for an appearance that has *no*
524
+ * custom canvas of its own.
525
+ *
526
+ * Ambient (no explicit `appearance`) mode emits both appearances' scales into one unlayered
527
+ * `<style>`, but a custom canvas only regenerates neutrals for the appearance it belongs to.
528
+ * Left undeclared, the other block doesn't fall back — nothing resets a custom property, so
529
+ * its sibling's canvas-tinted values simply stay in effect there, and being unlayered they
530
+ * outrank `[data-neutral-color]`'s own `@layer base` mapping too. Declaring the same token
531
+ * set closes that. `var(--<family>-*)` is itself appearance-aware (Radix ships the dark
532
+ * scales under `.dark`), so one declaration resolves correctly wherever the block applies.
533
+ */
534
+ function neutralFamilyVars(family) {
535
+ const ref = (suffix) => `var(--${family}-${suffix})`;
536
+ return scaleVars("neutral", Array.from({ length: 12 }, (_, i) => ref(`${i + 1}`)), Array.from({ length: 12 }, (_, i) => ref(`a${i + 1}`)), "var(--neutral-12)", "#fff", "var(--neutral-2)");
537
+ }
538
+ /**
192
539
  * `--neutral-*` declarations from the gray scale the generator produces alongside the
193
540
  * accent — normally discarded, because `[data-neutral-color]` points `--neutral-*` at a
194
541
  * static Radix family. Worth consuming only when the canvas is custom: the static
@@ -212,7 +559,11 @@ exports.accentSparkVarsP3 = accentSparkVarsP3;
212
559
  exports.appearanceForBackground = appearanceForBackground;
213
560
  exports.generateAccentScale = generateAccentScale;
214
561
  exports.getMatchingNeutral = getMatchingNeutral;
562
+ exports.invertAccentScale = invertAccentScale;
563
+ exports.isParsableColor = isParsableColor;
564
+ exports.neutralFamilyVars = neutralFamilyVars;
215
565
  exports.neutralSparkVars = neutralSparkVars;
216
566
  exports.neutralSparkVarsP3 = neutralSparkVarsP3;
567
+ exports.resolveAutoNeutral = resolveAutoNeutral;
217
568
 
218
569
  //# sourceMappingURL=accent-theme.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"accent-theme.cjs","names":["RadixColors","Color","generateRadixColors"],"sources":["../../src/lib/accent-theme.ts"],"sourcesContent":["// Accent is the one runtime knob (colour math, not a CSS-var swap): generate the\n// `--spark-*` scale with the same Radix algorithm live portals use, apply as inline vars.\nimport * as RadixColors from '@radix-ui/colors'\nimport Color from 'colorjs.io'\nimport { generateRadixColors } from './generate-radix-colors'\nimport type { SparkNeutral } from './theme-types'\n\n/** Shape returned by the vendored Radix generator (it doesn't export a named type). */\nexport type GeneratedColors = ReturnType<typeof generateRadixColors>\n\n/**\n * generateAccentScale output: the raw scale, but with two readable foregrounds on the\n * solid (step-9) fill, each meeting a different WCAG bar:\n * - `accentContrast` — 4.5:1 (WCAG 1.4.3), for button/label **text**.\n * - `accentOnSolid` — 3:1 (WCAG 1.4.11), for a **graphical glyph** (the checkbox check).\n * A checkmark is graphical, not text, so it uses the looser-but-correct 3:1 bar: white\n * where it clears 3:1 (most accents, incl. neutral & red — the convention), and a dark\n * accent-tint where white is too weak (e.g. orange). Using the 4.5:1 text contrast for\n * the glyph over-darkened it (a black tick on a red/neutral checkbox).\n */\nexport type SparkAccentScale = GeneratedColors & { accentOnSolid: string }\n\nexport type Appearance = 'light' | 'dark'\n\n/** Curated name → source color. `blue` = Radix blue; the no-attribute default `--spark-*` stays the epilot brand. */\nexport const CURATED_ACCENTS: Record<string, string> = {\n blue: RadixColors.blue.blue9,\n indigo: RadixColors.indigo.indigo9,\n violet: RadixColors.violet.violet9,\n purple: RadixColors.purple.purple9,\n cyan: RadixColors.cyan.cyan9,\n teal: RadixColors.teal.teal9,\n green: RadixColors.green.green9,\n grass: RadixColors.grass.grass9,\n orange: RadixColors.orange.orange9,\n red: RadixColors.red.red9,\n pink: RadixColors.pink.pink9,\n gray: RadixColors.gray.gray9,\n}\n\n/** ECP `DesignUtils` generator inputs, so a generated scale matches live portals. */\nconst DEFAULTS: Record<Appearance, { gray: string; background: string }> = {\n light: { gray: '#333333', background: '#ffffff' },\n // Matches ECP DesignUtils' DEFAULT_DARK_BACKGROUND_COLOR -- the alpha ramp\n // is composited against this, so it must match what's actually painted.\n dark: { gray: '#8b8d98', background: '#000000' },\n}\n\n/** Radix step-9 per neutral family — the generator's `gray` seed, so a regenerated\n neutral lands on the family `[data-neutral-color]` would have selected statically. */\nconst NEUTRAL_BASE: Record<Exclude<SparkNeutral, 'auto'>, string> = {\n slate: RadixColors.slate.slate9,\n mauve: RadixColors.mauve.mauve9,\n gray: RadixColors.gray.gray9,\n sage: RadixColors.sage.sage9,\n olive: RadixColors.olive.olive9,\n sand: RadixColors.sand.sand9,\n}\n\nexport interface GenerateScaleOptions {\n /**\n * Page canvas the scale is composited against. Drives the lightness curve of all 12\n * steps AND every alpha step (each is solved as `target = bg·(1-α) + fg·α`), so it\n * must match what's actually painted or the alpha ramp renders wrong.\n */\n background?: string\n /** Neutral family to regenerate against `background`. Pass a resolved family, not `'auto'`. */\n neutral?: Exclude<SparkNeutral, 'auto'>\n}\n\n/** Which appearance a canvas belongs to, by OKLCH lightness. */\nexport function appearanceForBackground(background: string): Appearance {\n const [lightness] = new Color(background).to('oklch').coords\n return (lightness ?? 1) >= 0.5 ? 'light' : 'dark'\n}\n\n/** deltaE2000 ≈ 2 is the just-noticeable bar — a good solve lands far inside it. */\nconst ALPHA_SOLVE_TOLERANCE = 2\n\n/**\n * Replace alpha steps the solver could not actually solve with their opaque solid.\n *\n * Each `aN` is solved so that compositing it over the background reproduces step N\n * (`target = bg·(1-α) + fg·α`). From a saturated canvas a desaturated ink target is\n * unreachable within alpha bounds, and Radix's solver **degrades silently** rather than\n * failing — on `#FFD400` it returns `#0000ff39` for slate a11, which composites to\n * 1.66:1 body text where the solid step is a correct 9.03:1.\n *\n * So verify the solve and fall back to the opaque value where it doesn't hold. Only ever\n * called for a custom background; on white/black every step solves exactly (deltaE ~0),\n * so the default output — and portal parity — is untouched either way.\n */\nfunction opaqueWhereUnsolvable(\n background: string,\n solid: string[],\n alpha: string[],\n): string[] {\n let bg: number[]\n try {\n bg = new Color(background).to('srgb').coords\n } catch {\n return alpha\n }\n return alpha.map((step, i) => {\n const target = solid[i]\n if (!target) return step\n try {\n const ink = new Color(step)\n const fg = ink.to('srgb').coords\n const a = ink.alpha as number\n const composited = new Color(\n 'srgb',\n [0, 1, 2].map((k) => bg[k]! * (1 - a) + fg[k]! * a) as [\n number,\n number,\n number,\n ],\n )\n return composited.deltaE(new Color(target), '2000') <=\n ALPHA_SOLVE_TOLERANCE\n ? step\n : target\n } catch {\n return target\n }\n })\n}\n\n/** Generate the Radix-style accent scale for one appearance from a single accent color. */\nexport function generateAccentScale(\n accent: string,\n appearance: Appearance,\n opts?: GenerateScaleOptions,\n): SparkAccentScale {\n const background = opts?.background ?? DEFAULTS[appearance].background\n const scale = generateRadixColors({\n appearance,\n accent,\n gray: opts?.neutral\n ? NEUTRAL_BASE[opts.neutral]\n : DEFAULTS[appearance].gray,\n background,\n })\n const solved = opts?.background\n ? {\n ...scale,\n accentScaleAlpha: opaqueWhereUnsolvable(\n background,\n scale.accentScale,\n scale.accentScaleAlpha,\n ) as typeof scale.accentScaleAlpha,\n accentScaleAlphaWideGamut: opaqueWhereUnsolvable(\n background,\n scale.accentScaleWideGamut,\n scale.accentScaleAlphaWideGamut,\n ) as typeof scale.accentScaleAlphaWideGamut,\n grayScaleAlpha: opaqueWhereUnsolvable(\n background,\n scale.grayScale,\n scale.grayScaleAlpha,\n ) as typeof scale.grayScaleAlpha,\n grayScaleAlphaWideGamut: opaqueWhereUnsolvable(\n background,\n scale.grayScaleWideGamut,\n scale.grayScaleAlphaWideGamut,\n ) as typeof scale.grayScaleAlphaWideGamut,\n }\n : scale\n // accentContrast → 4.5:1 (button text); accentOnSolid → 3:1 (graphical glyph,\n // see SparkAccentScale). Same picker, different WCAG threshold.\n return {\n ...solved,\n accentContrast: readableContrast(\n solved.accentScale[8]!,\n solved.accentContrast,\n MIN_TEXT_CONTRAST,\n ),\n accentOnSolid: readableContrast(\n solved.accentScale[8]!,\n solved.accentContrast,\n MIN_GRAPHIC_CONTRAST,\n ),\n }\n}\n\nconst MIN_TEXT_CONTRAST = 4.5 // WCAG 1.4.3 — button / label text\nconst MIN_GRAPHIC_CONTRAST = 3 // WCAG 1.4.11 — a check / indicator glyph on a solid fill\n\n// Pick the readable foreground on step 9: prefer the generator's native contrast\n// (usually white), else fall to progressively darker accent-tinted inks until one\n// clears `min`. Text uses 4.5:1; a graphical glyph (the checkmark) uses 3:1 — which\n// keeps white on most accents (incl. neutral 3.32:1, red 3.91:1) but flips to a dark\n// tint where white is too weak (e.g. orange, 2.97:1). The vendored generator keeps\n// white at APCA Lc >= 40, which can sit below both WCAG thresholds — hence this guard.\nfunction readableContrast(\n step9Hex: string,\n native: string,\n min: number,\n): string {\n const step9 = new Color(step9Hex)\n const [, chroma, hue] = step9.to('oklch').coords\n const tinted = (lightness: number) =>\n new Color('oklch', [\n lightness,\n Math.max(0.08 * (chroma ?? 0), 0.04),\n hue ?? 0,\n ])\n .to('srgb')\n .toString({ format: 'hex' })\n const candidates = [native, tinted(0.25), tinted(0.15)]\n const ratios = candidates.map((c) => new Color(c).contrast(step9, 'WCAG21'))\n const passing = ratios.findIndex((r) => r >= min)\n if (passing !== -1) return candidates[passing]!\n return candidates[ratios.indexOf(Math.max(...ratios))]!\n}\n\n/** sRGB `--spark-*` declarations for a generated accent scale. */\nexport function accentSparkVars(scale: SparkAccentScale): string[] {\n return sparkVars(\n scale.accentScale,\n scale.accentScaleAlpha,\n scale.accentContrast,\n scale.accentOnSolid,\n scale.accentSurface,\n scale.background,\n )\n}\n\n/** Wide-gamut (P3) `--spark-*` declarations — for a `color-gamut: p3` `@supports` block. */\nexport function accentSparkVarsP3(scale: SparkAccentScale): string[] {\n return sparkVars(\n scale.accentScaleWideGamut,\n scale.accentScaleAlphaWideGamut,\n scale.accentContrast,\n scale.accentOnSolid,\n scale.accentSurfaceWideGamut,\n scale.background,\n )\n}\n\n/** Radix's `grayColor=\"auto\"` pairing (accent family → gray), mapped to our 6 neutrals. */\nconst NEUTRAL_BY_ACCENT: Record<string, Exclude<SparkNeutral, 'auto'>> = {\n blue: 'slate',\n indigo: 'slate',\n cyan: 'slate',\n violet: 'mauve',\n purple: 'mauve',\n pink: 'mauve',\n red: 'mauve',\n teal: 'sage',\n green: 'sage',\n grass: 'olive',\n orange: 'sand',\n gray: 'gray',\n}\n\n/** Radix's \"auto\" gray: curated names use Radix's table; a custom hex is classified by OKLCH hue. */\nexport function getMatchingNeutral(\n accent: string,\n): Exclude<SparkNeutral, 'auto'> {\n const byName = NEUTRAL_BY_ACCENT[accent]\n if (byName) return byName\n try {\n const [, chroma, hueRaw] = new Color(accent).to('oklch').coords\n if (!chroma || chroma < 0.04) return 'gray'\n const hue = (((hueRaw ?? 0) % 360) + 360) % 360\n if (hue >= 300 || hue < 35) return 'mauve'\n if (hue < 115) return 'sand'\n if (hue < 145) return 'olive'\n if (hue < 200) return 'sage'\n return 'slate'\n } catch {\n return 'slate'\n }\n}\n\n/** The 27 tokens every scale family declares, under an arbitrary prefix. */\nfunction scaleVars(\n prefix: string,\n steps: string[],\n alpha: string[],\n contrast: string,\n onSolid: string,\n surface: string,\n): string[] {\n return [\n ...steps.map((c, i) => `--${prefix}-${i + 1}: ${c};`),\n ...alpha.map((c, i) => `--${prefix}-a${i + 1}: ${c};`),\n `--${prefix}-contrast: ${contrast};`,\n `--${prefix}-on-solid: ${onSolid};`,\n `--${prefix}-surface: ${surface};`,\n ]\n}\n\nfunction sparkVars(\n steps: string[],\n alpha: string[],\n contrast: string,\n onSolid: string,\n surface: string,\n background: string,\n): string[] {\n const indicator = steps[8]! // step 9 — the brand step (matches --spark-indicator/-track)\n return [\n ...scaleVars('spark', steps, alpha, contrast, onSolid, surface),\n `--spark-background: ${background};`,\n `--spark-indicator: ${indicator};`,\n `--spark-track: ${indicator};`,\n ]\n}\n\n/**\n * `--neutral-*` declarations from the gray scale the generator produces alongside the\n * accent — normally discarded, because `[data-neutral-color]` points `--neutral-*` at a\n * static Radix family. Worth consuming only when the canvas is custom: the static\n * families' alpha steps are pre-baked against white/black, so on any other background\n * they composite wrong (every gray text/border reads through `--neutral-a*`).\n *\n * `--neutral-surface` is step 2, matching the static families' own convention rather\n * than the generator's translucent `graySurface`.\n */\nexport function neutralSparkVars(scale: SparkAccentScale): string[] {\n return scaleVars(\n 'neutral',\n scale.grayScale,\n scale.grayScaleAlpha,\n readableContrast(scale.grayScale[8]!, '#fff', MIN_TEXT_CONTRAST),\n readableContrast(scale.grayScale[8]!, '#fff', MIN_GRAPHIC_CONTRAST),\n scale.grayScale[1]!,\n )\n}\n\n/** Wide-gamut (P3) twin of `neutralSparkVars`. */\nexport function neutralSparkVarsP3(scale: SparkAccentScale): string[] {\n return scaleVars(\n 'neutral',\n scale.grayScaleWideGamut,\n scale.grayScaleAlphaWideGamut,\n readableContrast(scale.grayScale[8]!, '#fff', MIN_TEXT_CONTRAST),\n readableContrast(scale.grayScale[8]!, '#fff', MIN_GRAPHIC_CONTRAST),\n scale.grayScaleWideGamut[1]!,\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"accent-theme.cjs","names":["RadixColors","Color","getAlphaColorSrgb","getAlphaColorP3","generateRadixColors","isSparkNeutralFamily"],"sources":["../../src/lib/accent-theme.ts"],"sourcesContent":["// Accent is the one runtime knob (colour math, not a CSS-var swap): generate the\n// `--spark-*` scale with the same Radix algorithm live portals use, apply as inline vars.\nimport * as RadixColors from '@radix-ui/colors'\nimport Color from 'colorjs.io'\nimport {\n generateRadixColors,\n getAlphaColorP3,\n getAlphaColorSrgb,\n} from './generate-radix-colors'\nimport {\n isSparkNeutralFamily,\n type SparkNeutral,\n type SparkNeutralFamily,\n} from './theme-types'\n\n/** Shape returned by the vendored Radix generator (it doesn't export a named type). */\nexport type GeneratedColors = ReturnType<typeof generateRadixColors>\n\n/**\n * generateAccentScale output: the raw scale, but with two readable foregrounds on the\n * solid (step-9) fill, each meeting a different WCAG bar:\n * - `accentContrast` — 4.5:1 (WCAG 1.4.3), for button/label **text**.\n * - `accentOnSolid` — 3:1 (WCAG 1.4.11), for a **graphical glyph** (the checkbox check).\n * A checkmark is graphical, not text, so it uses the looser-but-correct 3:1 bar: white\n * where it clears 3:1 (most accents, incl. neutral & red — the convention), and a dark\n * accent-tint where white is too weak (e.g. orange). Using the 4.5:1 text contrast for\n * the glyph over-darkened it (a black tick on a red/neutral checkbox).\n */\nexport type SparkAccentScale = GeneratedColors & { accentOnSolid: string }\n\nexport type Appearance = 'light' | 'dark'\n\n/** Curated name → source color. `blue` = Radix blue; the no-attribute default `--spark-*` stays the epilot brand. */\nexport const CURATED_ACCENTS: Record<string, string> = {\n blue: RadixColors.blue.blue9,\n indigo: RadixColors.indigo.indigo9,\n violet: RadixColors.violet.violet9,\n purple: RadixColors.purple.purple9,\n cyan: RadixColors.cyan.cyan9,\n teal: RadixColors.teal.teal9,\n green: RadixColors.green.green9,\n grass: RadixColors.grass.grass9,\n orange: RadixColors.orange.orange9,\n red: RadixColors.red.red9,\n pink: RadixColors.pink.pink9,\n gray: RadixColors.gray.gray9,\n}\n\n/** ECP `DesignUtils` generator inputs, so a generated scale matches live portals. */\nconst DEFAULTS: Record<Appearance, { gray: string; background: string }> = {\n light: { gray: '#333333', background: '#ffffff' },\n // Matches ECP DesignUtils' DEFAULT_DARK_BACKGROUND_COLOR -- the alpha ramp\n // is composited against this, so it must match what's actually painted.\n dark: { gray: '#8b8d98', background: '#000000' },\n}\n\n/** Radix step-9 per neutral family — the generator's `gray` seed, so a regenerated\n neutral lands on the family `[data-neutral-color]` would have selected statically. */\nconst NEUTRAL_BASE: Record<SparkNeutralFamily, string> = {\n slate: RadixColors.slate.slate9,\n mauve: RadixColors.mauve.mauve9,\n gray: RadixColors.gray.gray9,\n sage: RadixColors.sage.sage9,\n olive: RadixColors.olive.olive9,\n sand: RadixColors.sand.sand9,\n}\n\nexport interface GenerateScaleOptions {\n /**\n * Page canvas the scale is composited against. Drives the lightness curve of all 12\n * steps AND every alpha step (each is solved as `target = bg·(1-α) + fg·α`), so it\n * must match what's actually painted or the alpha ramp renders wrong.\n */\n background?: string\n /**\n * Neutral family, or an arbitrary color to seed the neutral scale (regenerated against\n * `background`, same as a named family). Pass a resolved value, not `'auto'` — this\n * option isn't itself typed to exclude it (an open string union can't forbid one\n * literal), but nothing in `<Theme>` ever forwards `'auto'` this far.\n */\n neutral?: SparkNeutral\n}\n\n/** Which appearance a canvas belongs to, by OKLCH lightness. */\nexport function appearanceForBackground(background: string): Appearance {\n const [lightness] = new Color(background).to('oklch').coords\n return (lightness ?? 1) >= 0.5 ? 'light' : 'dark'\n}\n\n/** deltaE2000 ≈ 2 is the just-noticeable bar — a good solve lands far inside it. */\nconst ALPHA_SOLVE_TOLERANCE = 2\n\n/**\n * OKLCH chroma below which a color reads as neutral rather than tinted — shared by every\n * \"is this canvas/accent actually chromatic\" check (getMatchingNeutral, resolveAutoNeutral,\n * accentScaleForCanvas).\n */\nconst CHROMATIC_THRESHOLD = 0.04\n\n/**\n * An alpha step past this needs to cover the canvas almost completely to reproduce its\n * target — at that point it isn't a tint anymore, and the opaque solid is both cheaper and\n * more predictable. Only applied to the accent ramp's canvas-tinted low steps (gap #12a,\n * ask 2 — specifically the steps `tintLowStepsFromCanvas` retargets; steps 6-12 are by-design\n * high-alpha ink/solid tokens even on plain white — capping *those* would be a regression, not\n * a fix). Same shape as the deltaE check below, widened from \"could not solve\" to \"solved, but\n * not usefully\".\n */\nconst ALPHA_CAP = 0.5\n\n/**\n * Replace alpha steps the solver could not actually solve — or, within `capAlphaSteps` leading\n * steps, solved only at a near-opaque alpha — with their opaque solid.\n *\n * Each `aN` is solved so that compositing it over the background reproduces step N\n * (`target = bg·(1-α) + fg·α`). From a saturated canvas a desaturated ink target is\n * unreachable within alpha bounds, and Radix's solver **degrades silently** rather than\n * failing — on `#FFD400` it returns `#0000ff39` for slate a11, which composites to\n * 1.66:1 body text where the solid step is a correct 9.03:1. Separately, even a solve that\n * *succeeds* can need alpha past `ALPHA_CAP` to get there — technically correct only over\n * that exact background, and no longer a usable \"tint\" anywhere else (gap #12a, ask 2).\n *\n * `space` must match whatever space `alpha` was actually solved in — `getAlphaColorP3` solves\n * per-channel in P3, and verifying that against an sRGB composite rejects perfectly good\n * solves (they were never trying to reproduce the sRGB composite in the first place).\n *\n * So verify the solve and fall back to the opaque value where either check fails. Only ever\n * called for a custom background; on white/black every step solves exactly at low alpha,\n * so the default output — and portal parity — is untouched either way.\n */\nfunction opaqueWhereUnsolvable(\n background: string,\n solid: string[],\n alpha: string[],\n capAlphaSteps = 0,\n space: 'srgb' | 'p3' = 'srgb',\n): string[] {\n let bg: number[]\n try {\n bg = new Color(background).to(space).coords\n } catch {\n return alpha\n }\n return alpha.map((step, i) => {\n const target = solid[i]\n if (!target) return step\n try {\n const ink = new Color(step)\n const fg = ink.to(space).coords\n const a = ink.alpha as number\n if (i < capAlphaSteps && a > ALPHA_CAP) return target\n const composited = new Color(\n space,\n [0, 1, 2].map((k) => bg[k]! * (1 - a) + fg[k]! * a) as [\n number,\n number,\n number,\n ],\n )\n return composited.deltaE(new Color(target), '2000') <=\n ALPHA_SOLVE_TOLERANCE\n ? step\n : target\n } catch {\n return target\n }\n })\n}\n\n/**\n * Weight (0–1) each of the accent scale's first 5 steps is pulled toward the canvas's own\n * hue/chroma; step 6 and up (borders, solid, text) stay the true accent untouched.\n */\nconst CANVAS_TINT_WEIGHTS = [1, 0.8, 0.6, 0.4, 0.2]\n\n/**\n * Pull the accent scale's low steps toward the canvas's own color (gap #12a, ask 1): Radix's\n * generator sets every step's hue/chroma from the accent itself and only re-curves lightness\n * against the background, so on a chromatic canvas the low steps (app/component background\n * territory) stay a flat wash of the accent instead of a tint of the page — right where the\n * now-canvas-tinted neutral scale (gap #12) visibly disagrees with it. Step 9 (the solid,\n * brand-defining step) and everything above step 5 is left exactly as Radix generated it.\n *\n * Mixed in OKLab (rectangular a/b), not OKLCH (polar hue + chroma): a circular hue\n * interpolation between two far-apart hues (a blue accent on a yellow-green canvas, say) has\n * to sweep through whatever unrelated hues sit on the \"shorter\" arc between them — a vivid\n * green/teal/cyan that belongs to neither endpoint. Linearly mixing a/b instead moves along\n * the straight line between the two points, which naturally desaturates toward neutral as the\n * two colors pull in more different directions, rather than swinging through a third, unrelated\n * hue. Lightness (shared between OKLab and OKLCH) is untouched either way — Radix's own\n * background-aware curve already owns it.\n *\n * Takes/returns `Color` objects rather than strings so callers can reuse the same tinted\n * coordinates for both the sRGB and wide-gamut representations without a lossy round trip.\n */\nfunction tintLowStepsFromCanvas(\n solidScale: readonly Color[],\n bg: Color,\n): Color[] {\n const [, bgA, bgB] = bg.to('oklab').coords\n return solidScale.map((step, i) => {\n const weight = CANVAS_TINT_WEIGHTS[i]\n if (!weight) return step\n const [lightness, a, b] = step.to('oklab').coords\n return new Color('oklab', [\n lightness ?? 0,\n (a ?? 0) * (1 - weight) + (bgA ?? 0) * weight,\n (b ?? 0) * (1 - weight) + (bgB ?? 0) * weight,\n ])\n })\n}\n\n/** Mirrors generate-radix-colors.tsx's private toOklchString (not exported by the vendored file). */\nfunction toOklchWideGamutString(color: Color): string {\n const L = +((color.coords[0] ?? 0) * 100).toFixed(1)\n return color\n .to('oklch')\n .toString({ precision: 4 })\n .replace(/(\\S+)(.+)/, `oklch(${L}%$2`)\n}\n\n/** Same fixed alpha the vendored generator uses for `accentSurface` — a stylistic choice, not a solve. */\nfunction surfaceAlphaFor(appearance: Appearance): number {\n return appearance === 'light' ? 0.8 : 0.5\n}\n\n/**\n * Canvas-tinted low steps for the accent scale, or the untouched scale (and `tinted: false`)\n * when the canvas isn't chromatic enough to bother (gap #12a, ask 1). Only indices\n * `0..CANVAS_TINT_WEIGHTS.length-1` are ever replaced — steps 6-12 come back byte-identical to\n * what Radix generated, in both representations (no lossy sRGB round trip for the untouched\n * wide-gamut steps).\n *\n * `tinted` tells the caller whether to scope `opaqueWhereUnsolvable`'s alpha cap in at all —\n * the cap only makes sense for steps this function actually retargeted; a caller that applied\n * it unconditionally on any custom `background` would wrongly force perfectly fine low-alpha\n * steps opaque on an *achromatic* custom background (an explicit `#000000`/`#f5f5f5`, say),\n * where nothing here ever ran.\n *\n * Re-solves alpha for the tinted indices too: the generator's own `accentScaleAlpha` was\n * solved against the *pre-tint* solid, so once that solid moves the old alpha value no longer\n * reproduces it — left alone, every tinted step would fail `opaqueWhereUnsolvable`'s deltaE\n * check and go opaque, defeating the point (the `soft`-card fill this exists for would never\n * actually be translucent on a tinted canvas). Reuses Radix's own alpha solver so the result\n * matches what the generator itself would have produced had it been given this target directly.\n *\n * `accentSurface` (the `surface` card variant's fill) gets the same treatment: the generator\n * computes it as a fixed-alpha composite of step 2 — index 1, always tinted — over the\n * background, so recomputing it from the *tinted* step 2 at that same fixed alpha (0.8 light,\n * 0.5 dark; not a solve) keeps it in sync with `soft`'s fill instead of staying the old,\n * untinted wash (review-findings.md #13).\n */\nfunction accentScaleForCanvas(\n scale: GeneratedColors,\n background: string,\n appearance: Appearance,\n): Pick<\n GeneratedColors,\n | 'accentScale'\n | 'accentScaleWideGamut'\n | 'accentScaleAlpha'\n | 'accentScaleAlphaWideGamut'\n | 'accentSurface'\n | 'accentSurfaceWideGamut'\n> & { tinted: boolean } {\n try {\n const bg = new Color(background).to('oklch')\n const [, bgChroma] = bg.coords\n if (!bgChroma || bgChroma < CHROMATIC_THRESHOLD)\n return { ...scale, tinted: false }\n const tinted = tintLowStepsFromCanvas(\n scale.accentScale.map((hex) => new Color(hex).to('oklch')),\n bg,\n )\n const accentScale = scale.accentScale.map((original, i) =>\n CANVAS_TINT_WEIGHTS[i]\n ? tinted[i]!.to('srgb').toString({ format: 'hex' })\n : original,\n ) as typeof scale.accentScale\n const accentScaleWideGamut = scale.accentScaleWideGamut.map((original, i) =>\n CANVAS_TINT_WEIGHTS[i] ? toOklchWideGamutString(tinted[i]!) : original,\n ) as typeof scale.accentScaleWideGamut\n const accentScaleAlpha = scale.accentScaleAlpha.map((original, i) =>\n CANVAS_TINT_WEIGHTS[i]\n ? getAlphaColorSrgb(accentScale[i]!, scale.background)\n : original,\n ) as typeof scale.accentScaleAlpha\n const accentScaleAlphaWideGamut = scale.accentScaleAlphaWideGamut.map(\n (original, i) =>\n CANVAS_TINT_WEIGHTS[i]\n ? getAlphaColorP3(accentScaleWideGamut[i]!, scale.background)\n : original,\n ) as typeof scale.accentScaleAlphaWideGamut\n const surfaceAlpha = surfaceAlphaFor(appearance)\n const accentSurface = getAlphaColorSrgb(\n accentScale[1]!,\n scale.background,\n surfaceAlpha,\n )\n const accentSurfaceWideGamut = getAlphaColorP3(\n accentScaleWideGamut[1]!,\n scale.background,\n surfaceAlpha,\n )\n return {\n accentScale,\n accentScaleWideGamut,\n accentScaleAlpha,\n accentScaleAlphaWideGamut,\n accentSurface,\n accentSurfaceWideGamut,\n tinted: true,\n }\n } catch {\n return { ...scale, tinted: false }\n }\n}\n\n/** Generate the Radix-style accent scale for one appearance from a single accent color. */\nexport function generateAccentScale(\n accent: string,\n appearance: Appearance,\n opts?: GenerateScaleOptions,\n): SparkAccentScale {\n const background = opts?.background ?? DEFAULTS[appearance].background\n const scale = generateRadixColors({\n appearance,\n accent,\n gray: opts?.neutral\n ? isSparkNeutralFamily(opts.neutral)\n ? NEUTRAL_BASE[opts.neutral]\n : opts.neutral\n : DEFAULTS[appearance].gray,\n background,\n })\n const {\n accentScale,\n accentScaleWideGamut,\n accentScaleAlpha: canvasAccentScaleAlpha,\n accentScaleAlphaWideGamut: canvasAccentScaleAlphaWideGamut,\n accentSurface,\n accentSurfaceWideGamut,\n tinted,\n } = opts?.background\n ? accentScaleForCanvas(scale, background, appearance)\n : { ...scale, tinted: false }\n const capAlphaSteps = tinted ? CANVAS_TINT_WEIGHTS.length : 0\n const solved = opts?.background\n ? {\n ...scale,\n accentScale,\n accentScaleWideGamut,\n accentSurface,\n accentSurfaceWideGamut,\n accentScaleAlpha: opaqueWhereUnsolvable(\n background,\n accentScale,\n canvasAccentScaleAlpha,\n capAlphaSteps,\n ) as typeof scale.accentScaleAlpha,\n accentScaleAlphaWideGamut: opaqueWhereUnsolvable(\n background,\n accentScaleWideGamut,\n canvasAccentScaleAlphaWideGamut,\n capAlphaSteps,\n 'p3',\n ) as typeof scale.accentScaleAlphaWideGamut,\n grayScaleAlpha: opaqueWhereUnsolvable(\n background,\n scale.grayScale,\n scale.grayScaleAlpha,\n ) as typeof scale.grayScaleAlpha,\n grayScaleAlphaWideGamut: opaqueWhereUnsolvable(\n background,\n scale.grayScaleWideGamut,\n scale.grayScaleAlphaWideGamut,\n 0,\n 'p3',\n ) as typeof scale.grayScaleAlphaWideGamut,\n }\n : scale\n // accentContrast → 4.5:1 (button text); accentOnSolid → 3:1 (graphical glyph,\n // see SparkAccentScale). Same picker, different WCAG threshold.\n return {\n ...solved,\n accentContrast: readableContrast(\n solved.accentScale[8]!,\n solved.accentContrast,\n MIN_TEXT_CONTRAST,\n ),\n accentOnSolid: readableContrast(\n solved.accentScale[8]!,\n solved.accentContrast,\n MIN_GRAPHIC_CONTRAST,\n ),\n }\n}\n\nconst MIN_TEXT_CONTRAST = 4.5 // WCAG 1.4.3 — button / label text\nconst MIN_GRAPHIC_CONTRAST = 3 // WCAG 1.4.11 — a check / indicator glyph on a solid fill\n\n// Pick the readable foreground on step 9: prefer the generator's native contrast\n// (usually white), else fall to progressively darker accent-tinted inks until one\n// clears `min`. Text uses 4.5:1; a graphical glyph (the checkmark) uses 3:1 — which\n// keeps white on most accents (incl. neutral 3.32:1, red 3.91:1) but flips to a dark\n// tint where white is too weak (e.g. orange, 2.97:1). The vendored generator keeps\n// white at APCA Lc >= 40, which can sit below both WCAG thresholds — hence this guard.\nfunction readableContrast(\n step9Hex: string,\n native: string,\n min: number,\n): string {\n const step9 = new Color(step9Hex)\n const [, chroma, hue] = step9.to('oklch').coords\n const tinted = (lightness: number) =>\n new Color('oklch', [\n lightness,\n Math.max(0.08 * (chroma ?? 0), 0.04),\n hue ?? 0,\n ])\n .to('srgb')\n .toString({ format: 'hex' })\n const ratioOf = (hex: string) => new Color(hex).contrast(step9, 'WCAG21')\n const candidates = [native, tinted(0.25), tinted(0.15)]\n const ratios = candidates.map(ratioOf)\n const passing = ratios.findIndex((r) => r >= min)\n if (passing !== -1) return candidates[passing]!\n\n // The ladder above is a *preference* (least extreme ink first), not a search — it\n // stops at 0.15 and so missed accents that only clear `min` a little darker still\n // (#936c89 landed at 4.47 when 0.05 gives 4.73). Contrast rises monotonically as\n // the ink's lightness moves away from the fill's, so search outward from step 9\n // toward whichever extreme is stronger: if a passing ink exists, this finds it.\n const lightness9 = step9.to('oklch').coords[0] ?? 0.5\n const toward = ratioOf(tinted(0)) >= ratioOf(tinted(1)) ? 0 : 1\n let fails = lightness9\n let passes = toward\n for (let i = 0; i < 16; i++) {\n const mid = (fails + passes) / 2\n if (ratioOf(tinted(mid)) >= min) passes = mid\n else fails = mid\n }\n const found = tinted(passes)\n if (ratioOf(found) >= min) return found\n\n // No ink of any lightness clears `min` on this fill — step 9 itself is too\n // mid-toned. Return the strongest available; the palette sweep flags the accent.\n const all = [...candidates, tinted(toward)]\n const allRatios = all.map(ratioOf)\n return all[allRatios.indexOf(Math.max(...allRatios))]!\n}\n\n/** sRGB `--spark-*` declarations for a generated accent scale. */\nexport function accentSparkVars(scale: SparkAccentScale): string[] {\n return sparkVars(\n scale.accentScale,\n scale.accentScaleAlpha,\n scale.accentContrast,\n scale.accentOnSolid,\n scale.accentSurface,\n scale.background,\n )\n}\n\n/** Wide-gamut (P3) `--spark-*` declarations — for a `color-gamut: p3` `@supports` block. */\nexport function accentSparkVarsP3(scale: SparkAccentScale): string[] {\n return sparkVars(\n scale.accentScaleWideGamut,\n scale.accentScaleAlphaWideGamut,\n scale.accentContrast,\n scale.accentOnSolid,\n scale.accentSurfaceWideGamut,\n scale.background,\n )\n}\n\n/** Radix's `grayColor=\"auto\"` pairing (accent family → gray), mapped to our 6 neutrals. */\nconst NEUTRAL_BY_ACCENT: Record<string, SparkNeutralFamily> = {\n blue: 'slate',\n indigo: 'slate',\n cyan: 'slate',\n violet: 'mauve',\n purple: 'mauve',\n pink: 'mauve',\n red: 'mauve',\n teal: 'sage',\n green: 'sage',\n grass: 'olive',\n orange: 'sand',\n gray: 'gray',\n}\n\n/** Radix's \"auto\" gray: curated names use Radix's table; a custom hex is classified by OKLCH hue. */\nexport function getMatchingNeutral(accent: string): SparkNeutralFamily {\n const byName = NEUTRAL_BY_ACCENT[accent]\n if (byName) return byName\n try {\n const [, chroma, hueRaw] = new Color(accent).to('oklch').coords\n if (!chroma || chroma < CHROMATIC_THRESHOLD) return 'gray'\n const hue = (((hueRaw ?? 0) % 360) + 360) % 360\n if (hue >= 300 || hue < 35) return 'mauve'\n if (hue < 115) return 'sand'\n if (hue < 145) return 'olive'\n if (hue < 200) return 'sage'\n return 'slate'\n } catch {\n return 'slate'\n }\n}\n\n/**\n * Fill steps (Radix roles 1-5), measured from the surface toward the *scrim* — the pole\n * opposite the ink. Magnitudes are unchanged from the original ink-directed ramp; only\n * the direction flips, so a fill and the text on it move apart instead of together.\n */\nconst INVERTED_FILL_RAMP = [0.06, 0.12, 0.2, 0.3, 0.4]\n\n/** Strongest border step (8, the hover/active edge). 6 is solved per accent; 7 splits them. */\nconst INVERTED_BORDER_TOP = 0.82\n\n/**\n * Ink steps (9-12), measured from the surface toward the ink. Step 9 (the solid fill)\n * lands on the ink itself; 10 eases back for hover; 11-12 are full-strength ink.\n */\nconst INVERTED_INK_RAMP = [1, 0.88, 1, 1]\n\n/**\n * Lowest alpha at which the ink, laid over the surface, is 3:1 distinguishable from it\n * (WCAG 1.4.11) — the floor for a border that has to read as an edge at all.\n *\n * Solved rather than hardcoded because it moves with the accent (0.57-0.72 across the\n * curated set). Contrast against the surface rises monotonically with `t`, so bisection\n * finds the boundary.\n *\n * Targets 1% above the floor rather than landing on it: the step ships as `#rrggbbaa`, and\n * 8-bit rounding on any of the four channels can drop an exact solve back under 3:1.\n */\nfunction solveBorderFloor(surface: Color, ink: Color): number {\n const target = MIN_GRAPHIC_CONTRAST * 1.01\n const ratioAt = (t: number) =>\n new Color(Color.mix(surface, ink, t, { space: 'srgb' })).contrast(\n surface,\n 'WCAG21',\n )\n let fails = 0\n let passes = 1\n for (let i = 0; i < 16; i++) {\n const mid = (fails + passes) / 2\n if (ratioAt(mid) >= target) passes = mid\n else fails = mid\n }\n return passes\n}\n\n/** OKLCH string in the wide-gamut format the generator emits (`oklch(96.1% 0.0155 254.2)`). */\nfunction oklchString(color: Color): string {\n const oklch = color.to('oklch')\n const lightness = +((oklch.coords[0] ?? 0) * 100).toFixed(1)\n return oklch\n .toString({ precision: 4 })\n .replace(/(\\S+)(.+)/, `oklch(${lightness}%$2`)\n}\n\n/**\n * Re-cast a scale for use ON its own solid (step-9) fill — the `inverted` knob.\n *\n * A solid accent control on a solid accent surface is invisible: both paint `--spark-9`.\n * Inverting swaps that pair — step 9 and its contrast ink trade places — so the solid fill\n * and its text are contrast-safe **by construction, for any accent**: `accentContrast` is\n * already the WCAG-checked readable ink for step 9 (`readableContrast`).\n *\n * The non-solid styles need a second idea, because the ink is also the text colour. A fill\n * blended surface→ink moves the background *toward* the foreground, so ink-on-fill contrast\n * falls monotonically with `t` — and measured across the curated set, the highest `t` that\n * still clears 4.5:1 (0.174) sits below the lowest `t` that is 3:1 distinguishable from the\n * surface (0.543). On that line no fill can be both legible and visible, on any accent.\n *\n * So fills blend the other way, toward the **scrim** — the lightness pole opposite the ink,\n * carrying the surface's own hue. Contrast against the ink now *rises* with `t`, which puts\n * every non-solid style at or above the solid pair's ratio for free. Borders keep stepping\n * toward the ink (a scrim-side border caps at ~2:1 on light accents like orange, so it can\n * never clear 1.4.11), starting at the solved 3:1 floor.\n *\n * Appearance-stable on purpose. The generator pins step 9 to the accent itself, so the\n * surface is identical in light and dark — which makes the whole inverted scale identical\n * too, and `[data-inverted]` needs no `.dark` twin.\n *\n * Two things this is deliberately NOT:\n * - **A positional mirror** (`1↔12`). It leaves the solid fill only 2.04:1 from the card in\n * dark and 2.23:1 on a light accent like orange — under the 3:1 graphical floor, i.e. still\n * invisible, which is the bug this exists to fix.\n * - **A regenerated on-surface ramp.** Feeding the surface back in as `background` produces\n * off-hue overlays (a warm wash on a blue card) and ink at 3.35:1.\n *\n * Values are literals, not `var(--spark-N)` aliases: a scale can't re-point to itself\n * (`--spark-1: var(--spark-12)` next to `--spark-12: var(--spark-1)` is a cycle, and CSS\n * discards both).\n */\nexport function invertAccentScale(scale: SparkAccentScale): SparkAccentScale {\n const surface = scale.accentScale[8]! // the solid fill this scale will sit on\n const ink = scale.accentContrast // already AA-checked against that fill\n const surfaceColor = new Color(surface)\n const inkColor = new Color(ink)\n\n // The scrim: opposite the ink in lightness, tinted with the surface's own hue by the\n // same rule readableContrast uses for its ink ladder. Derived from the surface, so this\n // introduces no new palette input.\n const [surfaceLightness, surfaceChroma, surfaceHue] =\n surfaceColor.to('oklch').coords\n const inkLightness = inkColor.to('oklch').coords[0] ?? 0\n const scrimColor = new Color('oklch', [\n inkLightness > (surfaceLightness ?? 0) ? 0.12 : 0.98,\n Math.max(0.08 * (surfaceChroma ?? 0), 0.02),\n surfaceHue ?? 0,\n ])\n\n const borderFloor = solveBorderFloor(surfaceColor, inkColor)\n const ramp: { toward: Color; t: number }[] = [\n ...INVERTED_FILL_RAMP.map((t) => ({ toward: scrimColor, t })),\n { toward: inkColor, t: borderFloor },\n { toward: inkColor, t: (borderFloor + INVERTED_BORDER_TOP) / 2 },\n { toward: inkColor, t: INVERTED_BORDER_TOP },\n ...INVERTED_INK_RAMP.map((t) => ({ toward: inkColor, t })),\n ]\n\n // sRGB, not OKLab: the browser composites `--spark-aN` as source-over in gamma sRGB, so\n // mixing the opaque twin in any other space makes `--spark-N` a different colour from it\n // (measured up to dE2000 2.8 on the old OKLab mix, over ALPHA_SOLVE_TOLERANCE).\n const blend = ({ toward, t }: { toward: Color; t: number }) =>\n new Color(Color.mix(surfaceColor, toward, t, { space: 'srgb' }))\n const steps = ramp.map((s) =>\n blend(s).to('srgb').toString({ format: 'hex' }),\n ) as typeof scale.accentScale\n const stepsWideGamut = ramp.map((s) =>\n oklchString(blend(s)),\n ) as typeof scale.accentScaleWideGamut\n // Mixing surface→X at t in sRGB is the same colour as X at alpha t over the surface, so\n // each alpha step is just its own pole at rising opacity — no solve needed.\n const alpha = ramp.map(({ toward, t }) => {\n const translucent = toward.clone()\n translucent.alpha = t\n return translucent.to('srgb').toString({ format: 'hex' })\n }) as typeof scale.accentScaleAlpha\n const alphaWideGamut = ramp.map(({ toward, t }) => {\n const translucent = toward.clone().to('p3')\n translucent.alpha = t\n return translucent\n .toString({ precision: 4 })\n .replace('color(p3 ', 'color(display-p3 ')\n }) as typeof scale.accentScaleAlphaWideGamut\n return {\n ...scale,\n accentScale: steps,\n accentScaleWideGamut: stepsWideGamut,\n accentScaleAlpha: alpha,\n accentScaleAlphaWideGamut: alphaWideGamut,\n // Ink on the inverted solid fill is the accent itself — the other half of the swapped pair.\n accentContrast: surface,\n accentOnSolid: surface,\n accentSurface: steps[1]!,\n accentSurfaceWideGamut: stepsWideGamut[1]!,\n }\n}\n\n/**\n * `neutralColor=\"auto\"` resolution: prefer the canvas's own tint when it has one — a\n * chromatic `background` seeds a visibly warmer/cooler ramp than bucketing it into one\n * of the 6 named families would (see gap #12). Falls back to accent-matched bucketing\n * when there's no tinted canvas to pull from.\n */\nexport function resolveAutoNeutral(\n accent: string,\n background?: string,\n): string {\n if (background) {\n try {\n const [, chroma] = new Color(background).to('oklch').coords\n if (chroma && chroma >= CHROMATIC_THRESHOLD) return background\n } catch {\n // fall through to accent-matched neutral\n }\n }\n return getMatchingNeutral(accent)\n}\n\n/**\n * Whether a string parses as a color at all — for validating a custom `neutralColor`\n * before it reaches the generator. An unparseable seed (e.g. a typo'd family name like\n * `\"slat\"`) would otherwise throw deep inside `generateRadixColors`, taking the whole\n * accent scale down with it via the caller's shared try/catch.\n */\nexport function isParsableColor(value: string): boolean {\n try {\n new Color(value).to('oklch')\n return true\n } catch {\n return false\n }\n}\n\n/** The 27 tokens every scale family declares, under an arbitrary prefix. */\nfunction scaleVars(\n prefix: string,\n steps: string[],\n alpha: string[],\n contrast: string,\n onSolid: string,\n surface: string,\n): string[] {\n return [\n ...steps.map((c, i) => `--${prefix}-${i + 1}: ${c};`),\n ...alpha.map((c, i) => `--${prefix}-a${i + 1}: ${c};`),\n `--${prefix}-contrast: ${contrast};`,\n `--${prefix}-on-solid: ${onSolid};`,\n `--${prefix}-surface: ${surface};`,\n ]\n}\n\nfunction sparkVars(\n steps: string[],\n alpha: string[],\n contrast: string,\n onSolid: string,\n surface: string,\n background: string,\n): string[] {\n const indicator = steps[8]! // step 9 — the brand step (matches --spark-indicator/-track)\n return [\n ...scaleVars('spark', steps, alpha, contrast, onSolid, surface),\n `--spark-background: ${background};`,\n `--spark-indicator: ${indicator};`,\n `--spark-track: ${indicator};`,\n ]\n}\n\n/**\n * The same `--neutral-*` tokens `neutralSparkVars` emits, pointed back at a static Radix\n * family instead of a generated scale — the counterpart for an appearance that has *no*\n * custom canvas of its own.\n *\n * Ambient (no explicit `appearance`) mode emits both appearances' scales into one unlayered\n * `<style>`, but a custom canvas only regenerates neutrals for the appearance it belongs to.\n * Left undeclared, the other block doesn't fall back — nothing resets a custom property, so\n * its sibling's canvas-tinted values simply stay in effect there, and being unlayered they\n * outrank `[data-neutral-color]`'s own `@layer base` mapping too. Declaring the same token\n * set closes that. `var(--<family>-*)` is itself appearance-aware (Radix ships the dark\n * scales under `.dark`), so one declaration resolves correctly wherever the block applies.\n */\nexport function neutralFamilyVars(\n family: Exclude<SparkNeutral, 'auto'>,\n): string[] {\n const ref = (suffix: string) => `var(--${family}-${suffix})`\n return scaleVars(\n 'neutral',\n Array.from({ length: 12 }, (_, i) => ref(`${i + 1}`)),\n Array.from({ length: 12 }, (_, i) => ref(`a${i + 1}`)),\n // The companion tokens' own static defaults, from spark-default.css's .light/.dark.\n 'var(--neutral-12)',\n '#fff',\n 'var(--neutral-2)',\n )\n}\n\n/**\n * `--neutral-*` declarations from the gray scale the generator produces alongside the\n * accent — normally discarded, because `[data-neutral-color]` points `--neutral-*` at a\n * static Radix family. Worth consuming only when the canvas is custom: the static\n * families' alpha steps are pre-baked against white/black, so on any other background\n * they composite wrong (every gray text/border reads through `--neutral-a*`).\n *\n * `--neutral-surface` is step 2, matching the static families' own convention rather\n * than the generator's translucent `graySurface`.\n */\nexport function neutralSparkVars(scale: SparkAccentScale): string[] {\n return scaleVars(\n 'neutral',\n scale.grayScale,\n scale.grayScaleAlpha,\n readableContrast(scale.grayScale[8]!, '#fff', MIN_TEXT_CONTRAST),\n readableContrast(scale.grayScale[8]!, '#fff', MIN_GRAPHIC_CONTRAST),\n scale.grayScale[1]!,\n )\n}\n\n/** Wide-gamut (P3) twin of `neutralSparkVars`. */\nexport function neutralSparkVarsP3(scale: SparkAccentScale): string[] {\n return scaleVars(\n 'neutral',\n scale.grayScaleWideGamut,\n scale.grayScaleAlphaWideGamut,\n readableContrast(scale.grayScale[8]!, '#fff', MIN_TEXT_CONTRAST),\n readableContrast(scale.grayScale[8]!, '#fff', MIN_GRAPHIC_CONTRAST),\n scale.grayScaleWideGamut[1]!,\n )\n}\n"],"mappings":"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"}