{"_id":"@amplib/color","name":"@amplib/color","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@amplib/color","description":"Color analysis utilities","version":"0.1.0","main":"dist/index.js","types":"dist/index.d.ts","publishConfig":{"access":"public"},"homepage":"https://amplib.app/color","repository":{"type":"git","url":"git+https://github.com/another-machine/public-library.git","directory":"packages/amplib-color"},"tsup":{"entry":["src/index.ts"],"clean":true,"dts":true,"format":"esm","minify":false,"sourcemap":true,"splitting":false},"scripts":{"build":"tsup","watch":"tsup --watch","test":"tsx test/run.ts","prepack":"npm run build"},"author":{"name":"Another Machine"},"license":"Apache-2.0","type":"module","devDependencies":{"tsx":"^4.19.2"},"_id":"@amplib/color@0.1.0","gitHead":"a72c752ed30de9a351bb5a739771f65c0bdc72a8","bugs":{"url":"https://github.com/another-machine/public-library/issues"},"_nodeVersion":"20.11.0","_npmVersion":"10.2.4","dist":{"integrity":"sha512-4rS8nM36T/Pgo6+uXQ69KqY+aLIPx/Eg7938dc21E90WpwI9fuw1W4RAbsaDqfwz0VD0WHLPeeF0uvxQygzu+Q==","shasum":"26bc3a0a24b3b517b8823fd7cd10fde1642c46c9","tarball":"https://registry.npmjs.org/@amplib/color/-/color-0.1.0.tgz","fileCount":6,"unpackedSize":30768,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIDi26VAnL+P+fG4S5BUSkbYGmLSIRKHXZ6vvCPJeVYJEAiBknaYsbpJAXhutJXIyBaeK2lO9o+dYvTvG4IvT6QoDHQ=="}]},"_npmUser":{"name":"jakealbaugh","email":"jake.albaugh@gmail.com"},"directories":{},"maintainers":[{"name":"jakealbaugh","email":"jake.albaugh@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/color_0.1.0_1785594168522_0.6898679311781124"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-01T14:22:48.352Z","0.1.0":"2026-08-01T14:22:48.645Z","modified":"2026-08-01T14:22:48.823Z"},"maintainers":[{"name":"jakealbaugh","email":"jake.albaugh@gmail.com"}],"description":"Color analysis utilities","homepage":"https://amplib.app/color","repository":{"type":"git","url":"git+https://github.com/another-machine/public-library.git","directory":"packages/amplib-color"},"author":{"name":"Another Machine"},"bugs":{"url":"https://github.com/another-machine/public-library/issues"},"license":"Apache-2.0","readme":"# @amplib/color\n\nColor analysis utilities.\n\n```ts\nimport { toPerceptual, fromPerceptual } from \"@amplib/color\";\n\ntoPerceptual(210); // 256.22 — display blue is much further round than it looks\nfromPerceptual(180); // 172.88 — the display hue that reads as halfway to cyan\n```\n\nDivide a hue wheel into six equal slices in HSV and the slices do not look\nequal. Divide it in oklch and they do. That is the entire purpose of this\npackage: a bijection between the hue you write down and the hue people see.\n\n```ts\n// Six hues that actually look evenly spaced.\nconst spaced = Array.from({ length: 6 }, (_, i) => fromPerceptual(i * 60));\n// 328.0, 33.8, 68.9, 172.9, 198.9, 274.6 — not 0, 60, 120, 180, 240, 300\n```\n\nNote where that list starts. Perceptual 0° is display **328°**, not 0° — the\nangle vision reads as the origin of the wheel sits in magenta, some way before\ndisplay red. The two wheels do not share a zero, so there is no offset you\ncould apply instead of the mapping.\n\n## Modules\n\n| Module                 | Description                                          |\n| ---------------------- | ---------------------------------------------------- |\n| `toPerceptual`         | Display (HSV) hue → perceptual (oklch) hue           |\n| `fromPerceptual`       | Perceptual (oklch) hue → display (HSV) hue           |\n| `worstRoundTripError`  | Measured disagreement between the two directions     |\n\n## Design\n\n**The wheel is severely non-linear, and not in the direction most descriptions\nsuggest.** Measured as perceptual degrees per display degree:\n\n| red   | orange | yellow | green | cyan  | blue  | deep blue | magenta |\n| ----- | ------ | ------ | ----- | ----- | ----- | --------- | ------- |\n| 0.21× | 1.76×  | 1.29×  | 0.05× | 2.23× | 0.14× | 0.01×     | 0.94×   |\n\nGreen and deep blue barely move — a wide sweep of display hue is almost one\nperceptual color — while cyan and orange stretch. Over two hundredfold between\nthe extremes.\n\n**The round-trip is measured, not assumed.** The forward table samples oklch\nonce per display degree; the inverse searches it backwards at twenty times that\nresolution, because the deep-blue zone squeezes roughly 16 display degrees into\nhalf a perceptual degree and would otherwise collapse to a single bin. The two\nare built by different methods and only approximately invert each other, so\n`worstRoundTripError()` measures the disagreement and `npm test` fails the\nbuild above 1.5°.\n\nRaw oklch hue also steps *backwards* by a fraction of a degree around display\n231–240°, which would make the mapping non-invertible exactly there. Every\nbackward run is replaced with a straight interpolation across it.\n\n**The error check is exported, not run on import.** A library that asserts at\nload time charges every consumer for the check whether they want it or not, and\nstill cannot fail a build when it matters.\n\n**Hue only, saturation and lightness untouched.** Full-saturation, full-value\nHSV is the sampling line, so this answers \"which angle\" and says nothing about\nhow light or vivid the result is. Anything needing the other two axes wants a\nfull color-space conversion, not this.\n\nMatrix constants from Björn Ottosson's oklab reference implementation (2020).\n","readmeFilename":"README.md","_rev":"1-dd20ced545a2fb891350fcd5356fcf4f"}