Skip to content

Colophon · reproducible

How this was made.

Vespera turns one AI-generated film into a scroll you drive yourself. No autoplay video, no streaming, no scrubbing a media element. Just still frames, drawn to a canvas at your scroll position. Here is the whole recipe.

The trick is that there is no film playing. The motion you feel is entirely yours: each scroll position asks for one specific frame, and that frame is painted to a canvas. Stop scrolling and the dusk holds perfectly still.

1. Generate the film

A single ten second clip was generated with Higgsfield (Kling 3.0): a slow, unbroken forward dolly through a twilight garden, blooms opening as the camera drifts. The prompt asks for one steady tempo, no cuts, and a matching first and last frame so the scrub feels seamless. The exact prompt is on the prompts page.

2. Slice it into frames

The clip is cut into 90 evenly spaced WebP frames at two resolutions, one for desktop and a lighter one for phones. Because the footage is soft and atmospheric, WebP compresses it to about 1.6MB for the whole desktop descent.

# one AI film -> evenly spaced WebP frames, desktop + mobile sets
python extract_frames.py \
  --input film.mp4 --output public/frames \
  --frames 90 --quality 66 \
  --desktop-res 1280x720 --mobile-res 720x404
# result: 90 frames, 1.6MB desktop / 0.8MB mobile

3. Scrub on a canvas

A fixed canvas fills the screen. As you scroll through a tall, transparent stage, the scroll fraction is mapped to a frame index, smoothed with a lerp, and drawn. Frames load progressively (a spread of anchor frames first, then the rest in batches) and decode off the main thread with createImageBitmap, so early scrolling never shows a white flash.

// scroll position within the descent stage -> a frame index
const total = stage.offsetHeight - innerHeight;
const p = clamp01(-stage.getBoundingClientRect().top / total);
const frame = Math.round(lerp(p) * (FRAME_COUNT - 1));
ctx.drawImage(frames[nearestLoaded(frame)], ...cover);   // no <video>

4. Let text dwell

Four lines of copy cross-fade at their own points in the descent, each holding while the film keeps drifting behind it. A bottom scrim keeps every line readable over the moving image.

Accessibility and performance

Ship it

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