@trishchuk/coolors-mcp 1.0.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 (197) hide show
  1. package/.claude/settings.local.json +39 -0
  2. package/.env +2 -0
  3. package/.github/ISSUE_TEMPLATE/bug_report.md +73 -0
  4. package/.github/ISSUE_TEMPLATE/feature_request.md +71 -0
  5. package/.github/pull_request_template.md +97 -0
  6. package/.github/workflows/ci.yml +127 -0
  7. package/.github/workflows/deploy-docs.yml +56 -0
  8. package/.github/workflows/release.yml +99 -0
  9. package/.mcp.json +12 -0
  10. package/.prettierignore +1 -0
  11. package/CLAUDE.md +201 -0
  12. package/DOCUMENTATION.md +274 -0
  13. package/GEMINI.md +54 -0
  14. package/LICENSE +21 -0
  15. package/README.md +401 -0
  16. package/demo/content_based_color.png +0 -0
  17. package/demo/music-player.html +621 -0
  18. package/demo/podcast-player.html +903 -0
  19. package/dist/bin/coolors-mcp.d.ts +1 -0
  20. package/dist/bin/coolors-mcp.js +154 -0
  21. package/dist/bin/coolors-mcp.js.map +1 -0
  22. package/dist/bin/server.d.ts +1 -0
  23. package/dist/bin/server.js +3292 -0
  24. package/dist/bin/server.js.map +1 -0
  25. package/dist/chunk-IQ7NN26V.js +114 -0
  26. package/dist/chunk-IQ7NN26V.js.map +1 -0
  27. package/dist/chunk-P3ARRKLS.js +1214 -0
  28. package/dist/chunk-P3ARRKLS.js.map +1 -0
  29. package/dist/color/index.d.ts +716 -0
  30. package/dist/color/index.js +153 -0
  31. package/dist/color/index.js.map +1 -0
  32. package/dist/coolors-mcp.d.ts +136 -0
  33. package/dist/coolors-mcp.js +7 -0
  34. package/dist/coolors-mcp.js.map +1 -0
  35. package/docs/.vitepress/cache/deps/@braintree_sanitize-url.js +93 -0
  36. package/docs/.vitepress/cache/deps/@braintree_sanitize-url.js.map +7 -0
  37. package/docs/.vitepress/cache/deps/_metadata.json +127 -0
  38. package/docs/.vitepress/cache/deps/chunk-BUSYA2B4.js +9 -0
  39. package/docs/.vitepress/cache/deps/chunk-BUSYA2B4.js.map +7 -0
  40. package/docs/.vitepress/cache/deps/chunk-JD3CXNQ6.js +12683 -0
  41. package/docs/.vitepress/cache/deps/chunk-JD3CXNQ6.js.map +7 -0
  42. package/docs/.vitepress/cache/deps/chunk-SYPOPCWC.js +9719 -0
  43. package/docs/.vitepress/cache/deps/chunk-SYPOPCWC.js.map +7 -0
  44. package/docs/.vitepress/cache/deps/cytoscape-cose-bilkent.js +4710 -0
  45. package/docs/.vitepress/cache/deps/cytoscape-cose-bilkent.js.map +7 -0
  46. package/docs/.vitepress/cache/deps/cytoscape.js +30278 -0
  47. package/docs/.vitepress/cache/deps/cytoscape.js.map +7 -0
  48. package/docs/.vitepress/cache/deps/dayjs.js +285 -0
  49. package/docs/.vitepress/cache/deps/dayjs.js.map +7 -0
  50. package/docs/.vitepress/cache/deps/debug.js +468 -0
  51. package/docs/.vitepress/cache/deps/debug.js.map +7 -0
  52. package/docs/.vitepress/cache/deps/package.json +3 -0
  53. package/docs/.vitepress/cache/deps/prismjs.js +1466 -0
  54. package/docs/.vitepress/cache/deps/prismjs.js.map +7 -0
  55. package/docs/.vitepress/cache/deps/prismjs_components_prism-bash.js +228 -0
  56. package/docs/.vitepress/cache/deps/prismjs_components_prism-bash.js.map +7 -0
  57. package/docs/.vitepress/cache/deps/prismjs_components_prism-javascript.js +142 -0
  58. package/docs/.vitepress/cache/deps/prismjs_components_prism-javascript.js.map +7 -0
  59. package/docs/.vitepress/cache/deps/prismjs_components_prism-json.js +27 -0
  60. package/docs/.vitepress/cache/deps/prismjs_components_prism-json.js.map +7 -0
  61. package/docs/.vitepress/cache/deps/prismjs_components_prism-python.js +65 -0
  62. package/docs/.vitepress/cache/deps/prismjs_components_prism-python.js.map +7 -0
  63. package/docs/.vitepress/cache/deps/prismjs_components_prism-typescript.js +53 -0
  64. package/docs/.vitepress/cache/deps/prismjs_components_prism-typescript.js.map +7 -0
  65. package/docs/.vitepress/cache/deps/prismjs_components_prism-yaml.js +73 -0
  66. package/docs/.vitepress/cache/deps/prismjs_components_prism-yaml.js.map +7 -0
  67. package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +4507 -0
  68. package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +7 -0
  69. package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js +584 -0
  70. package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js.map +7 -0
  71. package/docs/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js +1146 -0
  72. package/docs/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js.map +7 -0
  73. package/docs/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js +1667 -0
  74. package/docs/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js.map +7 -0
  75. package/docs/.vitepress/cache/deps/vitepress___minisearch.js +1814 -0
  76. package/docs/.vitepress/cache/deps/vitepress___minisearch.js.map +7 -0
  77. package/docs/.vitepress/cache/deps/vue.js +344 -0
  78. package/docs/.vitepress/cache/deps/vue.js.map +7 -0
  79. package/docs/.vitepress/components/ClientGrid.vue +125 -0
  80. package/docs/.vitepress/components/CodeBlock.vue +231 -0
  81. package/docs/.vitepress/components/ConfigModal.vue +477 -0
  82. package/docs/.vitepress/components/DiagramModal.vue +528 -0
  83. package/docs/.vitepress/components/TroubleshootingModal.vue +472 -0
  84. package/docs/.vitepress/config.js +162 -0
  85. package/docs/.vitepress/theme/FundingLayout.vue +251 -0
  86. package/docs/.vitepress/theme/Layout.vue +134 -0
  87. package/docs/.vitepress/theme/components/AdBanner.vue +317 -0
  88. package/docs/.vitepress/theme/components/AdPlaceholder.vue +78 -0
  89. package/docs/.vitepress/theme/components/FundingEffects.vue +322 -0
  90. package/docs/.vitepress/theme/components/FundingHero.vue +345 -0
  91. package/docs/.vitepress/theme/components/SupportSection.vue +511 -0
  92. package/docs/.vitepress/theme/custom-app.css +339 -0
  93. package/docs/.vitepress/theme/custom.css +699 -0
  94. package/docs/.vitepress/theme/index.js +25 -0
  95. package/docs/README.md +198 -0
  96. package/docs/concepts/accessibility.md +473 -0
  97. package/docs/concepts/color-spaces.md +222 -0
  98. package/docs/concepts/distance-metrics.md +384 -0
  99. package/docs/concepts/hct.md +261 -0
  100. package/docs/concepts/image-analysis.md +396 -0
  101. package/docs/concepts/material-design.md +306 -0
  102. package/docs/concepts/theme-matching.md +399 -0
  103. package/docs/examples/basic-colors.md +490 -0
  104. package/docs/examples/creating-themes.md +898 -0
  105. package/docs/examples/css-refactoring.md +824 -0
  106. package/docs/examples/image-extraction.md +882 -0
  107. package/docs/getting-started.md +366 -0
  108. package/docs/index.md +190 -0
  109. package/docs/installation.md +157 -0
  110. package/docs/tools/README.md +234 -0
  111. package/docs/tools/accessibility.md +614 -0
  112. package/docs/tools/color-operations.md +374 -0
  113. package/docs/tools/image-extraction.md +624 -0
  114. package/docs/tools/material-design.md +347 -0
  115. package/docs/tools/theme-matching.md +552 -0
  116. package/eslint.config.ts +14 -0
  117. package/examples/theme-matching.md +113 -0
  118. package/jsr.json +7 -0
  119. package/mcp-config.json +8 -0
  120. package/note.md +35 -0
  121. package/package.json +122 -0
  122. package/research_results.md +53 -0
  123. package/src/bin/coolors-mcp.ts +194 -0
  124. package/src/bin/server.ts +61 -0
  125. package/src/color/__tests__/conversions-argb.test.ts +198 -0
  126. package/src/color/__tests__/extract-colors.test.ts +360 -0
  127. package/src/color/__tests__/image-utils.test.ts +242 -0
  128. package/src/color/__tests__/reference-colors.test.ts +278 -0
  129. package/src/color/__tests__/round-trip.test.ts +197 -0
  130. package/src/color/conversions.test.ts +402 -0
  131. package/src/color/conversions.ts +393 -0
  132. package/src/color/dislike/__tests__/dislike-analyzer.test.ts +248 -0
  133. package/src/color/dislike/dislike-analyzer.ts +114 -0
  134. package/src/color/extract-colors.ts +228 -0
  135. package/src/color/hct/__tests__/hct-class.test.ts +232 -0
  136. package/src/color/hct/harmonization.ts +204 -0
  137. package/src/color/hct/hct-class.ts +109 -0
  138. package/src/color/hct/hct-solver.ts +168 -0
  139. package/src/color/hct/index.ts +39 -0
  140. package/src/color/hct/tonal-palette.ts +211 -0
  141. package/src/color/hct/types.ts +88 -0
  142. package/src/color/image-utils.ts +79 -0
  143. package/src/color/index.ts +87 -0
  144. package/src/color/material-theme.ts +157 -0
  145. package/src/color/metrics.test.ts +276 -0
  146. package/src/color/metrics.ts +281 -0
  147. package/src/color/quantize/__tests__/quantizer_celebi.test.ts +195 -0
  148. package/src/color/quantize/lab_point_provider.ts +55 -0
  149. package/src/color/quantize/point_provider.ts +27 -0
  150. package/src/color/quantize/quantizer_celebi.ts +51 -0
  151. package/src/color/quantize/quantizer_celebi_test.ts +71 -0
  152. package/src/color/quantize/quantizer_map.ts +47 -0
  153. package/src/color/quantize/quantizer_wsmeans.ts +232 -0
  154. package/src/color/quantize/quantizer_wu.ts +472 -0
  155. package/src/color/score/__tests__/score.test.ts +224 -0
  156. package/src/color/score/score.ts +175 -0
  157. package/src/color/types.ts +151 -0
  158. package/src/color/utils/color_utils.ts +292 -0
  159. package/src/color/utils/math_utils.ts +145 -0
  160. package/src/color/utils.test.ts +403 -0
  161. package/src/color/utils.ts +315 -0
  162. package/src/constants.ts +5 -0
  163. package/src/coolors-mcp.ts +37 -0
  164. package/src/examples/addition.ts +333 -0
  165. package/src/examples/color-demo.ts +125 -0
  166. package/src/examples/custom-logger.ts +201 -0
  167. package/src/examples/oauth-server.ts +113 -0
  168. package/src/examples/session-context.ts +269 -0
  169. package/src/session.ts +116 -0
  170. package/src/theme/__tests__/matcher.test.ts +180 -0
  171. package/src/theme/__tests__/parser.test.ts +148 -0
  172. package/src/theme/__tests__/refactor.test.ts +224 -0
  173. package/src/theme/index.ts +34 -0
  174. package/src/theme/matcher.ts +395 -0
  175. package/src/theme/parser.ts +392 -0
  176. package/src/theme/refactor.ts +360 -0
  177. package/src/theme/types.ts +152 -0
  178. package/src/tools/__tests__/gradient-generator.test.ts +206 -0
  179. package/src/tools/__tests__/palette-with-locks.test.ts +109 -0
  180. package/src/tools/color-conversion.tool.ts +54 -0
  181. package/src/tools/color-distance.tool.ts +41 -0
  182. package/src/tools/colors.ts +31 -0
  183. package/src/tools/contrast-checker.tool.ts +37 -0
  184. package/src/tools/dislike-analyzer.tool.ts +247 -0
  185. package/src/tools/gradient-generator.tool.ts +250 -0
  186. package/src/tools/image-extraction.tools.ts +289 -0
  187. package/src/tools/index.ts +39 -0
  188. package/src/tools/material-theme.tools.ts +250 -0
  189. package/src/tools/palette-generator.tool.ts +135 -0
  190. package/src/tools/palette-with-locks.tool.ts +221 -0
  191. package/src/tools/registry.ts +142 -0
  192. package/src/tools/simple-tools.ts +37 -0
  193. package/src/tools/theme-matching.tools.ts +334 -0
  194. package/src/types.ts +182 -0
  195. package/src/utils.ts +22 -0
  196. package/tsconfig.json +8 -0
  197. package/vitest.config.js +15 -0
@@ -0,0 +1,392 @@
1
+ /**
2
+ * Theme Parser
3
+ * Parses CSS variables and extracts theme colors
4
+ */
5
+
6
+ import type { SemanticRole, ThemeVariable, ThemeVariables } from "./types.js";
7
+
8
+ import { hexToRgb } from "../color/conversions.js";
9
+ import { rgbToHct } from "../color/hct/hct-solver.js";
10
+
11
+ /**
12
+ * Parse CSS content and extract theme variables
13
+ */
14
+ export function parseThemeVariables(css: string): ThemeVariables {
15
+ const variables: ThemeVariable[] = [];
16
+
17
+ // Match CSS custom properties
18
+ const varRegex = /--([\w-]+):\s*([^;]+);/g;
19
+ let match;
20
+
21
+ while ((match = varRegex.exec(css)) !== null) {
22
+ const [, name, value] = match;
23
+ const fullName = `--${name}`;
24
+ const trimmedValue = value.trim();
25
+
26
+ // Check if it's a color value
27
+ if (isColorValue(trimmedValue)) {
28
+ const colorValue = normalizeColorValue(trimmedValue);
29
+ if (colorValue) {
30
+ try {
31
+ const rgb = parseColorToRgb(colorValue);
32
+ const hct = rgbToHct(rgb);
33
+
34
+ variables.push({
35
+ hct,
36
+ name: fullName,
37
+ role: detectSemanticRole(name),
38
+ tone: hct.t,
39
+ value: colorValue,
40
+ });
41
+ } catch {
42
+ // Skip invalid color values
43
+ console.warn(
44
+ `Failed to parse color variable ${fullName}: ${trimmedValue}`,
45
+ );
46
+ }
47
+ }
48
+ }
49
+ }
50
+
51
+ // Organize variables by role
52
+ return organizeByRole(variables);
53
+ }
54
+
55
+ /**
56
+ * Parse CSS variables from an object (e.g., from getComputedStyle)
57
+ */
58
+ export function parseThemeVariablesFromObject(
59
+ variables: Record<string, string>,
60
+ ): ThemeVariables {
61
+ const themeVars: ThemeVariable[] = [];
62
+
63
+ for (const [name, value] of Object.entries(variables)) {
64
+ if (name.startsWith("--") && isColorValue(value)) {
65
+ const colorValue = normalizeColorValue(value);
66
+ if (colorValue) {
67
+ try {
68
+ const rgb = parseColorToRgb(colorValue);
69
+ const hct = rgbToHct(rgb);
70
+
71
+ themeVars.push({
72
+ hct,
73
+ name,
74
+ role: detectSemanticRole(name.slice(2)), // Remove -- prefix
75
+ tone: hct.t,
76
+ value: colorValue,
77
+ });
78
+ } catch {
79
+ console.warn(`Failed to parse color variable ${name}: ${value}`);
80
+ }
81
+ }
82
+ }
83
+ }
84
+
85
+ return organizeByRole(themeVars);
86
+ }
87
+
88
+ /**
89
+ * Detect semantic role from variable name
90
+ */
91
+ function detectSemanticRole(name: string): SemanticRole | undefined {
92
+ const lower = name.toLowerCase();
93
+
94
+ if (lower.includes("primary")) return "primary";
95
+ if (lower.includes("secondary")) return "secondary";
96
+ if (lower.includes("tertiary")) return "tertiary";
97
+ if (lower.includes("error") || lower.includes("danger")) return "error";
98
+ if (lower.includes("neutral") || lower.includes("gray")) return "neutral";
99
+ if (lower.includes("surface")) return "surface";
100
+ if (lower.includes("background") || lower.includes("bg")) return "background";
101
+ if (lower.includes("outline") || lower.includes("border")) return "outline";
102
+ if (lower.includes("shadow")) return "shadow";
103
+
104
+ return undefined;
105
+ }
106
+
107
+ /**
108
+ * HSL to RGB helper
109
+ */
110
+ function hslToRgb(
111
+ h: number,
112
+ s: number,
113
+ l: number,
114
+ ): { b: number; g: number; r: number } {
115
+ let b, g, r;
116
+
117
+ if (s === 0) {
118
+ r = g = b = l;
119
+ } else {
120
+ const hue2rgb = (p: number, q: number, t: number) => {
121
+ if (t < 0) t += 1;
122
+ if (t > 1) t -= 1;
123
+ if (t < 1 / 6) return p + (q - p) * 6 * t;
124
+ if (t < 1 / 2) return q;
125
+ if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
126
+ return p;
127
+ };
128
+
129
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
130
+ const p = 2 * l - q;
131
+ r = hue2rgb(p, q, h + 1 / 3);
132
+ g = hue2rgb(p, q, h);
133
+ b = hue2rgb(p, q, h - 1 / 3);
134
+ }
135
+
136
+ return { b, g, r };
137
+ }
138
+
139
+ /**
140
+ * Check if a value is a color
141
+ */
142
+ function isColorValue(value: string): boolean {
143
+ const trimmed = value.trim();
144
+
145
+ // Hex colors
146
+ if (/^#[0-9a-fA-F]{3,8}$/.test(trimmed)) return true;
147
+
148
+ // RGB/RGBA
149
+ if (/^rgba?\(/.test(trimmed)) return true;
150
+
151
+ // HSL/HSLA
152
+ if (/^hsla?\(/.test(trimmed)) return true;
153
+
154
+ // Named colors (basic check)
155
+ if (/^[a-z]+$/i.test(trimmed)) {
156
+ return isNamedColor(trimmed);
157
+ }
158
+
159
+ return false;
160
+ }
161
+
162
+ /**
163
+ * Check if string is a named color
164
+ */
165
+ function isNamedColor(name: string): boolean {
166
+ const namedColors = [
167
+ "black",
168
+ "white",
169
+ "red",
170
+ "green",
171
+ "blue",
172
+ "yellow",
173
+ "cyan",
174
+ "magenta",
175
+ "gray",
176
+ "grey",
177
+ "orange",
178
+ "purple",
179
+ "brown",
180
+ "pink",
181
+ "lime",
182
+ "navy",
183
+ "teal",
184
+ "silver",
185
+ "gold",
186
+ "indigo",
187
+ "violet",
188
+ "turquoise",
189
+ "coral",
190
+ ];
191
+ return namedColors.includes(name.toLowerCase());
192
+ }
193
+
194
+ /**
195
+ * Convert named color to hex (simplified)
196
+ */
197
+ function namedColorToHex(name: string): string {
198
+ const colors: Record<string, string> = {
199
+ black: "#000000",
200
+ blue: "#0000ff",
201
+ brown: "#a52a2a",
202
+ coral: "#ff7f50",
203
+ cyan: "#00ffff",
204
+ gold: "#ffd700",
205
+ gray: "#808080",
206
+ green: "#008000",
207
+ grey: "#808080",
208
+ indigo: "#4b0082",
209
+ lime: "#00ff00",
210
+ magenta: "#ff00ff",
211
+ navy: "#000080",
212
+ orange: "#ffa500",
213
+ pink: "#ffc0cb",
214
+ purple: "#800080",
215
+ red: "#ff0000",
216
+ silver: "#c0c0c0",
217
+ teal: "#008080",
218
+ turquoise: "#40e0d0",
219
+ violet: "#ee82ee",
220
+ white: "#ffffff",
221
+ yellow: "#ffff00",
222
+ };
223
+ return colors[name.toLowerCase()] || "#000000";
224
+ }
225
+
226
+ /**
227
+ * Normalize color value to a standard format
228
+ */
229
+ function normalizeColorValue(value: string): null | string {
230
+ const trimmed = value.trim();
231
+
232
+ // Already in a good format
233
+ if (/^#[0-9a-fA-F]{6}$/.test(trimmed)) {
234
+ return trimmed;
235
+ }
236
+
237
+ // Short hex
238
+ if (/^#[0-9a-fA-F]{3}$/.test(trimmed)) {
239
+ const [, r, g, b] = trimmed.match(/#(.)(.)(.)/)!;
240
+ return `#${r}${r}${g}${g}${b}${b}`;
241
+ }
242
+
243
+ // RGB/RGBA
244
+ if (/^rgba?\(/.test(trimmed)) {
245
+ return parseRgbString(trimmed);
246
+ }
247
+
248
+ // HSL/HSLA
249
+ if (/^hsla?\(/.test(trimmed)) {
250
+ return parseHslString(trimmed);
251
+ }
252
+
253
+ // Named colors
254
+ if (isNamedColor(trimmed)) {
255
+ return namedColorToHex(trimmed);
256
+ }
257
+
258
+ return null;
259
+ }
260
+
261
+ /**
262
+ * Organize variables by semantic role
263
+ */
264
+ function organizeByRole(variables: ThemeVariable[]): ThemeVariables {
265
+ const organized: ThemeVariables = {
266
+ custom: {},
267
+ };
268
+
269
+ for (const variable of variables) {
270
+ if (variable.role) {
271
+ const role = variable.role;
272
+ if (!organized[role]) {
273
+ organized[role] = [];
274
+ }
275
+ organized[role].push(variable);
276
+ } else {
277
+ // Extract custom role from name pattern (e.g., --color-brand-50, --accent-warm)
278
+ const colorPattern = variable.name.match(/--(?:color-)?([a-z]+)-\d+/i);
279
+ const namedPattern = variable.name.match(/--([a-z]+)-[a-z]+/i);
280
+
281
+ if (colorPattern) {
282
+ const customRole = colorPattern[1].toLowerCase();
283
+ if (!organized.custom![customRole]) {
284
+ organized.custom![customRole] = [];
285
+ }
286
+ organized.custom![customRole].push(variable);
287
+ } else if (namedPattern) {
288
+ const customRole = namedPattern[1].toLowerCase();
289
+ // Only use this pattern if it's a known semantic pattern
290
+ const knownPatterns = [
291
+ "accent",
292
+ "neutral",
293
+ "success",
294
+ "warning",
295
+ "info",
296
+ ];
297
+ if (knownPatterns.some((p) => customRole.includes(p))) {
298
+ if (!organized.custom![customRole]) {
299
+ organized.custom![customRole] = [];
300
+ }
301
+ organized.custom![customRole].push(variable);
302
+ } else {
303
+ // Unknown pattern, put in uncategorized
304
+ if (!organized.custom!["uncategorized"]) {
305
+ organized.custom!["uncategorized"] = [];
306
+ }
307
+ organized.custom!["uncategorized"].push(variable);
308
+ }
309
+ } else {
310
+ // Uncategorized
311
+ if (!organized.custom!["uncategorized"]) {
312
+ organized.custom!["uncategorized"] = [];
313
+ }
314
+ organized.custom!["uncategorized"].push(variable);
315
+ }
316
+ }
317
+ }
318
+
319
+ // Sort each role by tone
320
+ for (const role of Object.keys(organized)) {
321
+ if (Array.isArray(organized[role as keyof ThemeVariables])) {
322
+ (organized[role as keyof ThemeVariables] as ThemeVariable[]).sort(
323
+ (a, b) => a.tone - b.tone,
324
+ );
325
+ } else if (role === "custom" && organized.custom) {
326
+ for (const customRole of Object.keys(organized.custom)) {
327
+ organized.custom[customRole].sort((a, b) => a.tone - b.tone);
328
+ }
329
+ }
330
+ }
331
+
332
+ return organized;
333
+ }
334
+
335
+ /**
336
+ * Parse color to RGB
337
+ */
338
+ function parseColorToRgb(color: string): { b: number; g: number; r: number } {
339
+ // Hex color
340
+ if (color.startsWith("#")) {
341
+ return hexToRgb(color);
342
+ }
343
+
344
+ // RGB string
345
+ if (color.startsWith("rgb")) {
346
+ const match = color.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/);
347
+ if (match) {
348
+ return {
349
+ b: parseInt(match[3], 10) / 255,
350
+ g: parseInt(match[2], 10) / 255,
351
+ r: parseInt(match[1], 10) / 255,
352
+ };
353
+ }
354
+ }
355
+
356
+ throw new Error(`Unable to parse color: ${color}`);
357
+ }
358
+
359
+ /**
360
+ * Parse HSL/HSLA string to hex
361
+ */
362
+ function parseHslString(hsl: string): null | string {
363
+ const match = hsl.match(/hsla?\((\d+),\s*(\d+)%,\s*(\d+)%/);
364
+ if (match) {
365
+ const h = parseInt(match[1], 10) / 360;
366
+ const s = parseInt(match[2], 10) / 100;
367
+ const l = parseInt(match[3], 10) / 100;
368
+
369
+ // HSL to RGB conversion
370
+ const rgb = hslToRgb(h, s, l);
371
+ const r = Math.round(rgb.r * 255);
372
+ const g = Math.round(rgb.g * 255);
373
+ const b = Math.round(rgb.b * 255);
374
+
375
+ return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
376
+ }
377
+ return null;
378
+ }
379
+
380
+ /**
381
+ * Parse RGB/RGBA string to hex
382
+ */
383
+ function parseRgbString(rgb: string): null | string {
384
+ const match = rgb.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
385
+ if (match) {
386
+ const r = parseInt(match[1], 10);
387
+ const g = parseInt(match[2], 10);
388
+ const b = parseInt(match[3], 10);
389
+ return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
390
+ }
391
+ return null;
392
+ }