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,50 @@
1
+ import type { GamutId } from '../color/gamut.js'
2
+ import { toGamut } from '../color/gamut.js'
3
+ import { toColor } from '../color/parse.js'
4
+ import { componentsIn } from '../color/spaces.js'
5
+ import type { Color } from '../color/types.js'
6
+ import { checkMode } from '../color/unknown-name.js'
7
+
8
+ /** Options for `scaleFromBrand`. */
9
+ export interface BrandScaleOptions {
10
+ /** `light` runs from a near-white step 1 to dark text; `dark` the other way. Default `light`. */
11
+ mode?: 'light' | 'dark'
12
+ /** The page background, whose lightness step 1 takes. Default near-white, or near-black in `dark`. */
13
+ background?: Color | string
14
+ /** The gamut steps other than 9 must fit. Default `srgb`. */
15
+ gamut?: GamutId
16
+ }
17
+
18
+ // Radix-like lightness shapes, steps 1 to 9, as fractions of the way from
19
+ // step 1 to step 9; chroma of steps 1 to 8 as multiples of the brand's.
20
+ const LIGHT_PROGRESS = [0, 0.0376, 0.1, 0.1796, 0.2606, 0.3466, 0.4656, 0.6312, 1]
21
+ const CHROMA = [0.04, 0.08, 0.2, 0.32, 0.43, 0.52, 0.6, 0.75]
22
+
23
+ /**
24
+ * A 12-step UI scale from one brand color, after Radix's palette generator:
25
+ * the brand color is step 9 exactly (returned as given, never remapped), steps
26
+ * 1 to 8 run from the background to it, step 10 is its hover state, and steps
27
+ * 11 and 12 are low- and high-contrast text. Hue is the brand's throughout.
28
+ * Radix's generator also borrows the shape of the nearest of its hand-tuned
29
+ * scales; this one uses a single fixed shape instead.
30
+ */
31
+ export function scaleFromBrand(brand: Color | string, options: BrandScaleOptions = {}): Color[] {
32
+ const { mode = 'light', gamut = 'srgb' } = options
33
+ const dark = checkMode(mode) === 'dark'
34
+ const exact = toColor(brand)
35
+ const [l9, c9, h9] = componentsIn(exact, 'oklch') as [number, number, number | null]
36
+ const hue = h9 ?? 0
37
+ const bg = options.background
38
+ const l1 = bg ? (componentsIn(toColor(bg), 'oklch')[0] as number) : dark ? 0.18 : 0.992
39
+ const step = (l: number, c: number): Color => toGamut({ colorSpace: 'oklch', components: [l, c, hue] }, { gamut })
40
+
41
+ const out: Color[] = []
42
+ for (let i = 0; i < 8; i++) out.push(step(l1 + (l9 - l1) * (LIGHT_PROGRESS[i] as number), c9 * (CHROMA[i] as number)))
43
+ out.push(exact)
44
+ const hover = 0.03 / (l9 + 0.1)
45
+ out.push(step(dark ? l9 + hover : l9 - hover, c9 * 0.93))
46
+ const c8 = c9 * (CHROMA[7] as number)
47
+ out.push(step(dark ? 0.78 : 0.5, Math.min(c9 * 0.9, Math.max(c8, c9))))
48
+ out.push(step(dark ? 0.93 : 0.3, Math.min(c9 * 0.35, Math.max(c8, c9))))
49
+ return out
50
+ }
@@ -0,0 +1,48 @@
1
+ import { type ContrastOption, findContrast } from '../color/contrast.js'
2
+ import { toColor } from '../color/parse.js'
3
+ import { componentsIn } from '../color/spaces.js'
4
+ import type { Color } from '../color/types.js'
5
+
6
+ /** Options for `createContrastScale`. */
7
+ export interface ContrastScaleOptions {
8
+ /** The color the scale is built around: its hue and chroma carry to every step. */
9
+ seed: Color | string
10
+ /** What every step is measured against. */
11
+ background: Color | string
12
+ /** Target contrast per step, in order, or a record of named targets (`{ text: 7, border: 3 }`). */
13
+ targets: number[] | Record<string, number>
14
+ /** Default `wcag2`. */
15
+ contrast?: ContrastOption
16
+ /** Which way to look first from the background. Default: darker on a light background, lighter on a dark one. */
17
+ direction?: 'lighter' | 'darker'
18
+ }
19
+
20
+ /** One step of a contrast scale: the target it was given and the color and contrast it got. */
21
+ export interface ContrastStep {
22
+ /** The key in a record of targets; otherwise the step's index as a string. */
23
+ name: string
24
+ target: number
25
+ color: Color
26
+ /** The contrast the color achieves, measured on the color itself. */
27
+ contrast: number
28
+ /** False when no lightness reaches `target`; `color` is then the best one found. */
29
+ met: boolean
30
+ }
31
+
32
+ /**
33
+ * A Leonardo-style scale: each step is the color nearest the background's
34
+ * lightness, at the seed's hue and chroma, that reaches its target contrast.
35
+ * Contrast is the same across hues, which a fixed-lightness scale cannot give.
36
+ */
37
+ export function createContrastScale(options: ContrastScaleOptions): ContrastStep[] {
38
+ const { seed, background, targets, contrast, direction } = options
39
+ const [, chroma, hue] = componentsIn(toColor(seed), 'oklch') as [number, number, number | null]
40
+ const entries: [string, number][] = Array.isArray(targets)
41
+ ? targets.map((t, i) => [String(i), t])
42
+ : Object.entries(targets)
43
+ return entries.map(([name, target]) => ({
44
+ name,
45
+ target,
46
+ ...findContrast(background, target, { hue: hue ?? 0, chroma, contrast, direction })
47
+ }))
48
+ }
@@ -0,0 +1,91 @@
1
+ import { type MixOptions, mix } from '../color/mix.js'
2
+ import { toColor } from '../color/parse.js'
3
+ import type { Color } from '../color/types.js'
4
+
5
+ /** A color at a position on the gradient line. */
6
+ export interface GradientStop {
7
+ /** A CSS color string or a color object. Its alpha is the stop's alpha. */
8
+ color: Color | string
9
+ /**
10
+ * Where the stop sits, with 0 the start and 1 the end; it may fall outside
11
+ * 0 to 1. Leave it out to spread the stop evenly between its neighbors, as
12
+ * CSS gradients do.
13
+ */
14
+ at?: number
15
+ }
16
+
17
+ /** `in` and `hue` work as they do in `mix()`. The default space is `oklab`. */
18
+ export type GradientOptions = MixOptions
19
+
20
+ /**
21
+ * Places each stop the way CSS gradients do (CSS Images 3 section 3.5.3): a
22
+ * first stop without a position goes at 0 and a last one at 1, a position
23
+ * before an earlier stop's moves up to it, and a run of stops without a
24
+ * position spreads evenly between the stops around it.
25
+ */
26
+ function positions(stops: readonly GradientStop[]): number[] {
27
+ const n = stops.length
28
+ const at: (number | undefined)[] = stops.map((s, i) => {
29
+ if (s.at !== undefined && !Number.isFinite(s.at)) throw new Error(`stop ${i} has a position of ${s.at}`)
30
+ return s.at
31
+ })
32
+ if (at[0] === undefined) at[0] = 0
33
+ if (at[n - 1] === undefined) at[n - 1] = 1
34
+ let max = Number.NEGATIVE_INFINITY
35
+ for (let i = 0; i < n; i++) {
36
+ const p = at[i]
37
+ if (p !== undefined) at[i] = max = Math.max(max, p)
38
+ }
39
+ for (let i = 1; i < n; i++) {
40
+ if (at[i] !== undefined) continue
41
+ let j = i
42
+ while (at[j] === undefined) j++
43
+ const from = at[i - 1] as number
44
+ const step = ((at[j] as number) - from) / (j - i + 1)
45
+ for (let k = i; k < j; k++) at[k] = from + step * (k - i + 1)
46
+ }
47
+ return at as number[]
48
+ }
49
+
50
+ /**
51
+ * Builds `count` colors, evenly spaced from 0 to 1 along a gradient through
52
+ * `stops`, the way a CSS gradient blends them: each pair of neighboring stops
53
+ * is mixed with `mix()`, so `in` and `hue` work as they do there. A stop is
54
+ * `{ color, at? }`, or a bare color when it needs no position. Before the
55
+ * first stop and after the last, the color holds; where two stops share a
56
+ * position, the color changes there and the sample at that position takes the
57
+ * later stop. Colors come back in the `in` space, default `oklab`.
58
+ *
59
+ * Alpha comes from each stop's color, 1 when the color has none, so
60
+ * `rgb(0 0 255 / 0.2)` is a stop at alpha 0.2. Alpha is interpolated linearly
61
+ * between stops, and the other components are premultiplied by it, so a
62
+ * transparent stop does not pull its neighbor's color toward its own. A color
63
+ * whose alpha is 1 has no `alpha` key; a `none` alpha takes the neighboring
64
+ * stop's.
65
+ *
66
+ * ```ts
67
+ * createGradient([{ color: 'blue', at: 0 }, { color: 'lime', at: 0.6 }, { color: 'yellow', at: 1 }], 72)
68
+ * createGradient(['blue', 'lime'], 5, { in: 'oklch' })
69
+ * ```
70
+ */
71
+ export function createGradient(
72
+ stops: readonly (GradientStop | Color | string)[],
73
+ count: number,
74
+ options: GradientOptions = {}
75
+ ): Color[] {
76
+ if (stops.length < 2) throw new Error(`a gradient needs at least 2 stops, got ${stops.length}`)
77
+ if (!Number.isInteger(count) || count < 2) throw new Error(`count must be an integer of at least 2, got ${count}`)
78
+ const list = stops.map((s): GradientStop => (typeof s === 'string' || 'colorSpace' in s ? { color: s } : s))
79
+ const colors = list.map((s) => toColor(s.color))
80
+ const at = positions(list)
81
+ const last = list.length - 2
82
+ return Array.from({ length: count }, (_, i) => {
83
+ const t = i / (count - 1)
84
+ let s = 0
85
+ while (s < last && (at[s + 1] as number) <= t) s++
86
+ const from = at[s] as number
87
+ const width = (at[s + 1] as number) - from
88
+ const local = width > 0 ? Math.min(1, Math.max(0, (t - from) / width)) : t < from ? 0 : 1
89
+ return mix(colors[s] as Color, colors[s + 1] as Color, local, options)
90
+ })
91
+ }
@@ -0,0 +1,5 @@
1
+ export { type BrandScaleOptions, scaleFromBrand } from './brand.js'
2
+ export { type ContrastScaleOptions, type ContrastStep, createContrastScale } from './contrast-scale.js'
3
+ export { createGradient, type GradientOptions, type GradientStop } from './gradient.js'
4
+ export { createScale, type ScaleOptions, type ScalePreset } from './scale.js'
5
+ export { lstar, MATERIAL_TONES, type TonalPaletteOptions, tonalPalette, toneColor } from './tone.js'
@@ -0,0 +1,127 @@
1
+ import type { GamutId } from '../color/gamut.js'
2
+ import { toGamut } from '../color/gamut.js'
3
+ import { toColor } from '../color/parse.js'
4
+ import { componentsIn } from '../color/spaces.js'
5
+ import type { Color } from '../color/types.js'
6
+ import { unknownName } from '../color/unknown-name.js'
7
+ import { MATERIAL_TONES, toneColor } from './tone.js'
8
+
9
+ /** The built-in step curves. */
10
+ export type ScalePreset = 'tailwind-11' | 'radix-12' | 'tone-13' | 'even'
11
+
12
+ /** A step curve. `chroma` and `hueShift` are relative to the step at `anchor`, which takes the seed's own. */
13
+ interface Curve {
14
+ /** OKLCH lightness 0 to 1, or CIE L* tones 0 to 100 when `tone` is set. */
15
+ lightness: number[]
16
+ chroma: number[]
17
+ hueShift: number[]
18
+ anchor: number
19
+ tone?: boolean
20
+ }
21
+
22
+ // Tailwind v4 blue, 50 to 950. The preset is this one hand-tuned scale, so a
23
+ // seed of blue-500 gives Tailwind's blue back, and other hues get its shape.
24
+ // biome-ignore lint/suspicious/noApproximativeNumericConstant: 0.707 is Tailwind blue-400's lightness, not 1/sqrt(2)
25
+ const TW_L = [0.97, 0.932, 0.882, 0.809, 0.707, 0.623, 0.546, 0.488, 0.424, 0.379, 0.282]
26
+ const TW_C = [0.014, 0.032, 0.059, 0.105, 0.165, 0.214, 0.245, 0.243, 0.199, 0.146, 0.091]
27
+ const TW_H = [254.604, 255.585, 254.128, 251.813, 254.624, 259.815, 262.881, 264.376, 265.638, 265.522, 267.935]
28
+
29
+ // Radix-like light scale: step 9 is the pure solid, 11 and 12 are text.
30
+ const RADIX_L = [0.992, 0.978, 0.953, 0.925, 0.894, 0.857, 0.81, 0.745, 0.62, 0.585, 0.52, 0.32]
31
+ const RADIX_C = [0.04, 0.08, 0.2, 0.32, 0.43, 0.52, 0.6, 0.75, 1, 0.95, 0.9, 0.35]
32
+
33
+ function even(n: number): Curve {
34
+ const t = (i: number) => i / (n - 1)
35
+ const bell = (i: number) => Math.sin(Math.PI * (0.05 + 0.9 * t(i)))
36
+ const anchor = Math.round((n - 1) / 2)
37
+ return {
38
+ lightness: Array.from({ length: n }, (_, i) => 0.97 - 0.72 * t(i)),
39
+ chroma: Array.from({ length: n }, (_, i) => bell(i) / bell(anchor)),
40
+ hueShift: Array.from({ length: n }, () => 0),
41
+ anchor
42
+ }
43
+ }
44
+
45
+ const PRESETS: Record<string, Curve> = {
46
+ __proto__: null as never,
47
+ 'tailwind-11': {
48
+ lightness: TW_L,
49
+ chroma: TW_C.map((c) => c / TW_C[5]),
50
+ hueShift: TW_H.map((h) => h - TW_H[5]),
51
+ anchor: 5
52
+ },
53
+ 'radix-12': {
54
+ lightness: RADIX_L,
55
+ chroma: RADIX_C,
56
+ hueShift: RADIX_L.map(() => 0),
57
+ anchor: 8
58
+ },
59
+ 'tone-13': {
60
+ lightness: [...MATERIAL_TONES].reverse(),
61
+ chroma: MATERIAL_TONES.map(() => 1),
62
+ hueShift: MATERIAL_TONES.map(() => 0),
63
+ anchor: 6,
64
+ tone: true
65
+ }
66
+ }
67
+
68
+ /** Options for `createScale`. Only `seed` is required. */
69
+ export interface ScaleOptions {
70
+ /** The color the scale is built around. Its hue and chroma set the anchor step. */
71
+ seed: Color | string
72
+ /**
73
+ * A preset, or a number of steps for an even scale. Default `tailwind-11`.
74
+ * `tone-13` steps are CIE L* tones, so any two steps whose tones differ by
75
+ * 40 reach contrast 3 and by 50 about 4.5.
76
+ */
77
+ steps?: ScalePreset | number
78
+ /** Overrides the curve's lightness, one per step: OKLCH L 0 to 1 (L* tones 0 to 100 for `tone-13`). */
79
+ lightness?: number[]
80
+ /** Overrides chroma, as a multiplier of the seed's chroma, one per step. */
81
+ chroma?: number[]
82
+ /** Overrides hue drift, as degrees added to the seed's hue, one per step; a number is applied to every step. */
83
+ hueShift?: number[] | number
84
+ /** The gamut every step must fit. Default `srgb`. */
85
+ gamut?: GamutId
86
+ }
87
+
88
+ function curveOf(steps: ScalePreset | number): Curve {
89
+ if (typeof steps === 'number') {
90
+ if (!Number.isInteger(steps) || steps < 2) throw new Error(`steps must be an integer of at least 2, got ${steps}`)
91
+ return even(steps)
92
+ }
93
+ if (steps === 'even') return even(11)
94
+ const curve = PRESETS[steps]
95
+ if (!curve) throw unknownName('scale preset', steps, [...Object.keys(PRESETS), 'even'])
96
+ return curve
97
+ }
98
+
99
+ /**
100
+ * Builds a scale from light to dark around `seed`: one color per step, in
101
+ * OKLCH, mapped into `gamut`. The preset gives each step's lightness, chroma
102
+ * (relative to the seed's) and hue drift; pass `lightness`, `chroma` or
103
+ * `hueShift` to replace any of them.
104
+ */
105
+ export function createScale(options: ScaleOptions): Color[] {
106
+ const { seed, steps = 'tailwind-11', gamut = 'srgb' } = options
107
+ const curve = curveOf(steps)
108
+ const n = curve.lightness.length
109
+ const pick = <T>(name: string, over: T[] | undefined, base: T[]): T[] => {
110
+ if (over === undefined) return base
111
+ if (over.length !== n) throw new Error(`${name} needs ${n} values, got ${over.length}`)
112
+ return over
113
+ }
114
+ const lightness = pick('lightness', options.lightness, curve.lightness)
115
+ const chroma = pick('chroma', options.chroma, curve.chroma)
116
+ const drift =
117
+ typeof options.hueShift === 'number' ? curve.lightness.map(() => options.hueShift as number) : options.hueShift
118
+ const hueShift = pick('hueShift', drift, curve.hueShift)
119
+
120
+ const [, seedC, seedH] = componentsIn(toColor(seed), 'oklch') as [number, number, number | null]
121
+ const hue = seedH ?? 0
122
+ return lightness.map((l, i) => {
123
+ const c = seedC * (chroma[i] as number)
124
+ const h = hue + (hueShift[i] as number)
125
+ return curve.tone ? toneColor(l, c, h, gamut) : toGamut({ colorSpace: 'oklch', components: [l, c, h] }, { gamut })
126
+ })
127
+ }
@@ -0,0 +1,69 @@
1
+ import { type GamutId, inGamut } from '../color/gamut.js'
2
+ import { componentsIn } from '../color/spaces.js'
3
+ import type { Color } from '../color/types.js'
4
+
5
+ /** CIE L* (0 to 100) of a color: its relative luminance on the CIELAB lightness curve. */
6
+ export function lstar(color: Color): number {
7
+ const y = Math.max(0, componentsIn(color, 'xyz-d65')[1] as number)
8
+ return y > 216 / 24389 ? 116 * Math.cbrt(y) - 16 : (24389 / 27) * y
9
+ }
10
+
11
+ /** Material's tones: 0 is black, 100 is white. */
12
+ export const MATERIAL_TONES: readonly number[] = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 95, 99, 100]
13
+
14
+ /** Options for `tonalPalette`. */
15
+ export interface TonalPaletteOptions {
16
+ /** The gamut the colors must fit. Default `srgb`. */
17
+ gamut?: GamutId
18
+ }
19
+
20
+ /**
21
+ * The color at CIE L* `tone` with the given OKLCH hue and chroma, mapped into
22
+ * the gamut. OKLCH lightness is searched until the *mapped* color lands on
23
+ * `tone`, so chroma tapers where the gamut narrows and the tone stays true.
24
+ */
25
+ export function toneColor(tone: number, chroma: number, hue: number, gamut: GamutId = 'srgb'): Color {
26
+ if (tone <= 0) return { colorSpace: 'oklch', components: [0, 0, hue] }
27
+ if (tone >= 100) return { colorSpace: 'oklch', components: [1, 0, hue] }
28
+ // The hue is fixed, so its cosine and sine are taken once: OKLCH converts by
29
+ // way of OKLab with exactly these products, so the results are the same.
30
+ const r = (hue * Math.PI) / 180
31
+ const cos = Math.cos(r)
32
+ const sin = Math.sin(r)
33
+ const lab = (l: number, c: number): Color => ({ colorSpace: 'oklab', components: [l, c * cos, c * sin] })
34
+ // The most chroma, up to `chroma`, that fits at this lightness; no clipping, so L* is monotone in L.
35
+ const chromaAt = (l: number): number => {
36
+ if (inGamut(lab(l, chroma), gamut)) return chroma
37
+ let [lo, hi] = [0, chroma]
38
+ for (let i = 0; i < 24; i++) {
39
+ const mid = (lo + hi) / 2
40
+ if (inGamut(lab(l, mid), gamut)) lo = mid
41
+ else hi = mid
42
+ }
43
+ return lo
44
+ }
45
+ let lo = 0
46
+ let hi = 1
47
+ for (let i = 0; i < 40; i++) {
48
+ const mid = (lo + hi) / 2
49
+ if (lstar(lab(mid, chromaAt(mid))) < tone) lo = mid
50
+ else hi = mid
51
+ }
52
+ const l = (lo + hi) / 2
53
+ return { colorSpace: 'oklch', components: [l, chromaAt(l), hue] }
54
+ }
55
+
56
+ /**
57
+ * A tonal palette, as in Material: one OKLCH hue and chroma held across L*
58
+ * tones. Two colors whose tones differ by 40 or more reach WCAG 2 contrast 3,
59
+ * and by 50 or more about 4.5 (4.48 at the worst pair, 100 against 50), so a
60
+ * pair can be picked by tone alone. Default tones are Material's 13.
61
+ */
62
+ export function tonalPalette(
63
+ hue: number,
64
+ chroma: number,
65
+ tones: readonly number[] = MATERIAL_TONES,
66
+ options: TonalPaletteOptions = {}
67
+ ): Color[] {
68
+ return tones.map((t) => toneColor(t, chroma, hue, options.gamut))
69
+ }
@@ -0,0 +1,32 @@
1
+ import { type ToCssOptions, toCss } from '../color/serialize.js'
2
+ import { convert } from '../color/spaces.js'
3
+ import type { ColorSpaceId } from '../color/types.js'
4
+ import { cssNames, flatten, type TokenTree } from './tokens.js'
5
+
6
+ /** Options for `toCssVariables`. It also takes the options of `toCss`. */
7
+ export interface CssVariablesOptions extends ToCssOptions {
8
+ /** The first part of every name. Default `color`, so `primary.500` becomes `--color-primary-500`. */
9
+ prefix?: string
10
+ /** The rule's selector. Default `:root`. */
11
+ selector?: string
12
+ /** Convert every color to this space first. Default: print each in its own space. */
13
+ space?: ColorSpaceId
14
+ }
15
+
16
+ /** Prints one `--name: value;` line per color. */
17
+ export function declarations(tree: TokenTree, options: CssVariablesOptions & { prefix: string }): string[] {
18
+ const { prefix, space, ...css } = options
19
+ return [...cssNames(flatten(tree), prefix)].map(
20
+ ([name, color]) => `${name}: ${toCss(space ? convert(color, space) : color, css)};`
21
+ )
22
+ }
23
+
24
+ /**
25
+ * Exports colors as CSS custom properties in one rule:
26
+ * `:root {\n\t--color-primary-500: oklch(0.62 0.19 260);\n}`.
27
+ */
28
+ export function toCssVariables(tree: TokenTree, options: CssVariablesOptions = {}): string {
29
+ const { selector = ':root', prefix = 'color', ...rest } = options
30
+ const lines = declarations(tree, { ...rest, prefix })
31
+ return `${selector} {\n${lines.map((l) => `\t${l}\n`).join('')}}\n`
32
+ }
@@ -0,0 +1,62 @@
1
+ import { toGamut } from '../color/gamut.js'
2
+ import { toHex } from '../color/serialize.js'
3
+ import { convert } from '../color/spaces.js'
4
+ import type { Color, ColorSpaceId, Component } from '../color/types.js'
5
+ import { flatten, type TokenTree } from './tokens.js'
6
+
7
+ /** Options for `toDtcg` and `toDtcgValue`. */
8
+ export interface DtcgOptions {
9
+ /** Add the `hex` fallback (an sRGB `#rrggbb`, gamut-mapped when the color is outside sRGB). Default `true`. */
10
+ hex?: boolean
11
+ }
12
+
13
+ /** A color value as DTCG 2025.10 defines it. */
14
+ export interface DtcgColorValue {
15
+ colorSpace: Exclude<ColorSpaceId, 'display-p3-linear'>
16
+ components: (number | 'none')[]
17
+ alpha?: number | 'none'
18
+ hex?: string
19
+ }
20
+
21
+ /** A DTCG color token: a `$type` of `color` and its {@link DtcgColorValue}. */
22
+ export interface DtcgToken {
23
+ $type: 'color'
24
+ $value: DtcgColorValue
25
+ }
26
+
27
+ /** A DTCG group: named tokens and nested groups, as `toDtcg` returns for a token tree. */
28
+ export interface DtcgGroup {
29
+ [name: string]: DtcgToken | DtcgGroup
30
+ }
31
+
32
+ const none = (c: Component): number | 'none' => (c === null ? 'none' : c)
33
+
34
+ /** One color as a DTCG color value. `display-p3-linear` is not a DTCG space, so it becomes `display-p3`. */
35
+ export function toDtcgValue(color: Color, options: DtcgOptions = {}): DtcgColorValue {
36
+ const c = color.colorSpace === 'display-p3-linear' ? convert(color, 'display-p3') : color
37
+ const value: DtcgColorValue = {
38
+ colorSpace: c.colorSpace as DtcgColorValue['colorSpace'],
39
+ components: c.components.map(none)
40
+ }
41
+ if (c.alpha !== undefined && c.alpha !== 1) value.alpha = none(c.alpha)
42
+ if (options.hex !== false) value.hex = toHex(toGamut({ ...c, alpha: 1 }, { gamut: 'srgb' }))
43
+ return value
44
+ }
45
+
46
+ /**
47
+ * Exports colors as a DTCG 2025.10 token file: nested groups whose leaves are
48
+ * `{ "$type": "color", "$value": { colorSpace, components, alpha?, hex? } }`.
49
+ * `none` components print as `"none"`, as the format asks. The result is plain
50
+ * JSON, so `JSON.stringify` writes the file.
51
+ */
52
+ export function toDtcg(tree: TokenTree, options: DtcgOptions = {}): DtcgGroup {
53
+ // Groups have no prototype, so a token named `__proto__` or `constructor` is an ordinary key.
54
+ const root: DtcgGroup = Object.create(null)
55
+ for (const { path, color } of flatten(tree)) {
56
+ if (!path.length) throw new TypeError('A DTCG file needs a name for each token: pass an object, not a single color')
57
+ let group = root
58
+ for (const name of path.slice(0, -1)) group = (group[name] ??= Object.create(null)) as DtcgGroup
59
+ group[path[path.length - 1] as string] = { $type: 'color', $value: toDtcgValue(color, options) }
60
+ }
61
+ return root
62
+ }
@@ -0,0 +1,11 @@
1
+ export { type CssVariablesOptions, toCssVariables } from './css.js'
2
+ export {
3
+ type DtcgColorValue,
4
+ type DtcgGroup,
5
+ type DtcgOptions,
6
+ type DtcgToken,
7
+ toDtcg,
8
+ toDtcgValue
9
+ } from './dtcg.js'
10
+ export { type TailwindThemeOptions, toTailwindTheme } from './tailwind.js'
11
+ export type { TokenTree } from './tokens.js'
@@ -0,0 +1,24 @@
1
+ import type { ColorSpaceId } from '../color/types.js'
2
+ import { type CssVariablesOptions, declarations } from './css.js'
3
+ import type { TokenTree } from './tokens.js'
4
+
5
+ /** Options for `toTailwindTheme`. It also takes the options of `toCss`. */
6
+ export interface TailwindThemeOptions extends Omit<CssVariablesOptions, 'selector' | 'prefix'> {
7
+ /** The theme variable namespace. Default `color`, which makes `bg-primary-500` and `text-primary-500` work. */
8
+ namespace?: string
9
+ /** Write `@theme inline`, so utilities hold the value instead of a `var()` reference. */
10
+ inline?: boolean
11
+ /** Default `oklch`, the space Tailwind v4's own palette uses. */
12
+ space?: ColorSpaceId
13
+ }
14
+
15
+ /**
16
+ * Exports colors as a Tailwind v4 `@theme` block. Paste it into the CSS file
17
+ * that has `@import "tailwindcss"`. An array of 11 colors gets the keys
18
+ * 50 to 950, so a `tailwind-11` scale reads like Tailwind's own.
19
+ */
20
+ export function toTailwindTheme(tree: TokenTree, options: TailwindThemeOptions = {}): string {
21
+ const { namespace = 'color', inline = false, space = 'oklch', ...rest } = options
22
+ const lines = declarations(tree, { ...rest, space, prefix: namespace })
23
+ return `@theme${inline ? ' inline' : ''} {\n${lines.map((l) => `\t${l}\n`).join('')}}\n`
24
+ }
@@ -0,0 +1,82 @@
1
+ import { parse } from '../color/parse.js'
2
+ import type { Color } from '../color/types.js'
3
+ import type { SemanticPalette } from '../palette/semantic.js'
4
+
5
+ /**
6
+ * What the exporters accept: a color, a CSS color string, an array of them (a
7
+ * scale), or an object of any of these nested to any depth. A `SemanticPalette`
8
+ * works as is: its `scales` and its role pairs export, its `meta` and ratios do not.
9
+ */
10
+ export type TokenTree = SemanticPalette | Color | string | readonly TokenTree[] | { readonly [name: string]: TokenTree }
11
+
12
+ /** A color and the path of names that leads to it. */
13
+ export interface Token {
14
+ path: string[]
15
+ color: Color
16
+ }
17
+
18
+ const TAILWIND_STEPS = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950]
19
+
20
+ const isColor = (v: unknown): v is Color =>
21
+ typeof v === 'object' &&
22
+ v !== null &&
23
+ typeof (v as Color).colorSpace === 'string' &&
24
+ Array.isArray((v as Color).components)
25
+
26
+ // A semantic palette carries numbers (`ratio`, `target`, `meta`) that are not colors.
27
+ function dropMeasures(tree: Record<string, unknown>): Record<string, unknown> {
28
+ const { meta: _meta, scales, roles, ...rest } = tree
29
+ const pairs = Object.fromEntries(
30
+ Object.entries(roles as Record<string, { bg: Color; fg: Color }>).map(([name, p]) => [name, { bg: p.bg, fg: p.fg }])
31
+ )
32
+ return { ...rest, ...(scales as object), roles: pairs }
33
+ }
34
+
35
+ const isSemantic = (v: object): boolean => 'roles' in v && 'scales' in v && 'meta' in v
36
+
37
+ /**
38
+ * Walks a token tree into a flat list. An array gets the Tailwind keys
39
+ * (50 to 950) when it has 11 colors and 1-based numbers otherwise; pass an
40
+ * object to choose the keys. A value that is not a color, a string, an array
41
+ * or an object throws with its path.
42
+ */
43
+ export function flatten(tree: TokenTree): Token[] {
44
+ const out: Token[] = []
45
+ const walk = (node: unknown, path: string[]): void => {
46
+ if (typeof node === 'string') return void out.push({ path, color: parse(node) })
47
+ if (isColor(node)) return void out.push({ path, color: node })
48
+ if (Array.isArray(node)) {
49
+ node.forEach((v, i) => {
50
+ walk(v, [...path, String(node.length === TAILWIND_STEPS.length ? TAILWIND_STEPS[i] : i + 1)])
51
+ })
52
+ return
53
+ }
54
+ if (typeof node === 'object' && node !== null) {
55
+ const obj = isSemantic(node) ? dropMeasures(node as Record<string, unknown>) : (node as Record<string, unknown>)
56
+ for (const [k, v] of Object.entries(obj)) walk(v, [...path, k])
57
+ return
58
+ }
59
+ throw new TypeError(`${path.join('.') || '(root)'} is not a color, a string, an array or an object`)
60
+ }
61
+ walk(tree, [])
62
+ return out
63
+ }
64
+
65
+ /** `primaryContainer` and `Primary Container` both become `primary-container`. */
66
+ export const kebab = (name: string): string =>
67
+ name
68
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
69
+ .replace(/[^a-zA-Z0-9]+/g, '-')
70
+ .replace(/^-+|-+$/g, '')
71
+ .toLowerCase()
72
+
73
+ /** Names tokens for CSS: kebab-case segments joined by `-` after `prefix`. Two tokens with one name throw. */
74
+ export function cssNames(tokens: Token[], prefix: string): Map<string, Color> {
75
+ const out = new Map<string, Color>()
76
+ for (const { path, color } of tokens) {
77
+ const name = `--${[prefix, ...path].map(kebab).filter(Boolean).join('-')}`
78
+ if (out.has(name)) throw new Error(`${name} is produced by more than one token`)
79
+ out.set(name, color)
80
+ }
81
+ return out
82
+ }
@@ -1,2 +0,0 @@
1
- import type { RGB, RGBA, Alpha } from './interfaces.js';
2
- export declare function createColors(from: RGB | RGBA, to: RGB | RGBA, shades: number, alpha?: Alpha): RGBA[];
@@ -1,40 +0,0 @@
1
- "use strict";
2
- var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
3
- if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
4
- if (ar || !(i in from)) {
5
- if (!ar) ar = Array.prototype.slice.call(from, 0, i);
6
- ar[i] = from[i];
7
- }
8
- }
9
- return to.concat(ar || Array.prototype.slice.call(from));
10
- };
11
- Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.createColors = createColors;
13
- function createColors(from, to, shades, alpha) {
14
- if (alpha === void 0) { alpha = [
15
- from.length === 4 ? from[3] : to.length === 4 ? to[3] : 1,
16
- to.length === 4 ? to[3] : from.length === 4 ? from[3] : 1
17
- ]; }
18
- var start = __spreadArray([], from, true);
19
- var diff = [
20
- to[0] - from[0],
21
- to[1] - from[1],
22
- to[2] - from[2],
23
- alpha[1] - alpha[0]
24
- ];
25
- if (start.length === 3)
26
- start.push(alpha[0]);
27
- var inc = 1 / Math.max(shades - 1, 1);
28
- var rgba = [];
29
- for (var i = 0; i < shades; i++) {
30
- var color = [
31
- Math.round(start[0] + i * diff[0] * inc),
32
- Math.round(start[1] + i * diff[1] * inc),
33
- Math.round(start[2] + i * diff[2] * inc),
34
- start[3] + i * diff[3] * inc
35
- ];
36
- rgba.push(color);
37
- }
38
- return rgba;
39
- }
40
- //# sourceMappingURL=createColors.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"createColors.js","sourceRoot":"","sources":["../ts/createColors.ts"],"names":[],"mappings":";;;;;;;;;;;AAEA,oCAgCC;AAhCD,SAAgB,YAAY,CAC1B,IAAgB,EAChB,EAAc,EACd,MAAc,EACd,KAGC;IAHD,sBAAA,EAAA;QACE,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACzD,EAAE,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;KAC1D;IACD,IAAM,KAAK,qBAAO,IAAI,OAAC,CAAA;IACvB,IAAM,IAAI,GAAG;QACX,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;QACf,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;QACf,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;QACf,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC;KACpB,CAAA;IAED,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QACpB,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;IAEtB,IAAM,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,CAAC,CAAA;IAEvC,IAAM,IAAI,GAAU,EAAE,CAAA;IACtB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAChC,IAAM,KAAK,GAAG;YACZ,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC;YACxC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC;YACxC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC;YACxC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,GAAG;SAC7B,CAAA;QACD,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAClB,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC"}
@@ -1 +0,0 @@
1
- export {};