pro-visu docs
Generators

scroll-reel

Deterministic frame-stepped recordings — scroll reels, choreographed tours, scripted interactions, and social formats.

scroll-reel is the workhorse generator. By default it captures 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" }

Core sizing

OptionMeaning
width / heightViewport size in CSS px. Default 1280 × 800.
fpsOutput frames per second. Default 30 (max 120).
durationClip length in ms (when not using choreography/autoSections/routes). Default 6000.
deviceScaleFactorRender scale — higher captures crisper, then downscales into the video. Default 2 (max 4).
easingScroll easing: linear, easeInOutCubic (default), easeInOutQuad, easeOutCubic, easeInOutSine, easeInOutExpo, easeOutQuint.
crfx264 quality, 051 (lower = better/larger). Default 18.
fileNameOutput filename. Defaults to <slug(asset name)>.mp4.

Timing & navigation

OptionMeaning
startDelayMsDwell at the top before scrolling (ms). Default 500.
endDwellMsDwell at the bottom after scrolling (ms). Default 800.
waitUntilNavigation wait: "load", "domcontentloaded", "networkidle" (default), or "commit".
waitForSelectorOptional CSS selector to wait for (visible) before recording — e.g. a hero section.

Capture mode

OptionMeaning
capture"frames" (default) deterministic frame-stepping; "realtime" records live — a fallback for time-based hero animations or autoplay video.
workersParallel render contexts for "frames" (default ≈ half the cores).
frameFormatIntermediate frame format for "frames": "jpeg" (default) or "png" (lossless).

Choreography, auto-sections, variants, cards, annotations, aspect, and extra outputs are frames-only; realtime ignores them (with a warning). Scripted actions and focus record realtime by nature.

Motion & cinematography

options: {
  choreography: [
    { to: "#hero", holdMs: 1200 },
    { to: "#features", holdMs: 1500 },
    { to: "100%", durationMs: 1000 },
  ],
  kenBurns: { scaleTo: 1.06 },   // slow zoom over the clip
  loop: "boomerang",             // play forward then back → seamless loop
}
  • choreography: [{ to, durationMs?, holdMs?, easing? }] — replaces the single sweep with an authored sequence. to is a 0..1 number, an "NN%" string, or a CSS selector to bring into view. Per step: durationMs default 1200, holdMs default 800. Clip length becomes startDelayMs + Σ(step travel + hold) + endDwellMs.
  • autoSections: true | { minHeightFraction?, selector?, holdMs?, durationMs?, maxSections?, constantVelocity? } — auto-detect the page's sections and pan/hold through them within a fixed durationMs budget. Defaults: minHeightFraction 0.5 (max 2), holdMs 700, durationMs 12000, maxSections 8, constantVelocity true. Ignored if choreography is set.
  • kenBurns: { scaleFrom?, scaleTo?, easing?, originX?, originY? } — slow zoom (folds automatically under loop: "boomerang" so it stays seamless). Defaults: scaleFrom 1, scaleTo 1.08, originX/originY 0.5.
  • loop: "none" | "boomerang" — boomerang plays forward then back for a seamless loop. Default "none".

autoSections is the hands-off alternative to choreography — it finds the page's sections and paces a pan/hold through them inside a fixed time budget:

options: {
  autoSections: {
    durationMs: 12000,      // total budget — the whole pass fits inside it
    holdMs: 700,            // dwell on each detected section
    maxSections: 8,         // cap how many it stops on
    minHeightFraction: 0.5, // ignore sections shorter than half the viewport
  },
}

Clean capture

Suppress real-site noise so frames are clean and deterministic:

  • Page: hideSelectors: [], injectCss, clickSelectors: [] (best-effort consent dismissal), hideScrollbars (default true), pauseAnimations (default false), freezeClock (default false — pin Date.now / performance.now / Math.random).
  • Network: blockTrackers (default true — aborts common analytics/ads/session-replay), blockHosts: [], blockResourceTypes: [] (e.g. ["media", "font"]).
  • Settling: settlePerFrame (default on; off in --draft) waits for fonts + in-view images each frame, bounded by settleMaxMs (default 250).
options: {
  // Hide chrome that shouldn't be in the shot
  hideSelectors: ["#cookie-banner", ".intercom-launcher"],
  clickSelectors: ["#onetrust-accept-btn-handler"], // best-effort consent dismissal
  injectCss: ".promo-bar { display: none !important }",
  // Determinism
  freezeClock: true,        // pin Date.now / performance.now / Math.random
  pauseAnimations: true,    // hold CSS animations/transitions on their first frame
  // Network
  blockTrackers: true,      // default — aborts analytics/ads/session-replay
  blockHosts: ["widget.example.com"],
  blockResourceTypes: ["media"], // skip heavy autoplay video, etc.
}

Variants — one config, many assets

options: {
  colorScheme: "both",                              // → <name>-light and <name>-dark
  viewports: [
    { name: "desktop", width: 1440, height: 900 },
    { name: "mobile", width: 390, height: 844, deviceScaleFactor: 3 },
  ],                                                 // → <name>-desktop, <name>-mobile
}
  • colorScheme: "light" | "dark" | "both" (with themeClass to toggle a CSS-class theme).
  • viewports: [{ name, width, height, deviceScaleFactor? }].

The viewport × colour-scheme matrix is emitted as separate assets (<name>-<suffix>).

Output formats & framing

  • aspect: "16:9" | "9:16" | "1:1" | { width, height } with fit: "cover" | "contain" (default "cover") and padColor (default #0b0b0f, used by "contain") — reframe for social (e.g. 9:16 reels).
  • outputs: ("mp4" | "gif" | "webp" | "poster")[] (default ["mp4"]) — each becomes its own asset; gifFps tunes the GIF/WebP frame rate (default min(fps, 15), max 50).
  • intro / outro: { title?, subtitle?, background?, color?, durationMs?, fadeMs? } — a fade-in title card / end card. Defaults: background #0b0b0f, color #ffffff, durationMs 1500, fadeMs 400.
  • annotations: [{ text?, ring?, spotlight?, atMs?, untilMs?, position? }] — timed captions, a highlight ring around a selector, or a spotlight that dims everything else. Defaults: atMs 0, untilMs end of clip, position "top" | "bottom" | "center" (default "bottom").
options: {
  aspect: "9:16",
  outputs: ["mp4", "gif", "poster"],
  intro: { title: "Acme", subtitle: "Botanik" },
  annotations: [{ text: "Real-time data", ring: "#chart", atMs: 1000, untilMs: 3000 }],
}

Scripted interaction & element focus

Records realtime (interactions and their animations are time-based).

options: {
  cursor: { color: "#e91e63" },
  actions: [
    { do: "click", selector: "#menu-button" },
    { do: "hover", selector: ".dropdown a:first-child" },
    { do: "type", selector: "input[type=search]", text: "shoes" },
  ],
}
  • actions: [{ do, selector?, x?, y?, text?, to?, durationMs?, holdMs? }] where do is "move" | "click" | "hover" | "type" | "scrollTo" | "wait" — a scripted tour with a synthetic cursor: { show?, size?, color? }. Per action: durationMs default 700, holdMs default 600. Cursor defaults: show true, size 22, color #0b0b0f.
  • focus: { selector, padding?, actions?, holdMs? } — capture a single component (optionally trigger it first), cropped to its box. Defaults: padding 24, holdMs 2000.
options: {
  // Crop to one component, triggering its toggle before the hold
  focus: {
    selector: "#pricing-card",
    padding: 32,
    actions: [{ do: "click", selector: "#billing-annual" }],
    holdMs: 2500,
  },
}

Setting actions or focus emits a single asset and records realtime: variants (viewports / colorScheme: "both"), cards (intro / outro), annotations, aspect, and extra outputs are skipped — actions logs a warning if you set any of these.

Multi-page tour

options: {
  routes: [
    "https://site.com",
    { url: "https://site.com/pricing", autoSections: true },
    { url: "https://site.com/contact", durationMs: 2000 },
  ],
}

routes captures each page as a frame-stepped segment and concatenates them into one reel; aspect and extra outputs apply to the final tour. Variants are not expanded — viewports and colorScheme: "both" are skipped (a warning is logged), so a route tour emits a single asset. Per-route objects accept choreography / autoSections / durationMs.

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.

On this page