Perceptual color engine

Color that behaves
like light.

OKYCT is a zero-dependency perceptual color engine built for emissive displays. OKLab hue, CIE-anchored tone, and accessibility guaranteed by algebra — not by guesswork.

MIT licensed · TypeScript · zero runtime dependencies

Why OKYCT

Four guarantees, engineered into the math.

01

Perceptual hue, physical tone

Hue is the polar angle of OKLab, so blue stays blue in the deep region where CAM16 twists violet. Tone is CIE 1931 Y, so luminance is a measurable quantity instead of an opinion.

02

A single-variable Newton solver

Because Y is strictly monotonic in OKLab L, one analytic derivative converges in 5–7 iterations. No nested CAM16 bisection and no transcendental search.

03

Cusp-anchored gamut mapping

Out-of-gamut colors fold back along a constant-hue ray toward the cusp. ΔH is zero by construction, so vivid reds never drift to orange.

04

Contrast you can solve

Tone and Y are bijective, so WCAG 2.1 and APCA thresholds invert in closed form. Accessibility becomes an O(1) constraint rather than an audit sweep.

Measured benchmarks

OKYCT raises the bar across a wide range of benchmarks.

Every number below is measured live in your browser by @sandlada/benchmark — OKYCT, Google MCU, and culori on a shared protocol v2 seed.

Measured live from seed · #1A73E8

#1A73E8Drag to preview · release to apply
BenchmarkSetupOKYCTCIE L* + okyctToRgbGoogle MCUHCT tone solveculoriOKLCH L* solve
Tone accuracyWorst ΔL* vs shared targets
  • Common CIE L* stops: 10, 20, …, 95 (protocol v2).
  • Rank by p95 |measured L* − target|; lower wins.
  • OKYCT/culori: shared nearest sRGB quantizer; MCU: native 8-bit ARGB.
T10–T95 tonal scalep95 Δ0.14p95 Δ0.19p95 Δ0.24
Mean hue driftAverage over 10 stops
  • OKLab Δh vs seed, weighted by c/(c+0.1).
  • Mean over stops; near-neutral seeds (CIELAB C* < 2) skip hue ranking.
  • Lower weighted drift wins.
T10–T95 tonal scale0.08° w1.51° w0.12° w
Max hue driftp95 weighted OKLab drift
  • Same OKLab weighting as mean drift.
  • Report 95th percentile across the 10 stops.
  • Lower p95 wins.
T10–T95 tonal scalep95 0.24°p95 3.37°p95 0.24°
Semantic role AALight/dark MD3 role pairs (11 each)
  • Shared MD3 role tone contract; culori uses the benchmark token table.
  • WCAG 2.1 contrast ≥ 4.5:1 on output hex (fg on bg), per theme.
  • Gate only — count of passing pairs (max 11 light + 11 dark); not a hue rank.
T10–T95 tonal scaleL 11/11 · D 11/11L 11/11 · D 11/11L 11/11 · D 11/11
Solve timePer 10-stop scale
  • Median wall time to build one full L* ramp on the current seed.
  • 3 warm-up runs + 20 timed samples.
  • Lower median ms wins.
T10–T95 tonal scale0.11 ms0.48 ms0.79 ms
The premise

Paper palettes stopped making sense the moment screens started emitting light.

Borrowed color-appearance models were designed for reflected light. OKYCT starts from the physics of a pixel: an OKLab polar plane for hue, CIE 1931 Y for tone, and a strictly monotonic Newton solver in between. The result is a color system that stays honest on OLED, survives gamut clipping with zero hue drift, and proves its contrast instead of sampling for it.

Emissive physics

Built for panels that light themselves.

OLED, AMOLED and MicroOLED behave differently in the dark. OKYCT ships hardware profiles instead of one universal "dark mode".

#000000T0

OLED · true black

Tone 0

Pixels switch off completely for maximum static contrast and the lowest idle power draw.

#000D28T4

AMOLED · anti-smear

Tone 4

A sub-threshold bias keeps the organic layer awake, cutting step response from 18.5 ms to 1.6 ms at 120 Hz.

#00163BT8

MicroOLED · XR

Tone 8 / 92

Lifted blacks and a soft highlight ceiling remove pancake-lens halation in near-eye displays.

Spectrally weighted pixel power

P = 1.05·Rγ + 0.85·Gγ + 2.40·Bγ. Blue is the most expensive channel and the fastest to age, so hazardous hues are capped and nudged toward cyan.

Ppixel = 1.05·Rγ + 0.85·Gγ + 2.40·Bγ
Live palette

One seed. Thirteen tones. Every role.

The engine is running in your browser right now. Change the seed and the particles, the palette rows and the token roles all follow the same perceptual pipeline.

Primary
Secondary
Tertiary
Neutral
Neutral variant
Tone0 · 10 · 20 · 30 · 40 · 50 · 60 · 70 · 80 · 90 · 95 · 99 · 100
Tokens

A name for every job.

A ramp is a tone. A token is the job that tone is hired to do. OKYCT keeps a small set of roles: primary, surface, and status. Change the seed above and those jobs stay the same.

Primary
The control is held at a tone where its label still clears WCAG. The seed’s own brightness moves into the container.
Surface
Five steps, from dim to highest, carry depth. Cards and sheets do not need a drop shadow.
Status
Error, warning, success and info each arrive with an on-color, so the message and its ground are one pair.

Light

Primary
Primary container
Secondary
Tertiary
Surface dim
Container low
Surface container
Container high
Container highest
Error
Warning
Success
Info

Dark

Primary
Primary container
Secondary
Tertiary
Surface dim
Container low
Surface container
Container high
Container highest
Error
Warning
Success
Info

Ship color that holds up.

OKYCT is MIT licensed, dependency-free, and available for TypeScript today — with Dart and Rust implementations on the way.