# North Star

> **A research-grade color laboratory for the web.**
> **Every color space, every illuminant — digit-identical in JS, GLSL, and WGSL.**

Every conversion is generated from one table of cited constants. The matrix that
runs in your JavaScript is the matrix that runs in your shader — and CI verifies
it to the last digit. Stop hand-porting color matrices.

## Pillars

| Pillar | Target | 110% |
|---|---|---|
| **Coverage** | All 14 CSS Color 4 spaces + OKHSL/OKHSV; full CIE illuminant table + arbitrary white points; gamut checking *and* gamut mapping | Illuminants from correlated color temperature (`illuminantFromCCT`); CAT02/CAT16/von Kries alongside Bradford |
| **Verified precision** | Every conversion tested against derivational invariants and spec fixtures; round-trip bounds CI-enforced; every constant carries a citation | Differential testing against culori, colorjs.io, and @texel/color as oracles; precision-bounds table in the README, generated by CI |
| **Everywhere** | Core imports clean in Node / SSR / workers; GLSL + WGSL + JS emitted from a single descriptor pipeline over single-sourced constants | GPU parity tests in CI; shader-side gamut mapping |
| **Speed** | Zero-allocation out-param paths; precomposed routes; honest published benchmarks | SOTA: nothing maintained is faster, on conversion or gamut mapping — measured with @texel/color's own suite |

## Principles

1. **Derive, don't transcribe.** RGB↔XYZ matrices are computed at module load
   from cited primaries and white points. A hardcoded matrix is a transcription
   error waiting to happen; a derived one is checked against published values in CI.
2. **One source of truth.** Constants live in one table. JS functions, GLSL
   strings, and WGSL strings are all views of that table.
3. **Precision is a contract.** No rounding inside conversions. Bytes exist only
   at the explicit `toBytes`/`fromBytes` boundary. Error bounds are measured,
   published, and enforced — never implied.
4. **The math is honest about itself.** Hue is undefined at zero chroma; CSS
   white points differ from CIE 15 values in the fifth digit; gamut mapping is
   lossy. We document these instead of papering over them.

## Scope rule

**If a frozen spec defines it on coordinates, implement it exactly; strings
on the way out only; constants still in motion wait.**

## Anti-goals

- No color wrapper class — plain arrays in, plain arrays out
- ~~No CSS string parsing, ever~~ — **amended at v0.8**: purity is an
  instrument, not an identity. Strings are how colors arrive in the world,
  so the frozen Level 4 grammar is in scope — behind a fenced boundary
  (src/parse): the conversion core never sees a string, `none` becomes NaN
  under the numerical policy, and still-moving grammar (Level 5 relative
  color syntax) waits like every other unfrozen spec
- Alpha is not a color coordinate — the conversion core stays 3-channel;
  spec-defined alpha interactions (premultiplied mixing, serialization) are
  separate, consistently 4-channel entry points
- No palettes, harmonies, color naming, or gradient objects — those belong
  to applications (spec-defined interpolation itself is in scope)
- No unfrozen perceptual models (APCA waits for WCAG 3 to freeze)
