| name | element-handoff |
| description | motion-graphic primitive — a persistent element (chip, ball, object, logo) that translates, rotates, and recolors across scene boundaries via a sharedLayer that sees composition-global time. the cut between scenes is a hard bg-swap; the handoff is what makes the cuts read as one continuous idea rather than a slideshow. canonical to SP-A (marshmallow chip), SP-G (riso ball), SP-E (chaos sticker). |
element-handoff
Purpose
Implement a continuity element that survives scene boundaries. Unlike per-scene elements that unmount on scene end and remount on scene start (creating a visible flicker), an element-handoff element lives ABOVE the <Series> of scenes and reads composition-global frame, interpolating its properties smoothly across boundaries.
This is the formalization of the existing sharedLayers pattern in the Remotion atom — promoted to a first-class effect with its own atom so it can be discovered and reasoned about by the impl-brief atom.
Params (params.schema.json)
{
"id": "cta-chip",
"kind": "circle | rect | pill | image | svg-glyph",
"diameter": 160,
"width": 240,
"height": 96,
"fill": "#FF7BB6",
"icon": {
"glyph": "arrow-right",
"color": "#0F0F0F",
"size": 56
},
"image_src": "assets/ball.svg",
"keyframes": [
{
"atFrame": 0,
"x": 880,
"y": 1620,
"rotation": 0,
"scale": 1,
"opacity": 1,
"color": "#FF7BB6"
}
],
"rotation_easing": "bezier(0.22, 1, 0.36, 1)",
"color_easing": "linear",
"position_easing": "linear",
"z_order": 1000
}
Behaviour
The atom emits a React component that:
- Reads
useCurrentFrame() at composition level.
- For every channel (x, y, rotation, scale, opacity, color), finds the keyframe pair surrounding the current frame and interpolates per the channel's easing.
- Color interpolation uses RGB mix in the existing shared-layers-template.
- Renders the element with absolute positioning above all scene content.
The implementation lives at shared-layers-template.tsx in the Remotion atom — see skills/atoms/motion-graphics/create-motion-graphics-remotion/references/shared-layers-template.tsx. This effect atom is the spec; the template is the implementation. In Phase 5b they'll be merged.
Style pack overrides
SP-A (marshmallow)
- kind:
circle, diameter 160, fill primary, inner glyph arrow-right.
- Rotation snaps: 0° → 90° on scene 1→2 boundary, 90° → 180° on scene 2→3 boundary.
- Travel easing: spring damping 14, stiffness 200.
- Color: held at primary across all scenes (no recolor).
SP-E (chaos-card)
- kind:
image (varied per scene — could be a different sticker per beat).
- Always tilted ±5°.
- Travel easing: linear (chaos pack uses constant velocity).
SP-G (riso-illustration)
- kind:
image (illustrated object — ball, character).
- Travel on arc paths (not straight lines) — encoded as multi-keyframe segments.
- Often paired with a
hand-line-draw effect tracing the path.
SP-F (product-demo)
- Replaced by
aurora-blob-continuity which is a different mechanic. Don't use element-handoff in SP-F.
When to use
- Any scene boundary where the storyboard declares a continuity element.
- Whenever you want the cut between scenes to "carry forward" a visual identity (the chip is the brand mark in motion, the ball is the brand metaphor, etc.).
When NOT to use
- For elements that legitimately belong to one scene only (a headline, a card stack, a count-up). Those should be per-scene elements.
- In SP-F (use aurora-blob instead).
- In SP-D (continuous scroll has no scene boundaries to cross).
Quality Checks
- Every scene the element crosses has at least one keyframe at or near its boundary.
- No two consecutive keyframes share the same atFrame.
- Rotation channel uses non-linear easing (bezier or spring).
- Color keyframes (if any) interpolate through valid hex colors.
- The element's bounding box never enters the reserved text band at any frame (cross-check against
decoration_x_min).
Failure Modes
- Element teleports across a boundary. Add a travel keyframe between the idle keyframes. Default span: 20 frames (idle-end on scene N → travel-end on scene N+1).
- Element flickers at scene boundary. Likely caused by the layer mounting INSIDE a
<Sequence> instead of above the <Series>. The SharedLayers component must wrap the Series, not be a sibling inside it.
- Color interpolation produces muddy intermediate hex. Switch from RGB mix to OKLCH mix in the renderer, or interpolate through a designated intermediate color.
- Rotation interpolates the long way around. Constrain the keyframe delta to ≤180°, or split into multiple smaller rotations.