Gamut

An OKLCH palette forge. Pick one colour, get a tonal ramp at constant hue, and see the actual sRGB boundary it has to live inside. The two mapping strategies are shown together because the difference between them is the whole point.

Seed

Chroma curve
Preset hues

The sRGB gamut, sliced at this hue

Lightness runs left to right, chroma bottom to top, and the painted region is every colour a screen can actually show at this hue. The ceiling is the gamut boundary. The peak of it is the cusp: the most saturated this hue gets, and the lightness it happens at. White dots are your ramp steps after mapping; a hollow dot marks a step whose requested chroma was outside the boundary and had to be brought in.

L 0.0L 1.0

The ramp, resolved two ways

Both rows asked for exactly the same colours. The top row bisects on chroma with hue and lightness pinned, which is what a gamut mapper should do. The bottom row clips each sRGB channel independently, which is what your browser does with an out-of-gamut oklch(). When the rows differ, the bottom one has drifted off your hue.

Chroma bisection

hue and lightness held fixed hue error 0.000°

Per-channel clip

what the browser does hue error 0.000°

Light and dark, with real ratios

The ramp assigned to tokens and rendered both ways, with WCAG 2.2 contrast measured on the pairs that actually occur. 4.5 is the AA floor for body text, 3.0 for large text and for a border carrying meaning.

light
dark

Export

Custom properties for both themes, plus the ramp as raw steps. Hex, because it works everywhere; the oklch() originals are in the comment so the intent survives.