# Scenes & transitions

> Split a film into scene functions on a beat grid and join them with Layer.sequence and Transition.

## One function per scene

Each scene returns a full-frame box. Children key from the scene's own frame 0.

```ts title="src/scenes/hero.ts"
import { Layer, LayerAnimation } from "framefields";
import { H, PAPER, SANS, W } from "../theme.js";

export function hero() {
  return Layer.box({
    id: "hero",
    position: "absolute", x: 0, y: 0, width: W, height: H,
    background: "#000",
    children: [
      Layer.text("Every frame is a function of its number.", {
        position: "absolute", x: 140, y: 420, width: W - 280,
        fontFamily: SANS, fontSize: 120, fontWeight: 800, fill: PAPER,
      }).animate(LayerAnimation.create().fadeIn(0, 18)),
    ],
  });
}
```

## Join them: `Layer.sequence`

`Layer.sequence` places scenes end to end, overlaps each pair by its transition, and sets every scene's `startFrame` / `durationFrames` for you.

```ts title="src/film.ts"
import { Composition, Layer, Transition } from "framefields";
import { beats, FPS, H, W } from "./theme.js";
import { features, hero, outro, proof } from "./scenes/index.js";

const film = Layer.sequence(
  [
    { scene: hero(), duration: beats(8) },
    { scene: features(), duration: beats(12), transition: Transition.push("left", 18) },
    { scene: proof(), duration: beats(8), transition: Transition.wipe("right", 16) },
    { scene: outro(), duration: beats(6), transition: Transition.iris(20) },
  ],
  { width: W, height: H },
);

const comp = new Composition({
  width: W, height: H, fps: FPS,
  durationMs: (film.totalFrames / FPS) * 1000, // the sequence knows its length
});
comp.add(film);
```

`duration` includes the transition. A missing `transition` is a cut.

## Transitions

| Call | Look | Defaults |
| --- | --- | --- |
| `Transition.cut()` | hard cut | — |
| `Transition.crossfade(frames, ease)` | new scene fades in over the old | 15, `sine.inOut` |
| `Transition.fadeThrough(frames, ease)` | dip to background and back | 20, `sine.inOut` |
| `Transition.push(dir, frames, ease)` | new pushes old out | `left`, 18, `power3.inOut` |
| `Transition.slide(dir, frames, ease)` | new slides over old, which dims | `left`, 18, `power3.out` |
| `Transition.zoom(frames, ease)` | old grows and fades, new settles | 18, `power2.inOut` |
| `Transition.whip(dir, frames)` | fast motion-blurred push | `left`, 10 |
| `Transition.wipe(dir, frames, ease)` | hard edge sweeps across (matte) | `left`, 18, `power2.inOut` |
| `Transition.iris(frames, { at, ease })` | circle opens from a point (matte) | 20, centre |

`dir` is `"left" | "right" | "up" | "down"`. `at` is `[x, y]` as fractions of the frame.

## Carry elements across cuts

A badge, card or cursor that crosses a scene boundary with continuous position and velocity hides the cut. Put the carrier outside the sequence, on the composition, with its own keyframes spanning both scenes.
