/** * Colour canonicalisation, done by the engine that renders the page. * * The showcase has two callers with the same problem from opposite directions: * `figma/css-diff.ts` compares a Figma hex against a browser `oklch(...)`, and * `chrome/lib/token-values.ts` needs a hex for the few tokens the kit interpolated * itself, which therefore carry no hex in `tokens.css`. Neither wants a colour * library — the kit ships zero runtime dependencies on purpose, and the showcase * travels in the same tarball. */ export const pair = (n: number) => n.toString(16).padStart(2, '0'); /* One 1×1 canvas for the whole page. Painting is how a colour in a syntax neither side writes the same way — the browser answers `oklch(...)` for a token the kit authored in oklch, Figma answers hex — becomes comparable: the engine that renders both is the one asked to resolve them. */ let swatch: CanvasRenderingContext2D | null | undefined; /** * Any colour CSS understands → `#rrggbb`, or `#rrggbbaa` when it is not opaque, * or `'transparent'`. `undefined` when the value is not a colour at all. * * It has no cascade of its own, so `var(--x)` is not resolvable here — resolve * the reference first and paint what it resolves to. */ export function paintToHex(value: string): string | undefined { if (swatch === undefined) { swatch = document.createElement('canvas').getContext('2d', { willReadFrequently: true }); } const ctx = swatch; if (!ctx) return undefined; /* An unparseable value leaves `fillStyle` untouched, so a sentinel is the only way to tell "invalid" from "actually that colour". */ ctx.fillStyle = '#010203'; ctx.fillStyle = value; if (ctx.fillStyle === '#010203' && value !== '#010203') return undefined; /* Reading `fillStyle` back is not enough — a colour Chrome understands in a CSS Color 4 syntax comes back verbatim, in exactly the spelling that made it incomparable. Painting one pixel and reading it is the conversion. `ImageData` is non-premultiplied, so the bytes are the colour. */ ctx.clearRect(0, 0, 1, 1); ctx.fillRect(0, 0, 1, 1); const [r, g, b, a] = ctx.getImageData(0, 0, 1, 1).data; if (a === 0) return 'transparent'; const base = `#${pair(r)}${pair(g)}${pair(b)}`; return a >= 255 ? base : `${base}${pair(a)}`; }