@trishchuk/coolors-mcp 1.0.0 → 1.0.1

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 (66) hide show
  1. package/.claude/settings.local.json +2 -6
  2. package/.github/ISSUE_TEMPLATE/bug_report.md +20 -8
  3. package/.github/ISSUE_TEMPLATE/feature_request.md +22 -8
  4. package/.github/pull_request_template.md +33 -8
  5. package/.github/workflows/ci.yml +97 -97
  6. package/.github/workflows/deploy-docs.yml +9 -9
  7. package/.github/workflows/release.yml +15 -15
  8. package/README.md +26 -1
  9. package/TOOLS_UK.md +233 -0
  10. package/docs/.vitepress/cache/deps/@braintree_sanitize-url.js +30 -12
  11. package/docs/.vitepress/cache/deps/_metadata.json +1 -1
  12. package/docs/.vitepress/cache/deps/chunk-BUSYA2B4.js +9 -6
  13. package/docs/.vitepress/cache/deps/chunk-JD3CXNQ6.js +2543 -1612
  14. package/docs/.vitepress/cache/deps/chunk-SYPOPCWC.js +3508 -2529
  15. package/docs/.vitepress/cache/deps/cytoscape-cose-bilkent.js +1902 -1003
  16. package/docs/.vitepress/cache/deps/cytoscape.js +13303 -7347
  17. package/docs/.vitepress/cache/deps/dayjs.js +494 -272
  18. package/docs/.vitepress/cache/deps/debug.js +82 -38
  19. package/docs/.vitepress/cache/deps/prismjs.js +444 -272
  20. package/docs/.vitepress/cache/deps/prismjs_components_prism-bash.js +80 -73
  21. package/docs/.vitepress/cache/deps/prismjs_components_prism-javascript.js +93 -62
  22. package/docs/.vitepress/cache/deps/prismjs_components_prism-json.js +13 -13
  23. package/docs/.vitepress/cache/deps/prismjs_components_prism-python.js +34 -27
  24. package/docs/.vitepress/cache/deps/prismjs_components_prism-typescript.js +20 -17
  25. package/docs/.vitepress/cache/deps/prismjs_components_prism-yaml.js +75 -41
  26. package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +2005 -1438
  27. package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js +2 -2
  28. package/docs/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js +566 -229
  29. package/docs/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js +382 -270
  30. package/docs/.vitepress/cache/deps/vitepress___minisearch.js +334 -125
  31. package/docs/.vitepress/cache/deps/vue.js +2 -2
  32. package/docs/.vitepress/components/ClientGrid.vue +9 -3
  33. package/docs/.vitepress/components/CodeBlock.vue +51 -44
  34. package/docs/.vitepress/components/ConfigModal.vue +151 -67
  35. package/docs/.vitepress/components/DiagramModal.vue +186 -154
  36. package/docs/.vitepress/components/TroubleshootingModal.vue +101 -96
  37. package/docs/.vitepress/config.js +171 -141
  38. package/docs/.vitepress/theme/FundingLayout.vue +65 -54
  39. package/docs/.vitepress/theme/Layout.vue +21 -21
  40. package/docs/.vitepress/theme/components/AdBanner.vue +73 -52
  41. package/docs/.vitepress/theme/components/AdPlaceholder.vue +3 -3
  42. package/docs/.vitepress/theme/components/FundingEffects.vue +77 -53
  43. package/docs/.vitepress/theme/components/FundingHero.vue +78 -63
  44. package/docs/.vitepress/theme/components/SupportSection.vue +106 -89
  45. package/docs/.vitepress/theme/custom-app.css +19 -12
  46. package/docs/.vitepress/theme/custom.css +33 -25
  47. package/docs/.vitepress/theme/index.js +19 -16
  48. package/docs/concepts/accessibility.md +59 -47
  49. package/docs/concepts/color-spaces.md +28 -6
  50. package/docs/concepts/distance-metrics.md +45 -30
  51. package/docs/concepts/hct.md +30 -27
  52. package/docs/concepts/image-analysis.md +52 -21
  53. package/docs/concepts/material-design.md +43 -17
  54. package/docs/concepts/theme-matching.md +64 -40
  55. package/docs/examples/basic-colors.md +92 -108
  56. package/docs/examples/creating-themes.md +104 -108
  57. package/docs/examples/css-refactoring.md +33 -29
  58. package/docs/examples/image-extraction.md +145 -138
  59. package/docs/getting-started.md +45 -34
  60. package/docs/index.md +5 -1
  61. package/docs/installation.md +15 -1
  62. package/docs/tools/accessibility.md +74 -68
  63. package/docs/tools/image-extraction.md +62 -54
  64. package/docs/tools/theme-matching.md +45 -42
  65. package/note.md +1 -2
  66. package/package.json +2 -2
@@ -8,7 +8,7 @@ Learn how to extract colors from images and generate dynamic themes using Coolor
8
8
 
9
9
  ```javascript
10
10
  // Extract dominant colors
11
- "Extract the main colors from this image"
11
+ "Extract the main colors from this image";
12
12
 
13
13
  // Process:
14
14
  // 1. Quantizes image using Celebi algorithm (Wu + WSMeans)
@@ -21,7 +21,7 @@ Learn how to extract colors from images and generate dynamic themes using Coolor
21
21
 
22
22
  ```javascript
23
23
  // Create Material Design theme from image
24
- "Generate a theme from this album artwork"
24
+ "Generate a theme from this album artwork";
25
25
 
26
26
  // Result:
27
27
  // - Extracts dominant colors
@@ -63,12 +63,12 @@ img.src = 'path/to/image.jpg';
63
63
  ### Node.js with Sharp
64
64
 
65
65
  ```javascript
66
- const sharp = require('sharp');
66
+ const sharp = require("sharp");
67
67
 
68
68
  async function extractColorsFromFile(imagePath) {
69
69
  // Load and prepare image
70
70
  const { data, info } = await sharp(imagePath)
71
- .resize(300, 300, { fit: 'inside' }) // Resize for performance
71
+ .resize(300, 300, { fit: "inside" }) // Resize for performance
72
72
  .raw()
73
73
  .toBuffer({ resolveWithObject: true });
74
74
 
@@ -79,14 +79,14 @@ async function extractColorsFromFile(imagePath) {
79
79
  const result = await extractImageColors({
80
80
  imageData: pixels,
81
81
  maxColors: 8,
82
- minPopulation: 0.01 // Include colors with 1%+ coverage
82
+ minPopulation: 0.01, // Include colors with 1%+ coverage
83
83
  });
84
84
 
85
85
  return result;
86
86
  }
87
87
 
88
88
  // Usage
89
- const colors = await extractColorsFromFile('logo.png');
89
+ const colors = await extractColorsFromFile("logo.png");
90
90
  console.log(`Found ${colors.colors.length} dominant colors`);
91
91
  ```
92
92
 
@@ -94,53 +94,57 @@ console.log(`Found ${colors.colors.length} dominant colors`);
94
94
 
95
95
  ```html
96
96
  <!-- HTML -->
97
- <input type="file" id="imageUpload" accept="image/*">
97
+ <input type="file" id="imageUpload" accept="image/*" />
98
98
  <div id="colorPalette"></div>
99
99
 
100
100
  <script>
101
- document.getElementById('imageUpload').addEventListener('change', async (e) => {
102
- const file = e.target.files[0];
103
- if (!file) return;
104
-
105
- // Read file as image
106
- const img = new Image();
107
- const reader = new FileReader();
108
-
109
- reader.onload = async function(event) {
110
- img.src = event.target.result;
111
-
112
- img.onload = async function() {
113
- // Create canvas
114
- const canvas = document.createElement('canvas');
115
- const ctx = canvas.getContext('2d');
116
-
117
- // Resize for performance
118
- const maxSize = 400;
119
- const scale = Math.min(maxSize / img.width, maxSize / img.height, 1);
120
- canvas.width = img.width * scale;
121
- canvas.height = img.height * scale;
122
-
123
- // Draw and extract
124
- ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
125
- const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
126
-
127
- // Extract colors
128
- const colors = await extractImageColors({
129
- imageData: Array.from(imageData.data),
130
- maxColors: 6
131
- });
101
+ document
102
+ .getElementById("imageUpload")
103
+ .addEventListener("change", async (e) => {
104
+ const file = e.target.files[0];
105
+ if (!file) return;
132
106
 
133
- // Display palette
134
- displayPalette(colors.colors);
135
- };
136
- };
107
+ // Read file as image
108
+ const img = new Image();
109
+ const reader = new FileReader();
137
110
 
138
- reader.readAsDataURL(file);
139
- });
111
+ reader.onload = async function (event) {
112
+ img.src = event.target.result;
113
+
114
+ img.onload = async function () {
115
+ // Create canvas
116
+ const canvas = document.createElement("canvas");
117
+ const ctx = canvas.getContext("2d");
118
+
119
+ // Resize for performance
120
+ const maxSize = 400;
121
+ const scale = Math.min(maxSize / img.width, maxSize / img.height, 1);
122
+ canvas.width = img.width * scale;
123
+ canvas.height = img.height * scale;
124
+
125
+ // Draw and extract
126
+ ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
127
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
128
+
129
+ // Extract colors
130
+ const colors = await extractImageColors({
131
+ imageData: Array.from(imageData.data),
132
+ maxColors: 6,
133
+ });
134
+
135
+ // Display palette
136
+ displayPalette(colors.colors);
137
+ };
138
+ };
139
+
140
+ reader.readAsDataURL(file);
141
+ });
140
142
 
141
- function displayPalette(colors) {
142
- const palette = document.getElementById('colorPalette');
143
- palette.innerHTML = colors.map(color => `
143
+ function displayPalette(colors) {
144
+ const palette = document.getElementById("colorPalette");
145
+ palette.innerHTML = colors
146
+ .map(
147
+ (color) => `
144
148
  <div style="
145
149
  background: ${color.hex};
146
150
  width: 100px;
@@ -156,8 +160,10 @@ function displayPalette(colors) {
156
160
  font-size: 12px;
157
161
  ">${color.hex}</span>
158
162
  </div>
159
- `).join('');
160
- }
163
+ `,
164
+ )
165
+ .join("");
166
+ }
161
167
  </script>
162
168
  ```
163
169
 
@@ -176,14 +182,14 @@ class AlbumThemeGenerator {
176
182
  const extraction = await extractImageColors({
177
183
  imageData: pixels,
178
184
  maxColors: 5,
179
- targetChroma: 48 // Optimal for UI
185
+ targetChroma: 48, // Optimal for UI
180
186
  });
181
187
 
182
188
  // Generate Material theme
183
189
  const theme = await generateThemeFromImage({
184
190
  imageData: pixels,
185
- variant: 'expressive', // Creative for music
186
- contrastLevel: 0
191
+ variant: "expressive", // Creative for music
192
+ contrastLevel: 0,
187
193
  });
188
194
 
189
195
  // Create player theme
@@ -201,18 +207,18 @@ class AlbumThemeGenerator {
201
207
  textSecondary: theme.schemes.dark.onSurfaceVariant,
202
208
 
203
209
  // Visualizer colors
204
- visualizerColors: extraction.colors.map(c => c.hex)
210
+ visualizerColors: extraction.colors.map((c) => c.hex),
205
211
  };
206
212
  }
207
213
  }
208
214
 
209
215
  // Usage
210
216
  const player = new AlbumThemeGenerator();
211
- const theme = await player.generateFromArtwork('album.jpg');
217
+ const theme = await player.generateFromArtwork("album.jpg");
212
218
 
213
219
  // Apply theme
214
- document.querySelector('.player').style.background = theme.backgroundGradient;
215
- document.querySelector('.controls').style.color = theme.controlsColor;
220
+ document.querySelector(".player").style.background = theme.backgroundGradient;
221
+ document.querySelector(".controls").style.color = theme.controlsColor;
216
222
  ```
217
223
 
218
224
  ### Logo Brand Color Extraction
@@ -225,35 +231,35 @@ async function extractBrandColors(logoPath) {
225
231
  // Extract with focus on distinct colors
226
232
  const result = await extractImageColors({
227
233
  imageData: pixels,
228
- maxColors: 3, // Most logos have 1-3 colors
229
- minPopulation: 0.05 // 5% minimum for significance
234
+ maxColors: 3, // Most logos have 1-3 colors
235
+ minPopulation: 0.05, // 5% minimum for significance
230
236
  });
231
237
 
232
238
  // Check for disliked colors and fix
233
239
  const fixedColors = await fixDislikedColorsBatch({
234
- colors: result.colors.map(c => c.hex)
240
+ colors: result.colors.map((c) => c.hex),
235
241
  });
236
242
 
237
243
  // Build brand palette
238
244
  const brandPalette = {
239
245
  primary: fixedColors.fixedColors[0],
240
246
  secondary: fixedColors.fixedColors[1] || null,
241
- accent: fixedColors.fixedColors[2] || null
247
+ accent: fixedColors.fixedColors[2] || null,
242
248
  };
243
249
 
244
250
  // Generate full theme from primary
245
251
  const fullTheme = await generateMaterialTheme({
246
252
  sourceColor: brandPalette.primary,
247
- customColors: brandPalette.secondary ? [
248
- { name: 'brand', color: brandPalette.secondary }
249
- ] : []
253
+ customColors: brandPalette.secondary
254
+ ? [{ name: "brand", color: brandPalette.secondary }]
255
+ : [],
250
256
  });
251
257
 
252
258
  return {
253
259
  extractedColors: result.colors,
254
260
  brandPalette,
255
261
  fullTheme,
256
- cssVariables: generateBrandCSS(brandPalette)
262
+ cssVariables: generateBrandCSS(brandPalette),
257
263
  };
258
264
  }
259
265
 
@@ -265,7 +271,7 @@ function generateBrandCSS(palette) {
265
271
  --brand-accent: ${palette.accent || palette.primary};
266
272
 
267
273
  /* Generate shades */
268
- ${generateShades('brand-primary', palette.primary)}
274
+ ${generateShades("brand-primary", palette.primary)}
269
275
  }
270
276
  `;
271
277
  }
@@ -283,7 +289,7 @@ class ProductColorAnalyzer {
283
289
  const extracted = await extractImageColors({
284
290
  imageData: pixels,
285
291
  maxColors: 10,
286
- minPopulation: 0.02 // 2% threshold
292
+ minPopulation: 0.02, // 2% threshold
287
293
  });
288
294
 
289
295
  // Categorize colors
@@ -297,7 +303,7 @@ class ProductColorAnalyzer {
297
303
  accentColors: categorized.accents,
298
304
  availableVariants: variants,
299
305
  colorDescription: this.generateDescription(categorized),
300
- searchTags: this.generateSearchTags(categorized)
306
+ searchTags: this.generateSearchTags(categorized),
301
307
  };
302
308
  }
303
309
 
@@ -306,9 +312,9 @@ class ProductColorAnalyzer {
306
312
  const sorted = colors.sort((a, b) => b.population - a.population);
307
313
 
308
314
  return {
309
- primary: sorted[0], // Most dominant
310
- accents: sorted.slice(1, 4), // Next 3 colors
311
- details: sorted.slice(4) // Remaining colors
315
+ primary: sorted[0], // Most dominant
316
+ accents: sorted.slice(1, 4), // Next 3 colors
317
+ details: sorted.slice(4), // Remaining colors
312
318
  };
313
319
  }
314
320
 
@@ -317,10 +323,10 @@ class ProductColorAnalyzer {
317
323
  const hue = categorized.primary.hct.hue;
318
324
 
319
325
  return [
320
- { name: 'Original', color: categorized.primary.hex },
321
- { name: 'Darker', color: adjustTone(categorized.primary, -20) },
322
- { name: 'Lighter', color: adjustTone(categorized.primary, +20) },
323
- { name: 'Vibrant', color: adjustChroma(categorized.primary, +30) }
326
+ { name: "Original", color: categorized.primary.hex },
327
+ { name: "Darker", color: adjustTone(categorized.primary, -20) },
328
+ { name: "Lighter", color: adjustTone(categorized.primary, +20) },
329
+ { name: "Vibrant", color: adjustChroma(categorized.primary, +30) },
324
330
  ];
325
331
  }
326
332
 
@@ -331,21 +337,21 @@ class ProductColorAnalyzer {
331
337
  const tone = primary.hct.tone;
332
338
 
333
339
  // Generate human-readable description
334
- let description = '';
340
+ let description = "";
335
341
 
336
342
  // Hue description
337
- if (hue >= 0 && hue < 20) description += 'Red';
338
- else if (hue >= 20 && hue < 40) description += 'Orange';
339
- else if (hue >= 40 && hue < 60) description += 'Yellow';
343
+ if (hue >= 0 && hue < 20) description += "Red";
344
+ else if (hue >= 20 && hue < 40) description += "Orange";
345
+ else if (hue >= 40 && hue < 60) description += "Yellow";
340
346
  // ... etc
341
347
 
342
348
  // Saturation
343
- if (chroma < 20) description += ', muted';
344
- else if (chroma > 60) description += ', vibrant';
349
+ if (chroma < 20) description += ", muted";
350
+ else if (chroma > 60) description += ", vibrant";
345
351
 
346
352
  // Lightness
347
- if (tone < 30) description += ', dark';
348
- else if (tone > 70) description += ', light';
353
+ if (tone < 30) description += ", dark";
354
+ else if (tone > 70) description += ", light";
349
355
 
350
356
  return description;
351
357
  }
@@ -363,7 +369,7 @@ async function extractWebsiteTheme(screenshotUrl) {
363
369
  const colors = await extractImageColors({
364
370
  imageData: pixels,
365
371
  maxColors: 8,
366
- targetChroma: 40 // Lower chroma for web UI
372
+ targetChroma: 40, // Lower chroma for web UI
367
373
  });
368
374
 
369
375
  // Identify UI elements
@@ -375,34 +381,34 @@ async function extractWebsiteTheme(screenshotUrl) {
375
381
  primary: identified.brand || colors.colors[0].hex,
376
382
 
377
383
  // Background (lightest color)
378
- background: identified.background || '#ffffff',
384
+ background: identified.background || "#ffffff",
379
385
 
380
386
  // Surface (cards, modals)
381
- surface: identified.surface || '#ffffff',
387
+ surface: identified.surface || "#ffffff",
382
388
 
383
389
  // Text colors
384
- textPrimary: identified.textPrimary || '#1f2937',
385
- textSecondary: identified.textSecondary || '#6b7280',
390
+ textPrimary: identified.textPrimary || "#1f2937",
391
+ textSecondary: identified.textSecondary || "#6b7280",
386
392
 
387
393
  // Borders and dividers
388
- border: identified.border || '#e5e7eb',
394
+ border: identified.border || "#e5e7eb",
389
395
 
390
396
  // Interactive elements
391
397
  link: identified.link || colors.colors[0].hex,
392
- button: identified.button || colors.colors[0].hex
398
+ button: identified.button || colors.colors[0].hex,
393
399
  };
394
400
 
395
401
  return {
396
402
  extractedColors: colors,
397
403
  identifiedTheme: theme,
398
- css: generateThemeCSS(theme)
404
+ css: generateThemeCSS(theme),
399
405
  };
400
406
  }
401
407
 
402
408
  function identifyUIColors(colors) {
403
409
  const identified = {};
404
410
 
405
- colors.forEach(color => {
411
+ colors.forEach((color) => {
406
412
  const { hue, chroma, tone } = color.hct;
407
413
 
408
414
  // Very light colors are likely backgrounds
@@ -451,10 +457,10 @@ function extractFromRegion(imageData, region) {
451
457
  for (let col = x; col < x + width; col++) {
452
458
  const idx = (row * fullWidth + col) * 4;
453
459
  regionPixels.push(
454
- imageData[idx], // R
460
+ imageData[idx], // R
455
461
  imageData[idx + 1], // G
456
462
  imageData[idx + 2], // B
457
- imageData[idx + 3] // A
463
+ imageData[idx + 3], // A
458
464
  );
459
465
  }
460
466
  }
@@ -462,7 +468,7 @@ function extractFromRegion(imageData, region) {
462
468
  // Extract colors from region
463
469
  return extractImageColors({
464
470
  imageData: regionPixels,
465
- maxColors: 5
471
+ maxColors: 5,
466
472
  });
467
473
  }
468
474
 
@@ -479,20 +485,20 @@ async function extractForegroundColors(imageData, backgroundColor) {
479
485
  // Extract all colors
480
486
  const allColors = await extractImageColors({
481
487
  imageData,
482
- maxColors: 10
488
+ maxColors: 10,
483
489
  });
484
490
 
485
491
  // Filter out colors similar to background
486
- const foregroundColors = allColors.colors.filter(color => {
492
+ const foregroundColors = allColors.colors.filter((color) => {
487
493
  const distance = colorDistance(color.hex, backgroundColor);
488
- return distance > 10; // Significant difference
494
+ return distance > 10; // Significant difference
489
495
  });
490
496
 
491
497
  return foregroundColors;
492
498
  }
493
499
 
494
500
  // Usage: Extract product colors without white background
495
- const productColors = await extractForegroundColors(pixels, '#ffffff');
501
+ const productColors = await extractForegroundColors(pixels, "#ffffff");
496
502
  ```
497
503
 
498
504
  ### Weighted Extraction
@@ -511,10 +517,9 @@ function createWeightedPixelArray(imageData, width, height) {
511
517
 
512
518
  // Calculate weight based on distance from center
513
519
  const dist = Math.sqrt(
514
- Math.pow(x - centerX, 2) +
515
- Math.pow(y - centerY, 2)
520
+ Math.pow(x - centerX, 2) + Math.pow(y - centerY, 2),
516
521
  );
517
- const weight = 1 - (dist / maxDist) * 0.5; // 50-100% weight
522
+ const weight = 1 - (dist / maxDist) * 0.5; // 50-100% weight
518
523
 
519
524
  // Add pixel multiple times based on weight
520
525
  const copies = Math.ceil(weight * 3);
@@ -523,7 +528,7 @@ function createWeightedPixelArray(imageData, width, height) {
523
528
  imageData[idx],
524
529
  imageData[idx + 1],
525
530
  imageData[idx + 2],
526
- imageData[idx + 3]
531
+ imageData[idx + 3],
527
532
  );
528
533
  }
529
534
  }
@@ -536,7 +541,7 @@ function createWeightedPixelArray(imageData, width, height) {
536
541
  const weighted = createWeightedPixelArray(pixels, 300, 300);
537
542
  const centralColors = await extractImageColors({
538
543
  imageData: weighted,
539
- maxColors: 5
544
+ maxColors: 5,
540
545
  });
541
546
  ```
542
547
 
@@ -550,14 +555,14 @@ async function extractLikableColors(imageData) {
550
555
  // Extract colors
551
556
  const extracted = await extractImageColors({
552
557
  imageData,
553
- maxColors: 8
558
+ maxColors: 8,
554
559
  });
555
560
 
556
561
  // Check and fix disliked colors
557
- const colorHexes = extracted.colors.map(c => c.hex);
562
+ const colorHexes = extracted.colors.map((c) => c.hex);
558
563
  const fixed = await fixDislikedColorsBatch({
559
564
  colors: colorHexes,
560
- strategy: 'both' // Shift hue and adjust tone
565
+ strategy: "both", // Shift hue and adjust tone
561
566
  });
562
567
 
563
568
  // Map back to full color objects
@@ -567,7 +572,7 @@ async function extractLikableColors(imageData) {
567
572
  ...color,
568
573
  hex: fixed.results[i].fixed,
569
574
  wasFixed: true,
570
- originalHex: color.hex
575
+ originalHex: color.hex,
571
576
  };
572
577
  }
573
578
  return color;
@@ -576,7 +581,7 @@ async function extractLikableColors(imageData) {
576
581
  return {
577
582
  colors: likableColors,
578
583
  fixedCount: fixed.summary.fixed,
579
- originalColors: extracted.colors
584
+ originalColors: extracted.colors,
580
585
  };
581
586
  }
582
587
  ```
@@ -588,17 +593,23 @@ async function extractLikableColors(imageData) {
588
593
  function detectBileZoneColors(colors) {
589
594
  const problematic = [];
590
595
 
591
- colors.forEach(color => {
596
+ colors.forEach((color) => {
592
597
  const { hue, chroma, tone } = color.hct;
593
598
 
594
599
  // Bile zone: dark yellow-greens
595
- if (hue >= 50 && hue <= 120 && // Yellow-green range
596
- chroma >= 20 && chroma <= 50 && // Moderate saturation
597
- tone >= 20 && tone <= 50) { // Dark to medium
600
+ if (
601
+ hue >= 50 &&
602
+ hue <= 120 && // Yellow-green range
603
+ chroma >= 20 &&
604
+ chroma <= 50 && // Moderate saturation
605
+ tone >= 20 &&
606
+ tone <= 50
607
+ ) {
608
+ // Dark to medium
598
609
  problematic.push({
599
610
  color: color.hex,
600
611
  reason: 'Falls in universally disliked "bile zone"',
601
- suggestion: adjustToLikable(color)
612
+ suggestion: adjustToLikable(color),
602
613
  });
603
614
  }
604
615
  });
@@ -610,7 +621,7 @@ function adjustToLikable(color) {
610
621
  // Shift hue away from problematic range
611
622
  let newHue = color.hct.hue;
612
623
  if (newHue >= 50 && newHue <= 120) {
613
- newHue = newHue < 85 ? 40 : 130; // Shift to yellow or green
624
+ newHue = newHue < 85 ? 40 : 130; // Shift to yellow or green
614
625
  }
615
626
 
616
627
  // Increase tone for lighter appearance
@@ -634,24 +645,24 @@ async function preprocessImage(imageUrl) {
634
645
  const scale = Math.min(
635
646
  maxDimension / img.width,
636
647
  maxDimension / img.height,
637
- 1
648
+ 1,
638
649
  );
639
650
 
640
651
  // Apply slight blur to reduce noise
641
- const canvas = document.createElement('canvas');
642
- const ctx = canvas.getContext('2d');
652
+ const canvas = document.createElement("canvas");
653
+ const ctx = canvas.getContext("2d");
643
654
  canvas.width = img.width * scale;
644
655
  canvas.height = img.height * scale;
645
656
 
646
657
  // Enable image smoothing
647
658
  ctx.imageSmoothingEnabled = true;
648
- ctx.imageSmoothingQuality = 'high';
659
+ ctx.imageSmoothingQuality = "high";
649
660
 
650
661
  // Draw scaled image
651
662
  ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
652
663
 
653
664
  // Optional: Apply slight blur
654
- ctx.filter = 'blur(0.5px)';
665
+ ctx.filter = "blur(0.5px)";
655
666
  ctx.drawImage(canvas, 0, 0);
656
667
 
657
668
  return ctx.getImageData(0, 0, canvas.width, canvas.height);
@@ -680,7 +691,7 @@ class ColorExtractionCache {
680
691
  const pixels = await loadImage(imageUrl);
681
692
  const result = await extractImageColors({
682
693
  imageData: pixels,
683
- ...options
694
+ ...options,
684
695
  });
685
696
 
686
697
  // Cache result
@@ -706,7 +717,7 @@ class ColorExtractionCache {
706
717
  ```javascript
707
718
  // Process multiple images efficiently
708
719
  async function batchExtractColors(imageUrls, options = {}) {
709
- const batchSize = 5; // Process 5 at a time
720
+ const batchSize = 5; // Process 5 at a time
710
721
  const results = [];
711
722
 
712
723
  for (let i = 0; i < imageUrls.length; i += batchSize) {
@@ -714,18 +725,18 @@ async function batchExtractColors(imageUrls, options = {}) {
714
725
 
715
726
  // Process batch in parallel
716
727
  const batchResults = await Promise.all(
717
- batch.map(async url => {
728
+ batch.map(async (url) => {
718
729
  try {
719
730
  const pixels = await loadImage(url);
720
731
  return await extractImageColors({
721
732
  imageData: pixels,
722
- ...options
733
+ ...options,
723
734
  });
724
735
  } catch (error) {
725
736
  console.error(`Failed to process ${url}:`, error);
726
737
  return null;
727
738
  }
728
- })
739
+ }),
729
740
  );
730
741
 
731
742
  results.push(...batchResults);
@@ -734,12 +745,12 @@ async function batchExtractColors(imageUrls, options = {}) {
734
745
  if (options.onProgress) {
735
746
  options.onProgress({
736
747
  processed: Math.min(i + batchSize, imageUrls.length),
737
- total: imageUrls.length
748
+ total: imageUrls.length,
738
749
  });
739
750
  }
740
751
  }
741
752
 
742
- return results.filter(r => r !== null);
753
+ return results.filter((r) => r !== null);
743
754
  }
744
755
  ```
745
756
 
@@ -765,7 +776,7 @@ function useImageColors(imageUrl) {
765
776
  const pixels = await loadImageFromUrl(imageUrl);
766
777
  const result = await extractImageColors({
767
778
  imageData: pixels,
768
- maxColors: 5
779
+ maxColors: 5,
769
780
  });
770
781
  setColors(result.colors);
771
782
  } catch (err) {
@@ -811,11 +822,7 @@ function ImagePalette({ imageUrl }) {
811
822
  ```vue
812
823
  <template>
813
824
  <div class="image-theme-generator">
814
- <input
815
- type="file"
816
- @change="handleFileUpload"
817
- accept="image/*"
818
- >
825
+ <input type="file" @change="handleFileUpload" accept="image/*" />
819
826
 
820
827
  <div v-if="loading">Generating theme...</div>
821
828
 
@@ -837,7 +844,7 @@ export default {
837
844
  data() {
838
845
  return {
839
846
  theme: null,
840
- loading: false
847
+ loading: false,
841
848
  };
842
849
  },
843
850
 
@@ -853,7 +860,7 @@ export default {
853
860
  const theme = await this.generateTheme(pixels);
854
861
  this.theme = theme;
855
862
  } catch (error) {
856
- console.error('Failed to generate theme:', error);
863
+ console.error("Failed to generate theme:", error);
857
864
  } finally {
858
865
  this.loading = false;
859
866
  }
@@ -866,10 +873,10 @@ export default {
866
873
  async generateTheme(pixels) {
867
874
  return await generateThemeFromImage({
868
875
  imageData: pixels,
869
- variant: 'tonalSpot'
876
+ variant: "tonalSpot",
870
877
  });
871
- }
872
- }
878
+ },
879
+ },
873
880
  };
874
881
  </script>
875
882
  ```
@@ -879,4 +886,4 @@ export default {
879
886
  - Learn about [Creating Themes](./creating-themes.md) for comprehensive design systems
880
887
  - Explore [CSS Refactoring](./css-refactoring.md) to apply extracted colors
881
888
  - Read [Image Analysis Concepts](../concepts/image-analysis.md) for theory
882
- - Check [Image Extraction Tools](../tools/image-extraction.md) for API reference
889
+ - Check [Image Extraction Tools](../tools/image-extraction.md) for API reference