reelhouse

reelhouse · open-source demo

Footage in. A scroll sequence out, weighed.

Docs and a live playground for our image-sequence pipeline. Drop a clip, encode it into tiers in this tab, play it back against a scroll and see what it costs.

Open the playgroundRead the docs

loadingConcept visual · drawn in code
  1. 01 · rushesEncodeA video or a frame folder becomes tiered AVIF and WebP frames.
  2. 02 · reelPlayFrames follow the scroll, at the size this screen needs.
  3. 03 · heftWeighThe page is held to a budget, in CI and here.

Playground

Encode a clip here. Nothing leaves this tab.

rushes' preview encoder samples your video into frames and writes each tier with this browser's own image encoder. reel plays the result; the weights are the real bytes of those frames. For shipping, the same settings run through the CLI.

Your video
Tier widths
72
75
2.00 MB
400.0 kB

Waiting for a clip.

No clip yet

The tier table, the budget verdict and the commands to ship it appear here once a clip is encoded.

Docs

Four small libraries, one pipeline.

  1. footage
  2. rushes
  3. tiers + manifest
  4. reel
  5. heft

L02 · Encode

@quartifex/rushes

One command turns a video or a frame folder (a Blender render, an export) into tiered AVIF and WebP frames, posters, a manifest.json and a page-weight report against a budget.

npx rushes clip.mp4 --out public/sequences/clip \
  --widths 480,960,1600 --budget budget.json --strict
  • Video needs ffmpeg on the PATH (or --ffmpeg); frame folders do not.
  • Tiers up to 640 px keep every second frame, so phones download less.
  • In the browser, @quartifex/rushes/browser encodes previews, as in the playground above.

L01 · Play

@quartifex/reel

Plays a rushes sequence on a canvas against the scroll: the tier resolve picks for this screen, the current frame first, a bounded buffer, the nearest frame while the exact one loads.

import { bindScroll, createReel, loadManifest } from "@quartifex/reel";

const manifest = await loadManifest("/sequences/clip/manifest.json");
const reel = createReel(canvas, manifest, { baseUrl: "/sequences/clip/" });
bindScroll(reel, document.querySelector("#scene"));
  • GSAP is optional: scrubReel(reel, ScrollTrigger) from @quartifex/reel/gsap.
  • Under reduced motion it shows the poster and loads no frames.
  • urlFor maps frame paths to any URL: a CDN, signed links, object URLs.

L03 · Weigh

@quartifex/heft

Holds a page to its budget: weighs sequences, models and textures in the build, then loads, scrolls and clicks the page to measure transfer, long frames, layout shift and input delay.

npx heft --budget budget.json --dir public --url http://localhost:3000
# exit 1 when over; a GitHub Action is included
  • Budgets live in budget.json, per app, next to your code.
  • @quartifex/heft/browser has the evaluator and collectors for live dashboards.

L05 · Inspect

@quartifex/viewfinder

A development overlay for scroll scenes: every ScrollTrigger and reel on the page, live progress, frames in memory, chapter jumps, and recorded scroll paths you can replay.

import { createViewfinder } from "@quartifex/viewfinder";
import { reelSource } from "@quartifex/viewfinder/reel";

createViewfinder({ sources: [reelSource(reel, "hero")] }); // Alt+V
  • Mount it in development or behind a flag; it is not meant for visitors.
  • Open it on the playground's player to watch reel's buffer fill.