No description
  • TypeScript 55.6%
  • Svelte 29.9%
  • JavaScript 11.1%
  • CSS 2.4%
  • HTML 0.6%
  • Other 0.4%
Find a file
Asriel Camora 8fbb157b6e
Redesign: user-chosen hidden targets + corpus words; same-origin push
Training model change:
- Words now come from a built-in corpus (app/src/color/corpus.ts) instead of
  being typed by the user.
- The user picks each word's target color themselves in a setup flow
  (ui/Setup.svelte); the target is NEVER shown during training — you reproduce
  it from memory. Removes the anchor-seed, learn phase, and target/guess reveals.
- Progress cloud shows only the user's own reproductions clustering (no target
  marker), so nothing leaks the color.

Reminders (Mastodon-style, same origin):
- Push API served under color.collar.sh/push (Traefik PathPrefix + StripPrefix),
  no extra subdomain. server/Dockerfile added (Node 24 runs the TS entrypoint).
- Client push URL builder fixed to respect a base that includes a path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 23:05:46 -07:00
app Redesign: user-chosen hidden targets + corpus words; same-origin push 2026-07-02 23:05:46 -07:00
server Redesign: user-chosen hidden targets + corpus words; same-origin push 2026-07-02 23:05:46 -07:00
.gitignore Initial commit: Huemory — word→color association trainer 2026-07-02 22:36:44 -07:00
README.md Redesign: user-chosen hidden targets + corpus words; same-origin push 2026-07-02 23:05:46 -07:00

Huemory

Train stable, personal associations between words and exact colors, then drive down the variance of your reproductions over daily sessions. It's a calibration trainer, not a quiz: each day you reproduce the color you associate with a word from memory, the app measures how far off you are (perceptually), nudges you back, and tracks whether your picks are converging.

No numbers, ever — you only ever navigate and see colors.

The idea

Two constraints shape everything:

  • Perceptual, but never clipping. OKLab/OKLCh is perceptually uniform, but its reachable region (the display gamut) is a lumpy shape — navigating it hits clipping "walls" where a color can't actually be shown. So instead of navigating raw perceptual space, Huemory precomputes a rigid 3-D box (a parallelepiped with a fixed, linearly-independent basis) inscribed entirely inside the display gamut, oriented along the gamut's own principal axes (PCA). Every point in the box maps to a real, un-clipped color, and the axes are independent — so moving the point is a straight, uniform perceptual move. See app/src/color/solid.ts.
  • You choose the color; it's never shown again. The app supplies the words (from a built-in corpus — app/src/color/corpus.ts); for each, you pick the color you'll associate with it. During training that target is never displayed — you reproduce it purely from memory, and the app tracks the spread of your reproductions (the std-dev we minimize) as a tightening cluster. See app/src/session/target.ts.

When you miss, the nudge is a directional hint — arrows showing which way to move in the box — never the color itself, never a number.

Layout

huemory/
  app/     # the trainer — Vite + Svelte 5 PWA (installable, offline)
    src/color/    inscribed color solid, word corpus, OKLab math (culori) + tests
    src/session/  reproductions + spread, the daily state machine
    src/picker/   canvas color-plane + lightness bar, directional nudge overlay
    src/store/    IndexedDB (idb): words (+ chosen colors), picks, settings
    src/pwa/      service worker (push) + client subscribe
    src/ui/       screens (setup, session, words, settings)
  server/  # the only networked piece: Web Push sender for daily reminders

Develop

cd app
npm install
npm run dev      # http://localhost:5173
npm test         # color-solid + session unit tests
npm run build    # type-check + production PWA build

The color-solid tests prove the core promise: 20 000 random points in the box all convert to in-gamut sRGB (no clipping), and the basis is independent + orthogonal.

Daily reminders

Reminders use Web Push via the small sender in server/. Like Mastodon, it runs on the same origin — the push API is served under color.collar.sh/push (Traefik strips the prefix), so there's no extra subdomain. The app is pointed at it via app/.env (VITE_PUSH_SERVER_URL=https://color.collar.sh/push + VITE_VAPID_PUBLIC_KEY). See server/README.md for the server env (VAPID keypair, APP_URL, data path).

On mobile, "Add to Home Screen" first — installed PWAs are what can receive push.