Recipes
Practical configs for common showcase tasks.
Copy-paste starting points — each is an entry in your assets array (or a settings block, or the
command to run it).
A vertical social reel
A 9:16 clip with multiple output formats:
{
name: "social",
url: "https://your-site.com",
generator: "scroll-reel",
options: {
output: {
width: 430,
height: 932,
deviceScaleFactor: 2,
outputs: ["mp4", "gif", "poster"],
},
motion: { durationMs: 5000 },
reframe: { aspect: "9:16" },
},
}A choreographed product tour
Pause on each section instead of one continuous scroll:
{
name: "tour",
url: "https://your-site.com",
generator: "scroll-reel",
options: {
motion: {
choreography: [
{ to: "#hero", holdMs: 1200 },
{ to: "#features", holdMs: 1500 },
{ to: "#pricing", holdMs: 1500 },
{ to: "100%", durationMs: 1000 },
],
},
},
}A scripted interaction demo
Drive the UI with a synthetic cursor (the interaction generator; records realtime):
{
name: "search-demo",
url: "https://your-site.com",
generator: "interaction",
options: {
cursor: { color: "#e91e63" },
actions: [
{
do: "click",
selector: "#search",
},
{
do: "type",
selector: "#search input",
text: "leather tote",
},
{
do: "hover",
selector: ".results a:first-child",
},
],
},
}Responsive screenshots
Full-page captures at several breakpoints, plus a cropped element shot — each viewport (and each named element) writes its own file:
{
name: "shots",
url: "https://your-site.com",
generator: "screenshots",
options: {
viewports: [
{ name: "desktop", width: 1440, height: 900 },
{ name: "mobile", width: 390, height: 844 },
],
elements: [{ selector: "header", name: "nav" }],
},
}A media wall
Composite several captures into a seamless-looping wall — each column
lists the tiles it stacks (asset names run first; { src } files come straight from disk) and
carries its own motion:
assets: [
{
name: "shot-home",
url: "/",
generator: "screenshots",
options: { fullPage: false },
},
{
name: "shot-shop",
url: "/shop",
generator: "screenshots",
options: { fullPage: false },
},
{
name: "wall",
generator: "wall",
options: {
motion: {
durationMs: 16000,
pan: { direction: "left", loops: 1 },
},
columns: [
{
tiles: [{ src: "public/img/hero.jpg" }, "shot-home"],
direction: "down",
pulses: [
{
at: 0.1,
span: 0.15,
distance: 0.5,
},
],
},
{
tiles: ["shot-shop", { src: "public/img/hero.jpg" }],
direction: "up",
loops: 1,
stagger: 0.4,
},
{
tiles: ["shot-home", "shot-shop"],
stagger: 0.15,
},
],
// dial it in fast with preview: { enabled: true } + render: { capture: "realtime" }; remove both for the real render
},
},
]Capture mode: render a settled page
Reveal-on-scroll, count-ups, and scroll-snap capture as blank gaps, zeros, or the wrong frame — the animation is caught mid-flight. The fix is a flag pro-visu delivers and your app reads, so the page renders its final, settled state during capture only. Two halves:
1. pro-visu sends the signal. A cookie is the best fit for reels — SSR-readable and persisted across in-app navigation:
settings: {
capture: {
signals: { cookies: [{ name: "pv_capture", value: "1" }] },
},
}2. Your app reads it and settles. Read the flag server-side so the very first paint is already settled, and expose it as an attribute — then gate the animations in CSS:
import { cookies } from "next/headers";
export default async function RootLayout({ children }) {
const capture = (await cookies()).get("pv_capture")?.value === "1";
return (
<html lang="en" data-capture={capture ? "" : undefined}>
<body>{children}</body>
</html>
);
}/* reveals rendered visible, snap relaxed, transitions off */
[data-capture] [data-reveal] { opacity: 1 !important; transform: none !important; }
[data-capture] * { scroll-snap-type: none !important; transition: none !important; animation: none !important; }For count-ups and other JS-driven values, read the same flag and render the final number outright
(document.documentElement.hasAttribute("data-capture")). Any signal channel works the same way —
query, localStorage, or an initScript global instead of a cookie; see
capture.
Prefer not to touch the app?
capture.cleanuphides headers, banners, and widgets tool-side (hideSelectors,injectCss) with no app changes — but only the signals above can settle animations gated in your own JS.
Capture a deployed URL in CI
Skip the managed server and point at a live URL; add --no-sandbox for most CI runners:
settings: {
browser: { headless: true, args: ["--no-sandbox"] },
}pro-visu generate --skip-serverOnly rebuild what changed
Turn on caching to skip assets whose inputs and options are unchanged:
pro-visu generate --cache…or persist it in the config:
settings: { cache: true }