# Shapes, sequences, and scroll

## A persistent shape

```tsx
"use client";
import { useState } from "react";
import { DotSwarm, type Shape } from "dots-swarm";

export function StatusSwarm() {
  const [shape, setShape] = useState<Shape>("cloud");
  return <>
    <DotSwarm shape={shape} count={900} color="#eeeeee"
      transitionDuration={1.4} style={{ width: "100%", height: 320 }}
      label="Animated status" />
    <button onClick={() => setShape(shape === "cloud" ? "check" : "cloud")}>
      Change shape
    </button>
  </>;
}
```

`DotSwarm` props:

| Prop | Meaning |
| --- | --- |
| `shape` | Core `Shape` ID; defaults to `cloud` |
| `points` | Custom `{x,y,z}[]`, roughly normalized to -1…1; overrides `shape` |
| `motion` | For custom points: `rotate`, `swirl`, `wave`, `pulse`, or `orbit` |
| `count` | 24–2400; default 900 |
| `color` | CSS color; default `#eef0e9` |
| `speed` | Animation multiplier, 0.1–3; default 1 |
| `dotSize` | Dot radius; default 1.25 |
| `spread` | Formation scale; default 1 |
| `choreography` | `flow`, `scatter`, or `direct` |
| `transitionDuration` | Seconds at 1× speed; default 2.4 |
| `interactive` | Pointer repulsion; default false |
| `paused` | Freeze animation |
| `reducedMotion` | Reduce motion; OS preference is also respected |
| `style`, `className`, `label` | Layout and accessible description |

There are no `size`, `dark`, or assistant `state` props. Size the container; select colors from the app's theme; map business state to shape IDs.

Read `SHAPES`/`shapeInfo` for supported names, or the online catalog. For Pro shapes read [pro.md](pro.md), rather than passing their names into the core `shape` prop.

## Timed sequences

```tsx
"use client";
import { useMemo } from "react";
import { DotsSequence, DotsSequencePlayer } from "dots-swarm";

export function AssistantLoop() {
  const sequence = useMemo(() => new DotsSequence([
    { shape: "cloud", duration: 4 },
    { shape: "halo", duration: 5, transition: { duration: 1.4, path: "flow" } },
    { shape: "check", duration: 4, transition: { duration: 1.2 }, color: "#bde2d1" },
  ], { loop: true, autoplay: true, defaults: { count: 1000 } }), []);
  return <DotsSequencePlayer sequence={sequence} animate style={{ height: 340 }} />;
}
```

The player's connection manages playback. Step `duration` includes its incoming transition; remaining time holds the completed state. `animate` keeps native shape motion alive during the hold. Do not stretch transitions when the user wants more time admiring a completed shape.

Each step has exactly one `shape`, `element`, or `asset` name. `element` targets belong to `DotsUISequencePlayer` from `dots-swarm/ui`, not the shape player. Custom assets are supplied separately through `assets`; Pro shapes through `shapes={proShapes}`. Options include `name`, `loop`, `autoplay`, and appearance `defaults`. Keep built-in exports as named steps, not thousands of coordinates.

## Scroll through live formations

```tsx
"use client";
import { useRef } from "react";
import { SwarmSequence, type SwarmKeyframe } from "dots-swarm";
import { SwarmScrollPin, useSwarmScroll } from "dots-swarm/scroll";

const frames: readonly SwarmKeyframe[] = [
  { at: 0, shape: "halo", color: "#ffffff" },
  { at: 0.28, shape: "halo" },
  { at: 0.5, shape: "helix", rainbow: 1, path: "flow", stagger: 0.12 },
  { at: 0.78, shape: "helix", rainbow: 1 },
  { at: 1, shape: "cloud", rainbow: 0, dispersion: 0.35 },
];

export function ScrollStory() {
  const target = useRef<HTMLElement>(null);
  const { progress } = useSwarmScroll({ target, scrub: 0.15 });
  return <section ref={target} style={{ minHeight: "300svh" }}>
    <SwarmScrollPin style={{ display: "grid", placeItems: "center" }}>
      <SwarmSequence keyframes={frames} progress={progress} animate
        count={1200} style={{ width: "100%", height: "65svh" }} />
    </SwarmScrollPin>
  </section>;
}
```

`progress` is 0–1. The section's scroll distance sets pacing; `scrub: true` follows directly, a number adds seconds of smoothing. The default range is top/top to bottom/bottom; set `start`/`end` pairs for other boundaries. Use `offset` for a sticky header. Keep a readable static fallback for reduced motion; never gate essential content on progress reaching 1.

`SwarmSequence` interpolates shape plus color, rainbow (0–1), density (0–1 of a fixed pool), dispersion, turbulence, dotSize, opacity, scale, rotation (radians), and x/y. Repeated keyframes hold a state; different keyframes morph. `animate` supplies an independent live clock; otherwise completed shapes are static.

`useSwarmScroll` also accepts a `DotsSequence` in `sequence`, or triggers `{ at, direction, once, onCross }`. Use a stable triggers array when using `once`. Do not call actions in render. `SwarmScrollPin` is native sticky positioning; snapping is opt-in through `useSwarmScrollSnap` and should usually use `type: "proximity"` to preserve backward scrolling.
