pro-visu docs
Generators

scroll-reel

Deterministic frame-stepped scroll recordings — single sweeps, choreographed section pans, and social formats.

For AI / LLMs: View Markdown

scroll-reel is the workhorse generator, and it does one thing: record a page scrolling. Every capture is frame-stepped — it drives a virtual clock, screenshots each frame, and pipes them to ffmpeg — so output is frame-accurate, crisp (supersampled by deviceScaleFactor), parallelised across workers, and byte-identical run-to-run.

{
  name: "home",
  url: "https://your-site.com",
  generator: "scroll-reel",
}

By default it eases a single top→bottom sweep. The options switch it into choreographed or auto-section motion, emit light/dark and per-viewport variants, and reframe / re-encode the output for social. Site cleanup (hide the cookie banner, block trackers, freeze the clock) lives in settings.capture and applies to every URL capture.

Want a scripted click-through with a visible cursor, a clip cropped to one component, or a realtime recording of the live page (time-based hero animation, autoplay video)? That's the interaction generator.

Config options

Everything is optional. Reference is the interactive view; TypeScript is the same shape in code, every option at its default.

outputobject

Pixel size, encoding, and output formats.

widthnumberdefault 1280

Output width in CSS px.

heightnumberdefault 800

Output height in CSS px.

deviceScaleFactornumberdefault 2

Render scale, max 4 — higher captures crisper, then downscales into the video.

fpsnumberdefault 30

Output frames per second (max 120).

crfnumberdefault 18

x264 quality, 0–51 (lower = better quality / larger file).

fileNamestring

Output filename. Defaults to "<slug(asset name)>.mp4".

outputs('mp4' | 'gif' | 'webp' | 'poster')[]default ['mp4']

Files to emit per variant; each becomes its own asset.

gifFpsnumber

GIF / animated-WebP frame rate (max 50). Defaults to min(fps, 15).

pageobject

Page-load waiting + dwell at the ends of the scroll.

startDelayMsnumberdefault 500

Dwell at the top before scrolling (ms).

endDwellMsnumberdefault 800

Dwell at the bottom after scrolling (ms).

waitUntil'load' | 'domcontentloaded' | 'networkidle' | 'commit'default 'networkidle'

Page-load milestone to wait for before recording.

waitForSelectorstring

Optional element to wait for (visible) before recording, e.g. a hero section.

renderobject

Frame-stepped render tuning (parallelism, frame format, per-frame settling).

workersnumber

Parallel render workers, each its own browser context. Omit to auto-pick from cores + free memory.

frameFormat'jpeg' | 'png'default 'jpeg'

Intermediate frame format. "jpeg" is faster; "png" is lossless.

settlePerFramebooleandefault true

Wait for fonts + in-view images before each frame's screenshot. Off in --draft.

settleMaxMsnumberdefault 250

Max time (ms) to wait per frame for settling before screenshotting anyway.

motionobject

How the scroll moves: duration/easing, loop, and choreography.

durationMsnumberdefault 6000

Time to scroll from top to bottom (ms). Ignored when choreography / autoSections drives the length.

easing'linear' | 'ease-in' | 'ease-out' | 'ease-in-out' | 'ease-out-strong' | 'ease-in-out-strong'default 'ease-in-out'

Easing for the default top→bottom scroll.

loop'none' | 'boomerang' | 'straight'default 'none'

Loop style — works with the default sweep, choreography, and autoSections. "boomerang" plays the motion forward then back, retracing every stop; "straight" runs it once then glides straight back to the top so the clip loops.

choreographyChoreographyStep[]

Choreographed scroll: an ordered list of steps instead of one top→bottom sweep. Omit for the default single eased sweep. Clip length becomes startDelayMs + Σ(step travel + hold) + endDwellMs.

tonumber | stringrequired

Target: a 0..1 number, an "NN%" string, or a CSS selector to bring into view.

durationMsnumberdefault 1200

Travel time to this target (ms).

holdMsnumberdefault 800

Hold time at this target after arriving (ms).

easingEasingdefault 'ease-in-out'

Easing for the travel to this target.

autoSectionsboolean | AutoSections

Auto-choreograph: detect the page's sections and pan/hold through them within a fixed budget. true for defaults, or an object to tune. Ignored if choreography is set.

minHeightFractionnumberdefault 0.5

Min element height (as a fraction of the viewport, max 2) to count as a section.

selectorstring

Explicit section selector; overrides the heuristic. Omit to auto-detect.

headerSelectorstring

Sticky-header selector to measure the top inset from, for pages where the auto-detect picks the wrong element (e.g. a JS-fixed header). Sections land just below it. Omit to auto-detect; overridden by headerHeight.

headerHeightnumber

Sticky-header height in px, used verbatim as the top inset. Overrides the auto-detect and headerSelector. Omit to auto-detect.

holdMsnumberdefault 700

Hold at each detected section (ms).

durationMsnumberdefault 12000

Total clip length (ms) split across detected sections.

maxSectionsnumberdefault 8

Cap on the number of sections.

constantVelocitybooleandefault true

Distribute travel time by distance for uniform scroll speed.

includeFooterbooleandefault false

Scroll all the way to the page bottom (footer included). Off by default: footers aren't counted as sections and the reel ends at the last content section.

variantsobject

Variant matrix: each cell (color scheme × viewport) is emitted as its own asset.

colorScheme'light' | 'dark' | 'both'

Force a color scheme. "both" emits a light AND a dark asset (<name>-light / <name>-dark). Omit to leave as-is.

themeClassstring

Add this class to <html> before capture, e.g. to trigger a CSS-class dark theme.

viewportsViewport[]

Also capture the reel at these viewports; each emits an asset (<name>-<viewport name>).

namestringrequired

Label for this viewport — used in the asset id / filename (e.g. "desktop").

widthnumberrequired

Viewport width in CSS px.

heightnumberrequired

Viewport height in CSS px.

deviceScaleFactornumber

Override the generator-level deviceScaleFactor for this viewport. Omit to inherit it.

reframeobject

Reframe the output to a target aspect.

aspect'16:9' | '9:16' | '1:1' | { width, height }

Reframe the output to a target aspect: a preset or an explicit pixel box. Omit to keep the capture aspect.

fit'cover' | 'contain'default 'cover'

How to fit the capture into aspect: "cover" (scale + center-crop) or "contain" (scale + pad).

padColorstringdefault '#0b0b0f'

Pad color used by "contain".

{
  name: "home",
  url: "https://your-site.com",
  generator: "scroll-reel",
  options: {
    // --- sizing, encoding & output formats ---
    output: {
      width: 1280,
      height: 800,
      deviceScaleFactor: 2,
      fps: 30,
      crf: 18,
      // fileName: "home.mp4",       // optional; defaults to <slug(name)>.mp4
      outputs: ["mp4"],
      // gifFps: 15,                 // defaults to min(fps, 15)
    },

    // --- page load & end dwell ---
    page: {
      startDelayMs: 500,
      endDwellMs: 800,
      waitUntil: "networkidle",
      // waitForSelector: "#hero",   // optional; wait for an element first
    },

    // --- frame rendering ---
    render: {
      // workers: 4,                 // optional; auto-picks from cores + free memory
      frameFormat: "jpeg",
      settlePerFrame: true,
      settleMaxMs: 250,
    },

    // --- motion (default is a single top→bottom sweep) ---
    motion: {
      durationMs: 6000,
      easing: "ease-in-out",
      loop: "none",
      // choreography: [{ to: "#hero", holdMs: 1200 }, { to: "100%", durationMs: 1000 }],
      // autoSections: { minHeightFraction: 0.5, holdMs: 700, durationMs: 12000, maxSections: 8, constantVelocity: true, includeFooter: false },
    },

    // --- variants (each emitted as its own asset) ---
    variants: {
      // colorScheme: "both",        // optional; → <name>-light and <name>-dark
      // themeClass: "dark",         // optional; class added to <html>
      // viewports: [{ name: "mobile", width: 390, height: 844 }],
    },

    // --- reframing ---
    reframe: {
      // aspect: "9:16",             // optional; preset or { width, height }
      fit: "cover",
      padColor: "#0b0b0f",
    },
  },
}

Every option also has hover docs in pro-visu.config.ts — the authoring types are generated from the validation schema, so the editor always matches what the tool accepts.

Examples

Auto-sections

autoSections detects the page's sections and paces a pan/hold down the whole page within a fixed time budget — hands-off, no choreography to author.

{name: "home",url: "https://your-site.com",generator: "scroll-reel",options: {  motion: {    autoSections: {      durationMs: 9000,    },  },},}
autoSections walks the home page top to bottom, holding briefly on each detected section.

Boomerang loop

loop: "boomerang" plays the capture forward then back, so the first and last frames match.

{name: "docs-home",generator: "scroll-reel",options: {  output: {    deviceScaleFactor: 3,  },  motion: {    loop: "boomerang",    autoSections: {      durationMs: 22000,      holdMs: 1600,    },  },},}
A boomerang loop over an auto-sections pan — supersampled at deviceScaleFactor 3 for a crisp hero clip.

Straight loop

loop: "straight" runs the motion once, then glides straight back to the top — no retraced stops — so the clip's last frame lands where the first started and the output loops.

{name: "home",url: "https://your-site.com",generator: "scroll-reel",options: {  motion: {    loop: "straight",    autoSections: {      durationMs: 14000,    },  },},}
One auto-sections pass down the page, then a single glide home — a lighter-touch loop than boomerang.

Vertical (social) reframe

aspect: "9:16" reframes the same page for a Reel or Story. Add outputs for gif/poster to cover organic formats too.

{name: "home-vertical",url: "https://your-site.com",generator: "scroll-reel",options: {  output: {    width: 430,    height: 932,  },  motion: {    durationMs: 5000,  },  reframe: {    aspect: "9:16",  },},}
The same scroll, reframed vertical for social.

On this page