How it was made
A valley printed on the dark, legible only where your lantern rakes across it.
Umbra is a piece for one reader, in the second person: the page is pitch black and you are the only light. A soft lantern rides your cursor and a relief landscape — hills, a raking ridgeline, a lost house with one ember window — exists only where you shine, throwing long shadows that swing as you move. The story is printed on the terrain itself, so reading is an act of searching. Its single job is to make you feel that the valley is not something you look at, but something your attention keeps in existence.
Following the collection's contrast rule, the bright accent stays on lines and marks — the window, the hearth embers, link underlines — while text uses lamplight or a dimmed lamplight ink, so nothing small ever sits on ember.
Everything is drawn on a single 2D <canvas> — no images, no WebGL. A deterministic value-noise height-field (rolling hills + a ridged ridgeline + a carved basin, with a little house block and triangular roof stamped into it) is built once at a low working resolution, and its surface normals are precomputed from the height gradient.
The signature is the lighting. Each frame, only the pixels inside the lantern's pool are touched: for each one the code computes a Lambert term (surface normal against the 3-D direction to the lamp, so slopes that face you brighten and slopes that turn away fall dark) times a soft radial falloff, times a horizon self-shadow — a short ray marched across the height-field toward the light; if the terrain rises above the line of sight, the pixel is occluded. That is why the ridge and the house cast long shadows that lengthen and swing as the lamp moves. The result is written as an alpha mask and used to reveal a crisp, full-resolution albedo layer (the terrain material plus the printed story) via a single destination-in composite — so text and ground are only ever as visible as the light on them. A warm additive bloom, a living flame flicker, and embers rising from the window finish the lantern.
If JavaScript fails or the visitor asks for reduced motion, none of this runs: a fully-lit, fully-typeset reader holds the entire story in plain HTML, scrollable and legible. The dark is the enhancement, never the gate.
Drove the lantern in headless Chrome and sampled the canvas: the reveal was strong (lit ≈ 140 vs void ≈ 7) but three craft faults surfaced. The ground material was drawn as ~14px tiles and read blocky, so it was re-baked at low resolution and upscaled smoothly. The etched chrome sat around 2.5:1 contrast; the functional links were lifted to ≥4.6:1 while staying dim. And the mobile masthead subtitle collided with the nav under the scrollWidth probe's blind spot — it is now hidden below 600px. Hero spacing was eased so "UMBRA" resolves inside the pool.
Enriched the signature and added a second read. The lantern gained an organic flame flicker (layered sines) so it breathes like a candle, and the lost house became inhabited: when your light finds it, the window glows warmer and embers rise from the hearth and fade. Re-drove the loop onto the house to confirm the embers spawn and the shadow still swings.
Tested 375px (nav and wordmark within the box), DPR 2 and resize (still lights after a viewport change), focus-visible, and the reduced-motion path. Caught a real bug: overflow:hidden on the body — needed for the immersive canvas — was clipping the fallback reader so its lower half was unreachable. The fallback now scrolls, with the colophon flowing beneath the story. Removed one ornament (the hero's ember underline) to keep the word-out-of-darkness moment pure.