| name | hand-animation-framework |
| description | Multi-layer hand gesture animation with physics, Lottie preloading, and click feedback. Use when adding hand interactions to videos, implementing gesture-based UI demos, or building interactive overlays. |
| user-invocable | false |
Hand Animation Framework
WHEN TO USE (Triggers)
- When adding hand/finger interactions to a video composition
- When implementing tap, swipe, drag, scroll, or click gestures
- When hand movement needs physics-based natural motion (float, tilt)
- When click feedback needs visual effect (burst, press animation)
- When multiple hand layers need independent gesture control
- When hand animation coordinates don't align with phone mockup position
FAILED ATTEMPTS
| # | Attempt | Why Failed | Lesson |
|---|
| 1 | Linear interpolation between waypoints | Movement looked robotic, no natural feel | Use ultraSmoothEasing (Bezier 0.25, 0.1, 0.25, 1) for natural flow |
| 2 | Single hand layer for all scenes | Can't have independent gestures (tap in one area, scroll in another) | Multi-layer: Primary (scene-synced) + Secondary (independent) |
| 3 | Click animation loaded on-demand (async) | Visible delay between tap and click effect (~200ms gap) | Pre-load BOTH hand and click Lottie animations in parallel |
| 4 | Calculated hand position relative to zoom wrapper | Coordinates broke when zoom/pan changed | Calculate in COMPOSITION space (1080x1920), never relative to zoom |
| 5 | Guessing coordinates without reference | Wrong position 3-4 times per scene, wasted hours | Use docs/coordinate-map.md — look up named UI elements |
| 6 | Adding hand gestures without audio | Every scene missing SFX, caught too late | Always write codedPaths + audio entries TOGETHER (rule #46) |
| 7 | Trailing pointer waypoint after last click | Hand sits doing nothing after final action | End path with the click gesture, no trailing pointer |
CORRECT PATTERN
Core Data Structures
interface HandPathPoint {
x: number;
y: number;
scale?: number;
duration?: number;
gesture?: 'pointer' | 'click' | 'drag' | 'scroll' | 'open';
rotation?: number;
}
interface HandPhysicsConfig {
smoothing: number;
velocityScale: number;
maxRotation: number;
floatAmplitude: number;
floatSpeed: number;
autoRotate?: boolean;
}
Animation Pipeline
1. Build timeline: HandPathPoint[] -> frame-indexed positions
2. Enforce click duration: Click gestures >= 45 frames (1.5s at 30fps)
3. Find segment: Binary search for current frame in timeline
4. Interpolate: ultraSmoothEasing on segment progress
5. Compute velocity: sin(progress * pi) peak velocity
6. Calculate rotation: physics-based tilt or auto-rotate
7. Scale pulse: Subtle size increase during peak velocity
8. Gesture transition: Click at 90% arrival, others at 30%
Click Animation (Pre-loaded)
const [handLottie, clickLottie] = await Promise.all([
loadAnimation(handPath),
loadAnimation(clickEffectPath),
]);
if (gesture === 'click' && segmentProgress > 0.9) {
activeAnimation = clickLottie;
}
Phone Coordinate Mapping
Phone frame center: (207, 434)
Composition center: (540, 960)
At zoom S: compX = 540 + S * (phoneX - 207)
compY = 960 + offsetY + S * (phoneY - 434)
EVIDENCE
| Metric | Value | Source |
|---|
| Compositions using framework | 8 (all active) | Production |
| Hand animation presets | 26+ (gestures + pointers + click effects) | galleryData.ts |
| Click feedback latency | 0ms (pre-loaded) | Was 200ms with on-demand |
| Coordinate accuracy | Pixel-perfect on all zoom levels | Tested at 1x, 1.8x, 2.76x |
QUICK START (< 5 minutes)
For codedPaths.ts entries (SceneDirector-integrated gestures):
- Read coordinate map (30 sec):
docs/coordinate-map.md — look up target element coordinates
- Fill gesture spec (1 min): Rule #46 template — ACTION, TARGET (x,y), FRAME, AUDIO for each interaction
- Present spec to user (30 sec): Get approval before writing code
- Write codedPaths + audio TOGETHER (2 min): Both entries in same edit session
- Validate (30 sec):
npm run validate:hands — auto-runs via hook on save
For inline FloatingHand (direct component usage):
- Define path (1 min): Array of HandPathPoint with x/y coordinates
- Choose physics (30 sec): DEFAULT_PHYSICS works for most cases
- Call hook (1 min):
useHandAnimation(path, startFrame, physics)
- Render (1 min): Use HandState for position/rotation/scale
- Add click (1.5 min): Pre-load click Lottie, swap at 90% arrival