← habitat.css
☀︎

Every control, live

What Eink and Camo mean is on the home page — this is the control panel. Switch modes and contrast in the toolbar above and watch everything below, down to this page's own text selection and scrollbars, respond immediately. Camo reads your camera, on-device only; Eink falls back to the system clock for brightness until you grant it.

Accessibility contrast

The Contrast picker in the toolbar sets the ratios the palette engine must satisfy; changing it re-derives the camo palette in place and tunes eink's filter and ink. It defaults to AAA, or to High if your system asks for prefers-contrast: more.

Contrast targets enforced by the palette engine.
LevelBody textAccentsCharacter
AA4.5:13:1Closest camouflage
AAA7:13:1Comfortable reading (default)
High10:14.5:1Maximum legibility
The best interface light is the light already in the room.

Light or dark, on demand

Scheme in the toolbar can force every mode toward a light or dark identity, regardless of what the room or the system's own prefers-color-scheme would otherwise pick — Eink's paper jumps straight to its bright or dim endpoint, and Camo's derived palette keeps its background in that half of the range. It works the same way without any JavaScript at all: set data-habitat-scheme="dark" on <html>, or just add a .dark or .habitat-dark class if your app already toggles one.

Using the system

Import the stylesheet and let the sensor drive the variables:

<link rel="stylesheet" href="habitat.css">

import { HabitatSensor } from "habitat.css";
import { initHabitatBanner } from "habitat.css";

const sensor = new HabitatSensor();
const consent = initHabitatBanner(sensor);
consent.request(); // shows the banner, then starts sensing

Style your components only with --habitat-bg, --habitat-surface, --habitat-text, --habitat-text-muted, --habitat-accent and --habitat-border, and every mode — present and future — works for free. Even text selection and scrollbars wear the habitat palette.

Privacy & sensing

Every 15 seconds one tiny frame (64×48 pixels) is taken and blurred in the same instant it is drawn — no sharp image ever exists. It is analyzed entirely in this browser for brightness and eight dominant colors, then erased immediately: at most one blurred frame exists at any moment, never shown, stored, or sent anywhere.

Only the derived numbers — a brightness level and a few hex colors — persist, on this device. Stop halts the camera until you explicitly resume (switching modes will never restart it). Forget also erases everything persisted.