color-map 2.1.0 → 2.3.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 (412) hide show
  1. package/.claude-plugin/plugin.json +12 -0
  2. package/.codex-plugin/plugin.json +12 -0
  3. package/.cursor-plugin/plugin.json +12 -0
  4. package/CHANGELOG.md +94 -0
  5. package/README.md +71 -11
  6. package/bin/cli-CNBftDBE.js +6414 -0
  7. package/bin/color-map.js +9 -0
  8. package/bin/index.js +1 -0
  9. package/cjs/color/index.d.ts +5 -0
  10. package/cjs/color/index.js +52 -0
  11. package/cjs/color-wNrkrhC7.js +374 -0
  12. package/cjs/color-wNrkrhC7.js.map +1 -0
  13. package/cjs/contrast-XjF5Rwui.js +293 -0
  14. package/cjs/contrast-XjF5Rwui.js.map +1 -0
  15. package/cjs/gamut-CG1J2aat.d.ts +158 -0
  16. package/cjs/gamut-CG1J2aat.d.ts.map +1 -0
  17. package/cjs/index-BcEqCPHz.d.ts +137 -0
  18. package/cjs/index-BcEqCPHz.d.ts.map +1 -0
  19. package/cjs/index-CGKCqRqD.d.ts +111 -0
  20. package/cjs/index-CGKCqRqD.d.ts.map +1 -0
  21. package/cjs/index-Cv3iUUYE.d.ts +171 -0
  22. package/cjs/index-Cv3iUUYE.d.ts.map +1 -0
  23. package/cjs/index-Dw43Hj4v.d.ts +41 -0
  24. package/cjs/index-Dw43Hj4v.d.ts.map +1 -0
  25. package/cjs/index-Dy8UARqO.d.ts +72 -0
  26. package/cjs/index-Dy8UARqO.d.ts.map +1 -0
  27. package/cjs/index.d.ts +27 -5
  28. package/cjs/index.d.ts.map +1 -0
  29. package/cjs/index.js +89 -20
  30. package/cjs/index.js.map +1 -1
  31. package/cjs/legacy/index.d.ts +2 -0
  32. package/cjs/legacy/index.js +75 -0
  33. package/cjs/legacy/index.js.map +1 -0
  34. package/cjs/mix-Cs4owSbW.js +158 -0
  35. package/cjs/mix-Cs4owSbW.js.map +1 -0
  36. package/cjs/mix-DGlKoUw4.d.ts +31 -0
  37. package/cjs/mix-DGlKoUw4.d.ts.map +1 -0
  38. package/cjs/package.json +1 -1
  39. package/cjs/palette/index.d.ts +3 -0
  40. package/cjs/palette/index.js +8 -0
  41. package/cjs/palette-DyFV_KoJ.js +332 -0
  42. package/cjs/palette-DyFV_KoJ.js.map +1 -0
  43. package/cjs/parse-8mt2OMKf.js +1508 -0
  44. package/cjs/parse-8mt2OMKf.js.map +1 -0
  45. package/cjs/scale/index.d.ts +2 -0
  46. package/cjs/scale/index.js +11 -0
  47. package/cjs/scale-Cq9pXrc1.js +318 -0
  48. package/cjs/scale-Cq9pXrc1.js.map +1 -0
  49. package/cjs/semantic-CrEIo8tS.d.ts +52 -0
  50. package/cjs/semantic-CrEIo8tS.d.ts.map +1 -0
  51. package/cjs/serialize-C69DAmoE.d.ts +25 -0
  52. package/cjs/serialize-C69DAmoE.d.ts.map +1 -0
  53. package/cjs/serialize-Ci2X0cPN.js +103 -0
  54. package/cjs/serialize-Ci2X0cPN.js.map +1 -0
  55. package/cjs/tokens/index.d.ts +2 -0
  56. package/cjs/tokens/index.js +6 -0
  57. package/cjs/tokens-CzXxaMgM.js +168 -0
  58. package/cjs/tokens-CzXxaMgM.js.map +1 -0
  59. package/cjs/tone-BAhu-QzQ.js +118 -0
  60. package/cjs/tone-BAhu-QzQ.js.map +1 -0
  61. package/dist/color-map.es5.js +1 -1
  62. package/dist/color-map.es5.js.map +1 -1
  63. package/esm/color/a98-rgb.d.ts +7 -0
  64. package/esm/color/a98-rgb.d.ts.map +1 -0
  65. package/esm/color/a98-rgb.js +13 -0
  66. package/esm/color/a98-rgb.js.map +1 -0
  67. package/esm/color/argb.d.ts +6 -0
  68. package/esm/color/argb.d.ts.map +1 -0
  69. package/esm/color/argb.js +25 -0
  70. package/esm/color/argb.js.map +1 -0
  71. package/esm/color/calc.js +82 -0
  72. package/esm/color/calc.js.map +1 -0
  73. package/esm/color/compare.d.ts +20 -0
  74. package/esm/color/compare.d.ts.map +1 -0
  75. package/esm/color/compare.js +46 -0
  76. package/esm/color/compare.js.map +1 -0
  77. package/esm/color/contrast.d.ts +107 -0
  78. package/esm/color/contrast.d.ts.map +1 -0
  79. package/esm/color/contrast.js +242 -0
  80. package/esm/color/contrast.js.map +1 -0
  81. package/esm/color/convert.d.ts +9 -0
  82. package/esm/color/convert.d.ts.map +1 -0
  83. package/esm/color/convert.js +45 -0
  84. package/esm/color/convert.js.map +1 -0
  85. package/esm/color/display-p3-linear.d.ts +7 -0
  86. package/esm/color/display-p3-linear.d.ts.map +1 -0
  87. package/esm/color/display-p3-linear.js +12 -0
  88. package/esm/color/display-p3-linear.js.map +1 -0
  89. package/esm/color/display-p3.d.ts +7 -0
  90. package/esm/color/display-p3.d.ts.map +1 -0
  91. package/esm/color/display-p3.js +12 -0
  92. package/esm/color/display-p3.js.map +1 -0
  93. package/esm/color/expr.d.ts +43 -0
  94. package/esm/color/expr.d.ts.map +1 -0
  95. package/esm/color/expr.js +160 -0
  96. package/esm/color/expr.js.map +1 -0
  97. package/esm/color/gamut.d.ts +31 -0
  98. package/esm/color/gamut.d.ts.map +1 -0
  99. package/esm/color/gamut.js +210 -0
  100. package/esm/color/gamut.js.map +1 -0
  101. package/esm/color/hsl.d.ts +7 -0
  102. package/esm/color/hsl.d.ts.map +1 -0
  103. package/esm/color/hsl.js +67 -0
  104. package/esm/color/hsl.js.map +1 -0
  105. package/esm/color/hwb.d.ts +7 -0
  106. package/esm/color/hwb.d.ts.map +1 -0
  107. package/esm/color/hwb.js +40 -0
  108. package/esm/color/hwb.js.map +1 -0
  109. package/esm/color/index.d.ts +28 -0
  110. package/esm/color/index.js +27 -0
  111. package/esm/color/lab.d.ts +7 -0
  112. package/esm/color/lab.d.ts.map +1 -0
  113. package/esm/color/lab.js +33 -0
  114. package/esm/color/lab.js.map +1 -0
  115. package/esm/color/lch.d.ts +7 -0
  116. package/esm/color/lch.d.ts.map +1 -0
  117. package/esm/color/lch.js +11 -0
  118. package/esm/color/lch.js.map +1 -0
  119. package/esm/color/math.js +32 -0
  120. package/esm/color/math.js.map +1 -0
  121. package/esm/color/matrices.js +180 -0
  122. package/esm/color/matrices.js.map +1 -0
  123. package/esm/color/mix.d.ts +30 -0
  124. package/esm/color/mix.d.ts.map +1 -0
  125. package/esm/color/mix.js +152 -0
  126. package/esm/color/mix.js.map +1 -0
  127. package/esm/color/named.js +11 -0
  128. package/esm/color/named.js.map +1 -0
  129. package/esm/color/oklab.d.ts +7 -0
  130. package/esm/color/oklab.d.ts.map +1 -0
  131. package/esm/color/oklab.js +21 -0
  132. package/esm/color/oklab.js.map +1 -0
  133. package/esm/color/oklch.d.ts +7 -0
  134. package/esm/color/oklch.d.ts.map +1 -0
  135. package/esm/color/oklch.js +11 -0
  136. package/esm/color/oklch.js.map +1 -0
  137. package/esm/color/parse.d.ts +9 -0
  138. package/esm/color/parse.d.ts.map +1 -0
  139. package/esm/color/parse.js +452 -0
  140. package/esm/color/parse.js.map +1 -0
  141. package/esm/color/polar.js +27 -0
  142. package/esm/color/polar.js.map +1 -0
  143. package/esm/color/prophoto-rgb.d.ts +7 -0
  144. package/esm/color/prophoto-rgb.d.ts.map +1 -0
  145. package/esm/color/prophoto-rgb.js +18 -0
  146. package/esm/color/prophoto-rgb.js.map +1 -0
  147. package/esm/color/rec2020.d.ts +7 -0
  148. package/esm/color/rec2020.d.ts.map +1 -0
  149. package/esm/color/rec2020.js +13 -0
  150. package/esm/color/rec2020.js.map +1 -0
  151. package/esm/color/resolve.d.ts +18 -0
  152. package/esm/color/resolve.d.ts.map +1 -0
  153. package/esm/color/resolve.js +98 -0
  154. package/esm/color/resolve.js.map +1 -0
  155. package/esm/color/rgb.js +20 -0
  156. package/esm/color/rgb.js.map +1 -0
  157. package/esm/color/serialize.d.ts +24 -0
  158. package/esm/color/serialize.d.ts.map +1 -0
  159. package/esm/color/serialize.js +88 -0
  160. package/esm/color/serialize.js.map +1 -0
  161. package/esm/color/spaces.d.ts +6 -0
  162. package/esm/color/spaces.d.ts.map +1 -0
  163. package/esm/color/spaces.js +42 -0
  164. package/esm/color/spaces.js.map +1 -0
  165. package/esm/color/srgb-linear.d.ts +7 -0
  166. package/esm/color/srgb-linear.d.ts.map +1 -0
  167. package/esm/color/srgb-linear.js +12 -0
  168. package/esm/color/srgb-linear.js.map +1 -0
  169. package/esm/color/srgb.d.ts +7 -0
  170. package/esm/color/srgb.d.ts.map +1 -0
  171. package/esm/color/srgb.js +12 -0
  172. package/esm/color/srgb.js.map +1 -0
  173. package/esm/color/transfer.js +11 -0
  174. package/esm/color/transfer.js.map +1 -0
  175. package/esm/color/types.d.ts +23 -0
  176. package/esm/color/types.d.ts.map +1 -0
  177. package/esm/color/unknown-name.js +16 -0
  178. package/esm/color/unknown-name.js.map +1 -0
  179. package/esm/color/xyz-d50.d.ts +7 -0
  180. package/esm/color/xyz-d50.d.ts.map +1 -0
  181. package/esm/color/xyz-d50.js +15 -0
  182. package/esm/color/xyz-d50.js.map +1 -0
  183. package/esm/color/xyz-d65.d.ts +4 -0
  184. package/esm/color/xyz-d65.d.ts.map +1 -0
  185. package/esm/color/xyz-d65.js +10 -0
  186. package/esm/color/xyz-d65.js.map +1 -0
  187. package/esm/index.d.ts +63 -5
  188. package/esm/index.d.ts.map +1 -0
  189. package/esm/index.js +53 -4
  190. package/esm/index.js.map +1 -1
  191. package/esm/legacy/createColors.d.ts +9 -0
  192. package/esm/legacy/createColors.d.ts.map +1 -0
  193. package/esm/legacy/createColors.js +32 -0
  194. package/esm/legacy/createColors.js.map +1 -0
  195. package/esm/legacy/createColorsFromMap.d.ts +9 -0
  196. package/esm/legacy/createColorsFromMap.d.ts.map +1 -0
  197. package/esm/legacy/createColorsFromMap.js +24 -0
  198. package/esm/legacy/createColorsFromMap.js.map +1 -0
  199. package/esm/legacy/index.d.ts +6 -0
  200. package/esm/legacy/index.js +5 -0
  201. package/esm/legacy/interfaces.d.ts +19 -0
  202. package/esm/legacy/interfaces.d.ts.map +1 -0
  203. package/esm/{rgbHex.d.ts → legacy/rgbHex.d.ts} +2 -1
  204. package/esm/legacy/rgbHex.d.ts.map +1 -0
  205. package/esm/legacy/rgbHex.js +17 -0
  206. package/esm/legacy/rgbHex.js.map +1 -0
  207. package/esm/legacy/rgbaString.d.ts +4 -0
  208. package/esm/legacy/rgbaString.d.ts.map +1 -0
  209. package/esm/legacy/rgbaString.js +8 -0
  210. package/esm/legacy/rgbaString.js.map +1 -0
  211. package/esm/palette/dark.d.ts +23 -0
  212. package/esm/palette/dark.d.ts.map +1 -0
  213. package/esm/palette/dark.js +54 -0
  214. package/esm/palette/dark.js.map +1 -0
  215. package/esm/palette/harmony.d.ts +56 -0
  216. package/esm/palette/harmony.d.ts.map +1 -0
  217. package/esm/palette/harmony.js +115 -0
  218. package/esm/palette/harmony.js.map +1 -0
  219. package/esm/palette/index.d.ts +5 -0
  220. package/esm/palette/index.js +5 -0
  221. package/esm/palette/semantic.d.ts +53 -0
  222. package/esm/palette/semantic.d.ts.map +1 -0
  223. package/esm/palette/semantic.js +96 -0
  224. package/esm/palette/semantic.js.map +1 -0
  225. package/esm/palette/validate.d.ts +39 -0
  226. package/esm/palette/validate.d.ts.map +1 -0
  227. package/esm/palette/validate.js +56 -0
  228. package/esm/palette/validate.js.map +1 -0
  229. package/esm/scale/brand.d.ts +21 -0
  230. package/esm/scale/brand.d.ts.map +1 -0
  231. package/esm/scale/brand.js +63 -0
  232. package/esm/scale/brand.js.map +1 -0
  233. package/esm/scale/contrast-scale.d.ts +33 -0
  234. package/esm/scale/contrast-scale.d.ts.map +1 -0
  235. package/esm/scale/contrast-scale.js +26 -0
  236. package/esm/scale/contrast-scale.js.map +1 -0
  237. package/esm/scale/gradient.d.ts +38 -0
  238. package/esm/scale/gradient.d.ts.map +1 -0
  239. package/esm/scale/gradient.js +73 -0
  240. package/esm/scale/gradient.js.map +1 -0
  241. package/esm/scale/index.d.ts +6 -0
  242. package/esm/scale/index.js +6 -0
  243. package/esm/scale/scale.d.ts +31 -0
  244. package/esm/scale/scale.d.ts.map +1 -0
  245. package/esm/scale/scale.js +152 -0
  246. package/esm/scale/scale.js.map +1 -0
  247. package/esm/scale/tone.d.ts +25 -0
  248. package/esm/scale/tone.d.ts.map +1 -0
  249. package/esm/scale/tone.js +96 -0
  250. package/esm/scale/tone.js.map +1 -0
  251. package/esm/tokens/css.d.ts +18 -0
  252. package/esm/tokens/css.d.ts.map +1 -0
  253. package/esm/tokens/css.js +23 -0
  254. package/esm/tokens/css.js.map +1 -0
  255. package/esm/tokens/dtcg.d.ts +33 -0
  256. package/esm/tokens/dtcg.d.ts.map +1 -0
  257. package/esm/tokens/dtcg.js +42 -0
  258. package/esm/tokens/dtcg.js.map +1 -0
  259. package/esm/tokens/index.d.ts +5 -0
  260. package/esm/tokens/index.js +4 -0
  261. package/esm/tokens/tailwind.d.ts +19 -0
  262. package/esm/tokens/tailwind.d.ts.map +1 -0
  263. package/esm/tokens/tailwind.js +19 -0
  264. package/esm/tokens/tailwind.js.map +1 -0
  265. package/esm/tokens/tokens.d.ts +11 -0
  266. package/esm/tokens/tokens.d.ts.map +1 -0
  267. package/esm/tokens/tokens.js +77 -0
  268. package/esm/tokens/tokens.js.map +1 -0
  269. package/llms.txt +29 -0
  270. package/package.json +240 -56
  271. package/plugin.json +18 -0
  272. package/schemas/contrast.schema.json +24 -0
  273. package/schemas/convert.schema.json +60 -0
  274. package/schemas/export.schema.json +106 -0
  275. package/schemas/find-contrast.schema.json +51 -0
  276. package/schemas/harmony.schema.json +58 -0
  277. package/schemas/mix.schema.json +72 -0
  278. package/schemas/palette.schema.json +56 -0
  279. package/schemas/scale.schema.json +86 -0
  280. package/skills/color-map/SKILL.md +38 -0
  281. package/src/cli/bin.ts +11 -0
  282. package/src/cli/cli.ts +40 -0
  283. package/src/cli/commands/contrast.ts +36 -0
  284. package/src/cli/commands/convert.ts +37 -0
  285. package/src/cli/commands/export.ts +102 -0
  286. package/src/cli/commands/find-contrast.ts +37 -0
  287. package/src/cli/commands/harmony.ts +30 -0
  288. package/src/cli/commands/mix.ts +29 -0
  289. package/src/cli/commands/palette.ts +66 -0
  290. package/src/cli/commands/scale.ts +75 -0
  291. package/src/cli/index.ts +2 -0
  292. package/src/cli/schema-command.ts +31 -0
  293. package/src/cli/schemas.ts +86 -0
  294. package/src/cli/shared.ts +72 -0
  295. package/src/cli/testing.ts +27 -0
  296. package/src/color/a98-rgb.ts +11 -0
  297. package/src/color/argb.ts +21 -0
  298. package/src/color/calc.ts +84 -0
  299. package/src/color/compare.ts +53 -0
  300. package/src/color/contrast.ts +277 -0
  301. package/src/color/convert.ts +43 -0
  302. package/src/color/display-p3-linear.ts +9 -0
  303. package/src/color/display-p3.ts +9 -0
  304. package/src/color/expr.ts +158 -0
  305. package/src/color/gamut.ts +215 -0
  306. package/src/color/hsl.ts +57 -0
  307. package/src/color/hwb.ts +26 -0
  308. package/src/color/index.ts +43 -0
  309. package/src/color/lab.ts +29 -0
  310. package/src/color/lch.ts +8 -0
  311. package/src/color/math.ts +30 -0
  312. package/src/color/matrices.ts +91 -0
  313. package/src/color/mix.ts +158 -0
  314. package/src/color/named.ts +13 -0
  315. package/src/color/oklab.ts +19 -0
  316. package/src/color/oklch.ts +8 -0
  317. package/src/color/parse.ts +355 -0
  318. package/src/color/polar.ts +17 -0
  319. package/src/color/prophoto-rgb.ts +18 -0
  320. package/src/color/rec2020.ts +11 -0
  321. package/src/color/resolve.ts +87 -0
  322. package/src/color/rgb.ts +20 -0
  323. package/src/color/serialize.ts +96 -0
  324. package/src/color/spaces.ts +42 -0
  325. package/src/color/srgb-linear.ts +9 -0
  326. package/src/color/srgb.ts +9 -0
  327. package/src/color/test-utils.ts +10 -0
  328. package/src/color/transfer.ts +14 -0
  329. package/src/color/types.ts +41 -0
  330. package/src/color/unknown-name.ts +15 -0
  331. package/src/color/xyz-d50.ts +13 -0
  332. package/src/color/xyz-d65.ts +8 -0
  333. package/src/index.ts +37 -0
  334. package/src/legacy/createColors.ts +36 -0
  335. package/src/legacy/createColorsFromMap.ts +29 -0
  336. package/src/legacy/interfaces.ts +19 -0
  337. package/{ts → src/legacy}/rgbHex.ts +7 -7
  338. package/src/legacy/rgbaString.ts +6 -0
  339. package/src/palette/dark.ts +55 -0
  340. package/src/palette/harmony.ts +107 -0
  341. package/src/palette/index.ts +18 -0
  342. package/src/palette/semantic.ts +120 -0
  343. package/src/palette/validate.ts +82 -0
  344. package/src/scale/brand.ts +50 -0
  345. package/src/scale/contrast-scale.ts +48 -0
  346. package/src/scale/gradient.ts +91 -0
  347. package/src/scale/index.ts +5 -0
  348. package/src/scale/scale.ts +127 -0
  349. package/src/scale/tone.ts +69 -0
  350. package/src/tokens/css.ts +32 -0
  351. package/src/tokens/dtcg.ts +62 -0
  352. package/src/tokens/index.ts +11 -0
  353. package/src/tokens/tailwind.ts +24 -0
  354. package/src/tokens/tokens.ts +82 -0
  355. package/cjs/createColors.d.ts +0 -2
  356. package/cjs/createColors.js +0 -40
  357. package/cjs/createColors.js.map +0 -1
  358. package/cjs/createColors.spec.d.ts +0 -1
  359. package/cjs/createColors.spec.js +0 -176
  360. package/cjs/createColors.spec.js.map +0 -1
  361. package/cjs/createColorsFromMap.d.ts +0 -5
  362. package/cjs/createColorsFromMap.js +0 -25
  363. package/cjs/createColorsFromMap.js.map +0 -1
  364. package/cjs/createColorsFromMap.spec.d.ts +0 -1
  365. package/cjs/createColorsFromMap.spec.js +0 -11
  366. package/cjs/createColorsFromMap.spec.js.map +0 -1
  367. package/cjs/interfaces.d.ts +0 -14
  368. package/cjs/interfaces.js +0 -3
  369. package/cjs/interfaces.js.map +0 -1
  370. package/cjs/rgbHex.d.ts +0 -6
  371. package/cjs/rgbHex.js +0 -19
  372. package/cjs/rgbHex.js.map +0 -1
  373. package/cjs/rgbHex.spec.d.ts +0 -1
  374. package/cjs/rgbHex.spec.js +0 -16
  375. package/cjs/rgbHex.spec.js.map +0 -1
  376. package/cjs/rgbaString.d.ts +0 -2
  377. package/cjs/rgbaString.js +0 -7
  378. package/cjs/rgbaString.js.map +0 -1
  379. package/cjs/rgbaString.spec.d.ts +0 -1
  380. package/cjs/rgbaString.spec.js +0 -10
  381. package/cjs/rgbaString.spec.js.map +0 -1
  382. package/esm/createColors.d.ts +0 -2
  383. package/esm/createColors.js +0 -27
  384. package/esm/createColors.js.map +0 -1
  385. package/esm/createColors.spec.d.ts +0 -1
  386. package/esm/createColors.spec.js +0 -171
  387. package/esm/createColors.spec.js.map +0 -1
  388. package/esm/createColorsFromMap.d.ts +0 -5
  389. package/esm/createColorsFromMap.js +0 -22
  390. package/esm/createColorsFromMap.js.map +0 -1
  391. package/esm/createColorsFromMap.spec.d.ts +0 -1
  392. package/esm/createColorsFromMap.spec.js +0 -9
  393. package/esm/createColorsFromMap.spec.js.map +0 -1
  394. package/esm/interfaces.d.ts +0 -14
  395. package/esm/interfaces.js +0 -2
  396. package/esm/interfaces.js.map +0 -1
  397. package/esm/rgbHex.js +0 -16
  398. package/esm/rgbHex.js.map +0 -1
  399. package/esm/rgbHex.spec.d.ts +0 -1
  400. package/esm/rgbHex.spec.js +0 -14
  401. package/esm/rgbHex.spec.js.map +0 -1
  402. package/esm/rgbaString.d.ts +0 -2
  403. package/esm/rgbaString.js +0 -4
  404. package/esm/rgbaString.js.map +0 -1
  405. package/esm/rgbaString.spec.d.ts +0 -1
  406. package/esm/rgbaString.spec.js +0 -8
  407. package/esm/rgbaString.spec.js.map +0 -1
  408. package/ts/createColors.ts +0 -35
  409. package/ts/createColorsFromMap.ts +0 -26
  410. package/ts/interfaces.ts +0 -15
  411. package/ts/rgbaString.ts +0 -5
  412. package/{ts → src/legacy}/index.ts +1 -1
@@ -0,0 +1,66 @@
1
+ import { type cli, command, t } from 'clibuilder'
2
+ import { fullOption, orEmpty, result } from 'clibuilder/axi'
3
+ import { toCss, toHex } from '../../color/index.js'
4
+ import { semanticPalette, validate } from '../../palette/index.js'
5
+ import { GAMUTS, MODES, readColor, round } from '../shared.js'
6
+
7
+ const definition = command({
8
+ name: 'palette',
9
+ description:
10
+ 'A semantic palette from one brand color: primary, status colors, surface and outline, each with a text color and its WCAG 2 ratio',
11
+ arguments: [{ name: 'brand', description: 'The brand color', type: t.string() }],
12
+ options: {
13
+ mode: { description: 'light or dark', type: t.enum([...MODES]), default: 'light' },
14
+ target: {
15
+ description: 'The WCAG 2 ratio every text pair must reach',
16
+ type: t.number({ min: 1, max: 21 }),
17
+ default: 4.5
18
+ },
19
+ gamut: { description: 'The gamut every color must fit', type: t.enum([...GAMUTS]), default: 'srgb' },
20
+ full: fullOption
21
+ },
22
+ run(args) {
23
+ const brand = readColor(args.brand, 'brand')
24
+ const made = semanticPalette({ brand, mode: args.mode, target: args.target, gamut: args.gamut })
25
+ const checked = validate(made, { target: args.target, gamut: args.gamut })
26
+ const roles = Object.entries(made.roles).map(([role, p]) => ({
27
+ role,
28
+ bg: toHex(p.bg),
29
+ fg: toHex(p.fg),
30
+ ratio: round(p.ratio),
31
+ target: p.target,
32
+ pass: p.ratio >= p.target
33
+ }))
34
+ const failing = roles.filter((r) => !r.pass).length
35
+ const full = args.full === true
36
+ return result(
37
+ {
38
+ brand: args.brand,
39
+ mode: args.mode,
40
+ contrast: made.meta.contrast,
41
+ passing: roles.length - failing,
42
+ failing,
43
+ roles,
44
+ issues: orEmpty(
45
+ checked.issues.map((i) => ({ kind: i.kind, path: i.path, message: i.message })),
46
+ 'none: every pair meets its target and every color fits the gamut'
47
+ ),
48
+ ...(full
49
+ ? {
50
+ primary: made.scales.primary.map((c) => toCss(c)),
51
+ neutral: made.scales.neutral.map((c) => toCss(c))
52
+ }
53
+ : {})
54
+ },
55
+ {
56
+ help: [
57
+ `Run \`color-map export '${args.brand}' --source palette --mode ${args.mode} --to css\` for CSS variables`,
58
+ `Run \`color-map palette '${args.brand}' --mode ${args.mode === 'light' ? 'dark' : 'light'}\` for the other mode`
59
+ ]
60
+ }
61
+ )
62
+ }
63
+ })
64
+
65
+ /** Typed as the plain command so its declaration does not name args-minus' internals. */
66
+ export const palette: cli.Command = definition
@@ -0,0 +1,75 @@
1
+ import { type cli, command, t } from 'clibuilder'
2
+ import { CliError, exitCodes, fullOption, result } from 'clibuilder/axi'
3
+ import { type Color, contrast, inGamut, toCss, toHex } from '../../color/index.js'
4
+ import { createContrastScale, createScale, scaleFromBrand } from '../../scale/index.js'
5
+ import { GAMUTS, MODES, PRESETS, readColor, round } from '../shared.js'
6
+
7
+ const WHITE = '#ffffff'
8
+ const BLACK = '#000000'
9
+
10
+ function rows(colors: Color[], full: boolean) {
11
+ return colors.map((color, i) => ({
12
+ step: i + 1,
13
+ hex: toHex(color),
14
+ onWhite: round(contrast(color, WHITE)),
15
+ onBlack: round(contrast(color, BLACK)),
16
+ ...(full ? { css: toCss(color), inSrgb: inGamut(color, 'srgb') } : {})
17
+ }))
18
+ }
19
+
20
+ const definition = command({
21
+ name: 'scale',
22
+ description: 'Build a color scale from a seed color: a preset, a Radix-style brand scale, or contrast targets',
23
+ arguments: [{ name: 'seed', description: 'The color the scale is built around', type: t.string() }],
24
+ options: {
25
+ preset: { description: 'The scale shape', type: t.enum([...PRESETS]), default: 'tailwind-11' },
26
+ steps: { description: 'Steps of an even scale; replaces --preset', type: t.int({ min: 2 }).optional() },
27
+ brand: { description: 'Build a 12-step UI scale where the seed is step 9 exactly', type: t.boolean().optional() },
28
+ mode: { description: 'light or dark, for --brand', type: t.enum([...MODES]), default: 'light' },
29
+ background: {
30
+ description: 'The background: page color for --brand, or what --targets are measured against',
31
+ type: t.string().optional()
32
+ },
33
+ targets: {
34
+ description: 'Contrast ratios, one per step, such as 3,4.5,7: builds a contrast scale',
35
+ type: t.array(t.number({ min: 1, max: 21 })).optional()
36
+ },
37
+ gamut: { description: 'The gamut every step must fit', type: t.enum([...GAMUTS]), default: 'srgb' },
38
+ full: fullOption
39
+ },
40
+ run(args) {
41
+ const full = args.full === true
42
+ const seed = readColor(args.seed, 'seed')
43
+ const background = args.background === undefined ? undefined : readColor(args.background, 'background')
44
+ if (args.targets) {
45
+ if (!background)
46
+ throw new CliError('--targets needs --background', {
47
+ code: 'missing-option',
48
+ exitCode: exitCodes.usage,
49
+ details: { option: 'background' },
50
+ help: `Run \`color-map scale '${args.seed}' --targets 3 --targets 4.5 --background '#ffffff'\``
51
+ })
52
+ const steps = createContrastScale({ seed, background, targets: args.targets })
53
+ const rows = steps.map((s) => ({
54
+ step: Number(s.name) + 1,
55
+ target: s.target,
56
+ hex: toHex(s.color),
57
+ ratio: round(s.contrast),
58
+ met: s.met,
59
+ ...(full ? { css: toCss(s.color) } : {})
60
+ }))
61
+ return result({ kind: 'contrast', seed: args.seed, background: args.background, steps: rows })
62
+ }
63
+ const colors = args.brand
64
+ ? scaleFromBrand(seed, { mode: args.mode, background, gamut: args.gamut })
65
+ : createScale({ seed, steps: args.steps ?? args.preset, gamut: args.gamut })
66
+ const kind = args.brand ? 'brand-12' : args.steps ? `even-${args.steps}` : args.preset
67
+ return result(
68
+ { kind, seed: args.seed, steps: rows(colors, full) },
69
+ { help: `Run \`color-map export '${args.seed}' --source scale --to tailwind\` to get it as a Tailwind v4 theme` }
70
+ )
71
+ }
72
+ })
73
+
74
+ /** Typed as the plain command so its declaration does not name args-minus' internals. */
75
+ export const scale: cli.Command = definition
@@ -0,0 +1,2 @@
1
+ export { commands, createApp } from './cli.js'
2
+ export { type SchemaSource, schemaOf } from './schemas.js'
@@ -0,0 +1,31 @@
1
+ import { type cli, command, t } from 'clibuilder'
2
+ import { CliError, document, exitCodes, result } from 'clibuilder/axi'
3
+ import { type SchemaSource, schemaOf } from './schemas.js'
4
+
5
+ /** The `schema` command, bound to the commands it describes. */
6
+ export function schemaCommand(sources: readonly SchemaSource[]): cli.Command {
7
+ const definition = command({
8
+ name: 'schema',
9
+ description: "Print the JSON Schema of a command's arguments and options",
10
+ arguments: [
11
+ { name: 'command', description: 'The command to describe; leave out to list them', type: t.string().optional() }
12
+ ],
13
+ run(args) {
14
+ if (args.command === undefined)
15
+ return result(
16
+ { commands: sources.map((s) => s.name) },
17
+ { help: 'Run `color-map schema <command>` for one schema' }
18
+ )
19
+ const source = sources.find((s) => s.name === args.command)
20
+ if (!source)
21
+ throw new CliError(`no command named "${args.command}"`, {
22
+ code: 'unknown-command',
23
+ exitCode: exitCodes.usage,
24
+ details: { command: args.command },
25
+ help: 'Run `color-map schema` to list the commands'
26
+ })
27
+ return document(`${JSON.stringify(schemaOf(source), null, 2)}\n`)
28
+ }
29
+ })
30
+ return definition
31
+ }
@@ -0,0 +1,86 @@
1
+ /** The introspection data of a clibuilder `t` descriptor, as far as the schema needs it. */
2
+ interface Data {
3
+ kind: string
4
+ min?: number
5
+ max?: number
6
+ values?: readonly string[]
7
+ element?: Data
8
+ }
9
+
10
+ /** The slice of a clibuilder command that a schema is read from. */
11
+ export interface SchemaSource {
12
+ name: string
13
+ description?: string | undefined
14
+ arguments?:
15
+ | readonly { name: string; description?: string | undefined; type?: { [k: string]: any } | undefined }[]
16
+ | undefined
17
+ options?: Record<
18
+ string,
19
+ { description?: string | undefined; type?: { [k: string]: any } | undefined; default?: unknown } | undefined
20
+ >
21
+ }
22
+
23
+ type Json = Record<string, unknown>
24
+
25
+ function ofType(data: Data): Json {
26
+ switch (data.kind) {
27
+ case 'string':
28
+ return { type: 'string' }
29
+ case 'number':
30
+ case 'int': {
31
+ const { min, max } = data
32
+ return {
33
+ type: data.kind === 'int' ? 'integer' : 'number',
34
+ ...(min === undefined ? {} : { minimum: min }),
35
+ ...(max === undefined ? {} : { maximum: max })
36
+ }
37
+ }
38
+ case 'boolean':
39
+ return { type: 'boolean' }
40
+ case 'enum':
41
+ return { type: 'string', enum: [...(data.values as readonly string[])] }
42
+ case 'array':
43
+ return { type: 'array', items: ofType(data.element as Data) }
44
+ default:
45
+ // literal, custom and json are not used by this CLI's options
46
+ return {}
47
+ }
48
+ }
49
+
50
+ function property(entry: NonNullable<SchemaSource['options']>[string], extra: Json = {}): Json {
51
+ const { description, type, default: value } = entry ?? {}
52
+ return {
53
+ ...ofType((type ?? { kind: 'string' }) as Data),
54
+ ...(description === undefined ? {} : { description }),
55
+ ...(value === undefined ? {} : { default: value }),
56
+ ...extra
57
+ }
58
+ }
59
+
60
+ /**
61
+ * The JSON Schema (2020-12) of one command's arguments and options, read from
62
+ * the same descriptors the parser uses, so the two cannot drift. An argument
63
+ * or option with no default that is not optional is `required`.
64
+ */
65
+ export function schemaOf(command: SchemaSource): Json {
66
+ const properties: Record<string, Json> = {}
67
+ const required: string[] = []
68
+ for (const arg of command.arguments ?? []) {
69
+ properties[arg.name] = property(arg, { 'x-kind': 'argument' })
70
+ if (!arg.type?.isOptional) required.push(arg.name)
71
+ }
72
+ for (const [name, option] of Object.entries(command.options ?? {})) {
73
+ properties[name] = property(option, { 'x-kind': 'option' })
74
+ if (option && !option.type?.isOptional && option.default === undefined) required.push(name)
75
+ }
76
+ return {
77
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
78
+ $id: `https://unpkg.com/color-map/schemas/${command.name}.schema.json`,
79
+ title: `color-map ${command.name}`,
80
+ ...(command.description === undefined ? {} : { description: command.description }),
81
+ type: 'object',
82
+ properties,
83
+ required,
84
+ additionalProperties: false
85
+ }
86
+ }
@@ -0,0 +1,72 @@
1
+ import { CliError, exitCodes } from 'clibuilder/axi'
2
+ import { type Color, type ColorSpaceId, parseExpr, resolve, toCss, toHex } from '../color/index.js'
3
+
4
+ /** Every space `convert` can target, with the CSS names. */
5
+ export const SPACES = [
6
+ 'srgb',
7
+ 'srgb-linear',
8
+ 'hsl',
9
+ 'hwb',
10
+ 'lab',
11
+ 'lch',
12
+ 'oklab',
13
+ 'oklch',
14
+ 'display-p3',
15
+ 'display-p3-linear',
16
+ 'a98-rgb',
17
+ 'prophoto-rgb',
18
+ 'rec2020',
19
+ 'xyz-d50',
20
+ 'xyz-d65'
21
+ ] as const satisfies readonly ColorSpaceId[]
22
+
23
+ /** The bounded RGB spaces a color can be mapped into. */
24
+ export const GAMUTS = [
25
+ 'srgb',
26
+ 'srgb-linear',
27
+ 'display-p3',
28
+ 'display-p3-linear',
29
+ 'a98-rgb',
30
+ 'prophoto-rgb',
31
+ 'rec2020'
32
+ ] as const
33
+
34
+ export const PRESETS = ['tailwind-11', 'radix-12', 'tone-13', 'even'] as const
35
+ export const MODES = ['light', 'dark'] as const
36
+ export const HUE_METHODS = ['shorter', 'longer', 'increasing', 'decreasing'] as const
37
+ export const EXPORT_TARGETS = ['dtcg', 'css', 'tailwind'] as const
38
+ export const EXPORT_SOURCES = ['palette', 'scale'] as const
39
+ export const POLAR_SPACES = ['hsl', 'hwb', 'lch', 'oklch'] as const
40
+
41
+ /** Round to `digits` decimals; keeps TOON tables short. */
42
+ export const round = (n: number, digits = 2): number => Number(n.toFixed(digits))
43
+
44
+ /**
45
+ * Reads a color from its CSS text: any Color 4 form, plus `color-mix()`,
46
+ * relative colors and `light-dark()` (resolved for the light scheme).
47
+ * Fails with a coded usage error that names the text.
48
+ */
49
+ export function readColor(text: string, name = 'color'): Color {
50
+ try {
51
+ return resolve(parseExpr(text), { colorScheme: 'light' })
52
+ } catch (cause) {
53
+ throw new CliError(`cannot read ${name} "${text}" as a CSS color`, {
54
+ code: 'invalid-color',
55
+ exitCode: exitCodes.usage,
56
+ details: { [name]: text, reason: (cause as Error).message },
57
+ help: 'Pass a CSS color such as "#3b82f6", "oklch(0.7 0.15 250)" or "color-mix(in oklab, red, blue)"'
58
+ })
59
+ }
60
+ }
61
+
62
+ /** A color as the essential fields: CSS text and hex. `--full` adds the parts. */
63
+ export function describe(color: Color, full: boolean): Record<string, string | number | boolean | null> {
64
+ const base = { css: toCss(color), hex: toHex(color) }
65
+ if (!full) return base
66
+ return {
67
+ ...base,
68
+ space: color.colorSpace,
69
+ components: color.components.map((c) => (c === null ? 'none' : round(c, 5))).join(' '),
70
+ alpha: color.alpha === undefined ? 1 : color.alpha
71
+ }
72
+ }
@@ -0,0 +1,27 @@
1
+ import { Writable } from 'node:stream'
2
+ import { createApp } from './cli.js'
3
+
4
+ /** Runs the CLI in process and returns what it wrote and how it exited. */
5
+ export async function run(...argv: string[]) {
6
+ let out = ''
7
+ let err = ''
8
+ const sink = (append: (s: string) => void) =>
9
+ new Writable({
10
+ decodeStrings: false,
11
+ write(chunk, _encoding, callback) {
12
+ append(String(chunk))
13
+ callback()
14
+ }
15
+ })
16
+ const code = await createApp({
17
+ version: '1.0.0',
18
+ streams: { stdout: sink((s) => (out += s)), stderr: sink((s) => (err += s)) }
19
+ }).parse(['node', 'color-map', ...argv])
20
+ return { out, err, code: typeof code === 'number' ? code : 0 }
21
+ }
22
+
23
+ /** Runs with `--format json` and parses stdout. */
24
+ export async function runJson(...argv: string[]) {
25
+ const r = await run(...argv, '--format', 'json')
26
+ return { ...r, json: JSON.parse(r.out) as Record<string, any> }
27
+ }
@@ -0,0 +1,11 @@
1
+ import { a98ToXyz, xyzToA98 } from './matrices.js'
2
+ import { rgbSpace } from './rgb.js'
3
+ import { power } from './transfer.js'
4
+
5
+ const [toLinear, toGamma] = power(563 / 256)
6
+
7
+ /**
8
+ * The CSS `a98-rgb` space (Adobe RGB 1998). Components are red, green and blue in 0 to 1, with a 563/256 power transfer curve.
9
+ * Pass it to `createConverter` and to the narrow helpers such as `createMix` and `createToGamut`.
10
+ */
11
+ export const a98Rgb = rgbSpace('a98-rgb', toLinear, toGamma, a98ToXyz, xyzToA98)
@@ -0,0 +1,21 @@
1
+ import type { Color } from './types.js'
2
+
3
+ const byte = (n: number): number => Math.min(255, Math.max(0, Math.round(n * 255)))
4
+
5
+ /** An sRGB color from a 32-bit ARGB integer, as Material Color Utilities uses. */
6
+ export function fromArgb(argb: number): Color {
7
+ const n = argb >>> 0
8
+ return {
9
+ colorSpace: 'srgb',
10
+ components: [((n >>> 16) & 255) / 255, ((n >>> 8) & 255) / 255, (n & 255) / 255],
11
+ alpha: (n >>> 24) / 255
12
+ }
13
+ }
14
+
15
+ /** The unsigned 32-bit ARGB integer for an sRGB color. Convert other spaces to `srgb` first. */
16
+ export function toArgb(color: Color): number {
17
+ if (color.colorSpace !== 'srgb') throw new Error(`color-map: toArgb needs an srgb color, got "${color.colorSpace}"`)
18
+ const [r, g, b] = color.components
19
+ const a = color.alpha ?? 1
20
+ return ((byte(a) << 24) | (byte(r ?? 0) << 16) | (byte(g ?? 0) << 8) | byte(b ?? 0)) >>> 0
21
+ }
@@ -0,0 +1,84 @@
1
+ import { fail, UNITS } from './parse.js'
2
+
3
+ const TOKEN = /\s*(?:(\d*\.?\d+(?:e[+-]?\d+)?)([a-z%]*)|([a-z][a-z0-9-]*)|([-+*/(),]))/y
4
+
5
+ /**
6
+ * Evaluates the `calc()` subset relative colors accept: numbers (angles become
7
+ * degrees), `+ - * /`, parentheses, `calc`, `min`, `max`, `clamp`, the
8
+ * constants `pi` and `e`, and the channel keywords in `vars`.
9
+ */
10
+ export function evaluate(text: string, vars: Record<string, number>): number {
11
+ const tokens: string[] = []
12
+ TOKEN.lastIndex = 0
13
+ let end = 0
14
+ for (let m = TOKEN.exec(text); m; m = TOKEN.exec(text)) {
15
+ end = TOKEN.lastIndex
16
+ if (m[1] === undefined) tokens.push(m[3] ?? m[4])
17
+ else {
18
+ const unit = UNITS[m[2]]
19
+ if (unit === undefined) return fail(text, 'units other than angles are not supported in calc()', m[1] + m[2])
20
+ tokens.push(String(Number(m[1]) * unit))
21
+ }
22
+ }
23
+ if (end !== text.trimEnd().length) return fail(text, 'invalid calc() expression')
24
+ let pos = 0
25
+ const next = (): string => tokens[pos++]
26
+ const eat = (t: string): boolean => (tokens[pos] === t ? ++pos > 0 : false)
27
+ const need = (t: string): void => {
28
+ if (!eat(t)) fail(text, `expected "${t}" in calc()`, tokens[pos])
29
+ }
30
+ function sum(): number {
31
+ let v = product()
32
+ for (;;) {
33
+ if (eat('+')) v += product()
34
+ else if (eat('-')) v -= product()
35
+ else return v
36
+ }
37
+ }
38
+ function product(): number {
39
+ let v = unary()
40
+ for (;;) {
41
+ if (eat('*')) v *= unary()
42
+ else if (eat('/')) v /= unary()
43
+ else return v
44
+ }
45
+ }
46
+ function unary(): number {
47
+ if (eat('-')) return -unary()
48
+ if (eat('+')) return unary()
49
+ return atom()
50
+ }
51
+ function args(): number[] {
52
+ const list = [sum()]
53
+ while (eat(',')) list.push(sum())
54
+ need(')')
55
+ return list
56
+ }
57
+ function atom(): number {
58
+ const t = next()
59
+ if (t === undefined) return fail(text, 'unfinished calc() expression')
60
+ if (/^[\d.]/.test(t)) return Number(t)
61
+ if (t === '(') {
62
+ const v = sum()
63
+ need(')')
64
+ return v
65
+ }
66
+ if (!/^[a-z]/.test(t)) return fail(text, 'unexpected token', t)
67
+ if (eat('(')) {
68
+ const list = args()
69
+ if (t === 'calc' && list.length === 1) return list[0]
70
+ if (t === 'min') return Math.min(...list)
71
+ if (t === 'max') return Math.max(...list)
72
+ if (t === 'clamp' && list.length === 3) return Math.min(list[2], Math.max(list[0], list[1]))
73
+ return fail(text, 'unknown function', t)
74
+ }
75
+ if (t === 'pi') return Math.PI
76
+ if (t === 'e') return Math.E
77
+ const v = vars[t]
78
+ if (typeof v === 'number') return v
79
+ return fail(text, 'unknown keyword', t)
80
+ }
81
+ const value = sum()
82
+ if (pos < tokens.length) return fail(text, 'unexpected token', tokens[pos])
83
+ return value
84
+ }
@@ -0,0 +1,53 @@
1
+ import { type ComponentsIn, componentsOver, convertWith } from './convert.js'
2
+ import { oklab } from './oklab.js'
3
+ import { componentsIn } from './spaces.js'
4
+ import type { Color, Space } from './types.js'
5
+
6
+ /** Euclidean distance in OKLab, the ΔEOK of CSS Color 4 gamut mapping. */
7
+ export function deltaEOk(a: Color, b: Color): number {
8
+ return deltaEOkWith(componentsIn, a, b)
9
+ }
10
+
11
+ /** {@link deltaEOk} over the spaces `componentsIn` knows, which must include `oklab`. */
12
+ export function deltaEOkWith(componentsIn: ComponentsIn, a: Color, b: Color): number {
13
+ const [l1, a1, b1] = componentsIn(a, 'oklab') as number[]
14
+ const [l2, a2, b2] = componentsIn(b, 'oklab') as number[]
15
+ return Math.hypot(l1 - l2, a1 - a2, b1 - b2)
16
+ }
17
+
18
+ /**
19
+ * Builds {@link deltaEOk} over the spaces given, plus `oklab`, so a bundle
20
+ * contains only those. A color in a space not given throws, naming that space.
21
+ */
22
+ export function createDeltaEOk(...spaces: Space[]): (a: Color, b: Color) => number {
23
+ const over = componentsOver([oklab, ...spaces])
24
+ return (a, b) => deltaEOkWith(over, a, b)
25
+ }
26
+
27
+ const same = (x: number | null | undefined, y: number | null | undefined, epsilon: number): boolean =>
28
+ x === y || (x != null && y != null && Math.abs(x - y) <= epsilon)
29
+
30
+ /**
31
+ * True when both colors are the same within `epsilon` per component. Colors in
32
+ * different spaces are compared in the first one's space. `none` equals only
33
+ * `none`, and a missing alpha equals 1.
34
+ */
35
+ export function equals(a: Color, b: Color, epsilon = 1e-6): boolean {
36
+ return equalsWith(componentsIn, a, b, epsilon)
37
+ }
38
+
39
+ function equalsWith(componentsIn: ComponentsIn, a: Color, b: Color, epsilon: number): boolean {
40
+ const other = convertWith(componentsIn, b, a.colorSpace)
41
+ return (
42
+ same(a.alpha ?? 1, other.alpha ?? 1, epsilon) && a.components.every((c, i) => same(c, other.components[i], epsilon))
43
+ )
44
+ }
45
+
46
+ /**
47
+ * Builds {@link equals} over the spaces given, so a bundle contains only those.
48
+ * It needs no space of its own: give the spaces of both colors.
49
+ */
50
+ export function createEquals(...spaces: Space[]): (a: Color, b: Color, epsilon?: number) => boolean {
51
+ const over = componentsOver(spaces)
52
+ return (a, b, epsilon = 1e-6) => equalsWith(over, a, b, epsilon)
53
+ }