rustmotion 0.7.0

A CLI tool that renders motion design videos from JSON scenarios. No browser, no Node.js — just a single Rust binary.
Documentation
# Rule: Motion Path

Pour faire suivre une trajectoire à un composant — une courbe, un arc, un tracé en S — utilise l'effet d'animation `motion_path` plutôt que d'empiler des `translate` successifs dans une `timeline`.

## La forme

```json
{
  "type": "shape",
  "shape": "circle",
  "fill": "#F68F2B",
  "position": "absolute",
  "x": 160,
  "y": 700,
  "style": {
    "width": "56px",
    "height": "56px",
    "animation": [{
      "name": "motion_path",
      "path": "M0,0 C300,-320 750,-320 1100,-60",
      "delay": 0.2,
      "duration": 2.4,
      "orient": true,
      "orient_offset": 90,
      "easing": "ease_in_out"
    }]
  }
}
```

| Champ | Rôle |
|---|---|
| `path` | Données de chemin SVG (`M`/`L`/`H`/`V`/`C`/`S`/`Q`/`T`/`A`/`Z`) — **la même syntaxe** que `shape: { "type": "path", "data": ... }` |
| `delay`, `duration` | Fenêtre temporelle, comme tout autre effet |
| `loop` | Reprend au début à la fin du parcours |
| `orient` | Oriente le composant selon la tangente |
| `orient_offset` | Correction d'angle, en degrés |
| `easing` | Appliqué à la progression **le long du chemin**. Défaut linéaire = vitesse constante sur la courbe |

## Les coordonnées sont des deltas

Le chemin est relatif à la position que le layout aurait donnée au composant. `M0,0` est donc son point de repos, pas le coin du device — même convention qu'`orbit`.

Concrètement : positionne le composant normalement (`x`/`y`, ou le flux), puis décris la trajectoire **depuis là**.

## `orient_offset` n'est pas décoratif

Un composant est orienté selon la tangente, et la tangente pointe dans le sens du parcours. Si ton visuel pointe naturellement vers le haut — une flèche, une icône de fusée, un curseur — il apparaîtra tourné de 90° sur un chemin horizontal. `orient_offset: 90` le corrige.

Vérifie l'orientation au repos de ton visuel avant de conclure que `orient` est cassé.

## Le validateur voit la trajectoire

La position résout en `transform`, donc `rustmotion validate --strict-anim` détecte un composant qui sort du cadre en suivant sa courbe, et nomme l'instant :

```
bbox: [2046, 700] -> [2102, 756]   (viewport: 1920x1080)
hint: at t=1.70s (57% of scene), animation transforms (tx=1886, ty=0, …)
      push the bbox out of the viewport
```

C'est la raison de préférer `motion_path` à une position calculée à la main : une trajectoire écrite en dur dans des keyframes reste vérifiable, mais tu perds l'orientation automatique et la vitesse constante le long de la courbe.

## Recipe: the arc

The "arc motion path" effect — a dot that traces a curve while rotating to keep facing its trajectory — is a `motion_path` with a single cubic and `orient: true`. There's no `arc` shortcut: a cubic describes it exactly, and legibly.

```json
"animation": [{
  "name": "motion_path",
  "path": "M0,0 C260,-220 620,-220 880,0",
  "duration": 2.4,
  "orient": true,
  "easing": "ease_in_out"
}]
```

Both control points at the same height (`-220`) give a symmetric arc; pulling them closer to their respective endpoints flattens the middle and deepens the ends. For a **downward** arc, flip the sign: `C260,220 620,220 880,0`.

## Cas dégénérés

Un chemin vide ou impossible à parser est **rejeté au chargement**. Un chemin d'un seul point, ou de longueur nulle, tient la position avec une rotation nulle. Une `duration` négative ou nulle est rejetée par `validate`. Aucun de ces cas ne produit de `NaN`.