# Product demo

> Play your real UI like a screen recording: a scripted cursor that presses real buttons, a ripple at each press, a zoom that pushes in on the result and captions underneath, chained on one timeline.

Free under the MIT licence. Page: https://eased.dev/sets/product-demo

## Install the whole scene

The example installs every piece with it:

```bash
npx shadcn add https://eased.dev/r/demo-scene.json
```

## The pieces

Each works alone; `useDemoTimeline` (in eased-demo-core, installed with each) chains them.

- [Demo Cursor](https://eased.dev/components/demo-cursor): An animated demo cursor for React and shadcn, a scripted pointer that glides to real elements and presses them.
  - Install: `npx shadcn add https://eased.dev/r/demo-cursor.json`
  - Markdown: https://eased.dev/components/demo-cursor.md
- [Click Ripple](https://eased.dev/components/click-ripple): An animated click ripple for React and shadcn, a ring that spreads from the exact press point.
  - Install: `npx shadcn add https://eased.dev/r/click-ripple.json`
  - Markdown: https://eased.dev/components/click-ripple.md
- [Zoom Spotlight](https://eased.dev/components/zoom-spotlight): An animated zoom spotlight for React and shadcn, pushing in on one region of live UI and dimming the rest.
  - Install: `npx shadcn add https://eased.dev/r/zoom-spotlight.json`
  - Markdown: https://eased.dev/components/zoom-spotlight.md
- [Caption Ticker](https://eased.dev/components/caption-ticker): An animated caption ticker for React and shadcn, stepping short captions in under a scene one line at a time.
  - Install: `npx shadcn add https://eased.dev/r/caption-ticker.json`
  - Markdown: https://eased.dev/components/caption-ticker.md

## One timeline

Every piece reads the current step, and the cursor and the zoom find their targets by `data-demo` name, so the scene survives layout changes. The timeline pauses off-screen and in a hidden tab, and rests on `endStep` under reduced motion.

```tsx
const STEPS = [
  { id: "rest", duration: 1000 },
  { id: "aim", duration: 800 },
  { id: "press", duration: 160 },
  { id: "live", duration: 2400 },
] as const;

export function Scene() {
  const stage = useRef<HTMLDivElement>(null);
  const { step } = useDemoTimeline(STEPS, { endStep: "live", observe: stage });
  return (
    <div ref={stage} inert className="relative">
      <ZoomSpotlight active={step === "live"} target="result">
        <Button data-demo="deploy">{step === "live" ? "Deployed" : "Deploy"}</Button>
        <Card data-demo="result">
          <CardContent>…</CardContent>
        </Card>
        <DemoCursor to={step === "rest" ? "result" : "deploy"} pressed={step === "press"} />
        <ClickRipple trigger="demo" />
      </ZoomSpotlight>
      <CaptionTicker captions={["Press Deploy", "Live in 41 seconds"]} index={step === "live" ? 1 : 0} />
    </div>
  );
}
```

Need a guided product tour? Spotlight Tour in Pro: https://eased.dev/components/spotlight-tour
