Skip to content

About

Resources

Stars

49 stars

Watchers

3 watching

Forks

Latest commit

 

History

69 Commits

Folders and files

Repository files navigation

What the color? 🎨

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.

Architecture

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.

Guessing

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.

Data model

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.)

Development

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.js

Then 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.)

Browser support

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.

About

Resources

Stars

49 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages