A color guessing game for CSS developers. A random color appears; match it as closely as you can by dragging a color picker. Proximity is measured with Color.js’s OKLab ΔE, and your score (time per color) is kept across sessions.
Live at whathecolor.com.
A small Vue 3 app with no build step. Dependencies are vendored locally and wired up with an import map generated by nudeps, so the source runs directly in the browser. This mirrors the setup of zoelearns.com.
index.html Root component template (the app mounts on <body>)
index.js createApp(): game state, the round/win loop, the clock
importmap.js Generated by nudeps — maps "vue" / "colorjs.io" / "color-elements" to client_modules/
style.css Global layout + @imports each component’s CSS
lib/ Framework-agnostic model layer
round.js Round: target + guesses → proximity, ΔE, gradient, unique
timer.js Timer: an elapsed-time value object (formats mm:ss.d)
local.js Vue mixin: persist data paths to localStorage
components/ The score panel is the one real component; the rest are just CSS for
history-panel.js inline markup in index.html (purely-presentational bits aren't components)
history-panel.css
color-board.css Swatches + proximity bar
progression.css The guess-progression strip
The board (target + your-guess swatches and the proximity bar), the guess-progression strip, and
the guess input are written directly in index.html rather than as components — they’re purely
presentational. The picker is color-elements’ <color-picker>, registered in index.js via a
side-effect import and compilerOptions.isCustomElement.
The picker fires input on every slider step; index.js’s onPick reads the picker’s color and
records each distinct guess into attempts (driving the progression strip), while the latest one
drives the proximity readout, the “your color” swatch, and win detection. So the progression shows
the whole path of colors you moved through. Programmatic picker changes (the per-round reset)
don’t fire input, so they never leak in as guesses.
The picker’s color space defaults to OKLCh; append ?space=<id> (e.g. ?space=hsl, ?space=srgb)
to start in any Color.js space. Unknown ids fall back to OKLCh.
Append ?color=<css> (e.g. ?color=oklch(70%25 0.15 200)) for challenge mode: a fixed color
to guess instead of a random one. The picker defaults to that color’s own space (an explicit
?space= still wins), history is hidden, and winning offers “Try again” (the same color) rather
than “Next”. Unparseable colors fall back to the normal random game.
The mutable game state lives in index.js as plain reactive data: the solution, the list of
attempts (all CSS strings), the elapsed time, and the persisted history.
Everything derived — proximity, the “your color” swatch, the progression strip, win detection —
comes from a round computed that wraps the state in a Round (see lib/round.js).
Round is recreated whenever its inputs change and only ever exposes primitives, so live Color
objects never end up inside Vue’s reactivity system.
Color objects are not stored in reactive state; guesses are kept as CSS strings and re-parsed
on demand. This keeps state serializable (for localStorage) and avoids proxy-wrapping Color.js
internals. (color-elements depends on the same colorjs.io, so the import map dedupes it to a
single instance shared with our own code.)
Requires Node. Install dependencies and generate the import map:
npm install # installs into node_modules + devDeps (nudeps)
npm run build # runs nudeps: vendors deps into client_modules/ and writes importmap.jsThen serve the folder statically (any static server works) and open index.html.
npm run build is the only build step, and it just vendors dependencies — there is no
source compilation. Its outputs (client_modules/, importmap.js) and node_modules/ are
all generated and git-ignored, so run it after cloning and whenever you add or upgrade a
dependency in package.json. (Your deploy should run it too.)
Targets modern evergreen browsers (latest Chrome, Safari, Firefox). The styles use native CSS nesting and other Baseline features directly, with no PostCSS or other build step.