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,5 @@
1
+ import { DarkStrategy, DarkVariantOptions, darkVariant } from "./dark.js";
2
+ import { HarmonizeOptions, HarmonyOptions, HarmonyScheme, HarmonySpace, harmonize, harmony, inverse } from "./harmony.js";
3
+ import { RolePair, SemanticPalette, SemanticPaletteOptions, SemanticRole, semanticPalette } from "./semantic.js";
4
+ import { PaletteIssue, PaletteLike, ValidateOptions, ValidateResult, validate } from "./validate.js";
5
+ export { type DarkStrategy, type DarkVariantOptions, type HarmonizeOptions, type HarmonyOptions, type HarmonyScheme, type HarmonySpace, type PaletteIssue, type PaletteLike, type RolePair, type SemanticPalette, type SemanticPaletteOptions, type SemanticRole, type ValidateOptions, type ValidateResult, darkVariant, harmonize, harmony, inverse, semanticPalette, validate };
@@ -0,0 +1,5 @@
1
+ import { darkVariant } from "./dark.js";
2
+ import { harmonize, harmony, inverse } from "./harmony.js";
3
+ import { semanticPalette } from "./semantic.js";
4
+ import { validate } from "./validate.js";
5
+ export { darkVariant, harmonize, harmony, inverse, semanticPalette, validate };
@@ -0,0 +1,53 @@
1
+ import { ContrastOption } from "../color/contrast.js";
2
+ import { GamutId } from "../color/gamut.js";
3
+ import { Color } from "../color/types.js";
4
+ /** A background, the foreground drawn on it, and the contrast they reach. */
5
+ export interface RolePair {
6
+ bg: Color;
7
+ fg: Color;
8
+ /** The contrast of `fg` on `bg`, as the algorithm reports it (absolute value). */
9
+ ratio: number;
10
+ /** The contrast the pair was built to reach. */
11
+ target: number;
12
+ }
13
+ /** Options for `semanticPalette`. Only `brand` is required. */
14
+ export interface SemanticPaletteOptions {
15
+ /** The brand color; it sets the hue of the primary, the neutrals and the harmonized status colors. */
16
+ brand: Color | string;
17
+ /** Default `light`. */
18
+ mode?: 'light' | 'dark';
19
+ /** The contrast measure. Default `wcag2`. */
20
+ contrast?: ContrastOption;
21
+ /** The contrast text pairs must reach. Default 4.5, which fits WCAG 2 (pass about 60 for APCA Lc). */
22
+ target?: number;
23
+ /** The gamut every color must fit. Default `srgb`. */
24
+ gamut?: GamutId;
25
+ }
26
+ /** A role in a {@link SemanticPalette}. Each role holds a {@link RolePair}. */
27
+ export type SemanticRole = 'primary' | 'primaryContainer' | 'success' | 'warning' | 'danger' | 'info' | 'surface' | 'surfaceVariant' | 'outline';
28
+ /** What `semanticPalette` returns: the settings it used, the tonal scales it built from, and a pair for each role. */
29
+ export interface SemanticPalette {
30
+ meta: {
31
+ mode: 'light' | 'dark';
32
+ contrast: string;
33
+ target: number;
34
+ gamut: GamutId;
35
+ };
36
+ /** Material's 13 tones, dark to light, in the brand's hue (`primary`) and a faint tint of it (`neutral`). */
37
+ scales: {
38
+ primary: Color[];
39
+ neutral: Color[];
40
+ };
41
+ roles: Record<SemanticRole, RolePair>;
42
+ }
43
+ /**
44
+ * A role-based palette from one brand color: primary and its container, four
45
+ * status colors (success, warning, danger, info) harmonized toward the brand's
46
+ * hue, a surface and a variant tinted by the brand hue, and an outline. Every
47
+ * pair is built from L* tones, so a 50-tone gap or more reaches about 4.5 on
48
+ * WCAG 2 (the worst pair, 100 on 50, gives 4.48). Each pair records the ratio
49
+ * it reached; a foreground that falls short of `target` is replaced by the
50
+ * closest one that meets it, and `validate` reports any that cannot.
51
+ */
52
+ export declare function semanticPalette(options: SemanticPaletteOptions): SemanticPalette;
53
+ //# sourceMappingURL=semantic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semantic.d.ts","names":[],"sources":["../../src/palette/semantic.ts"],"mappings":";;;;iBAUiB;EAChB,IAAI;EACJ,IAAI;;EAEJ;;EAEA;;;iBAIgB;;EAEhB,OAAO;;EAEP;;EAEA,WAAW;;EAEX;;EAEA,QAAQ;;;YAIG;;iBAYK;EAChB;IAAQ;IAAwB;IAAkB;IAAgB,OAAO;;;EAEzE;IAAU,SAAS;IAAS,SAAS;;EACrC,OAAO,OAAO,cAAc;;;;;;;;;;;wBAmBb,gBAAgB,SAAS,yBAAyB"}
@@ -0,0 +1,96 @@
1
+ import { findContrast, pick } from "../color/contrast.js";
2
+ import { toColor } from "../color/parse.js";
3
+ import { componentsIn } from "../color/spaces.js";
4
+ import { checkMode } from "../color/unknown-name.js";
5
+ import { tonalPalette, toneColor } from "../scale/tone.js";
6
+ import { harmonize } from "./harmony.js";
7
+ // src/palette/semantic.ts
8
+ const BG_TONE = {
9
+ primary: [40, 80],
10
+ primaryContainer: [90, 30],
11
+ surface: [98, 6],
12
+ surfaceVariant: [90, 30]
13
+ };
14
+ const FG_TONE = {
15
+ primary: [100, 20],
16
+ primaryContainer: [10, 90],
17
+ surface: [10, 90],
18
+ surfaceVariant: [30, 80]
19
+ };
20
+ const STATUS = {
21
+ success: 145,
22
+ warning: 85,
23
+ danger: 25,
24
+ info: 240
25
+ };
26
+ /**
27
+ * A role-based palette from one brand color: primary and its container, four
28
+ * status colors (success, warning, danger, info) harmonized toward the brand's
29
+ * hue, a surface and a variant tinted by the brand hue, and an outline. Every
30
+ * pair is built from L* tones, so a 50-tone gap or more reaches about 4.5 on
31
+ * WCAG 2 (the worst pair, 100 on 50, gives 4.48). Each pair records the ratio
32
+ * it reached; a foreground that falls short of `target` is replaced by the
33
+ * closest one that meets it, and `validate` reports any that cannot.
34
+ */
35
+ function semanticPalette(options) {
36
+ const { mode = "light", contrast = "wcag2", target = 4.5, gamut = "srgb" } = options;
37
+ const fn = pick(contrast);
38
+ const dark = checkMode(mode) === "dark";
39
+ const i = dark ? 1 : 0;
40
+ const brand = toColor(options.brand);
41
+ const [, c, h] = componentsIn(brand, "oklch");
42
+ const hue = h ?? 0;
43
+ const chroma = Math.max(c, .04);
44
+ const tint = .015;
45
+ const pair = (bg, fgTone, fgChroma, need = target) => {
46
+ let fg = toneColor(fgTone, fgChroma, hue, gamut);
47
+ let ratio = Math.abs(fn(fg, bg));
48
+ if (ratio < need) {
49
+ const found = findContrast(bg, need, {
50
+ contrast: fn,
51
+ chroma: fgChroma,
52
+ hue
53
+ });
54
+ fg = found.color;
55
+ ratio = Math.abs(found.contrast);
56
+ }
57
+ return {
58
+ bg,
59
+ fg,
60
+ ratio,
61
+ target: need
62
+ };
63
+ };
64
+ const status = (name) => {
65
+ const base = toneColor(BG_TONE.primary[i], .14, STATUS[name], gamut);
66
+ return pair(harmonize(base, brand, { gamut }), FG_TONE.primary[i], .02);
67
+ };
68
+ const surface = toneColor(BG_TONE.surface[i], tint, hue, gamut);
69
+ const outline = pair(surface, dark ? 60 : 50, tint, 3);
70
+ return {
71
+ meta: {
72
+ mode,
73
+ contrast: fn.algorithm ?? "custom",
74
+ target,
75
+ gamut
76
+ },
77
+ scales: {
78
+ primary: tonalPalette(hue, chroma, void 0, { gamut }),
79
+ neutral: tonalPalette(hue, tint, void 0, { gamut })
80
+ },
81
+ roles: {
82
+ primary: pair(toneColor(BG_TONE.primary[i], chroma, hue, gamut), FG_TONE.primary[i], .02),
83
+ primaryContainer: pair(toneColor(BG_TONE.primaryContainer[i], chroma, hue, gamut), FG_TONE.primaryContainer[i], .02),
84
+ success: status("success"),
85
+ warning: status("warning"),
86
+ danger: status("danger"),
87
+ info: status("info"),
88
+ surface: pair(surface, FG_TONE.surface[i], tint),
89
+ surfaceVariant: pair(toneColor(BG_TONE.surfaceVariant[i], tint, hue, gamut), FG_TONE.surfaceVariant[i], tint),
90
+ outline
91
+ }
92
+ };
93
+ }
94
+ export { semanticPalette };
95
+
96
+ //# sourceMappingURL=semantic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semantic.js","names":[],"sources":["../../src/palette/semantic.ts"],"sourcesContent":["import { type ContrastOption, findContrast, pick } from '../color/contrast.js'\nimport type { GamutId } from '../color/gamut.js'\nimport { toColor } from '../color/parse.js'\nimport { componentsIn } from '../color/spaces.js'\nimport type { Color } from '../color/types.js'\nimport { checkMode } from '../color/unknown-name.js'\nimport { tonalPalette, toneColor } from '../scale/tone.js'\nimport { harmonize } from './harmony.js'\n\n/** A background, the foreground drawn on it, and the contrast they reach. */\nexport interface RolePair {\n\tbg: Color\n\tfg: Color\n\t/** The contrast of `fg` on `bg`, as the algorithm reports it (absolute value). */\n\tratio: number\n\t/** The contrast the pair was built to reach. */\n\ttarget: number\n}\n\n/** Options for `semanticPalette`. Only `brand` is required. */\nexport interface SemanticPaletteOptions {\n\t/** The brand color; it sets the hue of the primary, the neutrals and the harmonized status colors. */\n\tbrand: Color | string\n\t/** Default `light`. */\n\tmode?: 'light' | 'dark'\n\t/** The contrast measure. Default `wcag2`. */\n\tcontrast?: ContrastOption\n\t/** The contrast text pairs must reach. Default 4.5, which fits WCAG 2 (pass about 60 for APCA Lc). */\n\ttarget?: number\n\t/** The gamut every color must fit. Default `srgb`. */\n\tgamut?: GamutId\n}\n\n/** A role in a {@link SemanticPalette}. Each role holds a {@link RolePair}. */\nexport type SemanticRole =\n\t| 'primary'\n\t| 'primaryContainer'\n\t| 'success'\n\t| 'warning'\n\t| 'danger'\n\t| 'info'\n\t| 'surface'\n\t| 'surfaceVariant'\n\t| 'outline'\n\n/** What `semanticPalette` returns: the settings it used, the tonal scales it built from, and a pair for each role. */\nexport interface SemanticPalette {\n\tmeta: { mode: 'light' | 'dark'; contrast: string; target: number; gamut: GamutId }\n\t/** Material's 13 tones, dark to light, in the brand's hue (`primary`) and a faint tint of it (`neutral`). */\n\tscales: { primary: Color[]; neutral: Color[] }\n\troles: Record<SemanticRole, RolePair>\n}\n\n// Tones (light, dark) per role: Material's 2021 assignments (see the research notes, B-palettes).\nconst BG_TONE = { primary: [40, 80], primaryContainer: [90, 30], surface: [98, 6], surfaceVariant: [90, 30] } as const\nconst FG_TONE = { primary: [100, 20], primaryContainer: [10, 90], surface: [10, 90], surfaceVariant: [30, 80] } as const\n\n// OKLCH hues of the status colors, before they are harmonized toward the brand.\nconst STATUS = { success: 145, warning: 85, danger: 25, info: 240 } as const\n\n/**\n * A role-based palette from one brand color: primary and its container, four\n * status colors (success, warning, danger, info) harmonized toward the brand's\n * hue, a surface and a variant tinted by the brand hue, and an outline. Every\n * pair is built from L* tones, so a 50-tone gap or more reaches about 4.5 on\n * WCAG 2 (the worst pair, 100 on 50, gives 4.48). Each pair records the ratio\n * it reached; a foreground that falls short of `target` is replaced by the\n * closest one that meets it, and `validate` reports any that cannot.\n */\nexport function semanticPalette(options: SemanticPaletteOptions): SemanticPalette {\n\tconst { mode = 'light', contrast = 'wcag2', target = 4.5, gamut = 'srgb' } = options\n\tconst fn = pick(contrast)\n\tconst dark = checkMode(mode) === 'dark'\n\tconst i = dark ? 1 : 0\n\tconst brand = toColor(options.brand)\n\tconst [, c, h] = componentsIn(brand, 'oklch') as [number, number, number | null]\n\tconst hue = h ?? 0\n\tconst chroma = Math.max(c, 0.04)\n\tconst tint = 0.015\n\n\tconst pair = (bg: Color, fgTone: number, fgChroma: number, need = target): RolePair => {\n\t\tlet fg = toneColor(fgTone, fgChroma, hue, gamut)\n\t\tlet ratio = Math.abs(fn(fg, bg))\n\t\tif (ratio < need) {\n\t\t\tconst found = findContrast(bg, need, { contrast: fn, chroma: fgChroma, hue })\n\t\t\tfg = found.color\n\t\t\tratio = Math.abs(found.contrast)\n\t\t}\n\t\treturn { bg, fg, ratio, target: need }\n\t}\n\tconst status = (name: keyof typeof STATUS): RolePair => {\n\t\tconst base = toneColor(BG_TONE.primary[i], 0.14, STATUS[name], gamut)\n\t\treturn pair(harmonize(base, brand, { gamut }), FG_TONE.primary[i], 0.02)\n\t}\n\tconst surface = toneColor(BG_TONE.surface[i], tint, hue, gamut)\n\tconst outline = pair(surface, dark ? 60 : 50, tint, 3)\n\n\treturn {\n\t\tmeta: { mode, contrast: fn.algorithm ?? 'custom', target, gamut },\n\t\tscales: {\n\t\t\tprimary: tonalPalette(hue, chroma, undefined, { gamut }),\n\t\t\tneutral: tonalPalette(hue, tint, undefined, { gamut })\n\t\t},\n\t\troles: {\n\t\t\tprimary: pair(toneColor(BG_TONE.primary[i], chroma, hue, gamut), FG_TONE.primary[i], 0.02),\n\t\t\tprimaryContainer: pair(\n\t\t\t\ttoneColor(BG_TONE.primaryContainer[i], chroma, hue, gamut),\n\t\t\t\tFG_TONE.primaryContainer[i],\n\t\t\t\t0.02\n\t\t\t),\n\t\t\tsuccess: status('success'),\n\t\t\twarning: status('warning'),\n\t\t\tdanger: status('danger'),\n\t\t\tinfo: status('info'),\n\t\t\tsurface: pair(surface, FG_TONE.surface[i], tint),\n\t\t\tsurfaceVariant: pair(toneColor(BG_TONE.surfaceVariant[i], tint, hue, gamut), FG_TONE.surfaceVariant[i], tint),\n\t\t\toutline\n\t\t}\n\t}\n}\n"],"mappings":";;;;;;;AAsDA,MAAM,UAAU;CAAE,SAAS,CAAC,IAAI,EAAE;CAAG,kBAAkB,CAAC,IAAI,EAAE;CAAG,SAAS,CAAC,IAAI,CAAC;CAAG,gBAAgB,CAAC,IAAI,EAAE;AAAE;AAC5G,MAAM,UAAU;CAAE,SAAS,CAAC,KAAK,EAAE;CAAG,kBAAkB,CAAC,IAAI,EAAE;CAAG,SAAS,CAAC,IAAI,EAAE;CAAG,gBAAgB,CAAC,IAAI,EAAE;AAAE;AAG9G,MAAM,SAAS;CAAE,SAAS;CAAK,SAAS;CAAI,QAAQ;CAAI,MAAM;AAAI;;;;;;;;;;AAWlE,SAAgB,gBAAgB,SAAkD;CACjF,MAAM,EAAE,OAAO,SAAS,WAAW,SAAS,SAAS,KAAK,QAAQ,WAAW;CAC7E,MAAM,KAAK,KAAK,QAAQ;CACxB,MAAM,OAAO,UAAU,IAAI,MAAM;CACjC,MAAM,IAAI,OAAO,IAAI;CACrB,MAAM,QAAQ,QAAQ,QAAQ,KAAK;CACnC,MAAM,GAAG,GAAG,KAAK,aAAa,OAAO,OAAO;CAC5C,MAAM,MAAM,KAAK;CACjB,MAAM,SAAS,KAAK,IAAI,GAAG,GAAI;CAC/B,MAAM,OAAO;CAEb,MAAM,QAAQ,IAAW,QAAgB,UAAkB,OAAO,WAAqB;EACtF,IAAI,KAAK,UAAU,QAAQ,UAAU,KAAK,KAAK;EAC/C,IAAI,QAAQ,KAAK,IAAI,GAAG,IAAI,EAAE,CAAC;EAC/B,IAAI,QAAQ,MAAM;GACjB,MAAM,QAAQ,aAAa,IAAI,MAAM;IAAE,UAAU;IAAI,QAAQ;IAAU;GAAI,CAAC;GAC5E,KAAK,MAAM;GACX,QAAQ,KAAK,IAAI,MAAM,QAAQ;EAChC;EACA,OAAO;GAAE;GAAI;GAAI;GAAO,QAAQ;EAAK;CACtC;CACA,MAAM,UAAU,SAAwC;EACvD,MAAM,OAAO,UAAU,QAAQ,QAAQ,IAAI,KAAM,OAAO,OAAO,KAAK;EACpE,OAAO,KAAK,UAAU,MAAM,OAAO,EAAE,MAAM,CAAC,GAAG,QAAQ,QAAQ,IAAI,GAAI;CACxE;CACA,MAAM,UAAU,UAAU,QAAQ,QAAQ,IAAI,MAAM,KAAK,KAAK;CAC9D,MAAM,UAAU,KAAK,SAAS,OAAO,KAAK,IAAI,MAAM,CAAC;CAErD,OAAO;EACN,MAAM;GAAE;GAAM,UAAU,GAAG,aAAa;GAAU;GAAQ;EAAM;EAChE,QAAQ;GACP,SAAS,aAAa,KAAK,QAAQ,KAAA,GAAW,EAAE,MAAM,CAAC;GACvD,SAAS,aAAa,KAAK,MAAM,KAAA,GAAW,EAAE,MAAM,CAAC;EACtD;EACA,OAAO;GACN,SAAS,KAAK,UAAU,QAAQ,QAAQ,IAAI,QAAQ,KAAK,KAAK,GAAG,QAAQ,QAAQ,IAAI,GAAI;GACzF,kBAAkB,KACjB,UAAU,QAAQ,iBAAiB,IAAI,QAAQ,KAAK,KAAK,GACzD,QAAQ,iBAAiB,IACzB,GACD;GACA,SAAS,OAAO,SAAS;GACzB,SAAS,OAAO,SAAS;GACzB,QAAQ,OAAO,QAAQ;GACvB,MAAM,OAAO,MAAM;GACnB,SAAS,KAAK,SAAS,QAAQ,QAAQ,IAAI,IAAI;GAC/C,gBAAgB,KAAK,UAAU,QAAQ,eAAe,IAAI,MAAM,KAAK,KAAK,GAAG,QAAQ,eAAe,IAAI,IAAI;GAC5G;EACD;CACD;AACD"}
@@ -0,0 +1,39 @@
1
+ import { ContrastOption } from "../color/contrast.js";
2
+ import { GamutId } from "../color/gamut.js";
3
+ import { Color } from "../color/types.js";
4
+ import { RolePair } from "./semantic.js";
5
+ /** What `validate` accepts: a {@link SemanticPalette}, or any object with role pairs and/or scales. */
6
+ export interface PaletteLike {
7
+ roles?: Record<string, RolePair>;
8
+ scales?: Record<string, readonly Color[]>;
9
+ }
10
+ /** Options for `validate`. */
11
+ export interface ValidateOptions {
12
+ /** The measure used to re-check each pair. Default `wcag2`. */
13
+ contrast?: ContrastOption;
14
+ /** The target for a pair that carries none. Default 4.5. */
15
+ target?: number;
16
+ /** The gamut colors must fit; a color outside it was clipped or never mapped. Default `srgb`. */
17
+ gamut?: GamutId;
18
+ }
19
+ /** One problem `validate` found in a palette. */
20
+ export interface PaletteIssue {
21
+ kind: 'contrast' | 'lightness' | 'gamut';
22
+ /** Where: `roles.danger`, `scales.primary[3]`. */
23
+ path: string;
24
+ message: string;
25
+ }
26
+ /** What `validate` returns: `ok` is true when `issues` is empty. */
27
+ export interface ValidateResult {
28
+ ok: boolean;
29
+ issues: PaletteIssue[];
30
+ }
31
+ /**
32
+ * Checks a palette and lists what is wrong, so an agent can fix it:
33
+ * - `contrast`: a role pair whose measured contrast is under its target;
34
+ * - `lightness`: a scale whose OKLCH lightness does not change steadily in one
35
+ * direction from one step to the next;
36
+ * - `gamut`: a color outside the gamut (it would be clipped on display).
37
+ */
38
+ export declare function validate(palette: PaletteLike, options?: ValidateOptions): ValidateResult;
39
+ //# sourceMappingURL=validate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.d.ts","names":[],"sources":["../../src/palette/validate.ts"],"mappings":";;;;;iBAOiB;EAChB,QAAQ,eAAe;EACvB,SAAS,wBAAwB;;;iBAIjB;;EAEhB,WAAW;;EAEX;;EAEA,QAAQ;;;iBAIQ;EAChB;;EAEA;EACA;;;iBAIgB;EAChB;EACA,QAAQ;;;;;;;;;wBAUO,SAAS,SAAS,aAAa,UAAS,kBAAuB"}
@@ -0,0 +1,56 @@
1
+ import { pick } from "../color/contrast.js";
2
+ import { inGamut } from "../color/gamut.js";
3
+ import { componentsIn } from "../color/spaces.js";
4
+ // src/palette/validate.ts
5
+ /**
6
+ * Checks a palette and lists what is wrong, so an agent can fix it:
7
+ * - `contrast`: a role pair whose measured contrast is under its target;
8
+ * - `lightness`: a scale whose OKLCH lightness does not change steadily in one
9
+ * direction from one step to the next;
10
+ * - `gamut`: a color outside the gamut (it would be clipped on display).
11
+ */
12
+ function validate(palette, options = {}) {
13
+ const { contrast = "wcag2", target = 4.5, gamut = "srgb" } = options;
14
+ const fn = pick(contrast);
15
+ const issues = [];
16
+ const outside = (path, color) => {
17
+ if (!inGamut(color, gamut)) issues.push({
18
+ kind: "gamut",
19
+ path,
20
+ message: `${path} is outside ${gamut}`
21
+ });
22
+ };
23
+ for (const [name, p] of Object.entries(palette.roles ?? {})) {
24
+ const need = p.target ?? target;
25
+ const ratio = Math.abs(fn(p.fg, p.bg));
26
+ if (ratio < need) issues.push({
27
+ kind: "contrast",
28
+ path: `roles.${name}`,
29
+ message: `roles.${name} reaches ${ratio.toFixed(2)}, needs ${need}`
30
+ });
31
+ outside(`roles.${name}.bg`, p.bg);
32
+ outside(`roles.${name}.fg`, p.fg);
33
+ }
34
+ for (const [name, scale] of Object.entries(palette.scales ?? {})) {
35
+ const ls = scale.map((c) => componentsIn(c, "oklch")[0]);
36
+ const steps = ls.slice(1).map((l, k) => l - ls[k]);
37
+ const direction = Math.sign(steps.find((d) => d !== 0) ?? 0);
38
+ steps.forEach((d, k) => {
39
+ if (Math.sign(d) !== direction || Math.abs(d) < 1e-9) issues.push({
40
+ kind: "lightness",
41
+ path: `scales.${name}[${k + 1}]`,
42
+ message: `scales.${name} lightness does not change steadily at step ${k + 1}`
43
+ });
44
+ });
45
+ scale.forEach((c, k) => {
46
+ outside(`scales.${name}[${k}]`, c);
47
+ });
48
+ }
49
+ return {
50
+ ok: issues.length === 0,
51
+ issues
52
+ };
53
+ }
54
+ export { validate };
55
+
56
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.js","names":[],"sources":["../../src/palette/validate.ts"],"sourcesContent":["import { type ContrastOption, pick } from '../color/contrast.js'\nimport { type GamutId, inGamut } from '../color/gamut.js'\nimport { componentsIn } from '../color/spaces.js'\nimport type { Color } from '../color/types.js'\nimport type { RolePair } from './semantic.js'\n\n/** What `validate` accepts: a {@link SemanticPalette}, or any object with role pairs and/or scales. */\nexport interface PaletteLike {\n\troles?: Record<string, RolePair>\n\tscales?: Record<string, readonly Color[]>\n}\n\n/** Options for `validate`. */\nexport interface ValidateOptions {\n\t/** The measure used to re-check each pair. Default `wcag2`. */\n\tcontrast?: ContrastOption\n\t/** The target for a pair that carries none. Default 4.5. */\n\ttarget?: number\n\t/** The gamut colors must fit; a color outside it was clipped or never mapped. Default `srgb`. */\n\tgamut?: GamutId\n}\n\n/** One problem `validate` found in a palette. */\nexport interface PaletteIssue {\n\tkind: 'contrast' | 'lightness' | 'gamut'\n\t/** Where: `roles.danger`, `scales.primary[3]`. */\n\tpath: string\n\tmessage: string\n}\n\n/** What `validate` returns: `ok` is true when `issues` is empty. */\nexport interface ValidateResult {\n\tok: boolean\n\tissues: PaletteIssue[]\n}\n\n/**\n * Checks a palette and lists what is wrong, so an agent can fix it:\n * - `contrast`: a role pair whose measured contrast is under its target;\n * - `lightness`: a scale whose OKLCH lightness does not change steadily in one\n * direction from one step to the next;\n * - `gamut`: a color outside the gamut (it would be clipped on display).\n */\nexport function validate(palette: PaletteLike, options: ValidateOptions = {}): ValidateResult {\n\tconst { contrast = 'wcag2', target = 4.5, gamut = 'srgb' } = options\n\tconst fn = pick(contrast)\n\tconst issues: PaletteIssue[] = []\n\tconst outside = (path: string, color: Color) => {\n\t\tif (!inGamut(color, gamut)) issues.push({ kind: 'gamut', path, message: `${path} is outside ${gamut}` })\n\t}\n\n\tfor (const [name, p] of Object.entries(palette.roles ?? {})) {\n\t\tconst need = p.target ?? target\n\t\tconst ratio = Math.abs(fn(p.fg, p.bg))\n\t\tif (ratio < need)\n\t\t\tissues.push({\n\t\t\t\tkind: 'contrast',\n\t\t\t\tpath: `roles.${name}`,\n\t\t\t\tmessage: `roles.${name} reaches ${ratio.toFixed(2)}, needs ${need}`\n\t\t\t})\n\t\toutside(`roles.${name}.bg`, p.bg)\n\t\toutside(`roles.${name}.fg`, p.fg)\n\t}\n\n\tfor (const [name, scale] of Object.entries(palette.scales ?? {})) {\n\t\tconst ls = scale.map((c) => componentsIn(c, 'oklch')[0] as number)\n\t\tconst steps = ls.slice(1).map((l, k) => l - (ls[k] as number))\n\t\tconst direction = Math.sign(steps.find((d) => d !== 0) ?? 0)\n\t\tsteps.forEach((d, k) => {\n\t\t\tif (Math.sign(d) !== direction || Math.abs(d) < 1e-9)\n\t\t\t\tissues.push({\n\t\t\t\t\tkind: 'lightness',\n\t\t\t\t\tpath: `scales.${name}[${k + 1}]`,\n\t\t\t\t\tmessage: `scales.${name} lightness does not change steadily at step ${k + 1}`\n\t\t\t\t})\n\t\t})\n\t\tscale.forEach((c, k) => {\n\t\t\toutside(`scales.${name}[${k}]`, c)\n\t\t})\n\t}\n\treturn { ok: issues.length === 0, issues }\n}\n"],"mappings":";;;;;;;;;;;AA2CA,SAAgB,SAAS,SAAsB,UAA2B,CAAC,GAAmB;CAC7F,MAAM,EAAE,WAAW,SAAS,SAAS,KAAK,QAAQ,WAAW;CAC7D,MAAM,KAAK,KAAK,QAAQ;CACxB,MAAM,SAAyB,CAAC;CAChC,MAAM,WAAW,MAAc,UAAiB;EAC/C,IAAI,CAAC,QAAQ,OAAO,KAAK,GAAG,OAAO,KAAK;GAAE,MAAM;GAAS;GAAM,SAAS,GAAG,KAAK,cAAc;EAAQ,CAAC;CACxG;CAEA,KAAK,MAAM,CAAC,MAAM,MAAM,OAAO,QAAQ,QAAQ,SAAS,CAAC,CAAC,GAAG;EAC5D,MAAM,OAAO,EAAE,UAAU;EACzB,MAAM,QAAQ,KAAK,IAAI,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC;EACrC,IAAI,QAAQ,MACX,OAAO,KAAK;GACX,MAAM;GACN,MAAM,SAAS;GACf,SAAS,SAAS,KAAK,WAAW,MAAM,QAAQ,CAAC,EAAE,UAAU;EAC9D,CAAC;EACF,QAAQ,SAAS,KAAK,MAAM,EAAE,EAAE;EAChC,QAAQ,SAAS,KAAK,MAAM,EAAE,EAAE;CACjC;CAEA,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,QAAQ,UAAU,CAAC,CAAC,GAAG;EACjE,MAAM,KAAK,MAAM,KAAK,MAAM,aAAa,GAAG,OAAO,CAAC,CAAC,EAAY;EACjE,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG,MAAM,IAAK,GAAG,EAAa;EAC7D,MAAM,YAAY,KAAK,KAAK,MAAM,MAAM,MAAM,MAAM,CAAC,KAAK,CAAC;EAC3D,MAAM,SAAS,GAAG,MAAM;GACvB,IAAI,KAAK,KAAK,CAAC,MAAM,aAAa,KAAK,IAAI,CAAC,IAAI,MAC/C,OAAO,KAAK;IACX,MAAM;IACN,MAAM,UAAU,KAAK,GAAG,IAAI,EAAE;IAC9B,SAAS,UAAU,KAAK,8CAA8C,IAAI;GAC3E,CAAC;EACH,CAAC;EACD,MAAM,SAAS,GAAG,MAAM;GACvB,QAAQ,UAAU,KAAK,GAAG,EAAE,IAAI,CAAC;EAClC,CAAC;CACF;CACA,OAAO;EAAE,IAAI,OAAO,WAAW;EAAG;CAAO;AAC1C"}
@@ -0,0 +1,21 @@
1
+ import { GamutId } from "../color/gamut.js";
2
+ import { Color } from "../color/types.js";
3
+ /** Options for `scaleFromBrand`. */
4
+ export interface BrandScaleOptions {
5
+ /** `light` runs from a near-white step 1 to dark text; `dark` the other way. Default `light`. */
6
+ mode?: 'light' | 'dark';
7
+ /** The page background, whose lightness step 1 takes. Default near-white, or near-black in `dark`. */
8
+ background?: Color | string;
9
+ /** The gamut steps other than 9 must fit. Default `srgb`. */
10
+ gamut?: GamutId;
11
+ }
12
+ /**
13
+ * A 12-step UI scale from one brand color, after Radix's palette generator:
14
+ * the brand color is step 9 exactly (returned as given, never remapped), steps
15
+ * 1 to 8 run from the background to it, step 10 is its hover state, and steps
16
+ * 11 and 12 are low- and high-contrast text. Hue is the brand's throughout.
17
+ * Radix's generator also borrows the shape of the nearest of its hand-tuned
18
+ * scales; this one uses a single fixed shape instead.
19
+ */
20
+ export declare function scaleFromBrand(brand: Color | string, options?: BrandScaleOptions): Color[];
21
+ //# sourceMappingURL=brand.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brand.d.ts","names":[],"sources":["../../src/scale/brand.ts"],"mappings":";;;iBAQiB;;EAEhB;;EAEA,aAAa;;EAEb,QAAQ;;;;;;;;;;wBAgBO,eAAe,OAAO,gBAAgB,UAAS,oBAAyB"}
@@ -0,0 +1,63 @@
1
+ import { toGamut } from "../color/gamut.js";
2
+ import { toColor } from "../color/parse.js";
3
+ import { componentsIn } from "../color/spaces.js";
4
+ import { checkMode } from "../color/unknown-name.js";
5
+ // src/scale/brand.ts
6
+ const LIGHT_PROGRESS = [
7
+ 0,
8
+ .0376,
9
+ .1,
10
+ .1796,
11
+ .2606,
12
+ .3466,
13
+ .4656,
14
+ .6312,
15
+ 1
16
+ ];
17
+ const CHROMA = [
18
+ .04,
19
+ .08,
20
+ .2,
21
+ .32,
22
+ .43,
23
+ .52,
24
+ .6,
25
+ .75
26
+ ];
27
+ /**
28
+ * A 12-step UI scale from one brand color, after Radix's palette generator:
29
+ * the brand color is step 9 exactly (returned as given, never remapped), steps
30
+ * 1 to 8 run from the background to it, step 10 is its hover state, and steps
31
+ * 11 and 12 are low- and high-contrast text. Hue is the brand's throughout.
32
+ * Radix's generator also borrows the shape of the nearest of its hand-tuned
33
+ * scales; this one uses a single fixed shape instead.
34
+ */
35
+ function scaleFromBrand(brand, options = {}) {
36
+ const { mode = "light", gamut = "srgb" } = options;
37
+ const dark = checkMode(mode) === "dark";
38
+ const exact = toColor(brand);
39
+ const [l9, c9, h9] = componentsIn(exact, "oklch");
40
+ const hue = h9 ?? 0;
41
+ const bg = options.background;
42
+ const l1 = bg ? componentsIn(toColor(bg), "oklch")[0] : dark ? .18 : .992;
43
+ const step = (l, c) => toGamut({
44
+ colorSpace: "oklch",
45
+ components: [
46
+ l,
47
+ c,
48
+ hue
49
+ ]
50
+ }, { gamut });
51
+ const out = [];
52
+ for (let i = 0; i < 8; i++) out.push(step(l1 + (l9 - l1) * LIGHT_PROGRESS[i], c9 * CHROMA[i]));
53
+ out.push(exact);
54
+ const hover = .03 / (l9 + .1);
55
+ out.push(step(dark ? l9 + hover : l9 - hover, c9 * .93));
56
+ const c8 = c9 * CHROMA[7];
57
+ out.push(step(dark ? .78 : .5, Math.min(c9 * .9, Math.max(c8, c9))));
58
+ out.push(step(dark ? .93 : .3, Math.min(c9 * .35, Math.max(c8, c9))));
59
+ return out;
60
+ }
61
+ export { scaleFromBrand };
62
+
63
+ //# sourceMappingURL=brand.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brand.js","names":[],"sources":["../../src/scale/brand.ts"],"sourcesContent":["import type { GamutId } from '../color/gamut.js'\nimport { toGamut } from '../color/gamut.js'\nimport { toColor } from '../color/parse.js'\nimport { componentsIn } from '../color/spaces.js'\nimport type { Color } from '../color/types.js'\nimport { checkMode } from '../color/unknown-name.js'\n\n/** Options for `scaleFromBrand`. */\nexport interface BrandScaleOptions {\n\t/** `light` runs from a near-white step 1 to dark text; `dark` the other way. Default `light`. */\n\tmode?: 'light' | 'dark'\n\t/** The page background, whose lightness step 1 takes. Default near-white, or near-black in `dark`. */\n\tbackground?: Color | string\n\t/** The gamut steps other than 9 must fit. Default `srgb`. */\n\tgamut?: GamutId\n}\n\n// Radix-like lightness shapes, steps 1 to 9, as fractions of the way from\n// step 1 to step 9; chroma of steps 1 to 8 as multiples of the brand's.\nconst LIGHT_PROGRESS = [0, 0.0376, 0.1, 0.1796, 0.2606, 0.3466, 0.4656, 0.6312, 1]\nconst CHROMA = [0.04, 0.08, 0.2, 0.32, 0.43, 0.52, 0.6, 0.75]\n\n/**\n * A 12-step UI scale from one brand color, after Radix's palette generator:\n * the brand color is step 9 exactly (returned as given, never remapped), steps\n * 1 to 8 run from the background to it, step 10 is its hover state, and steps\n * 11 and 12 are low- and high-contrast text. Hue is the brand's throughout.\n * Radix's generator also borrows the shape of the nearest of its hand-tuned\n * scales; this one uses a single fixed shape instead.\n */\nexport function scaleFromBrand(brand: Color | string, options: BrandScaleOptions = {}): Color[] {\n\tconst { mode = 'light', gamut = 'srgb' } = options\n\tconst dark = checkMode(mode) === 'dark'\n\tconst exact = toColor(brand)\n\tconst [l9, c9, h9] = componentsIn(exact, 'oklch') as [number, number, number | null]\n\tconst hue = h9 ?? 0\n\tconst bg = options.background\n\tconst l1 = bg ? (componentsIn(toColor(bg), 'oklch')[0] as number) : dark ? 0.18 : 0.992\n\tconst step = (l: number, c: number): Color => toGamut({ colorSpace: 'oklch', components: [l, c, hue] }, { gamut })\n\n\tconst out: Color[] = []\n\tfor (let i = 0; i < 8; i++) out.push(step(l1 + (l9 - l1) * (LIGHT_PROGRESS[i] as number), c9 * (CHROMA[i] as number)))\n\tout.push(exact)\n\tconst hover = 0.03 / (l9 + 0.1)\n\tout.push(step(dark ? l9 + hover : l9 - hover, c9 * 0.93))\n\tconst c8 = c9 * (CHROMA[7] as number)\n\tout.push(step(dark ? 0.78 : 0.5, Math.min(c9 * 0.9, Math.max(c8, c9))))\n\tout.push(step(dark ? 0.93 : 0.3, Math.min(c9 * 0.35, Math.max(c8, c9))))\n\treturn out\n}\n"],"mappings":";;;;;AAmBA,MAAM,iBAAiB;CAAC;CAAG;CAAQ;CAAK;CAAQ;CAAQ;CAAQ;CAAQ;CAAQ;AAAC;AACjF,MAAM,SAAS;CAAC;CAAM;CAAM;CAAK;CAAM;CAAM;CAAM;CAAK;AAAI;;;;;;;;;AAU5D,SAAgB,eAAe,OAAuB,UAA6B,CAAC,GAAY;CAC/F,MAAM,EAAE,OAAO,SAAS,QAAQ,WAAW;CAC3C,MAAM,OAAO,UAAU,IAAI,MAAM;CACjC,MAAM,QAAQ,QAAQ,KAAK;CAC3B,MAAM,CAAC,IAAI,IAAI,MAAM,aAAa,OAAO,OAAO;CAChD,MAAM,MAAM,MAAM;CAClB,MAAM,KAAK,QAAQ;CACnB,MAAM,KAAK,KAAM,aAAa,QAAQ,EAAE,GAAG,OAAO,CAAC,CAAC,KAAgB,OAAO,MAAO;CAClF,MAAM,QAAQ,GAAW,MAAqB,QAAQ;EAAE,YAAY;EAAS,YAAY;GAAC;GAAG;GAAG;EAAG;CAAE,GAAG,EAAE,MAAM,CAAC;CAEjH,MAAM,MAAe,CAAC;CACtB,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK,IAAI,KAAK,KAAK,MAAM,KAAK,MAAO,eAAe,IAAe,KAAM,OAAO,EAAa,CAAC;CACrH,IAAI,KAAK,KAAK;CACd,MAAM,QAAQ,OAAQ,KAAK;CAC3B,IAAI,KAAK,KAAK,OAAO,KAAK,QAAQ,KAAK,OAAO,KAAK,GAAI,CAAC;CACxD,MAAM,KAAK,KAAM,OAAO;CACxB,IAAI,KAAK,KAAK,OAAO,MAAO,IAAK,KAAK,IAAI,KAAK,IAAK,KAAK,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC;CACtE,IAAI,KAAK,KAAK,OAAO,MAAO,IAAK,KAAK,IAAI,KAAK,KAAM,KAAK,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC;CACvE,OAAO;AACR"}
@@ -0,0 +1,33 @@
1
+ import { ContrastOption } from "../color/contrast.js";
2
+ import { Color } from "../color/types.js";
3
+ /** Options for `createContrastScale`. */
4
+ export interface ContrastScaleOptions {
5
+ /** The color the scale is built around: its hue and chroma carry to every step. */
6
+ seed: Color | string;
7
+ /** What every step is measured against. */
8
+ background: Color | string;
9
+ /** Target contrast per step, in order, or a record of named targets (`{ text: 7, border: 3 }`). */
10
+ targets: number[] | Record<string, number>;
11
+ /** Default `wcag2`. */
12
+ contrast?: ContrastOption;
13
+ /** Which way to look first from the background. Default: darker on a light background, lighter on a dark one. */
14
+ direction?: 'lighter' | 'darker';
15
+ }
16
+ /** One step of a contrast scale: the target it was given and the color and contrast it got. */
17
+ export interface ContrastStep {
18
+ /** The key in a record of targets; otherwise the step's index as a string. */
19
+ name: string;
20
+ target: number;
21
+ color: Color;
22
+ /** The contrast the color achieves, measured on the color itself. */
23
+ contrast: number;
24
+ /** False when no lightness reaches `target`; `color` is then the best one found. */
25
+ met: boolean;
26
+ }
27
+ /**
28
+ * A Leonardo-style scale: each step is the color nearest the background's
29
+ * lightness, at the seed's hue and chroma, that reaches its target contrast.
30
+ * Contrast is the same across hues, which a fixed-lightness scale cannot give.
31
+ */
32
+ export declare function createContrastScale(options: ContrastScaleOptions): ContrastStep[];
33
+ //# sourceMappingURL=contrast-scale.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contrast-scale.d.ts","names":[],"sources":["../../src/scale/contrast-scale.ts"],"mappings":";;;iBAMiB;;EAEhB,MAAM;;EAEN,YAAY;;EAEZ,oBAAoB;;EAEpB,WAAW;;EAEX;;;iBAIgB;;EAEhB;EACA;EACA,OAAO;;EAEP;;EAEA;;;;;;;wBAQe,oBAAoB,SAAS,uBAAuB"}
@@ -0,0 +1,26 @@
1
+ import { findContrast } from "../color/contrast.js";
2
+ import { toColor } from "../color/parse.js";
3
+ import { componentsIn } from "../color/spaces.js";
4
+ // src/scale/contrast-scale.ts
5
+ /**
6
+ * A Leonardo-style scale: each step is the color nearest the background's
7
+ * lightness, at the seed's hue and chroma, that reaches its target contrast.
8
+ * Contrast is the same across hues, which a fixed-lightness scale cannot give.
9
+ */
10
+ function createContrastScale(options) {
11
+ const { seed, background, targets, contrast, direction } = options;
12
+ const [, chroma, hue] = componentsIn(toColor(seed), "oklch");
13
+ return (Array.isArray(targets) ? targets.map((t, i) => [String(i), t]) : Object.entries(targets)).map(([name, target]) => ({
14
+ name,
15
+ target,
16
+ ...findContrast(background, target, {
17
+ hue: hue ?? 0,
18
+ chroma,
19
+ contrast,
20
+ direction
21
+ })
22
+ }));
23
+ }
24
+ export { createContrastScale };
25
+
26
+ //# sourceMappingURL=contrast-scale.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contrast-scale.js","names":[],"sources":["../../src/scale/contrast-scale.ts"],"sourcesContent":["import { type ContrastOption, findContrast } from '../color/contrast.js'\nimport { toColor } from '../color/parse.js'\nimport { componentsIn } from '../color/spaces.js'\nimport type { Color } from '../color/types.js'\n\n/** Options for `createContrastScale`. */\nexport interface ContrastScaleOptions {\n\t/** The color the scale is built around: its hue and chroma carry to every step. */\n\tseed: Color | string\n\t/** What every step is measured against. */\n\tbackground: Color | string\n\t/** Target contrast per step, in order, or a record of named targets (`{ text: 7, border: 3 }`). */\n\ttargets: number[] | Record<string, number>\n\t/** Default `wcag2`. */\n\tcontrast?: ContrastOption\n\t/** Which way to look first from the background. Default: darker on a light background, lighter on a dark one. */\n\tdirection?: 'lighter' | 'darker'\n}\n\n/** One step of a contrast scale: the target it was given and the color and contrast it got. */\nexport interface ContrastStep {\n\t/** The key in a record of targets; otherwise the step's index as a string. */\n\tname: string\n\ttarget: number\n\tcolor: Color\n\t/** The contrast the color achieves, measured on the color itself. */\n\tcontrast: number\n\t/** False when no lightness reaches `target`; `color` is then the best one found. */\n\tmet: boolean\n}\n\n/**\n * A Leonardo-style scale: each step is the color nearest the background's\n * lightness, at the seed's hue and chroma, that reaches its target contrast.\n * Contrast is the same across hues, which a fixed-lightness scale cannot give.\n */\nexport function createContrastScale(options: ContrastScaleOptions): ContrastStep[] {\n\tconst { seed, background, targets, contrast, direction } = options\n\tconst [, chroma, hue] = componentsIn(toColor(seed), 'oklch') as [number, number, number | null]\n\tconst entries: [string, number][] = Array.isArray(targets)\n\t\t? targets.map((t, i) => [String(i), t])\n\t\t: Object.entries(targets)\n\treturn entries.map(([name, target]) => ({\n\t\tname,\n\t\ttarget,\n\t\t...findContrast(background, target, { hue: hue ?? 0, chroma, contrast, direction })\n\t}))\n}\n"],"mappings":";;;;;;;;;AAoCA,SAAgB,oBAAoB,SAA+C;CAClF,MAAM,EAAE,MAAM,YAAY,SAAS,UAAU,cAAc;CAC3D,MAAM,GAAG,QAAQ,OAAO,aAAa,QAAQ,IAAI,GAAG,OAAO;CAI3D,QAHoC,MAAM,QAAQ,OAAO,IACtD,QAAQ,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,IACpC,OAAO,QAAQ,OAAO,EAAA,CACV,KAAK,CAAC,MAAM,aAAa;EACvC;EACA;EACA,GAAG,aAAa,YAAY,QAAQ;GAAE,KAAK,OAAO;GAAG;GAAQ;GAAU;EAAU,CAAC;CACnF,EAAE;AACH"}
@@ -0,0 +1,38 @@
1
+ import { MixOptions } from "../color/mix.js";
2
+ import { Color } from "../color/types.js";
3
+ /** A color at a position on the gradient line. */
4
+ export interface GradientStop {
5
+ /** A CSS color string or a color object. Its alpha is the stop's alpha. */
6
+ color: Color | string;
7
+ /**
8
+ * Where the stop sits, with 0 the start and 1 the end; it may fall outside
9
+ * 0 to 1. Leave it out to spread the stop evenly between its neighbors, as
10
+ * CSS gradients do.
11
+ */
12
+ at?: number;
13
+ }
14
+ /** `in` and `hue` work as they do in `mix()`. The default space is `oklab`. */
15
+ export type GradientOptions = MixOptions;
16
+ /**
17
+ * Builds `count` colors, evenly spaced from 0 to 1 along a gradient through
18
+ * `stops`, the way a CSS gradient blends them: each pair of neighboring stops
19
+ * is mixed with `mix()`, so `in` and `hue` work as they do there. A stop is
20
+ * `{ color, at? }`, or a bare color when it needs no position. Before the
21
+ * first stop and after the last, the color holds; where two stops share a
22
+ * position, the color changes there and the sample at that position takes the
23
+ * later stop. Colors come back in the `in` space, default `oklab`.
24
+ *
25
+ * Alpha comes from each stop's color, 1 when the color has none, so
26
+ * `rgb(0 0 255 / 0.2)` is a stop at alpha 0.2. Alpha is interpolated linearly
27
+ * between stops, and the other components are premultiplied by it, so a
28
+ * transparent stop does not pull its neighbor's color toward its own. A color
29
+ * whose alpha is 1 has no `alpha` key; a `none` alpha takes the neighboring
30
+ * stop's.
31
+ *
32
+ * ```ts
33
+ * createGradient([{ color: 'blue', at: 0 }, { color: 'lime', at: 0.6 }, { color: 'yellow', at: 1 }], 72)
34
+ * createGradient(['blue', 'lime'], 5, { in: 'oklch' })
35
+ * ```
36
+ */
37
+ export declare function createGradient(stops: readonly (GradientStop | Color | string)[], count: number, options?: GradientOptions): Color[];
38
+ //# sourceMappingURL=gradient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gradient.d.ts","names":[],"sources":["../../src/scale/gradient.ts"],"mappings":";;;iBAKiB;;EAEhB,OAAO;;;;;;EAMP;;;YAIW,kBAAkB;;;;;;;;;;;;;;;;;;;;;;wBAqDd,eACf,iBAAiB,eAAe,mBAChC,eACA,UAAS,kBACP"}
@@ -0,0 +1,73 @@
1
+ import { mix } from "../color/mix.js";
2
+ import { toColor } from "../color/parse.js";
3
+ // src/scale/gradient.ts
4
+ /**
5
+ * Places each stop the way CSS gradients do (CSS Images 3 section 3.5.3): a
6
+ * first stop without a position goes at 0 and a last one at 1, a position
7
+ * before an earlier stop's moves up to it, and a run of stops without a
8
+ * position spreads evenly between the stops around it.
9
+ */
10
+ function positions(stops) {
11
+ const n = stops.length;
12
+ const at = stops.map((s, i) => {
13
+ if (s.at !== void 0 && !Number.isFinite(s.at)) throw new Error(`stop ${i} has a position of ${s.at}`);
14
+ return s.at;
15
+ });
16
+ if (at[0] === void 0) at[0] = 0;
17
+ if (at[n - 1] === void 0) at[n - 1] = 1;
18
+ let max = Number.NEGATIVE_INFINITY;
19
+ for (let i = 0; i < n; i++) {
20
+ const p = at[i];
21
+ if (p !== void 0) at[i] = max = Math.max(max, p);
22
+ }
23
+ for (let i = 1; i < n; i++) {
24
+ if (at[i] !== void 0) continue;
25
+ let j = i;
26
+ while (at[j] === void 0) j++;
27
+ const from = at[i - 1];
28
+ const step = (at[j] - from) / (j - i + 1);
29
+ for (let k = i; k < j; k++) at[k] = from + step * (k - i + 1);
30
+ }
31
+ return at;
32
+ }
33
+ /**
34
+ * Builds `count` colors, evenly spaced from 0 to 1 along a gradient through
35
+ * `stops`, the way a CSS gradient blends them: each pair of neighboring stops
36
+ * is mixed with `mix()`, so `in` and `hue` work as they do there. A stop is
37
+ * `{ color, at? }`, or a bare color when it needs no position. Before the
38
+ * first stop and after the last, the color holds; where two stops share a
39
+ * position, the color changes there and the sample at that position takes the
40
+ * later stop. Colors come back in the `in` space, default `oklab`.
41
+ *
42
+ * Alpha comes from each stop's color, 1 when the color has none, so
43
+ * `rgb(0 0 255 / 0.2)` is a stop at alpha 0.2. Alpha is interpolated linearly
44
+ * between stops, and the other components are premultiplied by it, so a
45
+ * transparent stop does not pull its neighbor's color toward its own. A color
46
+ * whose alpha is 1 has no `alpha` key; a `none` alpha takes the neighboring
47
+ * stop's.
48
+ *
49
+ * ```ts
50
+ * createGradient([{ color: 'blue', at: 0 }, { color: 'lime', at: 0.6 }, { color: 'yellow', at: 1 }], 72)
51
+ * createGradient(['blue', 'lime'], 5, { in: 'oklch' })
52
+ * ```
53
+ */
54
+ function createGradient(stops, count, options = {}) {
55
+ if (stops.length < 2) throw new Error(`a gradient needs at least 2 stops, got ${stops.length}`);
56
+ if (!Number.isInteger(count) || count < 2) throw new Error(`count must be an integer of at least 2, got ${count}`);
57
+ const list = stops.map((s) => typeof s === "string" || "colorSpace" in s ? { color: s } : s);
58
+ const colors = list.map((s) => toColor(s.color));
59
+ const at = positions(list);
60
+ const last = list.length - 2;
61
+ return Array.from({ length: count }, (_, i) => {
62
+ const t = i / (count - 1);
63
+ let s = 0;
64
+ while (s < last && at[s + 1] <= t) s++;
65
+ const from = at[s];
66
+ const width = at[s + 1] - from;
67
+ const local = width > 0 ? Math.min(1, Math.max(0, (t - from) / width)) : t < from ? 0 : 1;
68
+ return mix(colors[s], colors[s + 1], local, options);
69
+ });
70
+ }
71
+ export { createGradient };
72
+
73
+ //# sourceMappingURL=gradient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gradient.js","names":[],"sources":["../../src/scale/gradient.ts"],"sourcesContent":["import { type MixOptions, mix } from '../color/mix.js'\nimport { toColor } from '../color/parse.js'\nimport type { Color } from '../color/types.js'\n\n/** A color at a position on the gradient line. */\nexport interface GradientStop {\n\t/** A CSS color string or a color object. Its alpha is the stop's alpha. */\n\tcolor: Color | string\n\t/**\n\t * Where the stop sits, with 0 the start and 1 the end; it may fall outside\n\t * 0 to 1. Leave it out to spread the stop evenly between its neighbors, as\n\t * CSS gradients do.\n\t */\n\tat?: number\n}\n\n/** `in` and `hue` work as they do in `mix()`. The default space is `oklab`. */\nexport type GradientOptions = MixOptions\n\n/**\n * Places each stop the way CSS gradients do (CSS Images 3 section 3.5.3): a\n * first stop without a position goes at 0 and a last one at 1, a position\n * before an earlier stop's moves up to it, and a run of stops without a\n * position spreads evenly between the stops around it.\n */\nfunction positions(stops: readonly GradientStop[]): number[] {\n\tconst n = stops.length\n\tconst at: (number | undefined)[] = stops.map((s, i) => {\n\t\tif (s.at !== undefined && !Number.isFinite(s.at)) throw new Error(`stop ${i} has a position of ${s.at}`)\n\t\treturn s.at\n\t})\n\tif (at[0] === undefined) at[0] = 0\n\tif (at[n - 1] === undefined) at[n - 1] = 1\n\tlet max = Number.NEGATIVE_INFINITY\n\tfor (let i = 0; i < n; i++) {\n\t\tconst p = at[i]\n\t\tif (p !== undefined) at[i] = max = Math.max(max, p)\n\t}\n\tfor (let i = 1; i < n; i++) {\n\t\tif (at[i] !== undefined) continue\n\t\tlet j = i\n\t\twhile (at[j] === undefined) j++\n\t\tconst from = at[i - 1] as number\n\t\tconst step = ((at[j] as number) - from) / (j - i + 1)\n\t\tfor (let k = i; k < j; k++) at[k] = from + step * (k - i + 1)\n\t}\n\treturn at as number[]\n}\n\n/**\n * Builds `count` colors, evenly spaced from 0 to 1 along a gradient through\n * `stops`, the way a CSS gradient blends them: each pair of neighboring stops\n * is mixed with `mix()`, so `in` and `hue` work as they do there. A stop is\n * `{ color, at? }`, or a bare color when it needs no position. Before the\n * first stop and after the last, the color holds; where two stops share a\n * position, the color changes there and the sample at that position takes the\n * later stop. Colors come back in the `in` space, default `oklab`.\n *\n * Alpha comes from each stop's color, 1 when the color has none, so\n * `rgb(0 0 255 / 0.2)` is a stop at alpha 0.2. Alpha is interpolated linearly\n * between stops, and the other components are premultiplied by it, so a\n * transparent stop does not pull its neighbor's color toward its own. A color\n * whose alpha is 1 has no `alpha` key; a `none` alpha takes the neighboring\n * stop's.\n *\n * ```ts\n * createGradient([{ color: 'blue', at: 0 }, { color: 'lime', at: 0.6 }, { color: 'yellow', at: 1 }], 72)\n * createGradient(['blue', 'lime'], 5, { in: 'oklch' })\n * ```\n */\nexport function createGradient(\n\tstops: readonly (GradientStop | Color | string)[],\n\tcount: number,\n\toptions: GradientOptions = {}\n): Color[] {\n\tif (stops.length < 2) throw new Error(`a gradient needs at least 2 stops, got ${stops.length}`)\n\tif (!Number.isInteger(count) || count < 2) throw new Error(`count must be an integer of at least 2, got ${count}`)\n\tconst list = stops.map((s): GradientStop => (typeof s === 'string' || 'colorSpace' in s ? { color: s } : s))\n\tconst colors = list.map((s) => toColor(s.color))\n\tconst at = positions(list)\n\tconst last = list.length - 2\n\treturn Array.from({ length: count }, (_, i) => {\n\t\tconst t = i / (count - 1)\n\t\tlet s = 0\n\t\twhile (s < last && (at[s + 1] as number) <= t) s++\n\t\tconst from = at[s] as number\n\t\tconst width = (at[s + 1] as number) - from\n\t\tconst local = width > 0 ? Math.min(1, Math.max(0, (t - from) / width)) : t < from ? 0 : 1\n\t\treturn mix(colors[s] as Color, colors[s + 1] as Color, local, options)\n\t})\n}\n"],"mappings":";;;;;;;;;AAyBA,SAAS,UAAU,OAA0C;CAC5D,MAAM,IAAI,MAAM;CAChB,MAAM,KAA6B,MAAM,KAAK,GAAG,MAAM;EACtD,IAAI,EAAE,OAAO,KAAA,KAAa,CAAC,OAAO,SAAS,EAAE,EAAE,GAAG,MAAM,IAAI,MAAM,QAAQ,EAAE,qBAAqB,EAAE,IAAI;EACvG,OAAO,EAAE;CACV,CAAC;CACD,IAAI,GAAG,OAAO,KAAA,GAAW,GAAG,KAAK;CACjC,IAAI,GAAG,IAAI,OAAO,KAAA,GAAW,GAAG,IAAI,KAAK;CACzC,IAAI,MAAM,OAAO;CACjB,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK;EAC3B,MAAM,IAAI,GAAG;EACb,IAAI,MAAM,KAAA,GAAW,GAAG,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC;CACnD;CACA,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK;EAC3B,IAAI,GAAG,OAAO,KAAA,GAAW;EACzB,IAAI,IAAI;EACR,OAAO,GAAG,OAAO,KAAA,GAAW;EAC5B,MAAM,OAAO,GAAG,IAAI;EACpB,MAAM,QAAS,GAAG,KAAgB,SAAS,IAAI,IAAI;EACnD,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK,GAAG,KAAK,OAAO,QAAQ,IAAI,IAAI;CAC5D;CACA,OAAO;AACR;;;;;;;;;;;;;;;;;;;;;;AAuBA,SAAgB,eACf,OACA,OACA,UAA2B,CAAC,GAClB;CACV,IAAI,MAAM,SAAS,GAAG,MAAM,IAAI,MAAM,0CAA0C,MAAM,QAAQ;CAC9F,IAAI,CAAC,OAAO,UAAU,KAAK,KAAK,QAAQ,GAAG,MAAM,IAAI,MAAM,+CAA+C,OAAO;CACjH,MAAM,OAAO,MAAM,KAAK,MAAqB,OAAO,MAAM,YAAY,gBAAgB,IAAI,EAAE,OAAO,EAAE,IAAI,CAAE;CAC3G,MAAM,SAAS,KAAK,KAAK,MAAM,QAAQ,EAAE,KAAK,CAAC;CAC/C,MAAM,KAAK,UAAU,IAAI;CACzB,MAAM,OAAO,KAAK,SAAS;CAC3B,OAAO,MAAM,KAAK,EAAE,QAAQ,MAAM,IAAI,GAAG,MAAM;EAC9C,MAAM,IAAI,KAAK,QAAQ;EACvB,IAAI,IAAI;EACR,OAAO,IAAI,QAAS,GAAG,IAAI,MAAiB,GAAG;EAC/C,MAAM,OAAO,GAAG;EAChB,MAAM,QAAS,GAAG,IAAI,KAAgB;EACtC,MAAM,QAAQ,QAAQ,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,IAAI,IAAI,QAAQ,KAAK,CAAC,IAAI,IAAI,OAAO,IAAI;EACxF,OAAO,IAAI,OAAO,IAAa,OAAO,IAAI,IAAa,OAAO,OAAO;CACtE,CAAC;AACF"}
@@ -0,0 +1,6 @@
1
+ import { BrandScaleOptions, scaleFromBrand } from "./brand.js";
2
+ import { ContrastScaleOptions, ContrastStep, createContrastScale } from "./contrast-scale.js";
3
+ import { GradientOptions, GradientStop, createGradient } from "./gradient.js";
4
+ import { ScaleOptions, ScalePreset, createScale } from "./scale.js";
5
+ import { MATERIAL_TONES, TonalPaletteOptions, lstar, tonalPalette, toneColor } from "./tone.js";
6
+ export { type BrandScaleOptions, type ContrastScaleOptions, type ContrastStep, type GradientOptions, type GradientStop, MATERIAL_TONES, type ScaleOptions, type ScalePreset, type TonalPaletteOptions, createContrastScale, createGradient, createScale, lstar, scaleFromBrand, tonalPalette, toneColor };
@@ -0,0 +1,6 @@
1
+ import { scaleFromBrand } from "./brand.js";
2
+ import { createContrastScale } from "./contrast-scale.js";
3
+ import { createGradient } from "./gradient.js";
4
+ import { createScale } from "./scale.js";
5
+ import { MATERIAL_TONES, lstar, tonalPalette, toneColor } from "./tone.js";
6
+ export { MATERIAL_TONES, createContrastScale, createGradient, createScale, lstar, scaleFromBrand, tonalPalette, toneColor };