# .smoothcapture Project Format (for agents)

A `.smoothcapture` bundle is a **directory** containing media plus JSON. The CLI covers recording, smart zoom, presets, and export; everything else is edited by rewriting `project.json` directly. All JSON is Codable-compatible with ISO-8601 dates; unknown/missing fields fall back to defaults, but keep the existing structure intact.

```
demo.smoothcapture/
├── project.json        # the full edit/effects document (this file is what you edit)
├── takes.json          # take index {takes:[{id,name,relativePath,bundleInfo,…}], selectedTakeId}
├── take-settings.json  # per-take look settings (background, crop, cursor, aspect)
├── bundleinfo.json     # recording metadata + media manifest (do not edit)
├── screen.mp4          # captured video
├── cursor.bin          # cursor positions/clicks (binary v5; some bundles have cursor.json v2)
├── webcam.mp4 / microphone.m4a / system-audio.m4a / keyboard.json   # optional
└── recording.log
```

**Golden rule:** never edit `project.json` while the project is open in the SmoothCapture editor (its autosave will clobber you) — or call `smoothcapture cli project reload --path <bundle>` right after your edit.

## project.json — fields worth editing

Only the commonly useful fields are listed; leave the rest untouched.

### Canvas & background
| Field | Type | Meaning |
|---|---|---|
| `backgroundPadding` | float | Video scale within canvas; `0.9` ≈ 10% padding, `1.0` = edge-to-edge |
| `backgroundCornerRadius` | float | Rounded corners on the video, ~`0.06` |
| `selectedBackgroundImage` | string | Bundled background name, e.g. `"Apple/20250901_Mac"` |
| `borderEnabled` / `borderWidth` / `borderColor` | bool/float/rgb | Border around the video; `borderStyle`: `solid`/`gradient`/`glow` |

Aspect ratio lives in **take-settings.json** (`previewAspectRatio`), raw values: `"Match Video"`, `"16:9"`, `"19.5:9"`, `"4:3"`, `"1:1"`, `"9:16"`, `"9:19.5"`.

### Device frames & 3D environments
| Field | Type | Meaning |
|---|---|---|
| `deviceFrameName` | string? | Frame from `cli device-frames` (e.g. `"3D/iPhone_17_Pro_Blue.usdc"`). Set it in **both** project.json and take-settings.json — take-settings carries the per-take value. `null` disables |
| `selectedEnvironmentImage` | string | HDR backdrop for 3D frames, from `cli environments` |
| `useEnvironmentAsBackground` | bool | Show the environment behind the device (usual for 3D) |
| `environmentExposure` / `environmentLightingExposure` | float | Backdrop / model lighting, default 1 |
| `environmentRotation` / `environmentPitch` | float | Environment camera angle, degrees |
| `environmentScreenReflection` | float | Glass reflection on the screen — additive since Jul 2026 (never dims the display); subtle values ~0.05–0.2 work well, 0 is the default |
| `environmentDepthOfFieldEnabled` / `...Amount` | bool/float | Backdrop blur, default on at 0.5 |

In 3D device mode, zoom effects pan the 3D camera. Manual-center zooms work well here — just remember `manualCenterX/Y` are offsets where `(0, 0)` = centered (see the zoom section); a stray positive X slides the phone left. A camera transform with `endScale > 1` is an alternative for push-ins when you also want tilt.

### Camera transforms (`cameraTransforms` array) — 3D motion
Tilt/dolly the whole scene; the go-to way to make 3D device shots feel alive:
```json
{
  "startTime": 0.0, "endTime": 7.0,
  "startTiltX": 4.0, "startTiltY": -16.0, "startTiltZ": 0.0,
  "endTiltX": 0.0, "endTiltY": 0.0, "endTiltZ": 0.0,
  "startPerspective": 0.3, "endPerspective": 0.15,
  "startScale": 0.93, "endScale": 0.97,
  "tiltInDuration": 1.2, "tiltOutDuration": 1.2,
  "continuousAnimation": true,
  "endTransitionReturnsToOriginal": false,
  "name": "Intro sweep"
}
```
- Tilts are **degrees**; `scale` 1.0 = full size (< 1 zooms out, > 1 dollies in); `perspective` ~0–0.5 subtle.
- `continuousAnimation: true` interpolates start→end across the whole block — slow cinematic motion.
- Chain adjacent blocks (share the boundary time) for multi-phase moves: angled intro → neutral → finale dolly-in (`endScale` ~1.2).
- `endTransitionReturnsToOriginal: true` animates back to neutral at the block's end.

### Zoom effects (`zoomEffects` array)
Prefer `cli project autozoom`; hand-write only when you need a zoom at an exact moment:
```json
{
  "id": "<uuid>",
  "startTime": 3.0, "endTime": 7.5,
  "startZoom": 0.9, "endZoom": 2.0,
  "zoomInDuration": 1.9, "zoomOutDuration": 1.9,
  "manualCenterEnabled": false,
  "name": "Zoom 1"
}
```
- Times are in **timeline seconds**. `startZoom` usually equals `backgroundPadding`; `endZoom` is the peak (2.0 = 2×).
- The camera **follows the cursor by default** while zoomed (cinematic). A legacy `cinematicMode` key may appear in saved projects; it is ignored on load.
- To zoom at a fixed point instead: `"manualCenterEnabled": true, "manualCenterX": 0.0, "manualCenterY": 0.0`. These are **signed offsets from center, not 0–1 coordinates**: `(0, 0)` is a dead-center push-in; positive X shifts the framed content **left** (camera pans right), negative X right; positive Y shifts it up, negative down. Small values (±0.1–0.3) go a long way — especially with a device frame, where an unintended X offset visibly slides the phone off-center.
- Do **not** write keyframes — they are regenerated from these scalar fields on load. When retiming an existing effect (e.g. one saved by autozoom), **delete its `keyframes` key** so stale keyframes can't win.
- Keep effects non-overlapping and inside the video duration.
- Autozoom anchors on clicks, so a zoom can peak during an app-launch animation (blank launch screen). Retime the effect to start after the content loads.

### Trims & speed (`videoSegments` array)
```json
{"id":"<uuid>","startTime":0,"endTime":12.4,"speed":1.0,"sourceTakeId":"take-1"}
```
Cut dead time by splitting into multiple segments that skip source ranges; `speed` > 1 fast-forwards a segment. Times are source-video seconds.

Image clips (from `project import --input <image>` or `File > Import Media into Project…`) carry `"isStillImage": true`. Their `endTime` is simply the clip duration and can be set to **any** length — it is not clamped to the underlying ~1s still-video file. `speed` is ignored for stills.

### Cursor
`showCursor` (bool), `cursorSize` (in take-settings), `cinematicCursorEnabled` (smoothed motion), `clickRippleEnabled`, `clickSoundEnabled`/`clickSoundVolume`, `cursorFollowEnabled` (camera follows cursor).

### Webcam
`webcamEnabled` (bool), `webcamPosition` (`bottomLeft`, `bottomRight`, `topLeft`, `topRight`, …), `webcamSize` (float, fraction of canvas), `webcamCornerRadius`, `webcamShape`, plus shadow/border fields prefixed `webcamShadow*`/`webcamBorder*`. Time-varying layouts (fullscreen intro → corner overlay) live in `cameraLayouts`.

### Keystroke overlay
`keystrokeDisplayEnabled` (bool) with `keystrokePosition`, `keystrokeFont`, etc. Requires the recording to contain keyboard data (Accessibility permission at record time).

### Audio
`recordingAudioVolume`, `systemAudioVolume` (0–1 floats), matching `*Delay` fields in seconds.

**Background music / overlay tracks** (`audioClips` array): copy the audio file into the bundle root, then reference it by `fileName`:
```json
{
  "id": "<uuid>",
  "name": "Product Uplift",
  "fileName": "product-uplift.mp3",
  "timelineStartTime": 0.0, "sourceStartTime": 0.0,
  "originalDuration": 19.05,
  "assetTotalDuration": 180.0,
  "speed": 1.0, "volume": 0.4, "delay": 0.0
}
```
`originalDuration` is how much of the track plays (clamp to the video length); `assetTotalDuration` is the file's full length. `volume` ~0.3–0.5 sits well under click sounds. Exports mix it automatically; `clickSoundEnabled` + `clickSoundVolume` (~0.15) adds tap feedback for silent recordings.

### Crop (take-settings.json)
`cropEnabled` plus `cropX/cropY/cropWidth/cropHeight` (normalized 0–1 of the source video).

## Verification tip

After editing, `smoothcapture cli export --path <bundle> --output check.mp4 --preset 1080p30` and inspect a frame (`ffmpeg -ss <t> -i check.mp4 -frames:v 1 frame.png`). The export renders exactly what the editor preview would show.
