# Core concepts

> Compositions, the frame clock, layer trees, local timing, layout and coordinates — the model everything else builds on.

## Every frame is a function of its number

A composition compiles to a deterministic program: `(composition, frame) → pixels`. There are no timers and no wall clock. Rendering frame 45 always produces the same image, in any order, on any run. This is what makes frames testable and renders parallel.

## Composition

```ts
import { Composition } from "framefields";

const comp = new Composition({
  width: 1920,
  height: 1080,
  fps: 30,               // default 60
  durationMs: 10_000,    // or durationFrames; omitted → computed from media
  backgroundColor: "#0e0d0c", // default "#000000"
  antialias3d: true,     // 4x MSAA for 3D meshes; false saves GPU memory
});
```

| Method | Does |
| --- | --- |
| `add(layer \| layer[])` | adds layers (or arrays, e.g. from `Lighting.studio()`) to the root |
| `addAudio(Layer.audio(...))` | adds a soundtrack |
| `apply(effect)` | applies an effect to the whole frame |
| `addSignal(name, signal)` | registers a named signal layers can bind by name |
| `withVision(options)` | runs vision on the composition, returns reactive signals |
| `lint()` | document checks, no GPU |
| `toSpec()` | the plain-JSON program (validate with `CompositorProgramSchema`) |
| `renderFrame` / `renderFrameGrid` / `renderVideo` | output, see [Rendering](/docs/guides/rendering/) |
| `computeDuration()` | duration in ms, from options or media |

## Layers

Every `Layer.*` builder returns a node you can chain: `.animate()`, `.withEffect()`, `.withMatte()`, `.withVision()`, `.pinToObject()`. Layers nest through `children`.

```ts
Layer.box({
  width: 720, height: 380, padding: 32,
  background: "#141721", borderRadius: 24,
  borderColor: "#262b3d", borderWidth: 1.5,
  children: [
    Layer.flex({
      dir: "column", gap: 16,
      children: [
        Layer.text("ENGINE", { fontSize: 16, fontWeight: 700, fill: "#6366f1", letterSpacing: 2 }),
        Layer.text("WebGPU motion", { fontSize: 48, fontWeight: 700, fill: "#f8fafc" }),
      ],
    }),
  ],
});
```

## Time

- `startFrame` and `durationFrames` place a layer on its parent's timeline.
- **Keyframes are local**: frame 0 is the layer's first visible frame. Never add the parent's `startFrame` to keyframe times.
- **Frames are integers.** Keyframe frames, start frames and durations must be whole numbers; `.keys()` rounds for you, other APIs reject decimals.
- Keyframes past a layer's `durationFrames` never play (lint/QA: `animation_truncated`).
- Signals are sampled at the **composition** frame, not the layer's local frame.

A beat grid keeps timing musical and readable:

```ts
export const FPS = 30;
export const BPM = 100;
export const BEAT = Math.round((FPS * 60) / BPM); // 18 frames
export const beats = (n: number) => Math.round(n * BEAT);
```

## Layout and position

- Containers lay children out with flexbox (Yoga): `Layer.flex({ dir, gap, justify, align, wrap, padding })`.
- Setting `x` or `y` without `position: "relative"` makes a layer **absolute** — removed from flex flow, positioned in its parent.
- **Animating `x`/`y` overrides layout.** A layer placed at `y: 400` animates with `fromTo("y", 440, 400)`, not `fromTo("y", 40, 0)`.
- Borders are `borderColor` + `borderWidth`. A CSS shorthand like `border: "1px solid red"` is ignored.

## Coordinates

- 2D: pixels, origin top-left, y down.
- 3D: the camera is calibrated so `z = 0` maps 1:1 onto canvas pixels. y is still down, so "up" is −y. See [3D](/docs/guides/3d/).

## Colors

Every color prop takes hex, `rgb()`, `hsl()`, `oklch()`, `oklab()` or a CSS name. Color keyframes blend in OKLab, so red → green passes through yellow. Gradients are CSS strings (`linear-`, `radial-`, `conic-gradient()`) usable as any `background` or shape `fill`.

## Project shape

```
src/theme.ts     tokens: size, fps, colors, fonts, beat grid
src/scenes/*.ts  one function per scene, each returns a full-frame box
src/film.ts      buildFilm(): fonts, Composition, Layer.sequence(scenes), grade
src/render.ts    frames | grid | preview | video
assets/          fonts, footage, audio, images, LUTs, models — in Git LFS
output/          renders, gitignored
```

Media in `assets/` is stored with Git LFS: push it with a normal `git push`, and run `git lfs pull` in any fresh checkout before rendering. See [Assets and Git LFS](/docs/agents/workflow/#assets-and-git-lfs).
