feat(motion): choreography engines + two renderer root causes fixed (#543)

The release cuts read as static screen recordings: one 6s cursor glide
that then vanished, a locked-off camera, metronomic flat card fades. And
two renderer defects were silently wrecking every cut:

- @hyperframes/producer floated (^0.7.36, no lockfile — image builds get
  whatever is latest): 0.7.60 fails EVERY render with "Cannot access 'rt'
  before initialization". Pinned 0.7.36 exact and committed
  package-lock.json so sidecar builds are reproducible.
- the producer's per-clip visibility scheduler runs on a clock that lags
  ~50% behind the encoded timeline on a 40s cut — the final frame showed
  the authored ~18s state, so the tail scenes (bell card, toast, outro)
  were silently missing from the MP4. This, not authoring, is why
  rendered cuts kept losing their late scenes. Fix: clip windows are for
  structural layers only (hero, panel frame, status); every beat rides
  base-hidden styles + delayed CSS animations, which run on the true
  clock. Verified frame-by-frame: all four cards, stats, toast, and
  outro now land exactly on schedule in both cuts.

Craft, made reusable in the kit instead of per-composition heroics:

- kit.js choreographCursor: data-waypoints="t x y [click]; ..." generates
  a multi-leg eased path with fade in/out, an idle-hand sway, and click
  rings + glyph press dips — the cursor behaves like a hand, never pops
  in, freezes, or blinks out
- kit.js choreographCamera: data-shots="t x y scale; ..." — push-ins
  toward each beat's focal point, pull-backs for reveals, settle to end
- release-0.25.0 both cuts re-choreographed: the cursor is the CEO's
  hand (settle on the intake while it types, ONE submit click, witness
  each card completing, acknowledge the toast, exit off-frame); the
  camera lives on every beat; springy card entrances replace flat fades
- motion/README.md gains 'Cinematography & rhythm' (shot-list-first, no
  locked-off camera, verify motion with frame PAIRS) + the clip-window
  rule; kit/README.md documents both engines; the dev video prompt block
  carries the craft bar

Verified end to end through the real sidecar: both cuts render green,
32-frame strips read visually — cursor travels and clicks on schedule,
camera moves, every scene present. motion pnpm test 15/15.

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
This commit is contained in:
Renzo F
2026-07-17 03:58:08 +02:00
committed by GitHub
co-authored by Renn F
parent e9ca7d4036
commit 3e801697f7
10 changed files with 3935 additions and 45 deletions
+10
View File
@@ -36,6 +36,16 @@ This composition is the library's reference point — match its restraint, don't
- **Layout** — anchor content asymmetrically (this clip sits in the lower two-thirds, left-aligned); avoid a perfectly centered card, which reads as a generic template rather than a designed frame.
- **AI tells to avoid** — no default AI-purple gradient wash, no centered-everything, no emoji as design elements, no one-font-one-size, no em dash in on-screen copy (voiceover script, highlight bullets, kicker text) or filler verbs ("Elevate", "Seamless", "Unleash", "Next-Gen").
## Cinematography & rhythm (demo register)
A panel-demo clip is a FILM of software being used, not a screenshot with captions. Before authoring, write a shot list: for every beat, where is the camera, where is the cursor, what changes on screen, and what caused it. Then build to it. The specific tells that get a cut bounced:
- **A locked-off camera.** Wrap the scene in `pk-camera` and drive it with `data-shots` (see `kit/README.md`): open slightly tight, push toward each beat's focal point (`scale <= 1.08`, translate <= ~160px — the audience should feel it, not see it), pull wide for a reveal, settle to end. A static frame for more than ~8s is dead air.
- **A cursor that doesn't behave like a hand.** Drive it with `data-waypoints`: it fades in, travels with eased legs, rests with an idle sway (kit adds this), clicks with a visible cause→effect (the click precedes the thing it triggers), and leaves the frame — it never pops in, freezes pixel-still, or blinks out mid-scene.
- **A metronome.** Identical beat lengths with identical flat entrances read as a slideshow. Vary entrance energy (the kit cards take a springy overshoot well), let a beat breathe after a click, and give the climax (stats/receipt) a different rhythm than the build.
- **Verify motion, not stills.** After `request_render`, sample PAIRS of frames ~0.5s apart around each cursor/camera beat and compare positions — a single frame proves presence, only a pair proves movement.
- **Clip windows are for structural layers only.** The renderer's per-clip visibility scheduler drifts badly behind the encoded timeline on long compositions (measured live: a 40s cut whose per-beat clips only reached the ~19s mark by the final frame — the entire tail silently missing from the MP4). Give `class="clip"` + `data-start`/`data-duration` only to full-length structural layers (the cold-open, the panel frame), and drive every BEAT inside them with the kit's pattern instead: base-hidden styles (`opacity: 0`) plus a delayed CSS animation (`animation-delay` + `forwards`/`both` fill) — those run on the correct clock. If a beat must also disappear, give it an exit animation, not a clip window.
## Panel-demo kit (`kit/`)
`kit/` is a second register alongside the release-announcement's text-card style: reusable `pk-`-namespaced CSS/HTML that recreates the control panel's look (dark chrome, task cards, status pills, toasts, a typing reveal, a cursor) so a composition can simulate the product actually being used, instead of announcing it over a headline. Use the **text-card register** (release-announcement's pattern) for version/feature announcements with no product visuals; use the **demo register** (`kit/`) whenever the story is "watch this happen in the app" — a task moving through the panel, a feature being triggered, an agent doing something visible.