@xivdyetools/core 1.16.0 → 1.17.2

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 (51) hide show
  1. package/LICENSE +37 -37
  2. package/README.md +436 -437
  3. package/dist/data/locales/de.json +1 -1
  4. package/dist/data/locales/en.json +1 -1
  5. package/dist/data/locales/fr.json +1 -1
  6. package/dist/data/locales/ja.json +1 -1
  7. package/dist/data/locales/ko.json +1 -1
  8. package/dist/data/locales/zh.json +1 -1
  9. package/dist/index.d.ts +2 -2
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js.map +1 -1
  12. package/dist/services/APIService.d.ts +24 -1
  13. package/dist/services/APIService.d.ts.map +1 -1
  14. package/dist/services/APIService.js +38 -41
  15. package/dist/services/APIService.js.map +1 -1
  16. package/dist/services/CharacterColorService.js +16 -16
  17. package/dist/services/CharacterColorService.js.map +1 -1
  18. package/dist/services/DyeService.js +3 -0
  19. package/dist/services/DyeService.js.map +1 -1
  20. package/dist/services/LocalizationService.js +9 -5
  21. package/dist/services/LocalizationService.js.map +1 -1
  22. package/dist/services/PaletteService.js +8 -7
  23. package/dist/services/PaletteService.js.map +1 -1
  24. package/dist/services/PresetService.js +2 -0
  25. package/dist/services/PresetService.js.map +1 -1
  26. package/dist/services/color/ColorConverter.d.ts +3 -0
  27. package/dist/services/color/ColorConverter.d.ts.map +1 -1
  28. package/dist/services/color/ColorConverter.js +23 -4
  29. package/dist/services/color/ColorConverter.js.map +1 -1
  30. package/dist/services/color/ColorblindnessSimulator.js +2 -2
  31. package/dist/services/color/ColorblindnessSimulator.js.map +1 -1
  32. package/dist/services/color/SpectralMixer.d.ts.map +1 -1
  33. package/dist/services/color/SpectralMixer.js +0 -1
  34. package/dist/services/color/SpectralMixer.js.map +1 -1
  35. package/dist/services/dye/DyeDatabase.js +23 -22
  36. package/dist/services/dye/DyeDatabase.js.map +1 -1
  37. package/dist/services/dye/DyeSearch.js +2 -0
  38. package/dist/services/dye/DyeSearch.js.map +1 -1
  39. package/dist/services/dye/HarmonyGenerator.js +2 -0
  40. package/dist/services/dye/HarmonyGenerator.js.map +1 -1
  41. package/dist/services/localization/LocaleRegistry.js +1 -3
  42. package/dist/services/localization/LocaleRegistry.js.map +1 -1
  43. package/dist/services/localization/TranslationProvider.js +1 -0
  44. package/dist/services/localization/TranslationProvider.js.map +1 -1
  45. package/dist/utils/index.js +6 -1
  46. package/dist/utils/index.js.map +1 -1
  47. package/dist/utils/kd-tree.js +6 -4
  48. package/dist/utils/kd-tree.js.map +1 -1
  49. package/dist/version.d.ts +1 -1
  50. package/dist/version.js +1 -1
  51. package/package.json +69 -91
package/README.md CHANGED
@@ -1,437 +1,436 @@
1
- # xivdyetools-core
2
-
3
- > Core color algorithms and dye database for XIV Dye Tools - Environment-agnostic TypeScript library for FFXIV dye color matching, harmony generation, and accessibility checking.
4
-
5
- [![npm version](https://img.shields.io/npm/v/xivdyetools-core)](https://www.npmjs.com/package/xivdyetools-core)
6
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
- [![TypeScript](https://img.shields.io/badge/TypeScript-5.3%2B-blue)](https://www.typescriptlang.org/)
8
-
9
- ## Features
10
-
11
- ✨ **Color Conversion** - RGB ↔ HSV ↔ Hex ↔ LAB ↔ RYB ↔ OKLAB ↔ OKLCH ↔ LCH ↔ HSL
12
- 🎨 **136 FFXIV Dyes** - Complete database with RGB/HSV/metadata
13
- 🖌️ **Advanced Color Mixing** - RYB, OKLAB, HSL, and Spectral (Kubelka-Munk)
14
- 🔬 **Spectral.js Integration** - Physics-based paint mixing (Blue + Yellow = Green!)
15
- 🎯 **Dye Matching** - Find closest dyes to any color
16
- 🌈 **Color Harmonies** - Triadic, complementary, analogous, and more
17
- 🖼️ **Palette Extraction** - K-means++ clustering for multi-color extraction from images
18
- ♿ **Accessibility** - Colorblindness simulation (Brettel 1997)
19
- 📡 **Universalis API** - Price data integration with caching
20
- 🔌 **Pluggable Cache** - Memory, localStorage, Redis support
21
- 🌍 **Environment Agnostic** - Works in Node.js, browsers, edge runtimes
22
- 🗣️ **6 Languages** - English, Japanese, German, French, Korean, Chinese
23
-
24
- ## Installation
25
-
26
- ```bash
27
- npm install xivdyetools-core
28
- ```
29
-
30
- ## Quick Start
31
-
32
- ### Browser (with bundler)
33
-
34
- ```typescript
35
- import { ColorService, DyeService, dyeDatabase } from 'xivdyetools-core';
36
-
37
- // Initialize services
38
- const dyeService = new DyeService(dyeDatabase);
39
-
40
- // Find closest dye to a color
41
- const closestDye = dyeService.findClosestDye('#FF6B6B');
42
- console.log(closestDye.name); // "Coral Pink"
43
-
44
- // Generate color harmonies
45
- const triadicDyes = dyeService.findTriadicDyes('#FF6B6B');
46
- console.log(triadicDyes.map(d => d.name)); // ["Turquoise Green", "Grape Purple"]
47
-
48
- // Color conversions
49
- const rgb = ColorService.hexToRgb('#FF6B6B');
50
- const hsv = ColorService.rgbToHsv(rgb.r, rgb.g, rgb.b);
51
- console.log(hsv); // { h: 0, s: 58.04, v: 100 }
52
- ```
53
-
54
- ### Node.js (Discord bot, API, CLI)
55
-
56
- ```typescript
57
- import {
58
- DyeService,
59
- APIService,
60
- dyeDatabase
61
- } from 'xivdyetools-core';
62
- import Redis from 'ioredis';
63
-
64
- // Initialize with Redis cache (for Discord bots)
65
- const redis = new Redis();
66
- const cacheBackend = new RedisCacheBackend(redis);
67
- const apiService = new APIService(cacheBackend);
68
- const dyeService = new DyeService(dyeDatabase);
69
-
70
- // Fetch live market prices
71
- const priceData = await apiService.getPriceData(5752); // Jet Black Dye
72
- console.log(`${priceData.currentMinPrice} Gil`);
73
-
74
- // Find harmony with pricing
75
- const baseDye = dyeService.findClosestDye('#000000');
76
- const harmonyDyes = dyeService.findComplementaryPair(baseDye.hex);
77
- ```
78
-
79
- ## Core Services
80
-
81
- ### ColorService
82
-
83
- Pure color conversion and manipulation algorithms.
84
-
85
- > **Memory Note**: ColorService uses LRU caches (5 caches × 1000 entries each = up to 5000 cached entries) for performance optimization. For long-running applications or memory-constrained environments, call `ColorService.clearCaches()` periodically to free memory. Each cache entry is approximately 50-100 bytes, so maximum memory usage is ~500KB.
86
-
87
- ```typescript
88
- import { ColorService } from 'xivdyetools-core';
89
-
90
- // Hex ↔ RGB
91
- const rgb = ColorService.hexToRgb('#FF6B6B');
92
- const hex = ColorService.rgbToHex(255, 107, 107);
93
-
94
- // RGB ↔ HSV
95
- const hsv = ColorService.rgbToHsv(255, 107, 107);
96
- const rgbFromHsv = ColorService.hsvToRgb(0, 58.04, 100);
97
-
98
- // Colorblindness simulation
99
- const simulated = ColorService.simulateColorblindness(
100
- { r: 255, g: 0, b: 0 },
101
- 'deuteranopia'
102
- );
103
-
104
- // Color distance (Euclidean in RGB space)
105
- const distance = ColorService.getColorDistance('#FF0000', '#00FF00');
106
-
107
- // LAB color space and DeltaE (perceptual color difference)
108
- const lab = ColorService.hexToLab('#FF6B6B');
109
- const deltaE = ColorService.getDeltaE('#FF0000', '#FF6B6B'); // CIE76 by default
110
- const deltaE2000 = ColorService.getDeltaE('#FF0000', '#FF6B6B', 'cie2000'); // CIEDE2000
111
-
112
- // Color inversion
113
- const inverted = ColorService.invert('#FF6B6B');
114
-
115
- // Cache management (for memory-constrained environments)
116
- ColorService.clearCaches();
117
- const cacheStats = ColorService.getCacheStats();
118
-
119
- // RYB Subtractive Color Mixing (paint-like mixing)
120
- // Blue + Yellow = Green (not gray like RGB!)
121
- const mixed = ColorService.mixColorsRyb('#0000FF', '#FFFF00');
122
- const partialMix = ColorService.mixColorsRyb('#FF0000', '#FFFF00', 0.3); // 30% yellow
123
-
124
- // RYB ↔ RGB conversions
125
- const ryb = ColorService.hexToRyb('#00FF00');
126
- const rgb = ColorService.rybToRgb(0, 255, 255); // Yellow+Blue = Green
127
- const hex = ColorService.rybToHex(255, 255, 0); // Red+Yellow = Orange
128
- ```
129
-
130
- ### DyeService
131
-
132
- FFXIV dye database management and color matching.
133
-
134
- ```typescript
135
- import { DyeService, dyeDatabase } from 'xivdyetools-core';
136
-
137
- const dyeService = new DyeService(dyeDatabase);
138
-
139
- // Database access
140
- const allDyes = dyeService.getAllDyes(); // 136 dyes
141
- const dyeById = dyeService.getDyeById(5752); // By itemID - Jet Black Dye
142
- const dyeByStain = dyeService.getByStainId(1); // By stainID - Snow White
143
- const categories = dyeService.getCategories(); // ['Neutral', 'Red', 'Blue', ...]
144
-
145
- // Color matching
146
- const closest = dyeService.findClosestDye('#FF6B6B');
147
- const nearby = dyeService.findDyesWithinDistance('#FF6B6B', 50, 5);
148
-
149
- // Harmony generation (default: fast hue-based matching)
150
- const triadic = dyeService.findTriadicDyes('#FF6B6B');
151
- const complementary = dyeService.findComplementaryPair('#FF6B6B');
152
- const analogous = dyeService.findAnalogousDyes('#FF6B6B', 30);
153
- const monochromatic = dyeService.findMonochromaticDyes('#FF6B6B', 6);
154
- const splitComplementary = dyeService.findSplitComplementaryDyes('#FF6B6B');
155
-
156
- // DeltaE-based harmony (perceptually accurate matching)
157
- const triadicDeltaE = dyeService.findTriadicDyes('#FF6B6B', {
158
- algorithm: 'deltaE',
159
- deltaEFormula: 'cie2000', // or 'cie76' (faster, default)
160
- });
161
-
162
- // Color space selection for hue rotation (v1.16.0+)
163
- // OKLCH produces more perceptually balanced harmonies
164
- const triadicOklch = dyeService.findTriadicDyes('#FF6B6B', { colorSpace: 'oklch' });
165
- const compLch = dyeService.findComplementaryPair('#FF6B6B', { colorSpace: 'lch' });
166
- // Available spaces: 'hsv' (default), 'oklch', 'lch', 'hsl'
167
-
168
- // Filtering
169
- const redDyes = dyeService.searchByCategory('Red');
170
- const searchResults = dyeService.searchByName('black');
171
- const filtered = dyeService.filterDyes({
172
- category: 'Special',
173
- excludeIds: [5752, 5753],
174
- minPrice: 0,
175
- maxPrice: 10000
176
- });
177
- ```
178
-
179
- ### PaletteService
180
-
181
- Multi-color palette extraction from images using K-means++ clustering.
182
-
183
- ```typescript
184
- import { PaletteService, DyeService, dyeDatabase } from 'xivdyetools-core';
185
-
186
- const paletteService = new PaletteService();
187
- const dyeService = new DyeService(dyeDatabase);
188
-
189
- // Extract from Canvas ImageData
190
- const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
191
- const pixels = PaletteService.pixelDataToRGBFiltered(imageData.data);
192
-
193
- // Extract dominant colors only
194
- const palette = paletteService.extractPalette(pixels, { colorCount: 4 });
195
- // Returns: Array<{ color: RGB, dominance: number }>
196
-
197
- // Extract and match to FFXIV dyes
198
- const matches = paletteService.extractAndMatchPalette(pixels, dyeService, {
199
- colorCount: 4,
200
- maxIterations: 25,
201
- convergenceThreshold: 1.0,
202
- maxSamples: 10000
203
- });
204
- // Returns: Array<{ extracted: RGB, matchedDye: Dye, distance: number, dominance: number }>
205
-
206
- // Helper: Convert raw pixel buffer (RGB, 3 bytes per pixel)
207
- const pixelsFromBuffer = PaletteService.pixelDataToRGB(buffer);
208
-
209
- // Helper: Convert RGBA ImageData, filtering transparent pixels
210
- const pixelsFromCanvas = PaletteService.pixelDataToRGBFiltered(imageData.data);
211
- ```
212
-
213
- ### APIService
214
-
215
- Universalis API integration with pluggable cache backends.
216
-
217
- ```typescript
218
- import { APIService, MemoryCacheBackend } from 'xivdyetools-core';
219
-
220
- // With memory cache (default)
221
- const apiService = new APIService();
222
-
223
- // With custom cache backend
224
- const cache = new MemoryCacheBackend();
225
- const apiService = new APIService(cache);
226
-
227
- // Fetch price data
228
- const priceData = await apiService.getPriceData(5752); // itemID
229
- const pricesWithDC = await apiService.getPriceData(5752, undefined, 'Aether');
230
-
231
- // Batch operations
232
- const prices = await apiService.getPricesForItems([5752, 5753, 5754]);
233
-
234
- // Cache management
235
- await apiService.clearCache();
236
- const stats = await apiService.getCacheStats();
237
-
238
- // API health check
239
- const { available, latency } = await apiService.getAPIStatus();
240
-
241
- // Utility methods
242
- const formatted = APIService.formatPrice(123456); // "123,456G"
243
- const trend = APIService.getPriceTrend(100, 80); // { trend: 'up', ... }
244
- ```
245
-
246
- ## Custom Cache Backends
247
-
248
- Implement the `ICacheBackend` interface for custom storage:
249
-
250
- ```typescript
251
- import { ICacheBackend, CachedData, PriceData } from 'xivdyetools-core';
252
- import Redis from 'ioredis';
253
-
254
- class RedisCacheBackend implements ICacheBackend {
255
- constructor(private redis: Redis) {}
256
-
257
- async get(key: string): Promise<CachedData<PriceData> | null> {
258
- const data = await this.redis.get(key);
259
- return data ? JSON.parse(data) : null;
260
- }
261
-
262
- async set(key: string, value: CachedData<PriceData>): Promise<void> {
263
- await this.redis.set(key, JSON.stringify(value), 'EX', value.ttl / 1000);
264
- }
265
-
266
- async delete(key: string): Promise<void> {
267
- await this.redis.del(key);
268
- }
269
-
270
- async clear(): Promise<void> {
271
- await this.redis.flushdb();
272
- }
273
-
274
- async keys(): Promise<string[]> {
275
- return await this.redis.keys('*');
276
- }
277
- }
278
-
279
- // Use with APIService
280
- const redis = new Redis();
281
- const cache = new RedisCacheBackend(redis);
282
- const apiService = new APIService(cache);
283
- ```
284
-
285
- ## TypeScript Types
286
-
287
- All services are fully typed with TypeScript:
288
-
289
- ```typescript
290
- import type {
291
- Dye,
292
- RGB,
293
- HSV,
294
- LAB,
295
- RYB,
296
- HexColor,
297
- PriceData,
298
- CachedData,
299
- VisionType,
300
- ErrorSeverity,
301
- ICacheBackend,
302
- HarmonyOptions,
303
- HarmonyMatchingAlgorithm,
304
- HarmonyColorSpace,
305
- DeltaEFormula
306
- } from 'xivdyetools-core';
307
- ```
308
-
309
- ## Constants
310
-
311
- Access color theory and API configuration constants:
312
-
313
- ```typescript
314
- import {
315
- RGB_MAX,
316
- HUE_MAX,
317
- VISION_TYPES,
318
- BRETTEL_MATRICES,
319
- UNIVERSALIS_API_BASE,
320
- API_CACHE_TTL
321
- } from 'xivdyetools-core';
322
- ```
323
-
324
- ## Utilities
325
-
326
- Helper functions for common tasks:
327
-
328
- ```typescript
329
- import {
330
- clamp,
331
- lerp,
332
- isValidHexColor,
333
- isValidRGB,
334
- retry,
335
- sleep,
336
- generateChecksum
337
- } from 'xivdyetools-core';
338
-
339
- // Validation
340
- const isValid = isValidHexColor('#FF6B6B'); // true
341
-
342
- // Math
343
- const clamped = clamp(150, 0, 100); // 100
344
- const interpolated = lerp(0, 100, 0.5); // 50
345
-
346
- // Async utilities
347
- await sleep(1000); // Wait 1 second
348
- const result = await retry(() => fetchData(), 3, 1000); // Retry with backoff
349
- ```
350
-
351
- ## Use Cases
352
-
353
- ### Discord Bot
354
- ```typescript
355
- // Implement /harmony command
356
- import { DyeService, dyeDatabase } from 'xivdyetools-core';
357
-
358
- const dyeService = new DyeService(dyeDatabase);
359
- const baseDye = dyeService.findClosestDye(userColor);
360
- const harmonyDyes = dyeService.findTriadicDyes(userColor);
361
- // Render color wheel, send Discord embed
362
- ```
363
-
364
- ### Web App
365
- ```typescript
366
- // Color matcher tool
367
- import { DyeService, dyeDatabase } from 'xivdyetools-core';
368
-
369
- const dyeService = new DyeService(dyeDatabase);
370
- const matchingDyes = dyeService.findDyesWithinDistance(imageColor, 50, 10);
371
- // Display results in UI
372
- ```
373
-
374
- ### CLI Tool
375
- ```typescript
376
- // Color conversion utility
377
- import { ColorService } from 'xivdyetools-core';
378
-
379
- const hex = process.argv[2];
380
- const rgb = ColorService.hexToRgb(hex);
381
- console.log(`RGB: ${rgb.r}, ${rgb.g}, ${rgb.b}`);
382
- ```
383
-
384
- ## Requirements
385
-
386
- - **Node.js** 18.0.0 or higher
387
- - **TypeScript** 5.3 or higher (for development)
388
-
389
- ## Browser Compatibility
390
-
391
- Works in all modern browsers with ES6 module support:
392
- - Chrome/Edge 89+
393
- - Firefox 88+
394
- - Safari 15+
395
-
396
- ## License
397
-
398
- MIT © 2025 Flash Galatine
399
-
400
- See [LICENSE](./LICENSE) for full details.
401
-
402
- ## Legal Notice
403
-
404
- **This is a fan-made tool and is not affiliated with or endorsed by Square Enix Co., Ltd. FINAL FANTASY is a registered trademark of Square Enix Holdings Co., Ltd.**
405
-
406
- ## Coming Soon
407
-
408
- **Budget-Aware Dye Suggestions** - Find affordable alternatives to expensive dyes based on current market prices. See [specification](../xivdyetools-docs/BUDGET_AWARE_SUGGESTIONS.md) for details.
409
-
410
- ## Related Projects
411
-
412
- - [XIV Dye Tools Web App](https://github.com/FlashGalatine/xivdyetools-web-app) - Interactive color tools for FFXIV
413
- - [XIV Dye Tools Discord Worker](https://github.com/FlashGalatine/xivdyetools-discord-worker) - Cloudflare Worker Discord bot using this package
414
-
415
- ## Support
416
-
417
- - **Issues**: [GitHub Issues](https://github.com/FlashGalatine/xivdyetools-core/issues)
418
- - **NPM Package**: [xivdyetools-core](https://www.npmjs.com/package/xivdyetools-core)
419
- - **Documentation**: [Full Docs](https://github.com/FlashGalatine/xivdyetools-core#readme)
420
-
421
- ## Connect With Me
422
-
423
- **Flash Galatine** | Balmung (Crystal)
424
-
425
- 🎮 **FFXIV**: [Lodestone Character](https://na.finalfantasyxiv.com/lodestone/character/7677106/)
426
- 📝 **Blog**: [Project Galatine](https://blog.projectgalatine.com/)
427
- 💻 **GitHub**: [@FlashGalatine](https://github.com/FlashGalatine)
428
- 🐦 **X / Twitter**: [@AsheJunius](https://x.com/AsheJunius)
429
- 📺 **Twitch**: [flashgalatine](https://www.twitch.tv/flashgalatine)
430
- 🌐 **BlueSky**: [projectgalatine.com](https://bsky.app/profile/projectgalatine.com)
431
- ❤️ **Patreon**: [ProjectGalatine](https://patreon.com/ProjectGalatine)
432
- **Ko-Fi**: [flashgalatine](https://ko-fi.com/flashgalatine)
433
- 💬 **Discord**: [Join Server](https://discord.gg/5VUSKTZCe5)
434
-
435
- ---
436
-
437
- **Made with ❤️ for the FFXIV community**
1
+ # xivdyetools-core
2
+
3
+ > Core color algorithms and dye database for XIV Dye Tools - Environment-agnostic TypeScript library for FFXIV dye color matching, harmony generation, and accessibility checking.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/xivdyetools-core)](https://www.npmjs.com/package/xivdyetools-core)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.3%2B-blue)](https://www.typescriptlang.org/)
8
+
9
+ ## Features
10
+
11
+ ✨ **Color Conversion** - RGB ↔ HSV ↔ Hex ↔ LAB ↔ RYB ↔ OKLAB ↔ OKLCH ↔ LCH ↔ HSL
12
+ 🎨 **136 FFXIV Dyes** - Complete database with RGB/HSV/metadata
13
+ 🖌️ **Advanced Color Mixing** - RYB, OKLAB, HSL, and Spectral (Kubelka-Munk)
14
+ 🔬 **Spectral.js Integration** - Physics-based paint mixing (Blue + Yellow = Green!)
15
+ 🎯 **Dye Matching** - Find closest dyes to any color
16
+ 🌈 **Color Harmonies** - Triadic, complementary, analogous, and more
17
+ 🖼️ **Palette Extraction** - K-means++ clustering for multi-color extraction from images
18
+ ♿ **Accessibility** - Colorblindness simulation (Brettel 1997)
19
+ 📡 **Universalis API** - Price data integration with caching
20
+ 🔌 **Pluggable Cache** - Memory, localStorage, Redis support
21
+ 🌍 **Environment Agnostic** - Works in Node.js, browsers, edge runtimes
22
+ 🗣️ **6 Languages** - English, Japanese, German, French, Korean, Chinese
23
+
24
+ ## Installation
25
+
26
+ ```bash
27
+ npm install xivdyetools-core
28
+ ```
29
+
30
+ ## Quick Start
31
+
32
+ ### Browser (with bundler)
33
+
34
+ ```typescript
35
+ import { ColorService, DyeService, dyeDatabase } from 'xivdyetools-core';
36
+
37
+ // Initialize services
38
+ const dyeService = new DyeService(dyeDatabase);
39
+
40
+ // Find closest dye to a color
41
+ const closestDye = dyeService.findClosestDye('#FF6B6B');
42
+ console.log(closestDye.name); // "Coral Pink"
43
+
44
+ // Generate color harmonies
45
+ const triadicDyes = dyeService.findTriadicDyes('#FF6B6B');
46
+ console.log(triadicDyes.map(d => d.name)); // ["Turquoise Green", "Grape Purple"]
47
+
48
+ // Color conversions
49
+ const rgb = ColorService.hexToRgb('#FF6B6B');
50
+ const hsv = ColorService.rgbToHsv(rgb.r, rgb.g, rgb.b);
51
+ console.log(hsv); // { h: 0, s: 58.04, v: 100 }
52
+ ```
53
+
54
+ ### Node.js (Discord bot, API, CLI)
55
+
56
+ ```typescript
57
+ import {
58
+ DyeService,
59
+ APIService,
60
+ dyeDatabase
61
+ } from 'xivdyetools-core';
62
+ import Redis from 'ioredis';
63
+
64
+ // Initialize with Redis cache (for Discord bots)
65
+ const redis = new Redis();
66
+ const cacheBackend = new RedisCacheBackend(redis);
67
+ const apiService = new APIService(cacheBackend);
68
+ const dyeService = new DyeService(dyeDatabase);
69
+
70
+ // Fetch live market prices
71
+ const priceData = await apiService.getPriceData(5752); // Jet Black Dye
72
+ console.log(`${priceData.currentMinPrice} Gil`);
73
+
74
+ // Find harmony with pricing
75
+ const baseDye = dyeService.findClosestDye('#000000');
76
+ const harmonyDyes = dyeService.findComplementaryPair(baseDye.hex);
77
+ ```
78
+
79
+ ## Core Services
80
+
81
+ ### ColorService
82
+
83
+ Pure color conversion and manipulation algorithms.
84
+
85
+ > **Memory Note**: ColorService uses LRU caches (5 caches × 1000 entries each = up to 5000 cached entries) for performance optimization. For long-running applications or memory-constrained environments, call `ColorService.clearCaches()` periodically to free memory. Each cache entry is approximately 50-100 bytes, so maximum memory usage is ~500KB.
86
+
87
+ ```typescript
88
+ import { ColorService } from 'xivdyetools-core';
89
+
90
+ // Hex ↔ RGB
91
+ const rgb = ColorService.hexToRgb('#FF6B6B');
92
+ const hex = ColorService.rgbToHex(255, 107, 107);
93
+
94
+ // RGB ↔ HSV
95
+ const hsv = ColorService.rgbToHsv(255, 107, 107);
96
+ const rgbFromHsv = ColorService.hsvToRgb(0, 58.04, 100);
97
+
98
+ // Colorblindness simulation
99
+ const simulated = ColorService.simulateColorblindness(
100
+ { r: 255, g: 0, b: 0 },
101
+ 'deuteranopia'
102
+ );
103
+
104
+ // Color distance (Euclidean in RGB space)
105
+ const distance = ColorService.getColorDistance('#FF0000', '#00FF00');
106
+
107
+ // LAB color space and DeltaE (perceptual color difference)
108
+ const lab = ColorService.hexToLab('#FF6B6B');
109
+ const deltaE = ColorService.getDeltaE('#FF0000', '#FF6B6B'); // CIE76 by default
110
+ const deltaE2000 = ColorService.getDeltaE('#FF0000', '#FF6B6B', 'cie2000'); // CIEDE2000
111
+
112
+ // Color inversion
113
+ const inverted = ColorService.invert('#FF6B6B');
114
+
115
+ // Cache management (for memory-constrained environments)
116
+ ColorService.clearCaches();
117
+ const cacheStats = ColorService.getCacheStats();
118
+
119
+ // RYB Subtractive Color Mixing (paint-like mixing)
120
+ // Blue + Yellow = Green (not gray like RGB!)
121
+ const mixed = ColorService.mixColorsRyb('#0000FF', '#FFFF00');
122
+ const partialMix = ColorService.mixColorsRyb('#FF0000', '#FFFF00', 0.3); // 30% yellow
123
+
124
+ // RYB ↔ RGB conversions
125
+ const ryb = ColorService.hexToRyb('#00FF00');
126
+ const rgb = ColorService.rybToRgb(0, 255, 255); // Yellow+Blue = Green
127
+ const hex = ColorService.rybToHex(255, 255, 0); // Red+Yellow = Orange
128
+ ```
129
+
130
+ ### DyeService
131
+
132
+ FFXIV dye database management and color matching.
133
+
134
+ ```typescript
135
+ import { DyeService, dyeDatabase } from 'xivdyetools-core';
136
+
137
+ const dyeService = new DyeService(dyeDatabase);
138
+
139
+ // Database access
140
+ const allDyes = dyeService.getAllDyes(); // 136 dyes
141
+ const dyeById = dyeService.getDyeById(5752); // By itemID - Jet Black Dye
142
+ const dyeByStain = dyeService.getByStainId(1); // By stainID - Snow White
143
+ const categories = dyeService.getCategories(); // ['Neutral', 'Red', 'Blue', ...]
144
+
145
+ // Color matching
146
+ const closest = dyeService.findClosestDye('#FF6B6B');
147
+ const nearby = dyeService.findDyesWithinDistance('#FF6B6B', 50, 5);
148
+
149
+ // Harmony generation (default: fast hue-based matching)
150
+ const triadic = dyeService.findTriadicDyes('#FF6B6B');
151
+ const complementary = dyeService.findComplementaryPair('#FF6B6B');
152
+ const analogous = dyeService.findAnalogousDyes('#FF6B6B', 30);
153
+ const monochromatic = dyeService.findMonochromaticDyes('#FF6B6B', 6);
154
+ const splitComplementary = dyeService.findSplitComplementaryDyes('#FF6B6B');
155
+
156
+ // DeltaE-based harmony (perceptually accurate matching)
157
+ const triadicDeltaE = dyeService.findTriadicDyes('#FF6B6B', {
158
+ algorithm: 'deltaE',
159
+ deltaEFormula: 'cie2000', // or 'cie76' (faster, default)
160
+ });
161
+
162
+ // Color space selection for hue rotation (v1.16.0+)
163
+ // OKLCH produces more perceptually balanced harmonies
164
+ const triadicOklch = dyeService.findTriadicDyes('#FF6B6B', { colorSpace: 'oklch' });
165
+ const compLch = dyeService.findComplementaryPair('#FF6B6B', { colorSpace: 'lch' });
166
+ // Available spaces: 'hsv' (default), 'oklch', 'lch', 'hsl'
167
+
168
+ // Filtering
169
+ const redDyes = dyeService.searchByCategory('Red');
170
+ const searchResults = dyeService.searchByName('black');
171
+ const filtered = dyeService.filterDyes({
172
+ category: 'Special',
173
+ excludeIds: [5752, 5753],
174
+ minPrice: 0,
175
+ maxPrice: 10000
176
+ });
177
+ ```
178
+
179
+ ### PaletteService
180
+
181
+ Multi-color palette extraction from images using K-means++ clustering.
182
+
183
+ ```typescript
184
+ import { PaletteService, DyeService, dyeDatabase } from 'xivdyetools-core';
185
+
186
+ const paletteService = new PaletteService();
187
+ const dyeService = new DyeService(dyeDatabase);
188
+
189
+ // Extract from Canvas ImageData
190
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
191
+ const pixels = PaletteService.pixelDataToRGBFiltered(imageData.data);
192
+
193
+ // Extract dominant colors only
194
+ const palette = paletteService.extractPalette(pixels, { colorCount: 4 });
195
+ // Returns: Array<{ color: RGB, dominance: number }>
196
+
197
+ // Extract and match to FFXIV dyes
198
+ const matches = paletteService.extractAndMatchPalette(pixels, dyeService, {
199
+ colorCount: 4,
200
+ maxIterations: 25,
201
+ convergenceThreshold: 1.0,
202
+ maxSamples: 10000
203
+ });
204
+ // Returns: Array<{ extracted: RGB, matchedDye: Dye, distance: number, dominance: number }>
205
+
206
+ // Helper: Convert raw pixel buffer (RGB, 3 bytes per pixel)
207
+ const pixelsFromBuffer = PaletteService.pixelDataToRGB(buffer);
208
+
209
+ // Helper: Convert RGBA ImageData, filtering transparent pixels
210
+ const pixelsFromCanvas = PaletteService.pixelDataToRGBFiltered(imageData.data);
211
+ ```
212
+
213
+ ### APIService
214
+
215
+ Universalis API integration with pluggable cache backends.
216
+
217
+ ```typescript
218
+ import { APIService, MemoryCacheBackend } from 'xivdyetools-core';
219
+
220
+ // With memory cache (default)
221
+ const apiService = new APIService();
222
+
223
+ // With custom cache backend
224
+ const cache = new MemoryCacheBackend();
225
+ const apiService = new APIService(cache);
226
+
227
+ // Fetch price data
228
+ const priceData = await apiService.getPriceData(5752); // itemID
229
+ const pricesWithDC = await apiService.getPriceData(5752, undefined, 'Aether');
230
+
231
+ // Batch operations
232
+ const prices = await apiService.getPricesForItems([5752, 5753, 5754]);
233
+
234
+ // Cache management
235
+ await apiService.clearCache();
236
+ const stats = await apiService.getCacheStats();
237
+
238
+ // API health check
239
+ const { available, latency } = await apiService.getAPIStatus();
240
+
241
+ // Utility methods
242
+ const formatted = APIService.formatPrice(123456); // "123,456G"
243
+ const trend = APIService.getPriceTrend(100, 80); // { trend: 'up', ... }
244
+ ```
245
+
246
+ ## Custom Cache Backends
247
+
248
+ Implement the `ICacheBackend` interface for custom storage:
249
+
250
+ ```typescript
251
+ import { ICacheBackend, CachedData, PriceData } from 'xivdyetools-core';
252
+ import Redis from 'ioredis';
253
+
254
+ class RedisCacheBackend implements ICacheBackend {
255
+ constructor(private redis: Redis) {}
256
+
257
+ async get(key: string): Promise<CachedData<PriceData> | null> {
258
+ const data = await this.redis.get(key);
259
+ return data ? JSON.parse(data) : null;
260
+ }
261
+
262
+ async set(key: string, value: CachedData<PriceData>): Promise<void> {
263
+ await this.redis.set(key, JSON.stringify(value), 'EX', value.ttl / 1000);
264
+ }
265
+
266
+ async delete(key: string): Promise<void> {
267
+ await this.redis.del(key);
268
+ }
269
+
270
+ async clear(): Promise<void> {
271
+ await this.redis.flushdb();
272
+ }
273
+
274
+ async keys(): Promise<string[]> {
275
+ return await this.redis.keys('*');
276
+ }
277
+ }
278
+
279
+ // Use with APIService
280
+ const redis = new Redis();
281
+ const cache = new RedisCacheBackend(redis);
282
+ const apiService = new APIService(cache);
283
+ ```
284
+
285
+ ## TypeScript Types
286
+
287
+ All services are fully typed with TypeScript:
288
+
289
+ ```typescript
290
+ import type {
291
+ Dye,
292
+ RGB,
293
+ HSV,
294
+ LAB,
295
+ RYB,
296
+ HexColor,
297
+ PriceData,
298
+ CachedData,
299
+ VisionType,
300
+ ErrorSeverity,
301
+ ICacheBackend,
302
+ HarmonyOptions,
303
+ HarmonyMatchingAlgorithm,
304
+ HarmonyColorSpace,
305
+ DeltaEFormula
306
+ } from 'xivdyetools-core';
307
+ ```
308
+
309
+ ## Constants
310
+
311
+ Access color theory and API configuration constants:
312
+
313
+ ```typescript
314
+ import {
315
+ RGB_MAX,
316
+ HUE_MAX,
317
+ VISION_TYPES,
318
+ BRETTEL_MATRICES,
319
+ UNIVERSALIS_API_BASE,
320
+ API_CACHE_TTL
321
+ } from 'xivdyetools-core';
322
+ ```
323
+
324
+ ## Utilities
325
+
326
+ Helper functions for common tasks:
327
+
328
+ ```typescript
329
+ import {
330
+ clamp,
331
+ lerp,
332
+ isValidHexColor,
333
+ isValidRGB,
334
+ retry,
335
+ sleep,
336
+ generateChecksum
337
+ } from 'xivdyetools-core';
338
+
339
+ // Validation
340
+ const isValid = isValidHexColor('#FF6B6B'); // true
341
+
342
+ // Math
343
+ const clamped = clamp(150, 0, 100); // 100
344
+ const interpolated = lerp(0, 100, 0.5); // 50
345
+
346
+ // Async utilities
347
+ await sleep(1000); // Wait 1 second
348
+ const result = await retry(() => fetchData(), 3, 1000); // Retry with backoff
349
+ ```
350
+
351
+ ## Use Cases
352
+
353
+ ### Discord Bot
354
+ ```typescript
355
+ // Implement /harmony command
356
+ import { DyeService, dyeDatabase } from 'xivdyetools-core';
357
+
358
+ const dyeService = new DyeService(dyeDatabase);
359
+ const baseDye = dyeService.findClosestDye(userColor);
360
+ const harmonyDyes = dyeService.findTriadicDyes(userColor);
361
+ // Render color wheel, send Discord embed
362
+ ```
363
+
364
+ ### Web App
365
+ ```typescript
366
+ // Color matcher tool
367
+ import { DyeService, dyeDatabase } from 'xivdyetools-core';
368
+
369
+ const dyeService = new DyeService(dyeDatabase);
370
+ const matchingDyes = dyeService.findDyesWithinDistance(imageColor, 50, 10);
371
+ // Display results in UI
372
+ ```
373
+
374
+ ### CLI Tool
375
+ ```typescript
376
+ // Color conversion utility
377
+ import { ColorService } from 'xivdyetools-core';
378
+
379
+ const hex = process.argv[2];
380
+ const rgb = ColorService.hexToRgb(hex);
381
+ console.log(`RGB: ${rgb.r}, ${rgb.g}, ${rgb.b}`);
382
+ ```
383
+
384
+ ## Requirements
385
+
386
+ - **Node.js** 18.0.0 or higher
387
+ - **TypeScript** 5.3 or higher (for development)
388
+
389
+ ## Browser Compatibility
390
+
391
+ Works in all modern browsers with ES6 module support:
392
+ - Chrome/Edge 89+
393
+ - Firefox 88+
394
+ - Safari 15+
395
+
396
+ ## License
397
+
398
+ MIT © 2025-2026 Flash Galatine
399
+
400
+ See [LICENSE](./LICENSE) for full details.
401
+
402
+ ## Legal Notice
403
+
404
+ **This is a fan-made tool and is not affiliated with or endorsed by Square Enix Co., Ltd. FINAL FANTASY is a registered trademark of Square Enix Holdings Co., Ltd.**
405
+
406
+ ## Coming Soon
407
+
408
+ **Budget-Aware Dye Suggestions** - Find affordable alternatives to expensive dyes based on current market prices. See [specification](../xivdyetools-docs/BUDGET_AWARE_SUGGESTIONS.md) for details.
409
+
410
+ ## Related Projects
411
+
412
+ - [XIV Dye Tools Web App](https://github.com/FlashGalatine/xivdyetools-web-app) - Interactive color tools for FFXIV
413
+ - [XIV Dye Tools Discord Worker](https://github.com/FlashGalatine/xivdyetools-discord-worker) - Cloudflare Worker Discord bot using this package
414
+
415
+ ## Support
416
+
417
+ - **Issues**: [GitHub Issues](https://github.com/FlashGalatine/xivdyetools-core/issues)
418
+ - **NPM Package**: [xivdyetools-core](https://www.npmjs.com/package/xivdyetools-core)
419
+ - **Documentation**: [Full Docs](https://github.com/FlashGalatine/xivdyetools-core#readme)
420
+
421
+ ## Connect With Me
422
+
423
+ **Flash Galatine** | Midgardsormr (Aether)
424
+
425
+ 🎮 **FFXIV**: [Lodestone Character](https://na.finalfantasyxiv.com/lodestone/character/7677106/)
426
+ 📝 **Blog**: [Project Galatine](https://blog.projectgalatine.com/)
427
+ 💻 **GitHub**: [@FlashGalatine](https://github.com/FlashGalatine)
428
+ 📺 **Twitch**: [flashgalatine](https://www.twitch.tv/flashgalatine)
429
+ 🌐 **BlueSky**: [projectgalatine.com](https://bsky.app/profile/projectgalatine.com)
430
+ ❤️ **Patreon**: [ProjectGalatine](https://patreon.com/ProjectGalatine)
431
+ **Ko-Fi**: [flashgalatine](https://ko-fi.com/flashgalatine)
432
+ 💬 **Discord**: [Join Server](https://discord.gg/5VUSKTZCe5)
433
+
434
+ ---
435
+
436
+ **Made with ❤️ for the FFXIV community**