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>
4.2 KiB
kit
Reusable plain-CSS/HTML building blocks that recreate the RoboCo control panel's look, for compositions that want to simulate the product rather than run a text card. Hand-rolled (no React, no Tailwind, no bundler) — this is a recreation of the panel's design language, not a port of its components. Load kit.css (and kit.js if you use the typing helper); no composition-level theme.css is needed, kit.css owns the reset + fonts.
<link rel="stylesheet" href="../../kit/kit.css" />
<script src="props.js"></script>
<script src="../../kit/kit.js"></script>
Every class is namespaced pk-. See compositions/panel-demo/ for a full worked example (task types in, a card appears, a cursor clicks it done, a toast confirms it).
Pieces
pk-frame— the app chrome: a slim sidebar (pk-frame__sidebar,pk-frame__navitem[--active]) and a top bar (pk-frame__topbar,pk-frame__search,pk-frame__statusfor the pulsing "Live" dot) around apk-frame__contentarea. Fixed sidebar/topbar sizing works at both 1080x1920 and 1080x1080 — only the content area's height changes.pk-column/pk-card— a kanban-ish list container (pk-column__header+ children) hosting task cards. A card ispk-card__title,pk-card__meta(chips +pk-card__assignee), andpk-card__status(a pill).pk-chip— a small team/priority tag:<span class="pk-chip pk-chip--purple">Frontend</span>. Variants:--purple,--blue,--green,--orange,--pink,--gray.pk-pill— a status pill:<span class="pk-pill pk-pill--progress">in progress</span>. Variants:--progress,--review,--approved,--completed. To swap one pill for another (e.g. progress -> completed), stack two pills at the same spot inside aposition: relativepk-card__statusand give the outgoing onepk-pill--swap-out(fades in then out) and the incoming onepk-pill--swap-in(fades in and stays), each with its own--pk-pill-enter-delay/--pk-pill-exit-delay(composition-absolute seconds — the same conventiondata-startuses).pk-toast— a notification card that slides in from a corner:pk-toast__icon+pk-toast__body(pk-toast__title,pk-toast__text). Position via--pk-toast-bottom(right-anchored).pk-type/pk-caret— character-by-character typing reveal. Write the target element's final text directly in the HTML, then callwindow.PanelKit.typeText(el, { delay, stagger })(fromkit.js) once, synchronously, before rendering starts — it splits the text intopk-type__charspans with staggeredanimation-delays (works with any font, no per-character width math).delayis the composition-absolute second the first character appears;staggerdefaults to 0.045s/char. Add a<span class="pk-caret"></span>next to it for the blinking caret.pk-cursor— a small CSS-shape cursor (pk-cursor__glyph). Preferred: give itdata-waypoints="t x y [click]; t x y; ..."(composition-absolute seconds, content-box px) and callPanelKit.choreographAllCursors()in the composition's inline script — kit.js generates a multi-leg eased path, a fade-in/out (never pop in or blink out), an idle-hand sway between legs, and a click ring + glyph press dip at every waypoint flaggedclick. The click should land ~0.2s BEFORE the thing it visually triggers. Legacy single-glide via--pk-cursor-x0/y0/x1/y1+--pk-cursor-delay/--pk-click-delaystill works for a one-move cameo.pk-camera— a full-stage wrapper driven bydata-shots="t x y scale; ..."+PanelKit.choreographAllCameras(): eased camera moves (push-ins toward the beat's focal point, pull-backs for reveals). Wrap the wholepk-framein one; everything inside, cursor included, rides the move. Keep it subtle —scale <= 1.08, translate <= ~160px — and end settled at identity.
Offline constraints (same as every composition)
No CDN, no npm runtime deps, no bundler — plain files only, loaded via relative paths (../../kit/... from a composition two levels down). Fonts are the vendored motion/public/fonts/*.woff2, loaded via @font-face in kit.css. Motion is CSS keyframes timed with animation-delay (seconds from frame 0 — no requestAnimationFrame, no Date.now()), so a render is frame-deterministic.