pro-visu docs

Recipes

Practical configs for common showcase tasks.

For AI / LLMs: View Markdown

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:

app/layout.tsx (Next.js)
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.cleanup hides 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-server

Only 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 }

On this page