chromonym 3.1.0 → 3.2.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 (81) hide show
  1. package/NOTICE.md +243 -1
  2. package/README.md +49 -44
  3. package/dist/convert.d.ts +73 -21
  4. package/dist/convert.d.ts.map +1 -1
  5. package/dist/convert.js +13 -2
  6. package/dist/convert.js.map +1 -1
  7. package/dist/defineColorPalette.d.ts +53 -0
  8. package/dist/defineColorPalette.d.ts.map +1 -0
  9. package/dist/defineColorPalette.js +103 -0
  10. package/dist/defineColorPalette.js.map +1 -0
  11. package/dist/detectFormat.d.ts +26 -3
  12. package/dist/detectFormat.d.ts.map +1 -1
  13. package/dist/detectFormat.js +20 -0
  14. package/dist/detectFormat.js.map +1 -1
  15. package/dist/identify.d.ts +63 -17
  16. package/dist/identify.d.ts.map +1 -1
  17. package/dist/identify.js.map +1 -1
  18. package/dist/index.d.ts +13 -4
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +12 -3
  21. package/dist/index.js.map +1 -1
  22. package/dist/indexing.d.ts +13 -3
  23. package/dist/indexing.d.ts.map +1 -1
  24. package/dist/indexing.js +67 -19
  25. package/dist/indexing.js.map +1 -1
  26. package/dist/palettes/crayola.d.ts +16 -0
  27. package/dist/palettes/crayola.d.ts.map +1 -1
  28. package/dist/palettes/crayola.js +16 -0
  29. package/dist/palettes/crayola.js.map +1 -1
  30. package/dist/palettes/fs595b.d.ts +467 -0
  31. package/dist/palettes/fs595b.d.ts.map +1 -0
  32. package/dist/palettes/fs595b.js +256 -0
  33. package/dist/palettes/fs595b.js.map +1 -0
  34. package/dist/palettes/fs595c.d.ts +1224 -0
  35. package/dist/palettes/fs595c.d.ts.map +1 -0
  36. package/dist/palettes/fs595c.js +633 -0
  37. package/dist/palettes/fs595c.js.map +1 -0
  38. package/dist/palettes/isccNbs.d.ts +562 -0
  39. package/dist/palettes/isccNbs.d.ts.map +1 -0
  40. package/dist/palettes/isccNbs.js +300 -0
  41. package/dist/palettes/isccNbs.js.map +1 -0
  42. package/dist/palettes/nbs.d.ts +578 -0
  43. package/dist/palettes/nbs.d.ts.map +1 -0
  44. package/dist/palettes/nbs.js +309 -0
  45. package/dist/palettes/nbs.js.map +1 -0
  46. package/dist/palettes/ncs.d.ts +3949 -0
  47. package/dist/palettes/ncs.d.ts.map +1 -0
  48. package/dist/palettes/ncs.js +2011 -0
  49. package/dist/palettes/ncs.js.map +1 -0
  50. package/dist/palettes/ntc.d.ts +3170 -0
  51. package/dist/palettes/ntc.d.ts.map +1 -0
  52. package/dist/palettes/ntc.js +1602 -0
  53. package/dist/palettes/ntc.js.map +1 -0
  54. package/dist/palettes/pantone.d.ts +18 -1
  55. package/dist/palettes/pantone.d.ts.map +1 -1
  56. package/dist/palettes/pantone.js +18 -1
  57. package/dist/palettes/pantone.js.map +1 -1
  58. package/dist/palettes/resene.d.ts +2802 -0
  59. package/dist/palettes/resene.d.ts.map +1 -0
  60. package/dist/palettes/resene.js +1422 -0
  61. package/dist/palettes/resene.js.map +1 -0
  62. package/dist/palettes/web.d.ts +10 -0
  63. package/dist/palettes/web.d.ts.map +1 -1
  64. package/dist/palettes/web.js +10 -0
  65. package/dist/palettes/web.js.map +1 -1
  66. package/dist/palettes/x11.d.ts +15 -10
  67. package/dist/palettes/x11.d.ts.map +1 -1
  68. package/dist/palettes/x11.js +15 -10
  69. package/dist/palettes/x11.js.map +1 -1
  70. package/dist/palettes/xkcd.d.ts +1880 -0
  71. package/dist/palettes/xkcd.d.ts.map +1 -0
  72. package/dist/palettes/xkcd.js +955 -0
  73. package/dist/palettes/xkcd.js.map +1 -0
  74. package/dist/resolve.d.ts +58 -14
  75. package/dist/resolve.d.ts.map +1 -1
  76. package/dist/resolve.js +32 -16
  77. package/dist/resolve.js.map +1 -1
  78. package/dist/types.d.ts +47 -0
  79. package/dist/types.d.ts.map +1 -1
  80. package/dist/types.js.map +1 -1
  81. package/package.json +45 -4
package/dist/index.js CHANGED
@@ -5,13 +5,22 @@ export { hslToRgba, rgbaToHsl } from './conversions/hsl.js';
5
5
  export { hsvToRgba, rgbaToHsv } from './conversions/hsv.js';
6
6
  export { pantoneToRgba, rgbaToPantone } from './conversions/pantone.js';
7
7
  export { rgbaToRgb, rgbToRgba } from './conversions/rgb.js';
8
- export { convert, fromRgba, toRgba } from './convert.js';
8
+ export { convert, fromRgba, toRgba, } from './convert.js';
9
+ export { defineColorPalette, } from './defineColorPalette.js';
9
10
  export { detectFormat, isColor } from './detectFormat.js';
10
- export { identify } from './identify.js';
11
+ export { identify, } from './identify.js';
11
12
  export { crayola } from './palettes/crayola.js';
13
+ export { fs595b } from './palettes/fs595b.js';
14
+ export { fs595c } from './palettes/fs595c.js';
15
+ export { isccNbs } from './palettes/isccNbs.js';
16
+ export { nbs } from './palettes/nbs.js';
17
+ export { ncs } from './palettes/ncs.js';
18
+ export { ntc } from './palettes/ntc.js';
12
19
  export { pantone } from './palettes/pantone.js';
20
+ export { resene } from './palettes/resene.js';
13
21
  export { web } from './palettes/web.js';
14
22
  export { x11 } from './palettes/x11.js';
15
- export { resolve } from './resolve.js';
23
+ export { xkcd } from './palettes/xkcd.js';
24
+ export { resolve, } from './resolve.js';
16
25
  export { COLOR_FORMATS } from './types.js';
17
26
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,sDAAsD;AAEtD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACxE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,EAAuB,YAAY,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EAAsB,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAyB,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,EAAyB,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,EAAqB,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAqB,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAqB,OAAO,EAAE,MAAM,cAAc,CAAC;AAwB1D,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC","sourcesContent":["// Public API barrel — explicit named re-exports only (tree-shake friendly).\n// No default export; no side effects at module scope.\n\nexport { hexToRgba, rgbaToHex } from './conversions/hex.js';\nexport { hslToRgba, rgbaToHsl } from './conversions/hsl.js';\nexport { hsvToRgba, rgbaToHsv } from './conversions/hsv.js';\nexport { pantoneToRgba, rgbaToPantone } from './conversions/pantone.js';\nexport { rgbaToRgb, rgbToRgba } from './conversions/rgb.js';\nexport { convert, fromRgba, toRgba } from './convert.js';\nexport { type DetectedFormat, detectFormat, isColor } from './detectFormat.js';\nexport { type IdentifyMatch, identify } from './identify.js';\nexport { type CrayolaColorName, crayola } from './palettes/crayola.js';\nexport { type PantoneColorName, pantone } from './palettes/pantone.js';\nexport { type WebColorName, web } from './palettes/web.js';\nexport { type X11ColorName, x11 } from './palettes/x11.js';\nexport { type ResolveMatch, resolve } from './resolve.js';\nexport type {\n ColorFormat,\n ColorInput,\n ColorValue,\n DistanceMetric,\n HexColor,\n HslInput,\n HslObject,\n HslString,\n HsvInput,\n HsvObject,\n HsvString,\n NormalizeFn,\n Palette,\n Rgba,\n RgbaInput,\n RgbaString,\n RgbaTuple,\n RgbInput,\n RgbObject,\n RgbString,\n RgbTuple,\n} from './types.js';\nexport { COLOR_FORMATS } from './types.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,sDAAsD;AAEtD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACxE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAIL,OAAO,EACP,QAAQ,EACR,MAAM,GACP,MAAM,cAAc,CAAC;AACtB,OAAO,EAEL,kBAAkB,GACnB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAuB,YAAY,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EAIL,QAAQ,GACT,MAAM,eAAe,CAAC;AACvB,OAAO,EAAyB,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,EAAwB,MAAM,EAAE,MAAM,sBAAsB,CAAC;AACpE,OAAO,EAAwB,MAAM,EAAE,MAAM,sBAAsB,CAAC;AACpE,OAAO,EAAyB,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,EAAqB,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAqB,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAqB,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAyB,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,EAAwB,MAAM,EAAE,MAAM,sBAAsB,CAAC;AACpE,OAAO,EAAqB,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAqB,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAsB,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAIL,OAAO,GACR,MAAM,cAAc,CAAC;AAyBtB,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC","sourcesContent":["// Public API barrel — explicit named re-exports only (tree-shake friendly).\n// No default export; no side effects at module scope.\n\nexport { hexToRgba, rgbaToHex } from './conversions/hex.js';\nexport { hslToRgba, rgbaToHsl } from './conversions/hsl.js';\nexport { hsvToRgba, rgbaToHsv } from './conversions/hsv.js';\nexport { pantoneToRgba, rgbaToPantone } from './conversions/pantone.js';\nexport { rgbaToRgb, rgbToRgba } from './conversions/rgb.js';\nexport {\n type ConvertNameOptions,\n type ConvertPaletteOptions,\n type ConvertStructuralOptions,\n convert,\n fromRgba,\n toRgba,\n} from './convert.js';\nexport {\n type DefineColorPaletteInput,\n defineColorPalette,\n} from './defineColorPalette.js';\nexport { type DetectedFormat, detectFormat, isColor } from './detectFormat.js';\nexport {\n type IdentifyMatch,\n type IdentifyOptions,\n type IdentifyRankedOptions,\n identify,\n} from './identify.js';\nexport { type CrayolaColorName, crayola } from './palettes/crayola.js';\nexport { type Fs595bColorName, fs595b } from './palettes/fs595b.js';\nexport { type Fs595cColorName, fs595c } from './palettes/fs595c.js';\nexport { type IsccNbsColorName, isccNbs } from './palettes/isccNbs.js';\nexport { type NbsColorName, nbs } from './palettes/nbs.js';\nexport { type NcsColorName, ncs } from './palettes/ncs.js';\nexport { type NtcColorName, ntc } from './palettes/ntc.js';\nexport { type PantoneColorName, pantone } from './palettes/pantone.js';\nexport { type ReseneColorName, resene } from './palettes/resene.js';\nexport { type WebColorName, web } from './palettes/web.js';\nexport { type X11ColorName, x11 } from './palettes/x11.js';\nexport { type XkcdColorName, xkcd } from './palettes/xkcd.js';\nexport {\n type ResolveFuzzyOptions,\n type ResolveMatch,\n type ResolveOptions,\n resolve,\n} from './resolve.js';\nexport type {\n ColorFormat,\n ColorInput,\n ColorValue,\n DistanceMetric,\n HexColor,\n HslInput,\n HslObject,\n HslString,\n HsvInput,\n HsvObject,\n HsvString,\n NormalizeFn,\n Palette,\n PaletteKey,\n Rgba,\n RgbaInput,\n RgbaString,\n RgbaTuple,\n RgbInput,\n RgbObject,\n RgbString,\n RgbTuple,\n} from './types.js';\nexport { COLOR_FORMATS } from './types.js';\n"]}
@@ -44,9 +44,19 @@ export declare function nearest(target: Rgba, space: AnyPalette, metric: Distanc
44
44
  * most of the gamut on ΔE76/2000); `euclidean-srgb` / `euclidean-linear`
45
45
  * are in their respective channel-unit Euclidean distances.
46
46
  *
47
- * Cost: O(n log n) for the sort vs O(n) for `nearest` — acceptable for
48
- * top-K queries. For strict "what's the single nearest?" stay on
49
- * `nearest` / `identify`.
47
+ * Cost:
48
+ * - `k < n`: O(n · k) via bounded insertion-sort; much faster than a
49
+ * full sort for small k on cheap metrics where allocation dominates.
50
+ * - `k >= n` or `k === undefined`: O(n log n) via full push-and-sort,
51
+ * returning every entry ranked.
52
+ *
53
+ * Tie-breaking: on exact-distance ties, the first-declared entry in
54
+ * the palette's `colors` object wins. Matches the single-match
55
+ * `nearest` contract.
56
+ *
57
+ * `k` normalization is forgiving: fractional values are rounded,
58
+ * negatives clamp to 0, and `NaN` / `±Infinity` collapse to 0
59
+ * (empty result). No throws.
50
60
  */
51
61
  export declare function nearestAll(target: Rgba, space: AnyPalette, metric: DistanceMetric, k?: number): Array<{
52
62
  name: string;
@@ -1 +1 @@
1
- {"version":3,"file":"indexing.d.ts","sourceRoot":"","sources":["../src/indexing.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEhE;;;;;;GAMG;AAEH,KAAK,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;AAKlC;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAWnE;AAID;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAU1E;AAsGD;;;;;;;;;GASG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,cAAc,GAAG,MAAM,CAavF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,IAAI,EACZ,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,cAAc,EACtB,CAAC,CAAC,EAAE,MAAM,GACT,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA2C3C","sourcesContent":["import { hexToRgba } from './conversions/hex.js';\nimport { rgbaToLab, rgbaToLinearRgb, rgbaToOklab } from './math/colorSpace.js';\nimport { deltaE76Squared, deltaE94, deltaE2000, deltaEokSquared } from './math/deltaE.js';\nimport { squaredDistanceRgb } from './math/euclideanDistance.js';\nimport type { DistanceMetric, Palette, Rgba } from './types.js';\n\n/**\n * Per-palette lookup indexes for name resolution and nearest-match.\n *\n * Caches are keyed by the Palette wrapper (WeakMap), so each palette's\n * indexes are built once per process and reused by every caller —\n * including BYO palettes the user supplies.\n */\n\ntype AnyPalette = Palette<string>;\n\nconst nameIndexCache = new WeakMap<AnyPalette, Map<string, string>>();\nconst rgbaIndexCache = new WeakMap<AnyPalette, Array<readonly [string, Rgba]>>();\n\n/**\n * Normalized-name → canonical-key map for a given palette. Built once\n * per palette using its own `normalize` function.\n */\nexport function getNameIndex(space: AnyPalette): Map<string, string> {\n let idx = nameIndexCache.get(space);\n if (idx === undefined) {\n idx = new Map();\n const { normalize } = space;\n for (const key of Object.keys(space.colors)) {\n idx.set(normalize(key), key);\n }\n nameIndexCache.set(space, idx);\n }\n return idx;\n}\n\nconst reverseIndexCache = new WeakMap<AnyPalette, Map<string, string>>();\n\n/**\n * Hex → canonical-key map (reverse of `palette.colors`). Built once per\n * palette. Used by `convert(color, { palette, format: 'NAME' })` for\n * O(1) exact reverse lookup. Returns `undefined` on miss — the caller\n * decides whether that's an error (convert throws; use `identify` for\n * nearest-match semantics).\n *\n * Hex values are lowercased for the key so callers can normalize their\n * input with `rgbaToHex(...).toLowerCase()` before lookup.\n */\nexport function getReverseNameIndex(space: AnyPalette): Map<string, string> {\n let idx = reverseIndexCache.get(space);\n if (idx === undefined) {\n idx = new Map();\n for (const [name, hex] of Object.entries(space.colors)) {\n idx.set((hex as string).toLowerCase(), name);\n }\n reverseIndexCache.set(space, idx);\n }\n return idx;\n}\n\n/**\n * Canonical-key → Rgba entries for a given palette, built by parsing\n * each hex value once. Used by nearest-match lookups (identify, rgbaToPantone).\n */\nfunction getRgbaIndex(space: AnyPalette): Array<readonly [string, Rgba]> {\n let idx = rgbaIndexCache.get(space);\n if (idx === undefined) {\n idx = Object.entries(space.colors).map(([name, hex]) => [name, hexToRgba(hex)] as const);\n rgbaIndexCache.set(space, idx);\n }\n return idx;\n}\n\n/**\n * Find the nearest-named entry in a palette to the given Rgba, using\n * squared Euclidean distance in sRGB (alpha ignored). Used by `identify`\n * and `rgbaToPantone` when metric === 'euclidean-srgb'.\n */\nfunction nearestByRgb(target: Rgba, space: AnyPalette): string {\n const entries = getRgbaIndex(space);\n let bestName = entries[0]?.[0] ?? '';\n let bestDistance = Number.POSITIVE_INFINITY;\n for (const [name, candidate] of entries) {\n const d = squaredDistanceRgb(target, candidate);\n if (d < bestDistance) {\n bestDistance = d;\n bestName = name;\n }\n }\n return bestName;\n}\n\n// Precomputed Lab entries for delta-E metrics. Same shape as rgba index.\ntype LabTriple = readonly [number, number, number];\nconst labIndexCache = new WeakMap<AnyPalette, Array<readonly [string, LabTriple]>>();\n\nfunction getLabIndex(space: AnyPalette): Array<readonly [string, LabTriple]> {\n let idx = labIndexCache.get(space);\n if (idx === undefined) {\n idx = getRgbaIndex(space).map(([name, rgba]) => [name, rgbaToLab(rgba)] as const);\n labIndexCache.set(space, idx);\n }\n return idx;\n}\n\n// Precomputed linear-RGB entries for euclidean-linear metric.\nconst linearIndexCache = new WeakMap<AnyPalette, Array<readonly [string, LabTriple]>>();\n\nfunction getLinearIndex(space: AnyPalette): Array<readonly [string, LabTriple]> {\n let idx = linearIndexCache.get(space);\n if (idx === undefined) {\n idx = getRgbaIndex(space).map(([name, rgba]) => [name, rgbaToLinearRgb(rgba)] as const);\n linearIndexCache.set(space, idx);\n }\n return idx;\n}\n\n// Precomputed OKLAB entries for deltaEok metric.\nconst oklabIndexCache = new WeakMap<AnyPalette, Array<readonly [string, LabTriple]>>();\n\nfunction getOklabIndex(space: AnyPalette): Array<readonly [string, LabTriple]> {\n let idx = oklabIndexCache.get(space);\n if (idx === undefined) {\n idx = getRgbaIndex(space).map(([name, rgba]) => [name, rgbaToOklab(rgba)] as const);\n oklabIndexCache.set(space, idx);\n }\n return idx;\n}\n\n/**\n * Generic argmin over an `[name, value]` entries array with a custom\n * distance function. Factored so the three metric branches in `nearest`\n * share one loop body instead of duplicating it three times.\n */\nfunction argminBy<T>(\n entries: Array<readonly [string, T]>,\n target: T,\n dist: (a: T, b: T) => number,\n): string {\n let bestName = entries[0]?.[0] ?? '';\n let bestDistance = Number.POSITIVE_INFINITY;\n for (const [name, candidate] of entries) {\n const d = dist(target, candidate);\n if (d < bestDistance) {\n bestDistance = d;\n bestName = name;\n }\n }\n return bestName;\n}\n\n// Squared Euclidean on a [number, number, number] triple. Used for the\n// linear-RGB metric (sharing shape with Lab but different meaning).\nfunction squaredDistanceTriple(a: LabTriple, b: LabTriple): number {\n const d0 = a[0] - b[0];\n const d1 = a[1] - b[1];\n const d2 = a[2] - b[2];\n return d0 * d0 + d1 * d1 + d2 * d2;\n}\n\n/**\n * Find the nearest-named entry in a palette under the specified distance\n * metric. Dispatches to the right index (rgba / linear-rgb / Lab) and\n * the right distance function. See `DistanceMetric` in types.ts for how\n * to pick a metric.\n *\n * `metric` is required — callers should pass `space.defaultMetric` if\n * they want the palette's declared preference. A silent default would\n * hide the distance-metric choice from the hot path.\n */\nexport function nearest(target: Rgba, space: AnyPalette, metric: DistanceMetric): string {\n if (metric === 'euclidean-srgb') return nearestByRgb(target, space);\n if (metric === 'euclidean-linear') {\n return argminBy(getLinearIndex(space), rgbaToLinearRgb(target), squaredDistanceTriple);\n }\n if (metric === 'deltaEok') {\n return argminBy(getOklabIndex(space), rgbaToOklab(target), deltaEokSquared);\n }\n const labTarget = rgbaToLab(target);\n const labIndex = getLabIndex(space);\n if (metric === 'deltaE76') return argminBy(labIndex, labTarget, deltaE76Squared);\n if (metric === 'deltaE94') return argminBy(labIndex, labTarget, deltaE94);\n return argminBy(labIndex, labTarget, deltaE2000);\n}\n\n/**\n * Rank every palette entry by distance to `target`. Returns an array of\n * `{ name, distance }` sorted ascending (nearest first). Optional `k`\n * truncates to the top-k matches.\n *\n * Distances are **real** metric values (not squared) — ΔE76 / ΔE94 /\n * ΔE2000 / ΔE-OK are in standard ΔE units (≈1 = just-noticeable for\n * most of the gamut on ΔE76/2000); `euclidean-srgb` / `euclidean-linear`\n * are in their respective channel-unit Euclidean distances.\n *\n * Cost: O(n log n) for the sort vs O(n) for `nearest` — acceptable for\n * top-K queries. For strict \"what's the single nearest?\" stay on\n * `nearest` / `identify`.\n */\nexport function nearestAll(\n target: Rgba,\n space: AnyPalette,\n metric: DistanceMetric,\n k?: number,\n): Array<{ name: string; distance: number }> {\n const ranked: Array<[string, number]> = [];\n\n if (metric === 'euclidean-srgb') {\n for (const [name, candidate] of getRgbaIndex(space)) {\n ranked.push([name, Math.sqrt(squaredDistanceRgb(target, candidate))]);\n }\n } else if (metric === 'euclidean-linear') {\n const tgt = rgbaToLinearRgb(target);\n for (const [name, candidate] of getLinearIndex(space)) {\n ranked.push([name, Math.sqrt(squaredDistanceTriple(tgt, candidate))]);\n }\n } else if (metric === 'deltaEok') {\n const tgt = rgbaToOklab(target);\n for (const [name, candidate] of getOklabIndex(space)) {\n ranked.push([name, Math.sqrt(deltaEokSquared(tgt, candidate))]);\n }\n } else {\n const tgt = rgbaToLab(target);\n const labIndex = getLabIndex(space);\n if (metric === 'deltaE76') {\n for (const [name, candidate] of labIndex) {\n ranked.push([name, Math.sqrt(deltaE76Squared(tgt, candidate))]);\n }\n } else if (metric === 'deltaE94') {\n for (const [name, candidate] of labIndex) {\n ranked.push([name, deltaE94(tgt, candidate)]);\n }\n } else {\n for (const [name, candidate] of labIndex) {\n ranked.push([name, deltaE2000(tgt, candidate)]);\n }\n }\n }\n\n ranked.sort((a, b) => a[1] - b[1]);\n const limit = k === undefined ? ranked.length : Math.min(Math.max(k, 0), ranked.length);\n const result: Array<{ name: string; distance: number }> = new Array(limit);\n for (let i = 0; i < limit; i++) {\n const entry = ranked[i];\n if (entry !== undefined) result[i] = { name: entry[0], distance: entry[1] };\n }\n return result;\n}\n"]}
1
+ {"version":3,"file":"indexing.d.ts","sourceRoot":"","sources":["../src/indexing.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEhE;;;;;;GAMG;AAEH,KAAK,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;AAKlC;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAWnE;AAID;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAU1E;AAsGD;;;;;;;;;GASG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,cAAc,GAAG,MAAM,CAavF;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,IAAI,EACZ,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,cAAc,EACtB,CAAC,CAAC,EAAE,MAAM,GACT,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAkF3C","sourcesContent":["import { hexToRgba } from './conversions/hex.js';\nimport { rgbaToLab, rgbaToLinearRgb, rgbaToOklab } from './math/colorSpace.js';\nimport { deltaE76Squared, deltaE94, deltaE2000, deltaEokSquared } from './math/deltaE.js';\nimport { squaredDistanceRgb } from './math/euclideanDistance.js';\nimport type { DistanceMetric, Palette, Rgba } from './types.js';\n\n/**\n * Per-palette lookup indexes for name resolution and nearest-match.\n *\n * Caches are keyed by the Palette wrapper (WeakMap), so each palette's\n * indexes are built once per process and reused by every caller —\n * including BYO palettes the user supplies.\n */\n\ntype AnyPalette = Palette<string>;\n\nconst nameIndexCache = new WeakMap<AnyPalette, Map<string, string>>();\nconst rgbaIndexCache = new WeakMap<AnyPalette, Array<readonly [string, Rgba]>>();\n\n/**\n * Normalized-name → canonical-key map for a given palette. Built once\n * per palette using its own `normalize` function.\n */\nexport function getNameIndex(space: AnyPalette): Map<string, string> {\n let idx = nameIndexCache.get(space);\n if (idx === undefined) {\n idx = new Map();\n const { normalize } = space;\n for (const key of Object.keys(space.colors)) {\n idx.set(normalize(key), key);\n }\n nameIndexCache.set(space, idx);\n }\n return idx;\n}\n\nconst reverseIndexCache = new WeakMap<AnyPalette, Map<string, string>>();\n\n/**\n * Hex → canonical-key map (reverse of `palette.colors`). Built once per\n * palette. Used by `convert(color, { palette, format: 'NAME' })` for\n * O(1) exact reverse lookup. Returns `undefined` on miss — the caller\n * decides whether that's an error (convert throws; use `identify` for\n * nearest-match semantics).\n *\n * Hex values are lowercased for the key so callers can normalize their\n * input with `rgbaToHex(...).toLowerCase()` before lookup.\n */\nexport function getReverseNameIndex(space: AnyPalette): Map<string, string> {\n let idx = reverseIndexCache.get(space);\n if (idx === undefined) {\n idx = new Map();\n for (const [name, hex] of Object.entries(space.colors)) {\n idx.set((hex as string).toLowerCase(), name);\n }\n reverseIndexCache.set(space, idx);\n }\n return idx;\n}\n\n/**\n * Canonical-key → Rgba entries for a given palette, built by parsing\n * each hex value once. Used by nearest-match lookups (identify, rgbaToPantone).\n */\nfunction getRgbaIndex(space: AnyPalette): Array<readonly [string, Rgba]> {\n let idx = rgbaIndexCache.get(space);\n if (idx === undefined) {\n idx = Object.entries(space.colors).map(([name, hex]) => [name, hexToRgba(hex)] as const);\n rgbaIndexCache.set(space, idx);\n }\n return idx;\n}\n\n/**\n * Find the nearest-named entry in a palette to the given Rgba, using\n * squared Euclidean distance in sRGB (alpha ignored). Used by `identify`\n * and `rgbaToPantone` when metric === 'euclidean-srgb'.\n */\nfunction nearestByRgb(target: Rgba, space: AnyPalette): string {\n const entries = getRgbaIndex(space);\n let bestName = entries[0]?.[0] ?? '';\n let bestDistance = Number.POSITIVE_INFINITY;\n for (const [name, candidate] of entries) {\n const d = squaredDistanceRgb(target, candidate);\n if (d < bestDistance) {\n bestDistance = d;\n bestName = name;\n }\n }\n return bestName;\n}\n\n// Precomputed Lab entries for delta-E metrics. Same shape as rgba index.\ntype LabTriple = readonly [number, number, number];\nconst labIndexCache = new WeakMap<AnyPalette, Array<readonly [string, LabTriple]>>();\n\nfunction getLabIndex(space: AnyPalette): Array<readonly [string, LabTriple]> {\n let idx = labIndexCache.get(space);\n if (idx === undefined) {\n idx = getRgbaIndex(space).map(([name, rgba]) => [name, rgbaToLab(rgba)] as const);\n labIndexCache.set(space, idx);\n }\n return idx;\n}\n\n// Precomputed linear-RGB entries for euclidean-linear metric.\nconst linearIndexCache = new WeakMap<AnyPalette, Array<readonly [string, LabTriple]>>();\n\nfunction getLinearIndex(space: AnyPalette): Array<readonly [string, LabTriple]> {\n let idx = linearIndexCache.get(space);\n if (idx === undefined) {\n idx = getRgbaIndex(space).map(([name, rgba]) => [name, rgbaToLinearRgb(rgba)] as const);\n linearIndexCache.set(space, idx);\n }\n return idx;\n}\n\n// Precomputed OKLAB entries for deltaEok metric.\nconst oklabIndexCache = new WeakMap<AnyPalette, Array<readonly [string, LabTriple]>>();\n\nfunction getOklabIndex(space: AnyPalette): Array<readonly [string, LabTriple]> {\n let idx = oklabIndexCache.get(space);\n if (idx === undefined) {\n idx = getRgbaIndex(space).map(([name, rgba]) => [name, rgbaToOklab(rgba)] as const);\n oklabIndexCache.set(space, idx);\n }\n return idx;\n}\n\n/**\n * Generic argmin over an `[name, value]` entries array with a custom\n * distance function. Factored so the three metric branches in `nearest`\n * share one loop body instead of duplicating it three times.\n */\nfunction argminBy<T>(\n entries: Array<readonly [string, T]>,\n target: T,\n dist: (a: T, b: T) => number,\n): string {\n let bestName = entries[0]?.[0] ?? '';\n let bestDistance = Number.POSITIVE_INFINITY;\n for (const [name, candidate] of entries) {\n const d = dist(target, candidate);\n if (d < bestDistance) {\n bestDistance = d;\n bestName = name;\n }\n }\n return bestName;\n}\n\n// Squared Euclidean on a [number, number, number] triple. Used for the\n// linear-RGB metric (sharing shape with Lab but different meaning).\nfunction squaredDistanceTriple(a: LabTriple, b: LabTriple): number {\n const d0 = a[0] - b[0];\n const d1 = a[1] - b[1];\n const d2 = a[2] - b[2];\n return d0 * d0 + d1 * d1 + d2 * d2;\n}\n\n/**\n * Find the nearest-named entry in a palette under the specified distance\n * metric. Dispatches to the right index (rgba / linear-rgb / Lab) and\n * the right distance function. See `DistanceMetric` in types.ts for how\n * to pick a metric.\n *\n * `metric` is required — callers should pass `space.defaultMetric` if\n * they want the palette's declared preference. A silent default would\n * hide the distance-metric choice from the hot path.\n */\nexport function nearest(target: Rgba, space: AnyPalette, metric: DistanceMetric): string {\n if (metric === 'euclidean-srgb') return nearestByRgb(target, space);\n if (metric === 'euclidean-linear') {\n return argminBy(getLinearIndex(space), rgbaToLinearRgb(target), squaredDistanceTriple);\n }\n if (metric === 'deltaEok') {\n return argminBy(getOklabIndex(space), rgbaToOklab(target), deltaEokSquared);\n }\n const labTarget = rgbaToLab(target);\n const labIndex = getLabIndex(space);\n if (metric === 'deltaE76') return argminBy(labIndex, labTarget, deltaE76Squared);\n if (metric === 'deltaE94') return argminBy(labIndex, labTarget, deltaE94);\n return argminBy(labIndex, labTarget, deltaE2000);\n}\n\n/**\n * Rank every palette entry by distance to `target`. Returns an array of\n * `{ name, distance }` sorted ascending (nearest first). Optional `k`\n * truncates to the top-k matches.\n *\n * Distances are **real** metric values (not squared) — ΔE76 / ΔE94 /\n * ΔE2000 / ΔE-OK are in standard ΔE units (≈1 = just-noticeable for\n * most of the gamut on ΔE76/2000); `euclidean-srgb` / `euclidean-linear`\n * are in their respective channel-unit Euclidean distances.\n *\n * Cost:\n * - `k < n`: O(n · k) via bounded insertion-sort; much faster than a\n * full sort for small k on cheap metrics where allocation dominates.\n * - `k >= n` or `k === undefined`: O(n log n) via full push-and-sort,\n * returning every entry ranked.\n *\n * Tie-breaking: on exact-distance ties, the first-declared entry in\n * the palette's `colors` object wins. Matches the single-match\n * `nearest` contract.\n *\n * `k` normalization is forgiving: fractional values are rounded,\n * negatives clamp to 0, and `NaN` / `±Infinity` collapse to 0\n * (empty result). No throws.\n */\nexport function nearestAll(\n target: Rgba,\n space: AnyPalette,\n metric: DistanceMetric,\n k?: number,\n): Array<{ name: string; distance: number }> {\n const rgbaIndex = getRgbaIndex(space);\n const n = rgbaIndex.length;\n\n // k normalization. Forgiving by design: fractional values are rounded,\n // negatives clamp to 0, NaN / ±Infinity collapse to 0 (empty result).\n // A previous version crashed with `RangeError: Invalid array length`\n // on NaN / fractional k because `new Array(NaN)` throws; the\n // `Number.isFinite` guard eliminates that class of bug.\n const safeK = k === undefined ? n : Number.isFinite(k) ? Math.max(0, Math.round(k)) : 0;\n if (safeK === 0) return [];\n\n // When the caller wants at least every entry ranked, the bounded\n // insertion-sort gives no benefit — it degenerates to O(n^2) on a\n // full result set. Use the plain push-and-sort path instead.\n // When safeK < n, the bounded insertion-sort keeps only the top k\n // entries seen so far, cutting work from O(n log n) to O(n · k)\n // with a much smaller constant; the win is dramatic for cheap\n // metrics (deltaEok at k=5 / 1950-entry ncs measured ~50x) where\n // the full sort's allocation dominates the distance math.\n const useBounded = safeK < n;\n\n const best: Array<{ name: string; distance: number }> = [];\n\n // Single per-candidate branch, chosen once up front. `offer` either\n // pushes unconditionally (full-sort path) or inserts into a bounded\n // sorted window (bounded path). Splitting the switch and the merge\n // strategy into a closure avoids a 6-way metric switch × 2 modes\n // duplication.\n const offer = useBounded\n ? (name: string, dist: number): void => {\n if (best.length < safeK) {\n let j = best.length;\n while (j > 0 && (best[j - 1] as { distance: number }).distance > dist) j--;\n best.splice(j, 0, { name, distance: dist });\n } else if (dist < (best[safeK - 1] as { distance: number }).distance) {\n let j = safeK - 1;\n while (j > 0 && (best[j - 1] as { distance: number }).distance > dist) j--;\n best.splice(j, 0, { name, distance: dist });\n best.pop();\n }\n }\n : (name: string, dist: number): void => {\n best.push({ name, distance: dist });\n };\n\n if (metric === 'euclidean-srgb') {\n for (const [name, candidate] of rgbaIndex) {\n offer(name, Math.sqrt(squaredDistanceRgb(target, candidate)));\n }\n } else if (metric === 'euclidean-linear') {\n const tgt = rgbaToLinearRgb(target);\n for (const [name, candidate] of getLinearIndex(space)) {\n offer(name, Math.sqrt(squaredDistanceTriple(tgt, candidate)));\n }\n } else if (metric === 'deltaEok') {\n const tgt = rgbaToOklab(target);\n for (const [name, candidate] of getOklabIndex(space)) {\n offer(name, Math.sqrt(deltaEokSquared(tgt, candidate)));\n }\n } else {\n const tgt = rgbaToLab(target);\n const labIndex = getLabIndex(space);\n if (metric === 'deltaE76') {\n for (const [name, candidate] of labIndex) {\n offer(name, Math.sqrt(deltaE76Squared(tgt, candidate)));\n }\n } else if (metric === 'deltaE94') {\n for (const [name, candidate] of labIndex) {\n offer(name, deltaE94(tgt, candidate));\n }\n } else {\n for (const [name, candidate] of labIndex) {\n offer(name, deltaE2000(tgt, candidate));\n }\n }\n }\n\n if (!useBounded) {\n best.sort((a, b) => a.distance - b.distance);\n }\n return best;\n}\n"]}
package/dist/indexing.js CHANGED
@@ -163,27 +163,80 @@ export function nearest(target, space, metric) {
163
163
  * most of the gamut on ΔE76/2000); `euclidean-srgb` / `euclidean-linear`
164
164
  * are in their respective channel-unit Euclidean distances.
165
165
  *
166
- * Cost: O(n log n) for the sort vs O(n) for `nearest` — acceptable for
167
- * top-K queries. For strict "what's the single nearest?" stay on
168
- * `nearest` / `identify`.
166
+ * Cost:
167
+ * - `k < n`: O(n · k) via bounded insertion-sort; much faster than a
168
+ * full sort for small k on cheap metrics where allocation dominates.
169
+ * - `k >= n` or `k === undefined`: O(n log n) via full push-and-sort,
170
+ * returning every entry ranked.
171
+ *
172
+ * Tie-breaking: on exact-distance ties, the first-declared entry in
173
+ * the palette's `colors` object wins. Matches the single-match
174
+ * `nearest` contract.
175
+ *
176
+ * `k` normalization is forgiving: fractional values are rounded,
177
+ * negatives clamp to 0, and `NaN` / `±Infinity` collapse to 0
178
+ * (empty result). No throws.
169
179
  */
170
180
  export function nearestAll(target, space, metric, k) {
171
- const ranked = [];
181
+ const rgbaIndex = getRgbaIndex(space);
182
+ const n = rgbaIndex.length;
183
+ // k normalization. Forgiving by design: fractional values are rounded,
184
+ // negatives clamp to 0, NaN / ±Infinity collapse to 0 (empty result).
185
+ // A previous version crashed with `RangeError: Invalid array length`
186
+ // on NaN / fractional k because `new Array(NaN)` throws; the
187
+ // `Number.isFinite` guard eliminates that class of bug.
188
+ const safeK = k === undefined ? n : Number.isFinite(k) ? Math.max(0, Math.round(k)) : 0;
189
+ if (safeK === 0)
190
+ return [];
191
+ // When the caller wants at least every entry ranked, the bounded
192
+ // insertion-sort gives no benefit — it degenerates to O(n^2) on a
193
+ // full result set. Use the plain push-and-sort path instead.
194
+ // When safeK < n, the bounded insertion-sort keeps only the top k
195
+ // entries seen so far, cutting work from O(n log n) to O(n · k)
196
+ // with a much smaller constant; the win is dramatic for cheap
197
+ // metrics (deltaEok at k=5 / 1950-entry ncs measured ~50x) where
198
+ // the full sort's allocation dominates the distance math.
199
+ const useBounded = safeK < n;
200
+ const best = [];
201
+ // Single per-candidate branch, chosen once up front. `offer` either
202
+ // pushes unconditionally (full-sort path) or inserts into a bounded
203
+ // sorted window (bounded path). Splitting the switch and the merge
204
+ // strategy into a closure avoids a 6-way metric switch × 2 modes
205
+ // duplication.
206
+ const offer = useBounded
207
+ ? (name, dist) => {
208
+ if (best.length < safeK) {
209
+ let j = best.length;
210
+ while (j > 0 && best[j - 1].distance > dist)
211
+ j--;
212
+ best.splice(j, 0, { name, distance: dist });
213
+ }
214
+ else if (dist < best[safeK - 1].distance) {
215
+ let j = safeK - 1;
216
+ while (j > 0 && best[j - 1].distance > dist)
217
+ j--;
218
+ best.splice(j, 0, { name, distance: dist });
219
+ best.pop();
220
+ }
221
+ }
222
+ : (name, dist) => {
223
+ best.push({ name, distance: dist });
224
+ };
172
225
  if (metric === 'euclidean-srgb') {
173
- for (const [name, candidate] of getRgbaIndex(space)) {
174
- ranked.push([name, Math.sqrt(squaredDistanceRgb(target, candidate))]);
226
+ for (const [name, candidate] of rgbaIndex) {
227
+ offer(name, Math.sqrt(squaredDistanceRgb(target, candidate)));
175
228
  }
176
229
  }
177
230
  else if (metric === 'euclidean-linear') {
178
231
  const tgt = rgbaToLinearRgb(target);
179
232
  for (const [name, candidate] of getLinearIndex(space)) {
180
- ranked.push([name, Math.sqrt(squaredDistanceTriple(tgt, candidate))]);
233
+ offer(name, Math.sqrt(squaredDistanceTriple(tgt, candidate)));
181
234
  }
182
235
  }
183
236
  else if (metric === 'deltaEok') {
184
237
  const tgt = rgbaToOklab(target);
185
238
  for (const [name, candidate] of getOklabIndex(space)) {
186
- ranked.push([name, Math.sqrt(deltaEokSquared(tgt, candidate))]);
239
+ offer(name, Math.sqrt(deltaEokSquared(tgt, candidate)));
187
240
  }
188
241
  }
189
242
  else {
@@ -191,28 +244,23 @@ export function nearestAll(target, space, metric, k) {
191
244
  const labIndex = getLabIndex(space);
192
245
  if (metric === 'deltaE76') {
193
246
  for (const [name, candidate] of labIndex) {
194
- ranked.push([name, Math.sqrt(deltaE76Squared(tgt, candidate))]);
247
+ offer(name, Math.sqrt(deltaE76Squared(tgt, candidate)));
195
248
  }
196
249
  }
197
250
  else if (metric === 'deltaE94') {
198
251
  for (const [name, candidate] of labIndex) {
199
- ranked.push([name, deltaE94(tgt, candidate)]);
252
+ offer(name, deltaE94(tgt, candidate));
200
253
  }
201
254
  }
202
255
  else {
203
256
  for (const [name, candidate] of labIndex) {
204
- ranked.push([name, deltaE2000(tgt, candidate)]);
257
+ offer(name, deltaE2000(tgt, candidate));
205
258
  }
206
259
  }
207
260
  }
208
- ranked.sort((a, b) => a[1] - b[1]);
209
- const limit = k === undefined ? ranked.length : Math.min(Math.max(k, 0), ranked.length);
210
- const result = new Array(limit);
211
- for (let i = 0; i < limit; i++) {
212
- const entry = ranked[i];
213
- if (entry !== undefined)
214
- result[i] = { name: entry[0], distance: entry[1] };
261
+ if (!useBounded) {
262
+ best.sort((a, b) => a.distance - b.distance);
215
263
  }
216
- return result;
264
+ return best;
217
265
  }
218
266
  //# sourceMappingURL=indexing.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"indexing.js","sourceRoot":"","sources":["../src/indexing.ts"],"names":[],"mappings":"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","sourcesContent":["import { hexToRgba } from './conversions/hex.js';\nimport { rgbaToLab, rgbaToLinearRgb, rgbaToOklab } from './math/colorSpace.js';\nimport { deltaE76Squared, deltaE94, deltaE2000, deltaEokSquared } from './math/deltaE.js';\nimport { squaredDistanceRgb } from './math/euclideanDistance.js';\nimport type { DistanceMetric, Palette, Rgba } from './types.js';\n\n/**\n * Per-palette lookup indexes for name resolution and nearest-match.\n *\n * Caches are keyed by the Palette wrapper (WeakMap), so each palette's\n * indexes are built once per process and reused by every caller —\n * including BYO palettes the user supplies.\n */\n\ntype AnyPalette = Palette<string>;\n\nconst nameIndexCache = new WeakMap<AnyPalette, Map<string, string>>();\nconst rgbaIndexCache = new WeakMap<AnyPalette, Array<readonly [string, Rgba]>>();\n\n/**\n * Normalized-name → canonical-key map for a given palette. Built once\n * per palette using its own `normalize` function.\n */\nexport function getNameIndex(space: AnyPalette): Map<string, string> {\n let idx = nameIndexCache.get(space);\n if (idx === undefined) {\n idx = new Map();\n const { normalize } = space;\n for (const key of Object.keys(space.colors)) {\n idx.set(normalize(key), key);\n }\n nameIndexCache.set(space, idx);\n }\n return idx;\n}\n\nconst reverseIndexCache = new WeakMap<AnyPalette, Map<string, string>>();\n\n/**\n * Hex → canonical-key map (reverse of `palette.colors`). Built once per\n * palette. Used by `convert(color, { palette, format: 'NAME' })` for\n * O(1) exact reverse lookup. Returns `undefined` on miss — the caller\n * decides whether that's an error (convert throws; use `identify` for\n * nearest-match semantics).\n *\n * Hex values are lowercased for the key so callers can normalize their\n * input with `rgbaToHex(...).toLowerCase()` before lookup.\n */\nexport function getReverseNameIndex(space: AnyPalette): Map<string, string> {\n let idx = reverseIndexCache.get(space);\n if (idx === undefined) {\n idx = new Map();\n for (const [name, hex] of Object.entries(space.colors)) {\n idx.set((hex as string).toLowerCase(), name);\n }\n reverseIndexCache.set(space, idx);\n }\n return idx;\n}\n\n/**\n * Canonical-key → Rgba entries for a given palette, built by parsing\n * each hex value once. Used by nearest-match lookups (identify, rgbaToPantone).\n */\nfunction getRgbaIndex(space: AnyPalette): Array<readonly [string, Rgba]> {\n let idx = rgbaIndexCache.get(space);\n if (idx === undefined) {\n idx = Object.entries(space.colors).map(([name, hex]) => [name, hexToRgba(hex)] as const);\n rgbaIndexCache.set(space, idx);\n }\n return idx;\n}\n\n/**\n * Find the nearest-named entry in a palette to the given Rgba, using\n * squared Euclidean distance in sRGB (alpha ignored). Used by `identify`\n * and `rgbaToPantone` when metric === 'euclidean-srgb'.\n */\nfunction nearestByRgb(target: Rgba, space: AnyPalette): string {\n const entries = getRgbaIndex(space);\n let bestName = entries[0]?.[0] ?? '';\n let bestDistance = Number.POSITIVE_INFINITY;\n for (const [name, candidate] of entries) {\n const d = squaredDistanceRgb(target, candidate);\n if (d < bestDistance) {\n bestDistance = d;\n bestName = name;\n }\n }\n return bestName;\n}\n\n// Precomputed Lab entries for delta-E metrics. Same shape as rgba index.\ntype LabTriple = readonly [number, number, number];\nconst labIndexCache = new WeakMap<AnyPalette, Array<readonly [string, LabTriple]>>();\n\nfunction getLabIndex(space: AnyPalette): Array<readonly [string, LabTriple]> {\n let idx = labIndexCache.get(space);\n if (idx === undefined) {\n idx = getRgbaIndex(space).map(([name, rgba]) => [name, rgbaToLab(rgba)] as const);\n labIndexCache.set(space, idx);\n }\n return idx;\n}\n\n// Precomputed linear-RGB entries for euclidean-linear metric.\nconst linearIndexCache = new WeakMap<AnyPalette, Array<readonly [string, LabTriple]>>();\n\nfunction getLinearIndex(space: AnyPalette): Array<readonly [string, LabTriple]> {\n let idx = linearIndexCache.get(space);\n if (idx === undefined) {\n idx = getRgbaIndex(space).map(([name, rgba]) => [name, rgbaToLinearRgb(rgba)] as const);\n linearIndexCache.set(space, idx);\n }\n return idx;\n}\n\n// Precomputed OKLAB entries for deltaEok metric.\nconst oklabIndexCache = new WeakMap<AnyPalette, Array<readonly [string, LabTriple]>>();\n\nfunction getOklabIndex(space: AnyPalette): Array<readonly [string, LabTriple]> {\n let idx = oklabIndexCache.get(space);\n if (idx === undefined) {\n idx = getRgbaIndex(space).map(([name, rgba]) => [name, rgbaToOklab(rgba)] as const);\n oklabIndexCache.set(space, idx);\n }\n return idx;\n}\n\n/**\n * Generic argmin over an `[name, value]` entries array with a custom\n * distance function. Factored so the three metric branches in `nearest`\n * share one loop body instead of duplicating it three times.\n */\nfunction argminBy<T>(\n entries: Array<readonly [string, T]>,\n target: T,\n dist: (a: T, b: T) => number,\n): string {\n let bestName = entries[0]?.[0] ?? '';\n let bestDistance = Number.POSITIVE_INFINITY;\n for (const [name, candidate] of entries) {\n const d = dist(target, candidate);\n if (d < bestDistance) {\n bestDistance = d;\n bestName = name;\n }\n }\n return bestName;\n}\n\n// Squared Euclidean on a [number, number, number] triple. Used for the\n// linear-RGB metric (sharing shape with Lab but different meaning).\nfunction squaredDistanceTriple(a: LabTriple, b: LabTriple): number {\n const d0 = a[0] - b[0];\n const d1 = a[1] - b[1];\n const d2 = a[2] - b[2];\n return d0 * d0 + d1 * d1 + d2 * d2;\n}\n\n/**\n * Find the nearest-named entry in a palette under the specified distance\n * metric. Dispatches to the right index (rgba / linear-rgb / Lab) and\n * the right distance function. See `DistanceMetric` in types.ts for how\n * to pick a metric.\n *\n * `metric` is required — callers should pass `space.defaultMetric` if\n * they want the palette's declared preference. A silent default would\n * hide the distance-metric choice from the hot path.\n */\nexport function nearest(target: Rgba, space: AnyPalette, metric: DistanceMetric): string {\n if (metric === 'euclidean-srgb') return nearestByRgb(target, space);\n if (metric === 'euclidean-linear') {\n return argminBy(getLinearIndex(space), rgbaToLinearRgb(target), squaredDistanceTriple);\n }\n if (metric === 'deltaEok') {\n return argminBy(getOklabIndex(space), rgbaToOklab(target), deltaEokSquared);\n }\n const labTarget = rgbaToLab(target);\n const labIndex = getLabIndex(space);\n if (metric === 'deltaE76') return argminBy(labIndex, labTarget, deltaE76Squared);\n if (metric === 'deltaE94') return argminBy(labIndex, labTarget, deltaE94);\n return argminBy(labIndex, labTarget, deltaE2000);\n}\n\n/**\n * Rank every palette entry by distance to `target`. Returns an array of\n * `{ name, distance }` sorted ascending (nearest first). Optional `k`\n * truncates to the top-k matches.\n *\n * Distances are **real** metric values (not squared) — ΔE76 / ΔE94 /\n * ΔE2000 / ΔE-OK are in standard ΔE units (≈1 = just-noticeable for\n * most of the gamut on ΔE76/2000); `euclidean-srgb` / `euclidean-linear`\n * are in their respective channel-unit Euclidean distances.\n *\n * Cost: O(n log n) for the sort vs O(n) for `nearest` — acceptable for\n * top-K queries. For strict \"what's the single nearest?\" stay on\n * `nearest` / `identify`.\n */\nexport function nearestAll(\n target: Rgba,\n space: AnyPalette,\n metric: DistanceMetric,\n k?: number,\n): Array<{ name: string; distance: number }> {\n const ranked: Array<[string, number]> = [];\n\n if (metric === 'euclidean-srgb') {\n for (const [name, candidate] of getRgbaIndex(space)) {\n ranked.push([name, Math.sqrt(squaredDistanceRgb(target, candidate))]);\n }\n } else if (metric === 'euclidean-linear') {\n const tgt = rgbaToLinearRgb(target);\n for (const [name, candidate] of getLinearIndex(space)) {\n ranked.push([name, Math.sqrt(squaredDistanceTriple(tgt, candidate))]);\n }\n } else if (metric === 'deltaEok') {\n const tgt = rgbaToOklab(target);\n for (const [name, candidate] of getOklabIndex(space)) {\n ranked.push([name, Math.sqrt(deltaEokSquared(tgt, candidate))]);\n }\n } else {\n const tgt = rgbaToLab(target);\n const labIndex = getLabIndex(space);\n if (metric === 'deltaE76') {\n for (const [name, candidate] of labIndex) {\n ranked.push([name, Math.sqrt(deltaE76Squared(tgt, candidate))]);\n }\n } else if (metric === 'deltaE94') {\n for (const [name, candidate] of labIndex) {\n ranked.push([name, deltaE94(tgt, candidate)]);\n }\n } else {\n for (const [name, candidate] of labIndex) {\n ranked.push([name, deltaE2000(tgt, candidate)]);\n }\n }\n }\n\n ranked.sort((a, b) => a[1] - b[1]);\n const limit = k === undefined ? ranked.length : Math.min(Math.max(k, 0), ranked.length);\n const result: Array<{ name: string; distance: number }> = new Array(limit);\n for (let i = 0; i < limit; i++) {\n const entry = ranked[i];\n if (entry !== undefined) result[i] = { name: entry[0], distance: entry[1] };\n }\n return result;\n}\n"]}
1
+ {"version":3,"file":"indexing.js","sourceRoot":"","sources":["../src/indexing.ts"],"names":[],"mappings":"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","sourcesContent":["import { hexToRgba } from './conversions/hex.js';\nimport { rgbaToLab, rgbaToLinearRgb, rgbaToOklab } from './math/colorSpace.js';\nimport { deltaE76Squared, deltaE94, deltaE2000, deltaEokSquared } from './math/deltaE.js';\nimport { squaredDistanceRgb } from './math/euclideanDistance.js';\nimport type { DistanceMetric, Palette, Rgba } from './types.js';\n\n/**\n * Per-palette lookup indexes for name resolution and nearest-match.\n *\n * Caches are keyed by the Palette wrapper (WeakMap), so each palette's\n * indexes are built once per process and reused by every caller —\n * including BYO palettes the user supplies.\n */\n\ntype AnyPalette = Palette<string>;\n\nconst nameIndexCache = new WeakMap<AnyPalette, Map<string, string>>();\nconst rgbaIndexCache = new WeakMap<AnyPalette, Array<readonly [string, Rgba]>>();\n\n/**\n * Normalized-name → canonical-key map for a given palette. Built once\n * per palette using its own `normalize` function.\n */\nexport function getNameIndex(space: AnyPalette): Map<string, string> {\n let idx = nameIndexCache.get(space);\n if (idx === undefined) {\n idx = new Map();\n const { normalize } = space;\n for (const key of Object.keys(space.colors)) {\n idx.set(normalize(key), key);\n }\n nameIndexCache.set(space, idx);\n }\n return idx;\n}\n\nconst reverseIndexCache = new WeakMap<AnyPalette, Map<string, string>>();\n\n/**\n * Hex → canonical-key map (reverse of `palette.colors`). Built once per\n * palette. Used by `convert(color, { palette, format: 'NAME' })` for\n * O(1) exact reverse lookup. Returns `undefined` on miss — the caller\n * decides whether that's an error (convert throws; use `identify` for\n * nearest-match semantics).\n *\n * Hex values are lowercased for the key so callers can normalize their\n * input with `rgbaToHex(...).toLowerCase()` before lookup.\n */\nexport function getReverseNameIndex(space: AnyPalette): Map<string, string> {\n let idx = reverseIndexCache.get(space);\n if (idx === undefined) {\n idx = new Map();\n for (const [name, hex] of Object.entries(space.colors)) {\n idx.set((hex as string).toLowerCase(), name);\n }\n reverseIndexCache.set(space, idx);\n }\n return idx;\n}\n\n/**\n * Canonical-key → Rgba entries for a given palette, built by parsing\n * each hex value once. Used by nearest-match lookups (identify, rgbaToPantone).\n */\nfunction getRgbaIndex(space: AnyPalette): Array<readonly [string, Rgba]> {\n let idx = rgbaIndexCache.get(space);\n if (idx === undefined) {\n idx = Object.entries(space.colors).map(([name, hex]) => [name, hexToRgba(hex)] as const);\n rgbaIndexCache.set(space, idx);\n }\n return idx;\n}\n\n/**\n * Find the nearest-named entry in a palette to the given Rgba, using\n * squared Euclidean distance in sRGB (alpha ignored). Used by `identify`\n * and `rgbaToPantone` when metric === 'euclidean-srgb'.\n */\nfunction nearestByRgb(target: Rgba, space: AnyPalette): string {\n const entries = getRgbaIndex(space);\n let bestName = entries[0]?.[0] ?? '';\n let bestDistance = Number.POSITIVE_INFINITY;\n for (const [name, candidate] of entries) {\n const d = squaredDistanceRgb(target, candidate);\n if (d < bestDistance) {\n bestDistance = d;\n bestName = name;\n }\n }\n return bestName;\n}\n\n// Precomputed Lab entries for delta-E metrics. Same shape as rgba index.\ntype LabTriple = readonly [number, number, number];\nconst labIndexCache = new WeakMap<AnyPalette, Array<readonly [string, LabTriple]>>();\n\nfunction getLabIndex(space: AnyPalette): Array<readonly [string, LabTriple]> {\n let idx = labIndexCache.get(space);\n if (idx === undefined) {\n idx = getRgbaIndex(space).map(([name, rgba]) => [name, rgbaToLab(rgba)] as const);\n labIndexCache.set(space, idx);\n }\n return idx;\n}\n\n// Precomputed linear-RGB entries for euclidean-linear metric.\nconst linearIndexCache = new WeakMap<AnyPalette, Array<readonly [string, LabTriple]>>();\n\nfunction getLinearIndex(space: AnyPalette): Array<readonly [string, LabTriple]> {\n let idx = linearIndexCache.get(space);\n if (idx === undefined) {\n idx = getRgbaIndex(space).map(([name, rgba]) => [name, rgbaToLinearRgb(rgba)] as const);\n linearIndexCache.set(space, idx);\n }\n return idx;\n}\n\n// Precomputed OKLAB entries for deltaEok metric.\nconst oklabIndexCache = new WeakMap<AnyPalette, Array<readonly [string, LabTriple]>>();\n\nfunction getOklabIndex(space: AnyPalette): Array<readonly [string, LabTriple]> {\n let idx = oklabIndexCache.get(space);\n if (idx === undefined) {\n idx = getRgbaIndex(space).map(([name, rgba]) => [name, rgbaToOklab(rgba)] as const);\n oklabIndexCache.set(space, idx);\n }\n return idx;\n}\n\n/**\n * Generic argmin over an `[name, value]` entries array with a custom\n * distance function. Factored so the three metric branches in `nearest`\n * share one loop body instead of duplicating it three times.\n */\nfunction argminBy<T>(\n entries: Array<readonly [string, T]>,\n target: T,\n dist: (a: T, b: T) => number,\n): string {\n let bestName = entries[0]?.[0] ?? '';\n let bestDistance = Number.POSITIVE_INFINITY;\n for (const [name, candidate] of entries) {\n const d = dist(target, candidate);\n if (d < bestDistance) {\n bestDistance = d;\n bestName = name;\n }\n }\n return bestName;\n}\n\n// Squared Euclidean on a [number, number, number] triple. Used for the\n// linear-RGB metric (sharing shape with Lab but different meaning).\nfunction squaredDistanceTriple(a: LabTriple, b: LabTriple): number {\n const d0 = a[0] - b[0];\n const d1 = a[1] - b[1];\n const d2 = a[2] - b[2];\n return d0 * d0 + d1 * d1 + d2 * d2;\n}\n\n/**\n * Find the nearest-named entry in a palette under the specified distance\n * metric. Dispatches to the right index (rgba / linear-rgb / Lab) and\n * the right distance function. See `DistanceMetric` in types.ts for how\n * to pick a metric.\n *\n * `metric` is required — callers should pass `space.defaultMetric` if\n * they want the palette's declared preference. A silent default would\n * hide the distance-metric choice from the hot path.\n */\nexport function nearest(target: Rgba, space: AnyPalette, metric: DistanceMetric): string {\n if (metric === 'euclidean-srgb') return nearestByRgb(target, space);\n if (metric === 'euclidean-linear') {\n return argminBy(getLinearIndex(space), rgbaToLinearRgb(target), squaredDistanceTriple);\n }\n if (metric === 'deltaEok') {\n return argminBy(getOklabIndex(space), rgbaToOklab(target), deltaEokSquared);\n }\n const labTarget = rgbaToLab(target);\n const labIndex = getLabIndex(space);\n if (metric === 'deltaE76') return argminBy(labIndex, labTarget, deltaE76Squared);\n if (metric === 'deltaE94') return argminBy(labIndex, labTarget, deltaE94);\n return argminBy(labIndex, labTarget, deltaE2000);\n}\n\n/**\n * Rank every palette entry by distance to `target`. Returns an array of\n * `{ name, distance }` sorted ascending (nearest first). Optional `k`\n * truncates to the top-k matches.\n *\n * Distances are **real** metric values (not squared) — ΔE76 / ΔE94 /\n * ΔE2000 / ΔE-OK are in standard ΔE units (≈1 = just-noticeable for\n * most of the gamut on ΔE76/2000); `euclidean-srgb` / `euclidean-linear`\n * are in their respective channel-unit Euclidean distances.\n *\n * Cost:\n * - `k < n`: O(n · k) via bounded insertion-sort; much faster than a\n * full sort for small k on cheap metrics where allocation dominates.\n * - `k >= n` or `k === undefined`: O(n log n) via full push-and-sort,\n * returning every entry ranked.\n *\n * Tie-breaking: on exact-distance ties, the first-declared entry in\n * the palette's `colors` object wins. Matches the single-match\n * `nearest` contract.\n *\n * `k` normalization is forgiving: fractional values are rounded,\n * negatives clamp to 0, and `NaN` / `±Infinity` collapse to 0\n * (empty result). No throws.\n */\nexport function nearestAll(\n target: Rgba,\n space: AnyPalette,\n metric: DistanceMetric,\n k?: number,\n): Array<{ name: string; distance: number }> {\n const rgbaIndex = getRgbaIndex(space);\n const n = rgbaIndex.length;\n\n // k normalization. Forgiving by design: fractional values are rounded,\n // negatives clamp to 0, NaN / ±Infinity collapse to 0 (empty result).\n // A previous version crashed with `RangeError: Invalid array length`\n // on NaN / fractional k because `new Array(NaN)` throws; the\n // `Number.isFinite` guard eliminates that class of bug.\n const safeK = k === undefined ? n : Number.isFinite(k) ? Math.max(0, Math.round(k)) : 0;\n if (safeK === 0) return [];\n\n // When the caller wants at least every entry ranked, the bounded\n // insertion-sort gives no benefit — it degenerates to O(n^2) on a\n // full result set. Use the plain push-and-sort path instead.\n // When safeK < n, the bounded insertion-sort keeps only the top k\n // entries seen so far, cutting work from O(n log n) to O(n · k)\n // with a much smaller constant; the win is dramatic for cheap\n // metrics (deltaEok at k=5 / 1950-entry ncs measured ~50x) where\n // the full sort's allocation dominates the distance math.\n const useBounded = safeK < n;\n\n const best: Array<{ name: string; distance: number }> = [];\n\n // Single per-candidate branch, chosen once up front. `offer` either\n // pushes unconditionally (full-sort path) or inserts into a bounded\n // sorted window (bounded path). Splitting the switch and the merge\n // strategy into a closure avoids a 6-way metric switch × 2 modes\n // duplication.\n const offer = useBounded\n ? (name: string, dist: number): void => {\n if (best.length < safeK) {\n let j = best.length;\n while (j > 0 && (best[j - 1] as { distance: number }).distance > dist) j--;\n best.splice(j, 0, { name, distance: dist });\n } else if (dist < (best[safeK - 1] as { distance: number }).distance) {\n let j = safeK - 1;\n while (j > 0 && (best[j - 1] as { distance: number }).distance > dist) j--;\n best.splice(j, 0, { name, distance: dist });\n best.pop();\n }\n }\n : (name: string, dist: number): void => {\n best.push({ name, distance: dist });\n };\n\n if (metric === 'euclidean-srgb') {\n for (const [name, candidate] of rgbaIndex) {\n offer(name, Math.sqrt(squaredDistanceRgb(target, candidate)));\n }\n } else if (metric === 'euclidean-linear') {\n const tgt = rgbaToLinearRgb(target);\n for (const [name, candidate] of getLinearIndex(space)) {\n offer(name, Math.sqrt(squaredDistanceTriple(tgt, candidate)));\n }\n } else if (metric === 'deltaEok') {\n const tgt = rgbaToOklab(target);\n for (const [name, candidate] of getOklabIndex(space)) {\n offer(name, Math.sqrt(deltaEokSquared(tgt, candidate)));\n }\n } else {\n const tgt = rgbaToLab(target);\n const labIndex = getLabIndex(space);\n if (metric === 'deltaE76') {\n for (const [name, candidate] of labIndex) {\n offer(name, Math.sqrt(deltaE76Squared(tgt, candidate)));\n }\n } else if (metric === 'deltaE94') {\n for (const [name, candidate] of labIndex) {\n offer(name, deltaE94(tgt, candidate));\n }\n } else {\n for (const [name, candidate] of labIndex) {\n offer(name, deltaE2000(tgt, candidate));\n }\n }\n }\n\n if (!useBounded) {\n best.sort((a, b) => a.distance - b.distance);\n }\n return best;\n}\n"]}
@@ -105,6 +105,22 @@ declare const crayolaColors: {
105
105
  readonly Wisteria: '#cda4de';
106
106
  };
107
107
  export type CrayolaColorName = keyof typeof crayolaColors;
108
+ /**
109
+ * Crayola crayon-color palette (84 entries), community-curated. Not
110
+ * Crayola-licensed or certified; see `NOTICE.md` for trademark details
111
+ * and the set's scope (classic 64 box + fluorescent neons + selected
112
+ * modern additions).
113
+ *
114
+ * Keys use the Title Case form printed on the crayon wrapper
115
+ * (`'Razzmatazz'`, `'Granny Smith Apple'`). Default metric is `'deltaEok'`:
116
+ * OKLAB's perceptual uniformity better separates the palette's
117
+ * saturated-heavy distribution than ΔE76.
118
+ *
119
+ * @example
120
+ * identify('#e20074', { palette: crayola }); // 'Razzmatazz'
121
+ * resolve('Granny Smith Apple', { palette: crayola }); // '#a8e4a0'
122
+ * crayola.colors.Razzmatazz; // '#e3256b'
123
+ */
108
124
  export declare const crayola: {
109
125
  readonly name: 'crayola';
110
126
  readonly colors: {
@@ -1 +1 @@
1
- {"version":3,"file":"crayola.d.ts","sourceRoot":"","sources":["../../src/palettes/crayola.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Palette } from '../types.js';\nimport { standardNormalize } from './normalize.js';\n\n/**\n * Crayola crayon colors — canonical subset. ~85 entries covering the\n * current 64-standard box, iconic fluorescents, and widely-recognized\n * modern additions.\n *\n * Keys are Title Case with spaces (`'Granny Smith Apple'`,\n * `'Razzmatazz'`) — how the names are printed on the crayon wrapper.\n * `standardNormalize` strips spaces + case + punctuation on input, so\n * `resolve('granny-smith-apple')` and `resolve('Granny Smith Apple')`\n * both hit the same entry.\n *\n * sRGB values are community-sourced approximations (Wikipedia's\n * well-cited list of Crayola crayon colors). They do not represent\n * a licensed Crayola reference. See NOTICE.md for trademark note.\n *\n * Default metric: `deltaEok`. The Crayola set spans a wide gamut with\n * many saturated brights (fluorescents especially) where CIELAB-based\n * metrics are less perceptually uniform than OKLAB. Same cost profile\n * as deltaE76, better neighbor selection in saturated regions.\n */\nconst crayolaColors = {\n // --- Current standard box: the 12 primary/secondary hue wheel ---\n Red: '#ee204d',\n 'Red Orange': '#ff5349',\n Orange: '#ff7538',\n 'Yellow Orange': '#ffae42',\n Yellow: '#fce883',\n 'Yellow Green': '#c5e384',\n Green: '#1cac78',\n 'Blue Green': '#0d98ba',\n Blue: '#1f75fe',\n 'Blue Violet': '#7366bd',\n Violet: '#926eae',\n 'Red Violet': '#c0448f',\n\n // --- Achromatics ---\n Black: '#232323',\n Brown: '#b4674d',\n White: '#ededed',\n Gray: '#95918c',\n\n // --- 64-box additions (pre-1990 core set) ---\n 'Carnation Pink': '#ffaacc',\n 'Sky Blue': '#80daeb',\n Tan: '#faa76c',\n Apricot: '#fdd9b5',\n Peach: '#ffcfab',\n 'Cadet Blue': '#b0b7c6',\n Salmon: '#ff9baa',\n 'Forest Green': '#6dae81',\n 'Lemon Yellow': '#fff44f',\n Mahogany: '#cd4a4c',\n Maroon: '#c8385a',\n Bittersweet: '#fd7c6e',\n Sepia: '#a5694f',\n 'Burnt Sienna': '#ea7e5d',\n 'Burnt Orange': '#ff7f49',\n 'Raw Sienna': '#d68a59',\n\n // --- Metallics ---\n Silver: '#cdc5c2',\n Gold: '#e7c697',\n Copper: '#dd9475',\n\n // --- Earth / nature tones ---\n 'Green Yellow': '#f0e891',\n 'Pine Green': '#158078',\n 'Olive Green': '#bab86c',\n Indigo: '#5d76cb',\n Periwinkle: '#c5d0e6',\n Cerulean: '#1dacd6',\n 'Sea Green': '#9fe2bf',\n 'Turquoise Blue': '#77dde7',\n\n // --- Fluorescents (the iconic \"Neon 24\" era) ---\n 'Atomic Tangerine': '#ff9966',\n 'Blizzard Blue': '#ace5ee',\n 'Hot Magenta': '#ff1dce',\n 'Laser Lemon': '#fdfc74',\n 'Neon Carrot': '#ffa343',\n 'Purple Pizzazz': '#fe4eda',\n 'Razzle Dazzle Rose': '#ff48d0',\n Razzmatazz: '#e3256b',\n \"Screamin' Green\": '#76ff7a',\n 'Shocking Pink': '#fb7efd',\n 'Tickle Me Pink': '#fc89ac',\n 'Unmellow Yellow': '#ffff66',\n 'Wild Watermelon': '#fc6c85',\n 'Outrageous Orange': '#ff6e4a',\n 'Electric Lime': '#ceff1d',\n\n // --- Modern / whimsical additions ---\n 'Granny Smith Apple': '#a8e4a0',\n 'Macaroni and Cheese': '#ffbd88',\n 'Banana Mania': '#fae7b5',\n 'Caribbean Green': '#1cd3a2',\n Cerise: '#dd4492',\n 'Cotton Candy': '#ffbcd9',\n 'Fuzzy Wuzzy': '#cc6666',\n Inchworm: '#b2ec5d',\n 'Jazzberry Jam': '#ca3767',\n Manatee: '#979aaa',\n 'Mango Tango': '#ff8243',\n Mauvelous: '#ef98aa',\n 'Midnight Blue': '#1a4876',\n 'Navy Blue': '#1974d2',\n 'Pacific Blue': '#1ca9c9',\n 'Piggy Pink': '#fddde6',\n Plum: '#8e4585',\n 'Purple Heart': '#7442c8',\n \"Robin's Egg Blue\": '#1fcecb',\n 'Royal Purple': '#7851a9',\n Scarlet: '#fc2847',\n Timberwolf: '#dbd7d2',\n Tumbleweed: '#deaa88',\n 'Violet Red': '#f75394',\n 'Vivid Violet': '#8f509d',\n Wisteria: '#cda4de',\n} as const;\n\nexport type CrayolaColorName = keyof typeof crayolaColors;\n\nexport const crayola = {\n name: 'crayola',\n colors: crayolaColors,\n normalize: standardNormalize,\n defaultMetric: 'deltaEok',\n} as const satisfies Palette<CrayolaColorName>;\n"]}
1
+ {"version":3,"file":"crayola.d.ts","sourceRoot":"","sources":["../../src/palettes/crayola.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,QAAA,MAAM,aAAa;aAEjB,GAAG,EAAE,SAAS;aACd,YAAY,EAAE,SAAS;aACvB,MAAM,EAAE,SAAS;aACjB,eAAe,EAAE,SAAS;aAC1B,MAAM,EAAE,SAAS;aACjB,cAAc,EAAE,SAAS;aACzB,KAAK,EAAE,SAAS;aAChB,YAAY,EAAE,SAAS;aACvB,IAAI,EAAE,SAAS;aACf,aAAa,EAAE,SAAS;aACxB,MAAM,EAAE,SAAS;aACjB,YAAY,EAAE,SAAS;aAGvB,KAAK,EAAE,SAAS;aAChB,KAAK,EAAE,SAAS;aAChB,KAAK,EAAE,SAAS;aAChB,IAAI,EAAE,SAAS;aAGf,gBAAgB,EAAE,SAAS;aAC3B,UAAU,EAAE,SAAS;aACrB,GAAG,EAAE,SAAS;aACd,OAAO,EAAE,SAAS;aAClB,KAAK,EAAE,SAAS;aAChB,YAAY,EAAE,SAAS;aACvB,MAAM,EAAE,SAAS;aACjB,cAAc,EAAE,SAAS;aACzB,cAAc,EAAE,SAAS;aACzB,QAAQ,EAAE,SAAS;aACnB,MAAM,EAAE,SAAS;aACjB,WAAW,EAAE,SAAS;aACtB,KAAK,EAAE,SAAS;aAChB,cAAc,EAAE,SAAS;aACzB,cAAc,EAAE,SAAS;aACzB,YAAY,EAAE,SAAS;aAGvB,MAAM,EAAE,SAAS;aACjB,IAAI,EAAE,SAAS;aACf,MAAM,EAAE,SAAS;aAGjB,cAAc,EAAE,SAAS;aACzB,YAAY,EAAE,SAAS;aACvB,aAAa,EAAE,SAAS;aACxB,MAAM,EAAE,SAAS;aACjB,UAAU,EAAE,SAAS;aACrB,QAAQ,EAAE,SAAS;aACnB,WAAW,EAAE,SAAS;aACtB,gBAAgB,EAAE,SAAS;aAG3B,kBAAkB,EAAE,SAAS;aAC7B,eAAe,EAAE,SAAS;aAC1B,aAAa,EAAE,SAAS;aACxB,aAAa,EAAE,SAAS;aACxB,aAAa,EAAE,SAAS;aACxB,gBAAgB,EAAE,SAAS;aAC3B,oBAAoB,EAAE,SAAS;aAC/B,UAAU,EAAE,SAAS;aACrB,iBAAiB,EAAE,SAAS;aAC5B,eAAe,EAAE,SAAS;aAC1B,gBAAgB,EAAE,SAAS;aAC3B,iBAAiB,EAAE,SAAS;aAC5B,iBAAiB,EAAE,SAAS;aAC5B,mBAAmB,EAAE,SAAS;aAC9B,eAAe,EAAE,SAAS;aAG1B,oBAAoB,EAAE,SAAS;aAC/B,qBAAqB,EAAE,SAAS;aAChC,cAAc,EAAE,SAAS;aACzB,iBAAiB,EAAE,SAAS;aAC5B,MAAM,EAAE,SAAS;aACjB,cAAc,EAAE,SAAS;aACzB,aAAa,EAAE,SAAS;aACxB,QAAQ,EAAE,SAAS;aACnB,eAAe,EAAE,SAAS;aAC1B,OAAO,EAAE,SAAS;aAClB,aAAa,EAAE,SAAS;aACxB,SAAS,EAAE,SAAS;aACpB,eAAe,EAAE,SAAS;aAC1B,WAAW,EAAE,SAAS;aACtB,cAAc,EAAE,SAAS;aACzB,YAAY,EAAE,SAAS;aACvB,IAAI,EAAE,SAAS;aACf,cAAc,EAAE,SAAS;aACzB,kBAAkB,EAAE,SAAS;aAC7B,cAAc,EAAE,SAAS;aACzB,OAAO,EAAE,SAAS;aAClB,UAAU,EAAE,SAAS;aACrB,UAAU,EAAE,SAAS;aACrB,YAAY,EAAE,SAAS;aACvB,cAAc,EAAE,SAAS;aACzB,QAAQ,EAAE,SAAS;CACX,CAAC;AAEX,MAAM,MAAM,gBAAgB,GAAG,MAAM,OAAO,aAAa,CAAC;AAE1D;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,OAAO;mBACZ,SAAS;;sBArHV,SAAS;+BACA,SAAS;yBACf,SAAS;kCACA,SAAS;yBAClB,SAAS;iCACD,SAAS;wBAClB,SAAS;+BACF,SAAS;uBACjB,SAAS;gCACA,SAAS;yBAChB,SAAS;+BACH,SAAS;wBAGhB,SAAS;wBACT,SAAS;wBACT,SAAS;uBACV,SAAS;mCAGG,SAAS;6BACf,SAAS;sBAChB,SAAS;0BACL,SAAS;wBACX,SAAS;+BACF,SAAS;yBACf,SAAS;iCACD,SAAS;iCACT,SAAS;2BACf,SAAS;yBACX,SAAS;8BACJ,SAAS;wBACf,SAAS;iCACA,SAAS;iCACT,SAAS;+BACX,SAAS;yBAGf,SAAS;uBACX,SAAS;yBACP,SAAS;iCAGD,SAAS;+BACX,SAAS;gCACR,SAAS;yBAChB,SAAS;6BACL,SAAS;2BACX,SAAS;8BACN,SAAS;mCACJ,SAAS;qCAGP,SAAS;kCACZ,SAAS;gCACX,SAAS;gCACT,SAAS;gCACT,SAAS;mCACN,SAAS;uCACL,SAAS;6BACnB,SAAS;oCACF,SAAS;kCACX,SAAS;mCACR,SAAS;oCACR,SAAS;oCACT,SAAS;sCACP,SAAS;kCACb,SAAS;uCAGJ,SAAS;wCACR,SAAS;iCAChB,SAAS;oCACN,SAAS;yBACpB,SAAS;iCACD,SAAS;gCACV,SAAS;2BACd,SAAS;kCACF,SAAS;0BACjB,SAAS;gCACH,SAAS;4BACb,SAAS;kCACH,SAAS;8BACb,SAAS;iCACN,SAAS;+BACX,SAAS;uBACjB,SAAS;iCACC,SAAS;qCACL,SAAS;iCACb,SAAS;0BAChB,SAAS;6BACN,SAAS;6BACT,SAAS;+BACP,SAAS;iCACP,SAAS;2BACf,SAAS;;;4BAyBJ,UAAU;CACmB,CAAC","sourcesContent":["import type { Palette } from '../types.js';\nimport { standardNormalize } from './normalize.js';\n\n/**\n * Crayola crayon colors — canonical subset. ~85 entries covering the\n * current 64-standard box, iconic fluorescents, and widely-recognized\n * modern additions.\n *\n * Keys are Title Case with spaces (`'Granny Smith Apple'`,\n * `'Razzmatazz'`) — how the names are printed on the crayon wrapper.\n * `standardNormalize` strips spaces + case + punctuation on input, so\n * `resolve('granny-smith-apple')` and `resolve('Granny Smith Apple')`\n * both hit the same entry.\n *\n * sRGB values are community-sourced approximations (Wikipedia's\n * well-cited list of Crayola crayon colors). They do not represent\n * a licensed Crayola reference. See NOTICE.md for trademark note.\n *\n * Default metric: `deltaEok`. The Crayola set spans a wide gamut with\n * many saturated brights (fluorescents especially) where CIELAB-based\n * metrics are less perceptually uniform than OKLAB. Same cost profile\n * as deltaE76, better neighbor selection in saturated regions.\n */\nconst crayolaColors = {\n // --- Current standard box: the 12 primary/secondary hue wheel ---\n Red: '#ee204d',\n 'Red Orange': '#ff5349',\n Orange: '#ff7538',\n 'Yellow Orange': '#ffae42',\n Yellow: '#fce883',\n 'Yellow Green': '#c5e384',\n Green: '#1cac78',\n 'Blue Green': '#0d98ba',\n Blue: '#1f75fe',\n 'Blue Violet': '#7366bd',\n Violet: '#926eae',\n 'Red Violet': '#c0448f',\n\n // --- Achromatics ---\n Black: '#232323',\n Brown: '#b4674d',\n White: '#ededed',\n Gray: '#95918c',\n\n // --- 64-box additions (pre-1990 core set) ---\n 'Carnation Pink': '#ffaacc',\n 'Sky Blue': '#80daeb',\n Tan: '#faa76c',\n Apricot: '#fdd9b5',\n Peach: '#ffcfab',\n 'Cadet Blue': '#b0b7c6',\n Salmon: '#ff9baa',\n 'Forest Green': '#6dae81',\n 'Lemon Yellow': '#fff44f',\n Mahogany: '#cd4a4c',\n Maroon: '#c8385a',\n Bittersweet: '#fd7c6e',\n Sepia: '#a5694f',\n 'Burnt Sienna': '#ea7e5d',\n 'Burnt Orange': '#ff7f49',\n 'Raw Sienna': '#d68a59',\n\n // --- Metallics ---\n Silver: '#cdc5c2',\n Gold: '#e7c697',\n Copper: '#dd9475',\n\n // --- Earth / nature tones ---\n 'Green Yellow': '#f0e891',\n 'Pine Green': '#158078',\n 'Olive Green': '#bab86c',\n Indigo: '#5d76cb',\n Periwinkle: '#c5d0e6',\n Cerulean: '#1dacd6',\n 'Sea Green': '#9fe2bf',\n 'Turquoise Blue': '#77dde7',\n\n // --- Fluorescents (the iconic \"Neon 24\" era) ---\n 'Atomic Tangerine': '#ff9966',\n 'Blizzard Blue': '#ace5ee',\n 'Hot Magenta': '#ff1dce',\n 'Laser Lemon': '#fdfc74',\n 'Neon Carrot': '#ffa343',\n 'Purple Pizzazz': '#fe4eda',\n 'Razzle Dazzle Rose': '#ff48d0',\n Razzmatazz: '#e3256b',\n \"Screamin' Green\": '#76ff7a',\n 'Shocking Pink': '#fb7efd',\n 'Tickle Me Pink': '#fc89ac',\n 'Unmellow Yellow': '#ffff66',\n 'Wild Watermelon': '#fc6c85',\n 'Outrageous Orange': '#ff6e4a',\n 'Electric Lime': '#ceff1d',\n\n // --- Modern / whimsical additions ---\n 'Granny Smith Apple': '#a8e4a0',\n 'Macaroni and Cheese': '#ffbd88',\n 'Banana Mania': '#fae7b5',\n 'Caribbean Green': '#1cd3a2',\n Cerise: '#dd4492',\n 'Cotton Candy': '#ffbcd9',\n 'Fuzzy Wuzzy': '#cc6666',\n Inchworm: '#b2ec5d',\n 'Jazzberry Jam': '#ca3767',\n Manatee: '#979aaa',\n 'Mango Tango': '#ff8243',\n Mauvelous: '#ef98aa',\n 'Midnight Blue': '#1a4876',\n 'Navy Blue': '#1974d2',\n 'Pacific Blue': '#1ca9c9',\n 'Piggy Pink': '#fddde6',\n Plum: '#8e4585',\n 'Purple Heart': '#7442c8',\n \"Robin's Egg Blue\": '#1fcecb',\n 'Royal Purple': '#7851a9',\n Scarlet: '#fc2847',\n Timberwolf: '#dbd7d2',\n Tumbleweed: '#deaa88',\n 'Violet Red': '#f75394',\n 'Vivid Violet': '#8f509d',\n Wisteria: '#cda4de',\n} as const;\n\nexport type CrayolaColorName = keyof typeof crayolaColors;\n\n/**\n * Crayola crayon-color palette (84 entries), community-curated. Not\n * Crayola-licensed or certified; see `NOTICE.md` for trademark details\n * and the set's scope (classic 64 box + fluorescent neons + selected\n * modern additions).\n *\n * Keys use the Title Case form printed on the crayon wrapper\n * (`'Razzmatazz'`, `'Granny Smith Apple'`). Default metric is `'deltaEok'`:\n * OKLAB's perceptual uniformity better separates the palette's\n * saturated-heavy distribution than ΔE76.\n *\n * @example\n * identify('#e20074', { palette: crayola }); // 'Razzmatazz'\n * resolve('Granny Smith Apple', { palette: crayola }); // '#a8e4a0'\n * crayola.colors.Razzmatazz; // '#e3256b'\n */\nexport const crayola = {\n name: 'crayola',\n colors: crayolaColors,\n normalize: standardNormalize,\n defaultMetric: 'deltaEok',\n} as const satisfies Palette<CrayolaColorName>;\n"]}
@@ -112,6 +112,22 @@ const crayolaColors = {
112
112
  'Vivid Violet': '#8f509d',
113
113
  Wisteria: '#cda4de',
114
114
  };
115
+ /**
116
+ * Crayola crayon-color palette (84 entries), community-curated. Not
117
+ * Crayola-licensed or certified; see `NOTICE.md` for trademark details
118
+ * and the set's scope (classic 64 box + fluorescent neons + selected
119
+ * modern additions).
120
+ *
121
+ * Keys use the Title Case form printed on the crayon wrapper
122
+ * (`'Razzmatazz'`, `'Granny Smith Apple'`). Default metric is `'deltaEok'`:
123
+ * OKLAB's perceptual uniformity better separates the palette's
124
+ * saturated-heavy distribution than ΔE76.
125
+ *
126
+ * @example
127
+ * identify('#e20074', { palette: crayola }); // 'Razzmatazz'
128
+ * resolve('Granny Smith Apple', { palette: crayola }); // '#a8e4a0'
129
+ * crayola.colors.Razzmatazz; // '#e3256b'
130
+ */
115
131
  export const crayola = {
116
132
  name: 'crayola',
117
133
  colors: crayolaColors,
@@ -1 +1 @@
1
- {"version":3,"file":"crayola.js","sourceRoot":"","sources":["../../src/palettes/crayola.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Palette } from '../types.js';\nimport { standardNormalize } from './normalize.js';\n\n/**\n * Crayola crayon colors — canonical subset. ~85 entries covering the\n * current 64-standard box, iconic fluorescents, and widely-recognized\n * modern additions.\n *\n * Keys are Title Case with spaces (`'Granny Smith Apple'`,\n * `'Razzmatazz'`) — how the names are printed on the crayon wrapper.\n * `standardNormalize` strips spaces + case + punctuation on input, so\n * `resolve('granny-smith-apple')` and `resolve('Granny Smith Apple')`\n * both hit the same entry.\n *\n * sRGB values are community-sourced approximations (Wikipedia's\n * well-cited list of Crayola crayon colors). They do not represent\n * a licensed Crayola reference. See NOTICE.md for trademark note.\n *\n * Default metric: `deltaEok`. The Crayola set spans a wide gamut with\n * many saturated brights (fluorescents especially) where CIELAB-based\n * metrics are less perceptually uniform than OKLAB. Same cost profile\n * as deltaE76, better neighbor selection in saturated regions.\n */\nconst crayolaColors = {\n // --- Current standard box: the 12 primary/secondary hue wheel ---\n Red: '#ee204d',\n 'Red Orange': '#ff5349',\n Orange: '#ff7538',\n 'Yellow Orange': '#ffae42',\n Yellow: '#fce883',\n 'Yellow Green': '#c5e384',\n Green: '#1cac78',\n 'Blue Green': '#0d98ba',\n Blue: '#1f75fe',\n 'Blue Violet': '#7366bd',\n Violet: '#926eae',\n 'Red Violet': '#c0448f',\n\n // --- Achromatics ---\n Black: '#232323',\n Brown: '#b4674d',\n White: '#ededed',\n Gray: '#95918c',\n\n // --- 64-box additions (pre-1990 core set) ---\n 'Carnation Pink': '#ffaacc',\n 'Sky Blue': '#80daeb',\n Tan: '#faa76c',\n Apricot: '#fdd9b5',\n Peach: '#ffcfab',\n 'Cadet Blue': '#b0b7c6',\n Salmon: '#ff9baa',\n 'Forest Green': '#6dae81',\n 'Lemon Yellow': '#fff44f',\n Mahogany: '#cd4a4c',\n Maroon: '#c8385a',\n Bittersweet: '#fd7c6e',\n Sepia: '#a5694f',\n 'Burnt Sienna': '#ea7e5d',\n 'Burnt Orange': '#ff7f49',\n 'Raw Sienna': '#d68a59',\n\n // --- Metallics ---\n Silver: '#cdc5c2',\n Gold: '#e7c697',\n Copper: '#dd9475',\n\n // --- Earth / nature tones ---\n 'Green Yellow': '#f0e891',\n 'Pine Green': '#158078',\n 'Olive Green': '#bab86c',\n Indigo: '#5d76cb',\n Periwinkle: '#c5d0e6',\n Cerulean: '#1dacd6',\n 'Sea Green': '#9fe2bf',\n 'Turquoise Blue': '#77dde7',\n\n // --- Fluorescents (the iconic \"Neon 24\" era) ---\n 'Atomic Tangerine': '#ff9966',\n 'Blizzard Blue': '#ace5ee',\n 'Hot Magenta': '#ff1dce',\n 'Laser Lemon': '#fdfc74',\n 'Neon Carrot': '#ffa343',\n 'Purple Pizzazz': '#fe4eda',\n 'Razzle Dazzle Rose': '#ff48d0',\n Razzmatazz: '#e3256b',\n \"Screamin' Green\": '#76ff7a',\n 'Shocking Pink': '#fb7efd',\n 'Tickle Me Pink': '#fc89ac',\n 'Unmellow Yellow': '#ffff66',\n 'Wild Watermelon': '#fc6c85',\n 'Outrageous Orange': '#ff6e4a',\n 'Electric Lime': '#ceff1d',\n\n // --- Modern / whimsical additions ---\n 'Granny Smith Apple': '#a8e4a0',\n 'Macaroni and Cheese': '#ffbd88',\n 'Banana Mania': '#fae7b5',\n 'Caribbean Green': '#1cd3a2',\n Cerise: '#dd4492',\n 'Cotton Candy': '#ffbcd9',\n 'Fuzzy Wuzzy': '#cc6666',\n Inchworm: '#b2ec5d',\n 'Jazzberry Jam': '#ca3767',\n Manatee: '#979aaa',\n 'Mango Tango': '#ff8243',\n Mauvelous: '#ef98aa',\n 'Midnight Blue': '#1a4876',\n 'Navy Blue': '#1974d2',\n 'Pacific Blue': '#1ca9c9',\n 'Piggy Pink': '#fddde6',\n Plum: '#8e4585',\n 'Purple Heart': '#7442c8',\n \"Robin's Egg Blue\": '#1fcecb',\n 'Royal Purple': '#7851a9',\n Scarlet: '#fc2847',\n Timberwolf: '#dbd7d2',\n Tumbleweed: '#deaa88',\n 'Violet Red': '#f75394',\n 'Vivid Violet': '#8f509d',\n Wisteria: '#cda4de',\n} as const;\n\nexport type CrayolaColorName = keyof typeof crayolaColors;\n\nexport const crayola = {\n name: 'crayola',\n colors: crayolaColors,\n normalize: standardNormalize,\n defaultMetric: 'deltaEok',\n} as const satisfies Palette<CrayolaColorName>;\n"]}
1
+ {"version":3,"file":"crayola.js","sourceRoot":"","sources":["../../src/palettes/crayola.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Palette } from '../types.js';\nimport { standardNormalize } from './normalize.js';\n\n/**\n * Crayola crayon colors — canonical subset. ~85 entries covering the\n * current 64-standard box, iconic fluorescents, and widely-recognized\n * modern additions.\n *\n * Keys are Title Case with spaces (`'Granny Smith Apple'`,\n * `'Razzmatazz'`) — how the names are printed on the crayon wrapper.\n * `standardNormalize` strips spaces + case + punctuation on input, so\n * `resolve('granny-smith-apple')` and `resolve('Granny Smith Apple')`\n * both hit the same entry.\n *\n * sRGB values are community-sourced approximations (Wikipedia's\n * well-cited list of Crayola crayon colors). They do not represent\n * a licensed Crayola reference. See NOTICE.md for trademark note.\n *\n * Default metric: `deltaEok`. The Crayola set spans a wide gamut with\n * many saturated brights (fluorescents especially) where CIELAB-based\n * metrics are less perceptually uniform than OKLAB. Same cost profile\n * as deltaE76, better neighbor selection in saturated regions.\n */\nconst crayolaColors = {\n // --- Current standard box: the 12 primary/secondary hue wheel ---\n Red: '#ee204d',\n 'Red Orange': '#ff5349',\n Orange: '#ff7538',\n 'Yellow Orange': '#ffae42',\n Yellow: '#fce883',\n 'Yellow Green': '#c5e384',\n Green: '#1cac78',\n 'Blue Green': '#0d98ba',\n Blue: '#1f75fe',\n 'Blue Violet': '#7366bd',\n Violet: '#926eae',\n 'Red Violet': '#c0448f',\n\n // --- Achromatics ---\n Black: '#232323',\n Brown: '#b4674d',\n White: '#ededed',\n Gray: '#95918c',\n\n // --- 64-box additions (pre-1990 core set) ---\n 'Carnation Pink': '#ffaacc',\n 'Sky Blue': '#80daeb',\n Tan: '#faa76c',\n Apricot: '#fdd9b5',\n Peach: '#ffcfab',\n 'Cadet Blue': '#b0b7c6',\n Salmon: '#ff9baa',\n 'Forest Green': '#6dae81',\n 'Lemon Yellow': '#fff44f',\n Mahogany: '#cd4a4c',\n Maroon: '#c8385a',\n Bittersweet: '#fd7c6e',\n Sepia: '#a5694f',\n 'Burnt Sienna': '#ea7e5d',\n 'Burnt Orange': '#ff7f49',\n 'Raw Sienna': '#d68a59',\n\n // --- Metallics ---\n Silver: '#cdc5c2',\n Gold: '#e7c697',\n Copper: '#dd9475',\n\n // --- Earth / nature tones ---\n 'Green Yellow': '#f0e891',\n 'Pine Green': '#158078',\n 'Olive Green': '#bab86c',\n Indigo: '#5d76cb',\n Periwinkle: '#c5d0e6',\n Cerulean: '#1dacd6',\n 'Sea Green': '#9fe2bf',\n 'Turquoise Blue': '#77dde7',\n\n // --- Fluorescents (the iconic \"Neon 24\" era) ---\n 'Atomic Tangerine': '#ff9966',\n 'Blizzard Blue': '#ace5ee',\n 'Hot Magenta': '#ff1dce',\n 'Laser Lemon': '#fdfc74',\n 'Neon Carrot': '#ffa343',\n 'Purple Pizzazz': '#fe4eda',\n 'Razzle Dazzle Rose': '#ff48d0',\n Razzmatazz: '#e3256b',\n \"Screamin' Green\": '#76ff7a',\n 'Shocking Pink': '#fb7efd',\n 'Tickle Me Pink': '#fc89ac',\n 'Unmellow Yellow': '#ffff66',\n 'Wild Watermelon': '#fc6c85',\n 'Outrageous Orange': '#ff6e4a',\n 'Electric Lime': '#ceff1d',\n\n // --- Modern / whimsical additions ---\n 'Granny Smith Apple': '#a8e4a0',\n 'Macaroni and Cheese': '#ffbd88',\n 'Banana Mania': '#fae7b5',\n 'Caribbean Green': '#1cd3a2',\n Cerise: '#dd4492',\n 'Cotton Candy': '#ffbcd9',\n 'Fuzzy Wuzzy': '#cc6666',\n Inchworm: '#b2ec5d',\n 'Jazzberry Jam': '#ca3767',\n Manatee: '#979aaa',\n 'Mango Tango': '#ff8243',\n Mauvelous: '#ef98aa',\n 'Midnight Blue': '#1a4876',\n 'Navy Blue': '#1974d2',\n 'Pacific Blue': '#1ca9c9',\n 'Piggy Pink': '#fddde6',\n Plum: '#8e4585',\n 'Purple Heart': '#7442c8',\n \"Robin's Egg Blue\": '#1fcecb',\n 'Royal Purple': '#7851a9',\n Scarlet: '#fc2847',\n Timberwolf: '#dbd7d2',\n Tumbleweed: '#deaa88',\n 'Violet Red': '#f75394',\n 'Vivid Violet': '#8f509d',\n Wisteria: '#cda4de',\n} as const;\n\nexport type CrayolaColorName = keyof typeof crayolaColors;\n\n/**\n * Crayola crayon-color palette (84 entries), community-curated. Not\n * Crayola-licensed or certified; see `NOTICE.md` for trademark details\n * and the set's scope (classic 64 box + fluorescent neons + selected\n * modern additions).\n *\n * Keys use the Title Case form printed on the crayon wrapper\n * (`'Razzmatazz'`, `'Granny Smith Apple'`). Default metric is `'deltaEok'`:\n * OKLAB's perceptual uniformity better separates the palette's\n * saturated-heavy distribution than ΔE76.\n *\n * @example\n * identify('#e20074', { palette: crayola }); // 'Razzmatazz'\n * resolve('Granny Smith Apple', { palette: crayola }); // '#a8e4a0'\n * crayola.colors.Razzmatazz; // '#e3256b'\n */\nexport const crayola = {\n name: 'crayola',\n colors: crayolaColors,\n normalize: standardNormalize,\n defaultMetric: 'deltaEok',\n} as const satisfies Palette<CrayolaColorName>;\n"]}