Skip to content

Colophon · reproducible

How this was made.

KELVIN has no images, no video and no 3D. The entire experience is colour and type, driven by one small script that moves the page along the colour-temperature scale. Here is the whole recipe.

The idea is literal: the page is lit by a colour temperature, and scrolling changes it. Warm and dim at the top, bright daylight in the middle, cool and deep at the end. Everything, the background, the text, the accent, the very weight of the letters, is tied to that one number.

1. One number drives everything

A tiny script maps your scroll position to a temperature, interpolates between a handful of colour stops, and writes the result into CSS variables on the root element. That is nearly all the JavaScript on the site.

// scroll -> temperature. One small script sets a few CSS variables.
const p = scrollY / (scrollHeight - innerHeight);        // 0..1
const s = sample(p);                                     // interpolate stops
root.style.setProperty('--k-bg', rgb(s.bg));
root.style.setProperty('--k-fg', rgb(s.fg));             // inverts with bg
root.style.setProperty('--k-accent', rgb(s.ac));
root.style.setProperty('--k-wght', s.w);                 // kinetic type
readout.textContent = Math.round(s.k) + ' K';

2. Let the whole design read those variables

Tailwind's theme colours are pointed straight at the live variables, so every utility class in the markup, backgrounds, text, accents, reacts to scroll without any per-element code.

/* tokens.css: Tailwind utilities point at the live variables */
@theme {
  --color-bg: var(--k-bg);
  --color-fg: var(--k-fg);
  --color-accent: var(--k-accent);
}
/* so bg-bg, text-fg, text-accent all react to scroll, everywhere. */

3. Make the type kinetic

The display face is Anybody, a variable font with a weight and a width axis. The engine also writes those axes, so the headlines get heavier and narrower in the warm light and lighter and wider in the cool. The readouts are JetBrains Mono.

/* the type morphs weight + width with the temperature */
.kinetic {
  font-variation-settings: "wght" var(--k-wght), "wdth" var(--k-wdth);
}

Contrast, at every temperature

Because the background travels from dark to light and back, the text colour is always the exact inverse, so it passes AA contrast at every point in the journey. The accent is only ever used for large display type and decoration, never small text, and the primary button inverts the foreground and background so it can never fall below contrast.

Accessibility and performance

Ship it

npm run build
wrangler pages deploy dist --project-name kelvin --branch main