From 72575d37d596e5a8f0f9fc3c1615a7528c719d5e Mon Sep 17 00:00:00 2001 From: Neil Date: Wed, 13 May 2026 20:26:13 +0530 Subject: [PATCH] feat: add yellow mascot bum dance animation (#1603) Co-authored-by: Neel Mistry Co-authored-by: Claude Sonnet 4.6 --- remotion/src/Mascot/YellowMascotBumDance.tsx | 419 +++++++++++++++++++ remotion/src/Root.tsx | 11 + 2 files changed, 430 insertions(+) create mode 100644 remotion/src/Mascot/YellowMascotBumDance.tsx diff --git a/remotion/src/Mascot/YellowMascotBumDance.tsx b/remotion/src/Mascot/YellowMascotBumDance.tsx new file mode 100644 index 000000000..2c3065c03 --- /dev/null +++ b/remotion/src/Mascot/YellowMascotBumDance.tsx @@ -0,0 +1,419 @@ +import React from "react"; +import { + AbsoluteFill, + Easing, + interpolate, + useCurrentFrame, + useVideoConfig, +} from "remotion"; + +/** + * YellowMascotBumDance + * + * Sequence: + * 0–59 : Front idle (gentle bob + head drift) + * 60–89 : Turn-around transition — scaleX squishes front to 0 + * 90–119 : Turn-around transition — scaleX expands back from 0 + * 120–269: Back view dancing — energetic bob, side sway, bum jiggle + */ +export const YellowMascotBumDance: React.FC = () => { + const frame = useCurrentFrame(); + const { fps, width, height } = useVideoConfig(); + + const size = Math.min(width, height) * 0.85; + + // ── Turn-around scaleX ───────────────────────────────────────────────────── + // 1 → 0 over frames 60-90, then 0 → 1 over frames 90-120 + const easeIO = Easing.inOut(Easing.cubic); + const turnScale = interpolate( + frame, + [60, 68, 76], + [1, 0, 1], + { extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: easeIO } + ); + + const showBack = frame >= 68; + + // ── Body bob ─────────────────────────────────────────────────────────────── + const bobFreq = showBack ? 2.8 : 1.2; + const bobAmp = showBack ? 22 : 12; + const bob = Math.sin((frame / fps) * Math.PI * bobFreq) * bobAmp; + + // ── Side sway (back dancing only) ────────────────────────────────────────── + const sway = showBack + ? Math.sin((frame / fps) * Math.PI * 1.4) * 18 + : 0; + + // ── Front: head drift + squash ───────────────────────────────────────────── + const dotPhase = (frame / fps) * Math.PI; + const headDx = Math.sin(dotPhase * 0.7) * 6; + const headDy = Math.sin(dotPhase) * 9; + const press = Math.max(0, Math.sin(dotPhase)); + const headSqY = 1 - 0.08 * press; + const headSqX = 1 + 0.05 * press; + + // ── Front: arm sway ──────────────────────────────────────────────────────── + const leftSway = Math.sin((frame / fps) * Math.PI * 1.6) * 7; + const rightSway = Math.sin((frame / fps) * Math.PI * 1.3 + 0.5) * 5; + + // ── Back: bum jiggle (two cheeks, opposite phase) ────────────────────────── + // Right cheek centre ≈ (628, 769) after the matrix transform + // Left cheek centre ≈ (336, 761) + const bumPhase = ((frame - 120) / fps) * Math.PI * 5.0; + const rBumDy = Math.sin(bumPhase) * 16; + const rBumDx = Math.sin(bumPhase * 0.6) * 9; + const rBumRot = Math.sin(bumPhase) * 6; + const lBumDy = Math.sin(bumPhase + Math.PI) * 16; + const lBumDx = Math.sin(bumPhase * 0.6 + Math.PI) * 9; + const lBumRot = Math.sin(bumPhase + Math.PI) * 6; + + // ── Back: dancing arms + // Left arm: positive (clockwise) base keeps arm swinging outward-left and visible + // Right arm: negative (counter-clockwise) base keeps arm swinging outward-right and visible + const danceArmL = Math.sin((frame / fps) * Math.PI * 2.4) * 22 + 20; + const danceArmR = Math.sin((frame / fps) * Math.PI * 2.4 + Math.PI) * 22 - 20; + + return ( + + + + + + + + + {/* Body */} + + + + + + + + + + + + + + + + + + {/* Head */} + + + + + + + + + + + + + + + + + + {/* Neck shadows */} + + + + + + + + + + + + {/* Front: left arm */} + + + + + + + + + + + + + + + + + + {/* Front: right arm */} + + + + + + + + + + + + + + + + + + {/* Back: right bum cheek */} + + + + + + + + + + + + + + + + {/* Back: left bum cheek */} + + + + + + + + + + + + + + + + {/* Eye highlight filters */} + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* Cheek highlight filters */} + + + + + + + + + + + + + {/* Ground shadow */} + + + {/* Turn-around wrapper — scales around centre (500, 500) */} + + + {/* Bob + sway wrapper */} + + + {/* ════════════ FRONT VIEW (frames 0–89) ════════════ */} + {!showBack && ( + <> + {/* Head */} + + + + + {/* Body */} + + + + {/* Left arm — gentle idle sway */} + + + + + {/* Right arm — gentle idle sway */} + + + + + {/* Neck shadows */} + + + + + + + + {/* Left eye */} + + + + + + + + + {/* Right eye */} + + + + + + + + + + + + {/* Left cheek */} + + + + + + {/* Right cheek */} + + + + + + {/* Mouth */} + + + + )} + + {/* ════════════ BACK VIEW (frames 90+) ════════════ */} + {showBack && ( + <> + {/* Back arms drawn FIRST so body overlaps shoulder — looks back-attached */} + {/* Filters omitted: fixed filterUnits="userSpaceOnUse" bounds would clip rotated arms */} + {/* Left arm — clockwise base keeps arm swinging to the left, visible outside body */} + + + + + {/* Right arm — counter-clockwise base keeps arm swinging to the right, visible outside body */} + + + + + {/* Head (back — plain circle, no face) */} + + + {/* Body drawn on top of arms — covers shoulder join, arms look back-facing */} + + + + {/* Neck shadows */} + + + + + + + + {/* Right bum cheek — jiggle around centre ≈ (628, 769) */} + + + + + + + {/* Left bum cheek — jiggle (opposite phase) around centre ≈ (336, 761) */} + + + + + + + )} + + + {/* end bob+sway */} + + + {/* end turn wrapper */} + + + + ); +}; diff --git a/remotion/src/Root.tsx b/remotion/src/Root.tsx index b660c27cf..e573d2c84 100644 --- a/remotion/src/Root.tsx +++ b/remotion/src/Root.tsx @@ -46,6 +46,7 @@ import { BlackMascotWink } from "./Mascot/mascot-black-wink"; import { BlackMascotCelebrate } from "./Mascot/mascot-black-celebrate"; import { BlackMascotHatWithBag } from "./Mascot/mascot-black-hat-with-bag"; import { BlackMascotLaughing } from "./Mascot/mascot-black-laughing"; +import { YellowMascotBumDance } from "./Mascot/YellowMascotBumDance"; export const RemotionRoot: React.FC = () => { return ( @@ -383,6 +384,16 @@ export const RemotionRoot: React.FC = () => { height={1080} defaultProps={{}} /> + + ); };