Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ColorCard

A palette picker that behaves like a paint-chip card: a fixed rack of sixteen sockets, of which you fill only as many as the job needs. Pick colours by dragging a triangle across a perceptual colour space and letting the tool distribute N points evenly inside it.

One self-contained HTML file. No build step, no dependencies, no network. Open it from disk, drop it on a static host, or embed it in an iframe.

five filled sockets of sixteen


Why it works this way

Most palette tools hand you a ramp between two colours. A ramp is the wrong object. Interpolate red to blue and you pass through purple — a colour on no flag, in no brand guide, on no approved paint card. A palette is a set, not a gradient, and the difference shows the moment you try to fill anything with red, white and blue.

So ColorCard produces a set. card[i], never mix(a, b, t).

The rack metaphor comes from trades that already work this way without naming it:

  • Automotive design — two-tone body and cabin, sometimes a third for stitch or calipers. The unused chips stay in the studio. The car does not display empty sockets.
  • Paint manufacturers — the deck is enormous; the job gets a short named pull of two, four, twelve. The rest stays in the binder.
  • Interior decorators — a scheme is a handful of chips taken from a room's light and its fabric. The sofa does not show the chips you left behind.

Hence: the rack is always 4×4. Sometimes only two wells have chips in them. Empty sockets never leave the page. The calling application receives a finished card and nothing else — not the empties, not the triangle, not the covering maths.

Two ideas that are not just colours

Lead. In stained glass the lead came is the metal between the panes — it borders every colour and is none of them. ColorCard carries it as a separate value, outside the palette array. If your application draws outlines, that is what the lead is for.

Clear. One socket may be empty-by-intent rather than unused: a hole. Regions that draw it should render transparent, so artwork arrives pre-masked for compositing. It travels as null in the array, so position is preserved.


Using it in your web app

1. Open it

const card = window.open(
  "colorcard/index.html?theme=4th%20of%20July&n=6",
  "colorcard",
  "width=1200,height=800"
);
if (!card) alert("Allow pop-ups to open the ColorCard.");

Query parameters, all optional:

param meaning
theme name of a built-in or saved card to open with
n how many sockets to start filled, 1–16

An iframe works identically — it posts to window.parent.

2. Listen for the result

window.addEventListener("message", ev => {
  // In production, check the origin you served ColorCard from:
  // if (ev.origin !== "https://example.com") return;
  const m = ev.data;
  if (!m || m.type !== "palette-use" || !Array.isArray(m.rgb)) return;

  const colours = m.rgb.map(c => c && `rgb(${c[0]},${c[1]},${c[2]})`);
  // a null entry is the CLEAR chip — render it transparent, not as a colour
});

Validate before you trust. ColorCard posts with targetOrigin: "*" so it works from file://, where origins are null. If you serve it over HTTP, check ev.origin and the message shape in your listener. Treat the payload as untrusted input — it is a message from another window.

3. The payload

{
  "type": "palette-use",
  "name": "4th of July",
  "n": 3,
  "rgb": [[179, 25, 66], [255, 255, 255], [10, 49, 97]],
  "lead": [17, 17, 17],
  "background": [216, 207, 194]
}
  • rgb — the filled sockets, in rack order, 1 to 16 entries. null means the clear chip.
  • lead — the outline colour. Not a member of rgb.
  • background — the context the chips were judged against. Useful if you are about to paint on it.
  • n — rgb.length, for convenience.

4. When there is no opener

If ColorCard is opened directly rather than by another page, Send downloads the same JSON instead of posting it. The file is a valid payload; parse it and carry on. Nothing is lost by using the tool standalone.


How it works

Colour space

Everything happens in Oklab (Björn Ottosson, 2020), a perceptually uniform space in which equal numeric distance is roughly equal perceived difference. That property is what makes "distribute these evenly" mean anything: the same operation in sRGB or HSV clusters colours where the space is perceptually stretched.

Conversions are Ottosson's published matrices, verified against his reference values to 6×10⁻⁵ and round-tripping 4,096 sRGB colours with a worst channel error of 0.0003 / 255.

The triangle

The canvas shows the a–b plane of Oklab at the mean lightness of the three corners. Colours outside the sRGB gamut render flat grey, so you can see where the triangle is reaching past what a screen can actually show.

A triangle on a chromaticity diagram is how colour science has always drawn a gamut — sRGB, Adobe RGB and Display P3 are each literally a triangle. That notation is borrowed here for selection: the triangle is the region you are willing to pick from.

Each corner carries its own lightness. A flat slice gives you three hues at one value — hue variation with no light and dark, which is not a palette. Three independent lightnesses tilt the triangle through the space, so the cover spans value as well as hue. Lightness is interpolated barycentrically across the triangle along with the a and b coordinates.

Covering the triangle with N colours

  • N = 3 — the corners.
  • N = 4 — the corners plus the centroid.
  • N = 5…16 — corners pinned, the remaining sites relaxed toward an even spread by Lloyd's algorithm: sample the triangle densely in barycentric coordinates, assign each sample to its nearest site, move each free site to the centroid of the samples that chose it, repeat. Forty-five rounds is well past the point where the eye can see further movement.

Corners stay pinned so the palette keeps the extremes you chose. An unconstrained relaxation pulls everything toward the middle and quietly narrows your range.


Design decisions worth knowing

The rack is fixed at sixteen and mostly empty. This is the whole idea, not a limitation. A generator that returns exactly N swatches gives you no sense of the deck the job was pulled from.

Lead is outside the array. Putting it in would make consumers guess which entry is the outline.

Clear is null, not a sentinel colour. A magic RGB triple would eventually collide with a real colour someone wanted.

Only filled sockets are transmitted. The empties, the triangle geometry, the lightness sliders and the relaxation all stay on the page.

The colour input is the browser's. <input type="color"> opens Chromium's or Firefox's own picker, not the operating system dialog — no web API exposes that. What you get is a saturation square, a hue slider, a hex field and, in Chromium, an eyedropper.


Prior art, honestly

The parts are not new; the combination appears to be. Related work worth knowing:

  • Adobe Color has the geometric half — drag a handle on a wheel and the others follow. But the shape is rigid, enforcing triadic, complementary, analogous. You steer it; you do not draw it.
  • iWantHue has the algorithmic half — clustering a perceptual subspace for maximally distinct colours. The region is set by hue/chroma/lightness sliders rather than drawn, and it optimises for distinctness rather than even coverage.
  • Oklab/OKLCH generators overwhelmingly produce tonal scales: fix hue and chroma, vary lightness. One dimension.
  • Gamut triangles on CIE chromaticity diagrams are the notation this borrows.

I am not aware of a tool combining a drawn region, per-corner lightness, even coverage, and a partially-filled fixed rack. That is a statement about my search, not a claim of priority. If you know of one, please open an issue — a pointer to prior art is more useful than a novelty claim.


Limitations

  • sRGB only. The gamut mask and all output assume sRGB. No P3 or Rec.2020.
  • No colour-blindness simulation or contrast checking. Even coverage in Oklab is not the same as distinguishable for everyone.
  • The triangle is one region. No multi-region or exclusion zones.
  • Saved cards live in localStorage, so they are per-browser and per-origin, and are lost if site data is cleared. There is no export of the saved set.
  • window.close() only works on a window that script opened. Opened by hand, the browser blocks it and the button says so instead.

Licence

MIT. See LICENSE.

Oklab is by Björn Ottosson, released into the public domain / under MIT at the author's option. The conversion matrices here are his.

Built for SpiroJoy, where the card fills the enclosed regions of a generative drawing and the lead draws the lines between them.

About

A palette picker that fills a fixed rack of sixteen sockets by covering a triangle in Oklab.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages