Studio demo

Plumbline.

An architecture practice I invented so there was something honest to put on a page: a room modelled in Blender, a camera move authored in my own tool, and a hero that runs off scroll position with no video player in sight. Scroll the banner below to drive the same move, or open the full page.

Studio demo · new

A room modelled, filmed, and scrubbed by scroll.

Open the full page

How it was built

Four steps, one afternoon.

Nothing here was bought. The model, the camera tool, the encode and the page are all mine, which is the only reason a change to any one of them takes minutes instead of a support ticket.

  1. 01

    Model the room in Blender, through an MCP bridge

    Blender runs an MCP server, so I talk to it the way I talk to any other tool: describe the room, get geometry back, look at a viewport screenshot, adjust. Walls, built-in shelving, the desk return, a rug, three small animals for scale. Materials are set from measured values rather than eyeballed, because the whole point is that the render and the room agree.

    Two things cost me real time and are worth knowing. Blender left in edit mode silently merges new primitives into whatever object is active, which renamed a wall and then deleted it. And glTF has no area lights, so a rig built on them exports dark.

    BridgeBlender MCP server, driven from Claude Code
    ExportGLB, so the file carries its own lights and materials
    TrapglTF stores lux, not watts: divide imported intensity by 683
  2. 02

    Author the camera move in Scrubforge

    Scrubforge is the tool I built for this. Load a model, drop camera keyframes on a timeline, drag the playhead, and watch the move. Splined position and rotation, right-click easing on any key, snapshot undo, and a loop blend that eases the last second back to frame one so the move can cycle without a seam.

    One rule holds the whole thing together: a single sampler function turns the timeline and a time value into a scene state, and the preview, the exporter and the shipped page all call it. There is no second implementation to drift.

    StackVite, three.js, mediabunny, no framework
    MoveTwelve seconds: eye level at the desk, up to plan, back down around the cutaway
    LoopSampled at the end equals sampled at zero, exactly
  3. 03

    Export so the frames can be seeked, not streamed

    A normal video keeps a keyframe every few seconds and interpolates the rest, which is why scrubbing one stutters. Everything here is exported all-intra: every single frame is a keyframe, so any scroll position lands on an exact image. Scrubforge writes that directly from the browser, which removes the ffmpeg pass the old playbook needed.

    Two outputs, two jobs. This banner is an all-keyframe H.264, small enough to sit on a page that is mostly about other things. The demo page instead loads a WebP sequence at the render's native width, because there the picture is the point.

    Banner1280 px H.264, 348 of 348 frames are keyframes, 8 MB
    Demo page348 WebP at 2560, quality 90, with a 1280 px set for phones
    Checked withffprobe, counting key_frame across every frame
  4. 04

    Build the page around the move

    Scroll position drives a value that chases its target instead of snapping to it, which is what makes the move read as filmed rather than dragged. Five notes fade in at the points where the camera is actually saying something, and each opens a longer note.

    Mobile first, and not as a slogan: on a phone the frame runs edge to edge at its full aspect with the caption underneath, so nothing is cropped and nothing sits on top of a lit plaster wall. Turn off animation and the hero becomes a still with all five notes as ordinary text. Turn off JavaScript and it does the same.

    Page weightNo framework, no GSAP, no CDN: one HTML file
    Never croppedFrame width is capped by height, so the whole render always fits
    Falls backReduced motion and no-script both degrade to a readable page
Open the full Plumbline page

One HTML file, no framework, scrub-ready from the first byte.

Beta