# Recipes

URL: /docs/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:

```ts
{
  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:

```ts
{
  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):

```ts
{
  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:

```ts
{
  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](/docs/generators/wall) — each column
lists the tiles it stacks (asset names run first; `{ src }` files come straight from disk) and
carries its own motion:

```ts
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:

```ts
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:

```tsx title="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>
  );
}
```

```css
/* 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`](/docs/configuration#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:

```ts
settings: {
  browser: { headless: true, args: ["--no-sandbox"] },
}
```

```bash
pro-visu generate --skip-server
```

## Only rebuild what changed

Turn on caching to skip assets whose inputs and options are unchanged:

```bash
pro-visu generate --cache
```

…or persist it in the config:

```ts
settings: { cache: true }
```
