Back to our work

Scroll-film pipeline

How soltani.org plays one continuous film as you scroll, with every word kept as real text.

Built by our co-founder. Will Soltani built it for his own site, soltani.org. The code is public.

soltani.org’s home page at the top: a dark cable-tray scene behind the name Will Soltani and his hero text.
soltani.org’s home page, where the film starts.

Layers involved: Design and UX, Web apps and Cloud and platform

Built with: Blender 5.2 (EEVEE), Python, ffmpeg, cwebp, Next.js 16, React 19, TypeScript, Tailwind CSS 4, S3 and CloudFront, Playwright

soltani.orgSource on GitHub

What it does

soltani.org’s homepage is one continuous film driven by scroll. A fixed canvas behind the page paints pre-rendered frames chosen by scroll position, and each section of the page is a chapter the film reveals.

There are two camera chains: landscape, and a separate portrait chain for phones. Each camera has eight films and 1,442 frames in all.

Six of the films come from one Blender scene built by a script: a 42U rack in a dark data hall, its units opening on rails. The other two use generated video.

No words are rendered into the frames. All text is real HTML on the page.

How it works

The rack scene is one Blender 5.2 scene built by a Python script, driven by a config file and rendered with EEVEE. Renders are deterministic: a check renders twice and compares the bytes.

Frames are WebP at quality 75. Landscape tiers are 768, 1280 and 1920 px wide; portrait tiers are 432, 720 and 1080 (the two generated films stop at 499 in portrait).

A visitor loads one tier, chosen by the screen’s pixel count. Save-Data gets the smallest.

A spring-smoothed clock follows the scroll position and picks frames from manifests at 24 frames per second.

Neighbouring films share their boundary frame byte for byte, confirmed by 130 identity checks across both cameras.

While a chapter is pinned, breathing tails (48 extra rendered frames on each of four rack films) keep the picture alive. Reduced motion and Save-Data fall back to stepped stills.

Section titles start as HTML plates mapped in perspective by a unit-tested homography.

Frames are served from a private S3 bucket through CloudFront, with origin access control and immutable caching.

Automated checks cover 21 behaviours, from load to reduced motion. A film QA tool checks stills and clips.

Decisions and tradeoffs

Pre-rendered, not real-time

Every frame is rendered ahead of time and painted to a plain 2D canvas, so no 3D engine runs in the browser. The cost is bytes: at the 1280 tier a full scroll is about 17 MB of frames.

A script, not hand animation

The rack scene is code and a config file, and its renders are deterministic, so a change re-renders predictably.

Text in HTML, never in frames

Screen readers, search engines and translation tools get real text, and a copy change never needs a re-render.

A camera for phones

A separate portrait chain frames the scene for tall screens instead of cropping the landscape film.

One tier per visit

Each visitor loads one tier. At the smallest phone tier a full scroll is 10.6 MB of frames, against 29.1 MB at the largest desktop tier.

Security

What can go wrong

Next step

See it at soltani.org, or read the code on GitHub. To build something like it, start a project.

Start a project

Next case study: ChapterFlow