diff --git a/remotion/src/Mascot/black-MascotCelebrate.tsx b/remotion/src/Mascot/black-MascotCelebrate.tsx new file mode 100644 index 000000000..8fc884417 --- /dev/null +++ b/remotion/src/Mascot/black-MascotCelebrate.tsx @@ -0,0 +1,356 @@ +import React from "react"; +import { + AbsoluteFill, + Easing, + interpolate, + useCurrentFrame, + useVideoConfig, +} from "remotion"; + +export const BlackMascotCelebrate: React.FC = () => { + const frame = useCurrentFrame(); + const { fps, width, height } = useVideoConfig(); + const p = (k: string) => `bmcl-${k}`; + + // ── Body bob — energetic 2 Hz bounce ──────────────────────────────────── + const bob = Math.sin((frame / fps) * Math.PI * 2.0) * 16; + + // ── 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 headSquashY = 1 - 0.09 * press; + const headSquashX = 1 + 0.06 * press; + + // ── Hat wobble ─────────────────────────────────────────────────────────── + const hatWobble = Math.sin((frame / fps) * Math.PI * 1.4) * 3.5; + + // ── Left arm — enthusiastic wave ───────────────────────────────────────── + const leftArmAngle = + -50 + Math.sin((frame / fps) * Math.PI * 3.0) * 18; + + // ── Right arm + horn — wave together ───────────────────────────────────── + const hornWave = Math.sin((frame / fps) * Math.PI * 2.5 + 0.5) * 8; + + // ── Confetti sparkle pulses ─────────────────────────────────────────────── + const sp = (phase: number) => + 0.62 + Math.sin((frame / fps) * Math.PI * 1.8 + phase) * 0.32; + + // ── Falling confetti particles ──────────────────────────────────────────── + const fallingDefs = [ + { delay: 0, x: 130, drift: 22, color: "#3C5FAD", w: 12, h: 8 }, + { delay: 8, x: 255, drift: -17, color: "#FBD387", w: 9, h: 9 }, + { delay: 3, x: 375, drift: 25, color: "#B1D37E", w: 11, h: 7 }, + { delay: 14, x: 495, drift: -20, color: "#929ED3", w: 8, h: 10 }, + { delay: 1, x: 630, drift: 18, color: "#F5A29A", w: 10, h: 8 }, + { delay: 17, x: 750, drift: -26, color: "#EDB371", w: 9, h: 11 }, + { delay: 6, x: 850, drift: 15, color: "#B2ACD2", w: 12, h: 7 }, + { delay: 11, x: 190, drift: 30, color: "#FDB1AF", w: 8, h: 9 }, + { delay: 2, x: 695, drift: -12, color: "#3C5FAD", w: 11, h: 8 }, + { delay: 19, x: 440, drift: 23, color: "#FBD387", w: 9, h: 10 }, + ]; + const fallPeriod = Math.round(fps * 2.2); + const getFall = (delay: number, startX: number, driftX: number) => { + if (frame < delay) return { x: startX, y: -80, rotation: 0, opacity: 0 }; + const c = (frame - delay) % fallPeriod; + const t = c / fallPeriod; + return { + x: startX + driftX * t + Math.sin(t * Math.PI * 5) * 13, + y: -80 + 1100 * t, + rotation: t * 540, + opacity: interpolate(t, [0, 0.04, 0.82, 1], [0, 1, 0.88, 0], { + extrapolateLeft: "clamp", extrapolateRight: "clamp", + }), + }; + }; + + const size = Math.min(width, height) * 0.82; + + return ( + + + + + + + + + {/* Body — from blackcelebrate.svg filter0 */} + + + + + + + + + + + + + + + + + + {/* Head circle — from blackcelebrate.svg filter1 */} + + + + + + + + + + + + + + + + + + {/* Neck shadows — filter2, filter3 */} + + + + + + + + + + + + {/* Left arm — from blackcelebrate.svg filter4 */} + + + + + + + + + + + + + + + + + + {/* Cheek highlights — filter5, filter6 */} + + + + + + + + + + + + {/* Raised right arm — from blackcelebrate.svg filter7 */} + + + + + + + + + + + + + + + + + + + {/* Falling confetti rain */} + {fallingDefs.map((cd, i) => { + const ft = getFall(cd.delay, cd.x, cd.drift); + return ( + + ); + })} + + {/* Ground shadow */} + + + {/* Everything bobs together */} + + + {/* Cone hat cluster — tracks head drift, wobbles at base */} + + + + + + + + + + + + + + + + + + + + {/* Body */} + + + {/* Left arm — waves enthusiastically */} + + + + + {/* Cone horn in hand + raised right arm — wave together */} + + {/* Horn cone */} + + + + + {/* Sparkle circles from horn */} + + + + + + + + + + + + {/* Horn base / tube connecting to arm */} + + {/* Raised right arm */} + + + + {/* Scattered party confetti pieces */} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* Head group: drift + squash */} + + + {/* Neck shadows */} + + + + + + + + {/* Head circle */} + + + {/* Happy ^^ eyes */} + + + + {/* Left cheek */} + + + + + + {/* Right cheek */} + + + + + + {/* Open mouth + tongue */} + + + + + {/* end head group */} + + + {/* end bob group */} + + + + ); +}; diff --git a/remotion/src/Mascot/black-MascotCrying.tsx b/remotion/src/Mascot/black-MascotCrying.tsx new file mode 100644 index 000000000..20b5aa246 --- /dev/null +++ b/remotion/src/Mascot/black-MascotCrying.tsx @@ -0,0 +1,364 @@ +import React from "react"; +import { + AbsoluteFill, + Easing, + interpolate, + useCurrentFrame, + useVideoConfig, +} from "remotion"; + +export const BlackMascotCrying: React.FC = () => { + const frame = useCurrentFrame(); + const { fps, width, height } = useVideoConfig(); + const p = (k: string) => `bmcry-${k}`; + + // ── Cry transition ───────────────────────────────────────────────────────── + const cryProgress = interpolate(frame, [60, 90], [0, 1], { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + easing: Easing.inOut(Easing.cubic), + }); + const normalFaceOpacity = 1 - cryProgress; + const cryFaceOpacity = cryProgress; + + // ── Body bob — calm idle blends into faster sob shudder ─────────────────── + const idleBob = Math.sin((frame / fps) * Math.PI * 1.2) * 14; + const sobBob = + Math.sin((frame / fps) * Math.PI * 2.8) * 10 + + Math.sin((frame / fps) * Math.PI * 5.5 + 0.7) * 3; + const bob = idleBob * (1 - cryProgress) + sobBob * cryProgress; + + // ── Head drift + squash ─────────────────────────────────────────────────── + const dotPhase = (frame / fps) * Math.PI; + const driftScale = 1 - cryProgress * 0.65; + const headDx = Math.sin(dotPhase * 0.7) * 6 * driftScale; + const headDy = Math.sin(dotPhase) * 9 * driftScale; + const press = Math.max(0, Math.sin(dotPhase)) * driftScale; + const headSquashY = 1 - 0.08 * press; + const headSquashX = 1 + 0.05 * press; + + // ── Arms — gentle idle sway, droop down when crying ────────────────────── + const leftSway = Math.sin((frame / fps) * Math.PI * 1.3) * 7; + const rightSway = Math.sin((frame / fps) * Math.PI * 1.3 + 1.0) * 6; + const leftArmAngle = leftSway + cryProgress * 14; + const rightArmAngle = rightSway + cryProgress * 14; + + // ── Blink — only during idle phase ─────────────────────────────────────── + const blinkPeriod = Math.round(fps * 2.6); + const blinkOffset = Math.round(blinkPeriod / 2); + const inBlink = cryProgress < 0.15 && (frame + blinkOffset) % blinkPeriod < 6; + const eyeScaleNormal = inBlink ? 0.12 : 1; + + // ── Cheeks — flush more as crying intensifies ───────────────────────────── + const cheekOpacity = + 0.82 + cryProgress * 0.16 + Math.sin((frame / fps) * Math.PI * 1.1) * 0.05; + + // ── Tears ───────────────────────────────────────────────────────────────── + const tearPeriod = Math.round(fps * 1.6); + const getTear = (delayFrames: number, eyeX: number, eyeStartY: number) => { + const startAt = 90 + delayFrames; + if (frame < startAt) return { x: eyeX, y: eyeStartY, opacity: 0 }; + const cycleFrame = (frame - startAt) % tearPeriod; + const t = cycleFrame / tearPeriod; + const y = eyeStartY + t * 170; + const opacity = + interpolate(t, [0, 0.07, 0.68, 1.0], [0, 0.9, 0.75, 0], { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + }) * cryProgress; + return { x: eyeX, y, opacity }; + }; + + const tL1 = getTear(0, 395, 485); + const tL2 = getTear(Math.round(fps * 0.55), 408, 485); + const tR1 = getTear(Math.round(fps * 0.22), 592, 485); + const tR2 = getTear(Math.round(fps * 0.80), 603, 485); + + const size = Math.min(width, height) * 0.82; + + return ( + + + + {/* Ground shadow */} + + + + + + {/* Body */} + + + + + + + + + + + + + + + + + + {/* Head circle */} + + + + + + + + + + + + + + + + + + {/* Neck shadows */} + + + + + + + + + + + + {/* Left arm */} + + + + + + + + + + + + + + + + + + {/* Right arm */} + + + + + + + + + + + + + + + + + + {/* Normal left eye highlights */} + + + + + + + + + + + + {/* Normal right eye highlights */} + + + + + + + + + + + + + + + + + {/* Cheek highlights */} + + + + + + + + + + + + + {/* Ground shadow */} + + + {/* Everything bobs together */} + + + {/* Body */} + + + {/* Left arm */} + + + + + {/* Right arm */} + + + + + {/* Tears */} + {[tL1, tL2, tR1, tR2].map((t, i) => ( + + + + ))} + + {/* Head group: drift + squash */} + + + {/* Neck shadows */} + + + + + + + + {/* Head circle */} + + + {/* Normal eyes */} + + {/* Left eye */} + + + + + + + + + + + {/* Right eye */} + + + + + + + + + + + + + + + {/* Crying eyes */} + + + + + + {/* Cheeks */} + + + + + + + + + + + + + + {/* Normal smile */} + + + + + + {/* Sad frown */} + + + + + + + {/* end head group */} + + + {/* end bob group */} + + + + ); +}; diff --git a/remotion/src/Mascot/black-MascotHatWithBag.tsx b/remotion/src/Mascot/black-MascotHatWithBag.tsx new file mode 100644 index 000000000..27f5b50e8 --- /dev/null +++ b/remotion/src/Mascot/black-MascotHatWithBag.tsx @@ -0,0 +1,347 @@ +import React from "react"; +import { + AbsoluteFill, + interpolate, + useCurrentFrame, + useVideoConfig, +} from "remotion"; + +export const BlackMascotHatWithBag: React.FC = () => { + const frame = useCurrentFrame(); + const { fps, width, height } = useVideoConfig(); + const p = (k: string) => `bmhb-${k}`; + + // ── Body bob ───────────────────────────────────────────────────────────── + const bob = Math.sin((frame / fps) * Math.PI * 1.2) * 10; + + // ── Head drift + squash ───────────────────────────────────────────────── + const dotPhase = (frame / fps) * Math.PI; + const headDx = Math.sin(dotPhase * 0.7) * 5; + const headDy = Math.sin(dotPhase) * 7; + const press = Math.max(0, Math.sin(dotPhase)); + const headSquashY = 1 - 0.08 * press; + const headSquashX = 1 + 0.05 * press; + + // ── Left arm — gentle idle sway ───────────────────────────────────────── + const leftArmAngle = Math.sin((frame / fps) * Math.PI * 0.8) * 8; + + // ── Right arm — opposite phase ────────────────────────────────────────── + const rightArmAngle = Math.sin((frame / fps) * Math.PI * 0.8 + Math.PI) * 8; + + // ── Bag pendulum ──────────────────────────────────────────────────────── + const bagSwing = Math.sin((frame / fps) * Math.PI * 1.0 + 0.45) * 3; + + // ── Blink ─────────────────────────────────────────────────────────────── + const blinkPeriod = Math.round(fps * 3.5); + const blinkDur = Math.round(fps * 0.13); + const blinkPhase = frame % blinkPeriod; + const eyeScaleY = + blinkPhase < blinkDur + ? interpolate( + blinkPhase, + [0, blinkDur * 0.35, blinkDur * 0.65, blinkDur], + [1, 0.06, 0.06, 1], + { extrapolateLeft: "clamp", extrapolateRight: "clamp" } + ) + : 1; + + const size = Math.min(width, height) * 0.82; + + return ( + + + + + + + + + {/* Body — from blackhatwithbag.svg filter0 */} + + + + + + + + + + + + + + + + + + {/* Head circle — from blackhatwithbag.svg filter1 */} + + + + + + + + + + + + + + + + + + {/* Neck shadows — filter2, filter3 */} + + + + + + + + + + + + {/* Left arm — from blackhatwithbag.svg filter4 */} + + + + + + + + + + + + + + + + + + {/* Left eye highlights — filter5, filter6 */} + + + + + + + + + + + + {/* Right eye highlights — filter7, filter8, filter9 */} + + + + + + + + + + + + + + + + + {/* Cheek highlights — filter10, filter11 */} + + + + + + + + + + + + {/* Right arm — from blackhatwithbag.svg filter12 */} + + + + + + + + + + + + + + + + + + {/* Hat shadow — filter13 */} + + + + + + + {/* Hat buckle details — filter14, filter15 */} + + + + + + + + + + + + + {/* Ground shadow */} + + + {/* Everything bobs */} + + + {/* Body */} + + + {/* Left arm */} + + + + + {/* Bag — gentle pendulum sway */} + + {/* Bag strap */} + + {/* Main bag body */} + + {/* Bag shadow */} + + {/* Bag clasp */} + + + + + + {/* Bag buckle dots */} + + + + + {/* Right arm */} + + + + + {/* Head group: drift (hat outside squash so it isn't distorted) */} + + + {/* Hat cluster — moves with head drift */} + + + + + + + + + + + + + + + {/* Head content: squash/stretch */} + + + {/* Neck shadows */} + + + + + + + + {/* Head circle */} + + + {/* Left eye */} + + + + + + + + + + + {/* Right eye */} + + + + + + + + + + + + + + {/* Left cheek */} + + + + + + {/* Right cheek */} + + + + + + {/* Smirk mouth */} + + + + + {/* end squash group */} + + {/* end head drift group */} + + + {/* end bob group */} + + + + ); +}; diff --git a/remotion/src/Mascot/black-MascotIdle.tsx b/remotion/src/Mascot/black-MascotIdle.tsx new file mode 100644 index 000000000..ffcda42f2 --- /dev/null +++ b/remotion/src/Mascot/black-MascotIdle.tsx @@ -0,0 +1,286 @@ +import React from "react"; +import { AbsoluteFill, useCurrentFrame, useVideoConfig } from "remotion"; + +/** + * Black idle mascot — uses exact paths and filters from BlackIdelmascot.svg + * with the same bob, head-drift, arm-sway, and blink animations as the yellow idle. + */ +export const BlackMascotIdle: React.FC = () => { + const frame = useCurrentFrame(); + const { fps, width, height } = useVideoConfig(); + + // Gentle bob for the whole character. + const bob = Math.sin((frame / fps) * Math.PI * 1.2) * 14; + + // Head dot drifts independently and squashes when pressing into the body. + const dotPhase = (frame / fps) * Math.PI * 1.0; + const dotDx = Math.sin(dotPhase * 0.7) * 6; + const dotDy = Math.sin(dotPhase) * 9; + const press = Math.max(0, Math.sin(dotPhase)); + const dotSquashY = 1 - 0.08 * press; + const dotSquashX = 1 + 0.05 * press; + + // Left arm gentle sway. + const leftSway = Math.sin((frame / fps) * Math.PI * 1.6) * 7; + // Steady right arm sway — mirrors left arm with slight phase offset. + const steadySway = Math.sin((frame / fps) * Math.PI * 1.6 + 0.3) * 6; + + // Blink every ~2.6s for ~6 frames. + const blinkPeriod = Math.round(fps * 2.6); + const blinkOffset = Math.round(blinkPeriod / 2); + const inBlink = (frame + blinkOffset) % blinkPeriod < 6; + const eyeScale = inBlink ? 0.12 : 1; + + const size = Math.min(width, height) * 0.85; + + return ( + + + + {/* Ground shadow gradient */} + + + + + + {/* Body filter — from BlackIdelmascot.svg filter0_iig */} + + + + + + + + + + + + + + + + + + + + + {/* Head filter — from BlackIdelmascot.svg filter1_iig */} + + + + + + + + + + + + + + + + + + + + + {/* Neck shadow filters */} + + + + + + + + + + + + {/* Left arm filter — from BlackIdelmascot.svg filter4_iig */} + + + + + + + + + + + + + + + + + + + + + {/* Right steady arm filter — from BlackIdelmascot.svg filter5_iig */} + + + + + + + + + + + + + + + + + + + + + {/* Left eye highlight filters */} + + + + + + + + + + + + {/* Right eye highlight filters */} + + + + + + + + + + + + + + + + + {/* Cheek highlight filters */} + + + + + + + + + + + + + {/* Ground shadow */} + + + + + {/* Everything bobs together */} + + + {/* Head — drifts + squashes independently */} + + + + + + + {/* Body */} + + + + + {/* Right steady arm — gentle sway */} + + + + + + + {/* Left arm — gentle sway */} + + + + + + + {/* Neck shadows */} + + + + + + + + {/* Left eye — scaleY collapses on blink */} + + + {!inBlink && ( + <> + + + + + + + + )} + + + {/* Right eye — scaleY collapses on blink */} + + + {!inBlink && ( + <> + + + + + + + + + + + )} + + + {/* Left cheek */} + + + + + + {/* Right cheek */} + + + + + + {/* Mouth */} + + + + + + ); +}; diff --git a/remotion/src/Mascot/black-MascotLaughing.tsx b/remotion/src/Mascot/black-MascotLaughing.tsx new file mode 100644 index 000000000..3c5f568ec --- /dev/null +++ b/remotion/src/Mascot/black-MascotLaughing.tsx @@ -0,0 +1,236 @@ +import React from "react"; +import { + AbsoluteFill, + useCurrentFrame, + useVideoConfig, +} from "remotion"; + +/** + * BlackMascotLaughing — black variant of YellowMascotLaughing. + * + * Both arms wave out-of-phase with laughter. + * Body bounces rapidly + shakes horizontally. + * Head tilts side-to-side. + * Happy ^^ eyes, open mouth + tongue. + * Filter matrices from BlackIdelmascot.svg. + */ +export const BlackMascotLaughing: React.FC = () => { + const frame = useCurrentFrame(); + const { fps, width, height } = useVideoConfig(); + const p = (k: string) => `bmla-${k}`; + + // ── Body bounce — rapid 3 Hz laughter bounce ──────────────────────────── + const bob = Math.sin((frame / fps) * Math.PI * 3.0) * 18; + + // ── Horizontal body wobble — small 5 Hz side shake ────────────────────── + const wobble = Math.sin((frame / fps) * Math.PI * 5.0) * 5; + + // ── Head drift + squash ───────────────────────────────────────────────── + const dotPhase = (frame / fps) * Math.PI; + const headDx = Math.sin(dotPhase * 2.5) * 8 + wobble * 0.5; + const headDy = Math.sin(dotPhase * 3.0) * 9; + const press = Math.max(0, Math.sin(dotPhase * 3.0)); + const headSquashY = 1 - 0.1 * press; + const headSquashX = 1 + 0.07 * press; + + // ── Head tilt — side-to-side laugh ────────────────────────────────────── + const headTilt = Math.sin((frame / fps) * Math.PI * 2.0) * 9; + + // ── Both arms shake with laughter (opposite phases) ───────────────────── + const leftArmAngle = -15 + Math.sin((frame / fps) * Math.PI * 3.5) * 25; + const rightArmAngle = 15 + Math.sin((frame / fps) * Math.PI * 3.5 + 1.1) * 25; + + const size = Math.min(width, height) * 0.82; + + return ( + + + + + + + + + {/* Body */} + + + + + + + + + + + + + + + + + + {/* Head circle */} + + + + + + + + + + + + + + + + + + {/* Neck shadows */} + + + + + + + + + + + + {/* Left arm */} + + + + + + + + + + + + + + + + + + {/* Right arm */} + + + + + + + + + + + + + + + + + + {/* Cheek highlights */} + + + + + + + + + + + + + {/* Ground shadow */} + + + {/* ── Everything bobs + wobbles ─────────────────────────────────────── */} + + + {/* ── Body ────────────────────────────────────────────────────────── */} + + + {/* ── Left arm — shakes up with laughter ─────────────────────────── */} + + + + + {/* ── Right arm — shakes up with laughter (opposite phase) ─────────── */} + + + + + {/* ── Head group: drift + tilt + squash ───────────────────────────── */} + + + + {/* Neck shadows */} + + + + + + + + {/* Head circle */} + + + {/* Happy ^^ eyes */} + + + + {/* Left cheek */} + + + + + + {/* Right cheek */} + + + + + + {/* Open mouth + tongue */} + + + + + + {/* end head group */} + + + {/* end bob group */} + + + + ); +}; diff --git a/remotion/src/Mascot/black-MascotListening.tsx b/remotion/src/Mascot/black-MascotListening.tsx new file mode 100644 index 000000000..fcc7afb86 --- /dev/null +++ b/remotion/src/Mascot/black-MascotListening.tsx @@ -0,0 +1,306 @@ +import React from "react"; +import { AbsoluteFill, useCurrentFrame, useVideoConfig } from "remotion"; + +/** + * Black listening mascot — uses exact paths and filters from BlackIdelmascot.svg. + * Replicates yellow-MascotListening: + * • Prominent head tilt toward the raised side (primary listening cue) + * • Right arm held outward with gentle continuous sway + * • Left arm gentle idle sway + * • Slow body bob (calm, attentive breathing) + * • Slow blink (focused/listening) + * • Cheek warmth pulse + */ +export const BlackMascotListening: React.FC = () => { + const frame = useCurrentFrame(); + const { fps, width, height } = useVideoConfig(); + const p = (k: string) => `bmli-${k}`; + + // Body bob — slow, calm breathing rhythm. + const bob = Math.sin((frame / fps) * Math.PI * 0.9) * 9; + + // Head tilt — prominent attentive listening tilt to the right. + const headTiltBase = 11; + const headTiltOscillate = Math.sin((frame / fps) * Math.PI * 0.35) * 3; + const headTilt = headTiltBase + headTiltOscillate; + + // Subtle head nod while tilted. + const headNodY = Math.sin((frame / fps) * Math.PI * 0.55) * 6; + const headNodX = Math.sin((frame / fps) * Math.PI * 0.28) * 3; + + // Left arm — gentle idle sway. + const leftSway = Math.sin((frame / fps) * Math.PI * 1.1) * 6; + + // Right arm — held outward, gentle continuous sway. + const armSway = Math.sin((frame / fps) * Math.PI * 0.75) * 5; + const rightArmAngle = -62 + armSway; + + // Blink — slower, focused (attentive listener). + const blinkPeriod = Math.round(fps * 3.5); + const blinkOffset = Math.round(blinkPeriod * 0.3); + const inBlink = (frame + blinkOffset) % blinkPeriod < 5; + const eyeScale = inBlink ? 0.1 : 1; + + // Cheek warmth pulse. + const cheekOpacity = 0.78 + Math.sin((frame / fps) * Math.PI * 0.85 + 0.6) * 0.09; + + const size = Math.min(width, height) * 0.82; + + // Head tilt pivot: bottom of head circle = neck joint. + const headPivotX = 493; + const headPivotY = 255; // cy(145) + r(110) + + return ( + + + + {/* Ground shadow */} + + + + + + {/* Body filter — from BlackIdelmascot.svg filter0_iig */} + + + + + + + + + + + + + + + + + + {/* Head filter — from BlackIdelmascot.svg filter1_iig */} + + + + + + + + + + + + + + + + + + {/* Neck shadow filters */} + + + + + + + + + + + + {/* Left arm filter — from BlackIdelmascot.svg filter4_iig */} + + + + + + + + + + + + + + + + + + {/* Right arm filter — from BlackIdelmascot.svg filter5_iig */} + + + + + + + + + + + + + + + + + + {/* Left eye highlight filters */} + + + + + + + + + + + + {/* Right eye highlight filters */} + + + + + + + + + + + + + + + + + {/* Cheek highlight filters */} + + + + + + + + + + + + + {/* Ground shadow */} + + + {/* Everything bobs together */} + + + {/* Body */} + + + {/* Left arm — gentle idle sway */} + + + + + {/* Right arm — held outward with gentle sway */} + + + + + {/* Head group — everything tilts together */} + + + {/* Neck shadows */} + + + + + + + + {/* Head circle */} + + + {/* Left eye */} + + + {!inBlink && ( + <> + + + + + + + + )} + + + {/* Right eye */} + + + {!inBlink && ( + <> + + + + + + + + + + + )} + + + {/* Left cheek */} + + + + + + + + {/* Right cheek */} + + + + + + + + {/* Mouth — closed attentive smile */} + + + + + {/* end head group */} + + + {/* end bob group */} + + + + ); +}; diff --git a/remotion/src/Mascot/black-MascotLoading.tsx b/remotion/src/Mascot/black-MascotLoading.tsx new file mode 100644 index 000000000..80c3ca410 --- /dev/null +++ b/remotion/src/Mascot/black-MascotLoading.tsx @@ -0,0 +1,197 @@ +import React from "react"; +import { AbsoluteFill, useCurrentFrame, useVideoConfig } from "remotion"; +import { LoadingFace } from "../Ghosty/lib/LoadingFace"; + +/** + * Black loading mascot — uses exact paths and filters from BlackIdelmascot.svg + * with the same bob, head-drift, arm-sway animations as the black idle, + * but replaces the face with a spinning loading ring. + */ +export const BlackMascotLoading: React.FC = () => { + const frame = useCurrentFrame(); + const { fps, width, height } = useVideoConfig(); + + // Gentle bob for the whole character. + const bob = Math.sin((frame / fps) * Math.PI * 1.2) * 14; + + // Head dot drifts independently and squashes when pressing into the body. + const dotPhase = (frame / fps) * Math.PI * 1.0; + const dotDx = Math.sin(dotPhase * 0.7) * 6; + const dotDy = Math.sin(dotPhase) * 9; + const press = Math.max(0, Math.sin(dotPhase)); + const dotSquashY = 1 - 0.08 * press; + const dotSquashX = 1 + 0.05 * press; + + // Left arm gentle sway. + const leftSway = Math.sin((frame / fps) * Math.PI * 1.6) * 7; + // Steady right arm sway — mirrors left arm with slight phase offset. + const steadySway = Math.sin((frame / fps) * Math.PI * 1.6 + 0.3) * 6; + + const size = Math.min(width, height) * 0.85; + + return ( + + + + {/* Ground shadow gradient */} + + + + + + {/* Body filter — from BlackIdelmascot.svg filter0_iig */} + + + + + + + + + + + + + + + + + + + + + {/* Head filter — from BlackIdelmascot.svg filter1_iig */} + + + + + + + + + + + + + + + + + + + + + {/* Neck shadow filters */} + + + + + + + + + + + + {/* Left arm filter — from BlackIdelmascot.svg filter4_iig */} + + + + + + + + + + + + + + + + + + + + + {/* Right steady arm filter — from BlackIdelmascot.svg filter5_iig */} + + + + + + + + + + + + + + + + + + + + + + {/* Ground shadow */} + + + + + {/* Everything bobs together */} + + + {/* Head — drifts + squashes independently */} + + + + + + + {/* Body */} + + + + + {/* Right steady arm — gentle sway */} + + + + + + + {/* Left arm — gentle sway */} + + + + + + + {/* Neck shadows */} + + + + + + + + {/* Loading face — spinning ring, same center/scale as recording dot (495, 495, 70%). */} + + + + + + + ); +}; diff --git a/remotion/src/Mascot/black-MascotLove.tsx b/remotion/src/Mascot/black-MascotLove.tsx new file mode 100644 index 000000000..2d751a866 --- /dev/null +++ b/remotion/src/Mascot/black-MascotLove.tsx @@ -0,0 +1,370 @@ +import React from "react"; +import { AbsoluteFill, Easing, interpolate, useCurrentFrame, useVideoConfig } from "remotion"; + +/** + * Black love mascot — uses exact paths and filters from BlackIdelmascot.svg. + * Replicates yellow-MascotLove: + * 0 – 89 : normal idle (bob, head drift, arm sway, blink) + * 90 – 120 : heart eyes fade IN + * 120 – 210: heart eyes pulse, cheeks flush, mini hearts float up + * 210 – 240: heart eyes fade OUT + * 240 – 270: normal idle again → clean loop + */ +export const BlackMascotLove: React.FC = () => { + const frame = useCurrentFrame(); + const { fps, width, height } = useVideoConfig(); + const p = (k: string) => `bmlv-${k}`; + + // Heart transition. + const heartProgress = interpolate( + frame, + [90, 120, 210, 240], + [0, 1, 1, 0], + { extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: Easing.inOut(Easing.cubic) } + ); + const normalEyeOpacity = 1 - heartProgress; + const heartEyeOpacity = heartProgress; + + // Heart pulse: 2 beats/s, amplitude grows with heartProgress. + const heartBeat = 1 + Math.sin((frame / fps) * Math.PI * 4) * 0.09 * heartProgress; + + // Body bob. + const bob = Math.sin((frame / fps) * Math.PI * 1.2) * 14; + + // 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.07 * press; + const headSqX = 1 + 0.04 * press; + + // Arms — gentle idle sway. + const leftSway = Math.sin((frame / fps) * Math.PI * 1.3) * 7; + const rightSway = Math.sin((frame / fps) * Math.PI * 1.3 + 1.0) * 6; + + // Blink — only during normal eye phase. + const blinkPeriod = Math.round(fps * 2.8); + const blinkOffset = Math.round(blinkPeriod / 2); + const inBlink = heartProgress < 0.1 && (frame + blinkOffset) % blinkPeriod < 5; + const eyeScaleNormal = inBlink ? 0.1 : 1; + + // Cheek — flushes more during heart phase. + const cheekOpacity = + 0.82 + heartProgress * 0.15 + + Math.sin((frame / fps) * Math.PI * 1.1 + 1.0) * 0.06; + + // Floating mini hearts. + const floatHeart = (startF: number, x: number, baseY: number, sz: number) => { + const prog = interpolate(frame, [startF, startF + 48], [0, 1], { + extrapolateLeft: "clamp", extrapolateRight: "clamp", + }); + const y = baseY - 72 * prog + bob; + const op = interpolate( + frame, + [startF, startF + 6, startF + 38, startF + 48], + [0, 0.9, 0.9, 0], + { extrapolateLeft: "clamp", extrapolateRight: "clamp" } + ) * heartProgress; + const sc = sz * interpolate(frame, [startF, startF + 8], [0.5, 1], { + extrapolateLeft: "clamp", extrapolateRight: "clamp", + }); + return { x, y, op, sc }; + }; + const hA = floatHeart(124, 415, 388, 0.9); + const hB = floatHeart(152, 568, 382, 0.8); + const hC = floatHeart(176, 490, 358, 1.0); + + const size = Math.min(width, height) * 0.82; + + // Heart-eye SVG → idelMascot coordinate shift (same as yellow). + const HX = 113.386; + const HY = 31; + + return ( + + + + {/* Ground shadow */} + + + + + + {/* Body filter — from BlackIdelmascot.svg filter0_iig */} + + + + + + + + + + + + + + + + + + {/* Head circle filter — from BlackIdelmascot.svg filter1_iig */} + + + + + + + + + + + + + + + + + + {/* Neck shadow filters */} + + + + + + + + + + + + {/* Left arm filter — from BlackIdelmascot.svg filter4_iig */} + + + + + + + + + + + + + + + + + + {/* Right arm filter — from BlackIdelmascot.svg filter5_iig */} + + + + + + + + + + + + + + + + + + {/* Normal left eye highlights */} + + + + + + + + + + + + {/* Normal right eye highlights */} + + + + + + + + + + + + + + + + + {/* Cheek highlight filters */} + + + + + + + + + + + + {/* Pink glow blur (behind heart eyes) */} + + + + + + {/* Ground shadow */} + + + {/* Floating mini hearts (bob-synced, gated by heartProgress) */} + {[hA, hB, hC].map((h, i) => ( + + + + ))} + + {/* Everything bobs together */} + + + {/* Body */} + + + {/* Left arm */} + + + + + {/* Right arm */} + + + + + {/* Head group: drift + squash */} + + + {/* Neck shadows */} + + + + + + + + {/* Head circle */} + + + {/* Normal round eyes (fade out as heart phase begins) */} + + {/* Left eye */} + + + + + + + + + + + {/* Right eye */} + + + + + + + + + + + + + + + {/* Heart eyes (fade in, pulse with love) */} + + {/* Soft pink glow behind the hearts */} + + + + {/* Left heart eye */} + + + + + + + + {/* Right heart eye */} + + + + + + + + {/* Cheeks (flush during heart phase) */} + + + + + + + + + + + + + + {/* Mouth — closed content smile */} + + + + + {/* end head group */} + + + {/* end bob group */} + + + + ); +}; diff --git a/remotion/src/Mascot/black-MascotPickup.tsx b/remotion/src/Mascot/black-MascotPickup.tsx new file mode 100644 index 000000000..965cdd440 --- /dev/null +++ b/remotion/src/Mascot/black-MascotPickup.tsx @@ -0,0 +1,310 @@ +import React from "react"; +import { AbsoluteFill, interpolate, useCurrentFrame, useVideoConfig } from "remotion"; + +/** + * Black pickup mascot — uses exact paths and filters from BlackIdelmascot.svg + * with the same bouncy squash-and-stretch animation as yellow-MascotPickup, + * plus the idle bob, head-drift, arm-sway, and blink. + */ +export const BlackMascotPickup: React.FC = () => { + const frame = useCurrentFrame(); + const { fps, width, height } = useVideoConfig(); + const t = frame / fps; + + // Three bounces with decreasing squash + a small upward hop each peak. + const times = [0, 0.18, 0.36, 0.54, 0.72, 0.90, 1.08, 4.0]; + const sx = interpolate(t, times, [1, 1.18, 1, 1.12, 1, 1.06, 1, 1], { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + }); + const sy = interpolate(t, times, [1, 0.74, 1, 0.82, 1, 0.91, 1, 1], { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + }); + const ly = interpolate(t, times, [0, 0, -90, 0, -50, 0, -20, 0], { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + }); + + // Gentle bob for the whole character. + const bob = Math.sin((frame / fps) * Math.PI * 1.2) * 14; + + // Head dot drifts independently and squashes when pressing into the body. + const dotPhase = (frame / fps) * Math.PI * 1.0; + const dotDx = Math.sin(dotPhase * 0.7) * 6; + const dotDy = Math.sin(dotPhase) * 9; + const press = Math.max(0, Math.sin(dotPhase)); + const dotSquashY = 1 - 0.08 * press; + const dotSquashX = 1 + 0.05 * press; + + // Left arm gentle sway. + const leftSway = Math.sin((frame / fps) * Math.PI * 1.6) * 7; + // Steady right arm sway — mirrors left arm with slight phase offset. + const steadySway = Math.sin((frame / fps) * Math.PI * 1.6 + 0.3) * 6; + + // Blink every ~2.6s for ~6 frames. + const blinkPeriod = Math.round(fps * 2.6); + const blinkOffset = Math.round(blinkPeriod / 2); + const inBlink = (frame + blinkOffset) % blinkPeriod < 6; + const eyeScale = inBlink ? 0.12 : 1; + + const size = Math.min(width, height) * 0.85; + + return ( + + + + + {/* Ground shadow gradient */} + + + + + + {/* Body filter — from BlackIdelmascot.svg filter0_iig */} + + + + + + + + + + + + + + + + + + + + + {/* Head filter — from BlackIdelmascot.svg filter1_iig */} + + + + + + + + + + + + + + + + + + + + + {/* Neck shadow filters */} + + + + + + + + + + + + {/* Left arm filter — from BlackIdelmascot.svg filter4_iig */} + + + + + + + + + + + + + + + + + + + + + {/* Right steady arm filter — from BlackIdelmascot.svg filter5_iig */} + + + + + + + + + + + + + + + + + + + + + {/* Left eye highlight filters */} + + + + + + + + + + + + {/* Right eye highlight filters */} + + + + + + + + + + + + + + + + + {/* Cheek highlight filters */} + + + + + + + + + + + + + {/* Ground shadow */} + + + + + {/* Everything bobs together */} + + + {/* Head — drifts + squashes independently */} + + + + + + + {/* Body */} + + + + + {/* Right steady arm — gentle sway */} + + + + + + + {/* Left arm — gentle sway */} + + + + + + + {/* Neck shadows */} + + + + + + + + {/* Left eye — scaleY collapses on blink */} + + + {!inBlink && ( + <> + + + + + + + + )} + + + {/* Right eye — scaleY collapses on blink */} + + + {!inBlink && ( + <> + + + + + + + + + + + )} + + + {/* Left cheek */} + + + + + + {/* Right cheek */} + + + + + + {/* Mouth */} + + + + + + + ); +}; diff --git a/remotion/src/Mascot/black-MascotRecording.tsx b/remotion/src/Mascot/black-MascotRecording.tsx new file mode 100644 index 000000000..9074e5fd0 --- /dev/null +++ b/remotion/src/Mascot/black-MascotRecording.tsx @@ -0,0 +1,197 @@ +import React from "react"; +import { AbsoluteFill, useCurrentFrame, useVideoConfig } from "remotion"; +import { RecordingFace } from "../Ghosty/lib/RecordingFace"; + +/** + * Black recording mascot — uses exact paths and filters from BlackIdelmascot.svg + * with the same bob, head-drift, arm-sway animations as the black idle, + * but replaces the face with a pulsing red recording dot. + */ +export const BlackMascotRecording: React.FC = () => { + const frame = useCurrentFrame(); + const { fps, width, height } = useVideoConfig(); + + // Gentle bob for the whole character. + const bob = Math.sin((frame / fps) * Math.PI * 1.2) * 14; + + // Head dot drifts independently and squashes when pressing into the body. + const dotPhase = (frame / fps) * Math.PI * 1.0; + const dotDx = Math.sin(dotPhase * 0.7) * 6; + const dotDy = Math.sin(dotPhase) * 9; + const press = Math.max(0, Math.sin(dotPhase)); + const dotSquashY = 1 - 0.08 * press; + const dotSquashX = 1 + 0.05 * press; + + // Left arm gentle sway. + const leftSway = Math.sin((frame / fps) * Math.PI * 1.6) * 7; + // Steady right arm sway — mirrors left arm with slight phase offset. + const steadySway = Math.sin((frame / fps) * Math.PI * 1.6 + 0.3) * 6; + + const size = Math.min(width, height) * 0.85; + + return ( + + + + {/* Ground shadow gradient */} + + + + + + {/* Body filter — from BlackIdelmascot.svg filter0_iig */} + + + + + + + + + + + + + + + + + + + + + {/* Head filter — from BlackIdelmascot.svg filter1_iig */} + + + + + + + + + + + + + + + + + + + + + {/* Neck shadow filters */} + + + + + + + + + + + + {/* Left arm filter — from BlackIdelmascot.svg filter4_iig */} + + + + + + + + + + + + + + + + + + + + + {/* Right steady arm filter — from BlackIdelmascot.svg filter5_iig */} + + + + + + + + + + + + + + + + + + + + + + {/* Ground shadow */} + + + + + {/* Everything bobs together */} + + + {/* Head — drifts + squashes independently */} + + + + + + + {/* Body */} + + + + + {/* Right steady arm — gentle sway */} + + + + + + + {/* Left arm — gentle sway */} + + + + + + + {/* Neck shadows */} + + + + + + + + {/* Recording face — pulsing dot, centered at (495, 495): 25px lower + 70% scale. */} + + + + + + + ); +}; diff --git a/remotion/src/Mascot/black-MascotSleep.tsx b/remotion/src/Mascot/black-MascotSleep.tsx new file mode 100644 index 000000000..94febc680 --- /dev/null +++ b/remotion/src/Mascot/black-MascotSleep.tsx @@ -0,0 +1,345 @@ +import React from "react"; +import { AbsoluteFill, Easing, interpolate, useCurrentFrame, useVideoConfig } from "remotion"; + +/** + * Black sleep mascot — uses exact paths and filters from BlackIdelmascot.svg. + * Replicates yellow-MascotSleep: blinks normally, then eyes slowly droop closed, + * then sleep-arc eyes appear and Zzz letters float upward. + */ +export const BlackMascotSleep: React.FC = () => { + const frame = useCurrentFrame(); + const { fps, width, height } = useVideoConfig(); + + // Gentle bob for the whole character. + const bob = Math.sin((frame / fps) * Math.PI * 1.2) * 14; + + // Head dot drifts independently and squashes when pressing into the body. + const dotPhase = (frame / fps) * Math.PI * 1.0; + const dotDx = Math.sin(dotPhase * 0.7) * 6; + const dotDy = Math.sin(dotPhase) * 9; + const press = Math.max(0, Math.sin(dotPhase)); + const dotSquashY = 1 - 0.08 * press; + const dotSquashX = 1 + 0.05 * press; + + // Left arm gentle sway. + const leftSway = Math.sin((frame / fps) * Math.PI * 1.6) * 7; + // Steady right arm sway. + const steadySway = Math.sin((frame / fps) * Math.PI * 1.6 + 0.3) * 6; + + // Normal blink every ~2.6s for ~6 frames. + const blinkPeriod = Math.round(fps * 2.6); + const blinkOffset = Math.round(blinkPeriod / 2); + const inBlink = (frame + blinkOffset) % blinkPeriod < 6; + const blinkScale = inBlink ? 0.12 : 1; + + // Sleep animation — slow eye-close then floating Zzz. + const sleepStartFrame = Math.round(fps * 2.5); + const sleepFullFrame = Math.round(fps * 4.0); + const inSleepTransition = frame >= sleepStartFrame; + const sleepProgress = interpolate(frame, [sleepStartFrame, sleepFullFrame], [0, 1], { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + easing: Easing.inOut(Easing.cubic), + }); + const isAsleep = frame >= sleepFullFrame; + + // Eye openness: normal blink while awake, slow droop during sleep transition. + const eyeScale = inSleepTransition ? Math.max(0, 1 - sleepProgress) : blinkScale; + // Suppress blink highlights mid-droop so pupils don't pop on/off. + const effectiveInBlink = inSleepTransition ? false : inBlink; + // Switch to sleep-arc eyes once eyelids have closed. + const showSleepEyes = eyeScale <= 0.06; + + // Floating Z letters — staggered, drift up and fade out. + const zPeriod = Math.round(fps * 2.2); + const zBaseStart = sleepFullFrame + Math.round(fps * 0.4); + const getZ = (delay: number, baseX: number, fontSize: number) => { + const startAt = zBaseStart + delay; + if (!isAsleep || frame < startAt) return { x: baseX, y: 220, opacity: 0, fontSize }; + const cycleFrame = (frame - startAt) % zPeriod; + const t = cycleFrame / zPeriod; + return { + x: baseX + t * 20, + y: 220 - t * 120, + opacity: interpolate(t, [0, 0.1, 0.72, 1], [0, 1, 0.85, 0]), + fontSize, + }; + }; + const z1 = getZ(0, 605, 40); + const z2 = getZ(Math.round(fps * 0.72), 624, 56); + const z3 = getZ(Math.round(fps * 1.44), 643, 76); + + const size = Math.min(width, height) * 0.85; + + return ( + + + + {/* Ground shadow gradient */} + + + + + + {/* Body filter — from BlackIdelmascot.svg filter0_iig */} + + + + + + + + + + + + + + + + + + + + + {/* Head filter — from BlackIdelmascot.svg filter1_iig */} + + + + + + + + + + + + + + + + + + + + + {/* Neck shadow filters */} + + + + + + + + + + + + {/* Left arm filter — from BlackIdelmascot.svg filter4_iig */} + + + + + + + + + + + + + + + + + + + + + {/* Right steady arm filter — from BlackIdelmascot.svg filter5_iig */} + + + + + + + + + + + + + + + + + + + + + {/* Left eye highlight filters */} + + + + + + + + + + + + {/* Right eye highlight filters */} + + + + + + + + + + + + + + + + + {/* Cheek highlight filters */} + + + + + + + + + + + + + {/* Ground shadow */} + + + + + {/* Everything bobs together */} + + + {/* Head — drifts + squashes independently */} + + + + + + + {/* Body */} + + + + + {/* Right steady arm — gentle sway */} + + + + + + + {/* Left arm — gentle sway */} + + + + + + + {/* Neck shadows */} + + + + + + + + {/* Sleep arc eyes — visible only once eyelids are fully closed */} + {showSleepEyes && ( + <> + + + + )} + + {/* Left eye — scaleY droops during sleep transition, hidden once sleep-arc shows */} + {!showSleepEyes && ( + + + {!effectiveInBlink && ( + <> + + + + + + + + )} + + )} + + {/* Right eye — scaleY droops during sleep transition, hidden once sleep-arc shows */} + {!showSleepEyes && ( + + + {!effectiveInBlink && ( + <> + + + + + + + + + + + )} + + )} + + {/* Left cheek */} + + + + + + {/* Right cheek */} + + + + + + {/* Mouth */} + + + + {/* Zzz — floating letters that drift up after mascot falls asleep */} + {isAsleep && ( + <> + Z + Z + Z + + )} + + + + ); +}; diff --git a/remotion/src/Mascot/black-MascotTalking.tsx b/remotion/src/Mascot/black-MascotTalking.tsx new file mode 100644 index 000000000..7491ea9c0 --- /dev/null +++ b/remotion/src/Mascot/black-MascotTalking.tsx @@ -0,0 +1,300 @@ +import React from "react"; +import { AbsoluteFill, useCurrentFrame, useVideoConfig } from "remotion"; + +/** + * Black talking mascot — uses exact paths and filters from BlackIdelmascot.svg + * with the same bob, head-drift, arm-sway, and blink as the black idle, + * but replaces the static mouth with a lip-sync jaw-drop animation. + */ +export const BlackMascotTalking: React.FC = () => { + const frame = useCurrentFrame(); + const { fps, width, height } = useVideoConfig(); + + // Gentle bob for the whole character. + const bob = Math.sin((frame / fps) * Math.PI * 1.2) * 14; + + // Head dot drifts independently and squashes when pressing into the body. + const dotPhase = (frame / fps) * Math.PI * 1.0; + const dotDx = Math.sin(dotPhase * 0.7) * 6; + const dotDy = Math.sin(dotPhase) * 9; + const press = Math.max(0, Math.sin(dotPhase)); + const dotSquashY = 1 - 0.08 * press; + const dotSquashX = 1 + 0.05 * press; + + // Left arm gentle sway. + const leftSway = Math.sin((frame / fps) * Math.PI * 1.6) * 7; + // Steady right arm sway — mirrors left arm with slight phase offset. + const steadySway = Math.sin((frame / fps) * Math.PI * 1.6 + 0.3) * 6; + + // Blink every ~2.6s for ~6 frames. + const blinkPeriod = Math.round(fps * 2.6); + const blinkOffset = Math.round(blinkPeriod / 2); + const inBlink = (frame + blinkOffset) % blinkPeriod < 6; + const eyeScale = inBlink ? 0.12 : 1; + + // Lip sync — ~1.5–2.3 Hz for natural speech pace. + const talkA = Math.abs(Math.sin((frame / fps) * Math.PI * 3.0)); + const talkB = Math.abs(Math.sin((frame / fps) * Math.PI * 4.6 + 1.2)); + const mouthOpen = Math.max(talkA, talkB * 0.8); + // Tongue fades in only when mouth is open enough. + const tongueOpacity = Math.min(1, Math.max(0, (mouthOpen - 0.15) / 0.35)); + + const size = Math.min(width, height) * 0.85; + + return ( + + + + {/* Ground shadow gradient */} + + + + + + {/* Body filter — from BlackIdelmascot.svg filter0_iig */} + + + + + + + + + + + + + + + + + + + + + {/* Head filter — from BlackIdelmascot.svg filter1_iig */} + + + + + + + + + + + + + + + + + + + + + {/* Neck shadow filters */} + + + + + + + + + + + + {/* Left arm filter — from BlackIdelmascot.svg filter4_iig */} + + + + + + + + + + + + + + + + + + + + + {/* Right steady arm filter — from BlackIdelmascot.svg filter5_iig */} + + + + + + + + + + + + + + + + + + + + + {/* Left eye highlight filters */} + + + + + + + + + + + + {/* Right eye highlight filters */} + + + + + + + + + + + + + + + + + {/* Cheek highlight filters */} + + + + + + + + + + + + + {/* Ground shadow */} + + + + + {/* Everything bobs together */} + + + {/* Head — drifts + squashes independently */} + + + + + + + {/* Body */} + + + + + {/* Right steady arm — gentle sway */} + + + + + + + {/* Left arm — gentle sway */} + + + + + + + {/* Neck shadows */} + + + + + + + + {/* Left eye — scaleY collapses on blink */} + + + {!inBlink && ( + <> + + + + + + + + )} + + + {/* Right eye — scaleY collapses on blink */} + + + {!inBlink && ( + <> + + + + + + + + + + + )} + + + {/* Left cheek */} + + + + + + {/* Right cheek */} + + + + + + {/* Talking mouth — pivot at top edge (y=508), scales downward like a jaw drop */} + + + + + + + + + ); +}; diff --git a/remotion/src/Mascot/black-MascotThinking.tsx b/remotion/src/Mascot/black-MascotThinking.tsx new file mode 100644 index 000000000..5e1249550 --- /dev/null +++ b/remotion/src/Mascot/black-MascotThinking.tsx @@ -0,0 +1,330 @@ +import React from "react"; +import { AbsoluteFill, Easing, interpolate, useCurrentFrame, useVideoConfig } from "remotion"; + +/** + * Black thinking mascot — uses exact paths and filters from BlackIdelmascot.svg. + * Replicates yellow-MascotThinking: starts idle then transitions into thinking pose — + * left arm raises toward chin, head tilts, eyes look up-left, smile becomes "hmm". + */ +export const BlackMascotThinking: React.FC = () => { + const frame = useCurrentFrame(); + const { fps, width, height } = useVideoConfig(); + + // Gentle bob for the whole character. + const bob = Math.sin((frame / fps) * Math.PI * 1.2) * 14; + + // Head dot drifts independently and squashes when pressing into the body. + const dotPhase = (frame / fps) * Math.PI * 1.0; + const dotDx = Math.sin(dotPhase * 0.7) * 6; + const dotDy = Math.sin(dotPhase) * 9; + const press = Math.max(0, Math.sin(dotPhase)); + const dotSquashY = 1 - 0.08 * press; + const dotSquashX = 1 + 0.05 * press; + + // Left arm gentle sway (idle baseline). + const leftSway = Math.sin((frame / fps) * Math.PI * 1.6) * 7; + // Steady right arm sway. + const steadySway = Math.sin((frame / fps) * Math.PI * 1.6 + 0.3) * 6; + + // Blink every ~2.6s for ~6 frames. + const blinkPeriod = Math.round(fps * 2.6); + const blinkOffset = Math.round(blinkPeriod / 2); + const inBlink = (frame + blinkOffset) % blinkPeriod < 6; + const eyeScale = inBlink ? 0.12 : 1; + + // Thinking transition — starts at 1s, fully in at 2s. + const thinkStartFrame = Math.round(fps * 1.0); + const thinkFullFrame = Math.round(fps * 2.0); + const thinkProgress = interpolate(frame, [thinkStartFrame, thinkFullFrame], [0, 1], { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + easing: Easing.inOut(Easing.cubic), + }); + const isThinking = thinkProgress >= 1; + + // Left arm raises toward body/chin, then gently oscillates. + const thinkArmOscillate = isThinking ? Math.sin((frame / fps) * Math.PI * 0.5) * 2 : 0; + const effectiveLeftSway = interpolate(thinkProgress, [0, 1], [leftSway, -128]) + thinkArmOscillate; + + // Head tilts slightly toward raised arm. + const headTilt = isThinking + ? -4.5 + Math.sin((frame / fps) * Math.PI * 0.38) * 1.8 + : interpolate(thinkProgress, [0, 1], [0, -4.5]); + + // Eyes drift up-left. + const thinkEyeX = thinkProgress * -6; + const thinkEyeY = thinkProgress * -9; + + const size = Math.min(width, height) * 0.85; + + return ( + + + + {/* Ground shadow gradient */} + + + + + + {/* Body filter — from BlackIdelmascot.svg filter0_iig */} + + + + + + + + + + + + + + + + + + + + + {/* Head filter — from BlackIdelmascot.svg filter1_iig */} + + + + + + + + + + + + + + + + + + + + + {/* Neck shadow filters */} + + + + + + + + + + + + {/* Left arm filter — from BlackIdelmascot.svg filter4_iig */} + + + + + + + + + + + + + + + + + + + + + {/* Right steady arm filter — from BlackIdelmascot.svg filter5_iig */} + + + + + + + + + + + + + + + + + + + + + {/* Left eye highlight filters */} + + + + + + + + + + + + {/* Right eye highlight filters */} + + + + + + + + + + + + + + + + + {/* Cheek highlight filters */} + + + + + + + + + + + + + {/* Ground shadow */} + + + + + {/* Everything bobs together */} + + + {/* Head — drifts + squashes independently */} + + + + + + + {/* Body */} + + + + + {/* Right steady arm — gentle sway */} + + + + + + + {/* Left arm — raises toward chin while thinking */} + + + + + + + {/* Neck shadows */} + + + + + + + + {/* Face — rotates for head tilt */} + + + {/* Left eye — gaze drifts up-left while thinking */} + + + + {!inBlink && ( + <> + + + + + + + + )} + + + + {/* Right eye — gaze drifts up-left while thinking */} + + + + {!inBlink && ( + <> + + + + + + + + + + + )} + + + + {/* Left cheek */} + + + + + + {/* Right cheek */} + + + + + + {/* Normal smile — fades out as thinking kicks in */} + + + + + + {/* "Hmm" mouth — asymmetric slight frown, fades in */} + + + + + + ); +}; diff --git a/remotion/src/Mascot/black-MascotWave.tsx b/remotion/src/Mascot/black-MascotWave.tsx new file mode 100644 index 000000000..dfc41facc --- /dev/null +++ b/remotion/src/Mascot/black-MascotWave.tsx @@ -0,0 +1,297 @@ +import React from "react"; +import { AbsoluteFill, Easing, interpolate, useCurrentFrame, useVideoConfig } from "remotion"; + +/** + * Black wave mascot — uses exact paths and filters from BlackIdelmascot.svg + * for the body, head, and left arm. The right waving arm uses the same path as + * yellow-MascotWave2 with #3A3A3A fill and black-tuned inner shadow filter. + * Same keyframe hi-wave animation: 3 swings then a rest pause, loops every 2.4s. + */ +export const BlackMascotWave: React.FC = () => { + const frame = useCurrentFrame(); + const { fps, width, height } = useVideoConfig(); + + // Gentle bob for the whole character. + const bob = Math.sin((frame / fps) * Math.PI * 1.2) * 14; + + // Head dot drifts independently and squashes when pressing into the body. + const dotPhase = (frame / fps) * Math.PI * 1.0; + const dotDx = Math.sin(dotPhase * 0.7) * 6; + const dotDy = Math.sin(dotPhase) * 9; + const press = Math.max(0, Math.sin(dotPhase)); + const dotSquashY = 1 - 0.08 * press; + const dotSquashX = 1 + 0.05 * press; + + // Right arm wave — 3 swings then rest, loops every 2.4s. + const easeInOut = Easing.inOut(Easing.cubic); + const wavePeriod = Math.round(fps * 2.4); + const frameInCycle = frame % wavePeriod; + const wave = interpolate( + frameInCycle, + [0, wavePeriod * 0.12, wavePeriod * 0.25, wavePeriod * 0.38, wavePeriod * 0.50, wavePeriod * 0.62, wavePeriod * 0.75, wavePeriod], + [0, -9, 0, -7, 0, -5, 0, 0], + { extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: easeInOut } + ); + + // Left arm gentle sway. + const leftSway = Math.sin((frame / fps) * Math.PI * 1.6) * 7; + + // Blink every ~2.6s for ~6 frames. + const blinkPeriod = Math.round(fps * 2.6); + const blinkOffset = Math.round(blinkPeriod / 2); + const inBlink = (frame + blinkOffset) % blinkPeriod < 6; + const eyeScale = inBlink ? 0.12 : 1; + + const size = Math.min(width, height) * 0.85; + + return ( + + + + {/* Ground shadow gradient */} + + + + + + {/* Body filter — from BlackIdelmascot.svg filter0_iig */} + + + + + + + + + + + + + + + + + + + + + {/* Head filter — from BlackIdelmascot.svg filter1_iig */} + + + + + + + + + + + + + + + + + + + + + {/* Neck shadow filters */} + + + + + + + + + + + + {/* Left arm filter — from BlackIdelmascot.svg filter4_iig */} + + + + + + + + + + + + + + + + + + + + + {/* Right wave arm filter — bounds from MascotCharacter f4, black inner shadow values */} + + + + + + + + + + + + + + + + + + + + + {/* Left eye highlight filters */} + + + + + + + + + + + + {/* Right eye highlight filters */} + + + + + + + + + + + + + + + + + {/* Cheek highlight filters */} + + + + + + + + + + + + + {/* Ground shadow */} + + + + + {/* Everything bobs together */} + + + {/* Head — drifts + squashes independently */} + + + + + + + {/* Body */} + + + + + {/* Right waving arm — rotates around pivot (776, 568) */} + + + + + + + {/* Left arm — gentle sway */} + + + + + + + {/* Neck shadows */} + + + + + + + + {/* Left eye — scaleY collapses on blink */} + + + {!inBlink && ( + <> + + + + + + + + )} + + + {/* Right eye — scaleY collapses on blink */} + + + {!inBlink && ( + <> + + + + + + + + + + + )} + + + {/* Left cheek */} + + + + + + {/* Right cheek */} + + + + + + {/* Mouth */} + + + + + + ); +}; diff --git a/remotion/src/Mascot/black-MascotWink.tsx b/remotion/src/Mascot/black-MascotWink.tsx new file mode 100644 index 000000000..0cf16a262 --- /dev/null +++ b/remotion/src/Mascot/black-MascotWink.tsx @@ -0,0 +1,314 @@ +import React from "react"; +import { + AbsoluteFill, + Easing, + interpolate, + useCurrentFrame, + useVideoConfig, +} from "remotion"; + +export const BlackMascotWink: React.FC = () => { + const frame = useCurrentFrame(); + const { fps, width, height } = useVideoConfig(); + const p = (k: string) => `bmwk-${k}`; + + // ── Relaxed body bob ───────────────────────────────────────────────────── + const bob = Math.sin((frame / fps) * Math.PI * 1.0) * 10; + + // ── Head drift + squash ───────────────────────────────────────────────── + const dotPhase = (frame / fps) * Math.PI; + const headDx = Math.sin(dotPhase * 0.7) * 5; + const headDy = Math.sin(dotPhase) * 7; + const press = Math.max(0, Math.sin(dotPhase)); + const headSquashY = 1 - 0.07 * press; + const headSquashX = 1 + 0.05 * press; + + // ── Wink transition: right eye open → wink ────────────────────────────── + const winkProgress = interpolate(frame, [60, 78], [0, 1], { + easing: Easing.inOut(Easing.quad), + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + }); + const openRightEyeOpacity = 1 - winkProgress; + const winkEyeOpacity = winkProgress; + + // Slight head tilt as wink comes in + const headTilt = interpolate(frame, [60, 85], [0, 4], { + easing: Easing.out(Easing.quad), + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + }); + + // ── Left eye blink — only after wink is set (frame 95+) ───────────────── + const blinkPeriod = Math.round(fps * 3.5); + const blinkDur = Math.round(fps * 0.14); + const blinkOffset = frame < 95 ? 0 : (frame - 95) % blinkPeriod; + const leftEyeScaleY = + blinkOffset < blinkDur + ? interpolate( + blinkOffset, + [0, blinkDur * 0.35, blinkDur * 0.65, blinkDur], + [1, 0.06, 0.06, 1], + { extrapolateLeft: "clamp", extrapolateRight: "clamp" } + ) + : 1; + + // ── Right arm — waves only after wink is set ──────────────────────────── + const rightArmWave = interpolate(frame, [70, 95], [0, 1], { + easing: Easing.out(Easing.quad), + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + }); + const rightArmAngle = + (10 + Math.sin((frame / fps) * Math.PI * 2.5) * 22) * rightArmWave; + + // ── Left arm — gentle idle sway ───────────────────────────────────────── + const leftArmAngle = Math.sin((frame / fps) * Math.PI * 0.9) * 7; + + const size = Math.min(width, height) * 0.82; + + return ( + + + + + + + + + {/* Body */} + + + + + + + + + + + + + + + + + + {/* Head circle */} + + + + + + + + + + + + + + + + + + {/* Neck shadows */} + + + + + + + + + + + + {/* Left arm */} + + + + + + + + + + + + + + + + + + {/* Right arm */} + + + + + + + + + + + + + + + + + + {/* Left eye highlights */} + + + + + + + + + + + + {/* Right open eye highlights */} + + + + + + + + + + + + {/* Cheek highlights */} + + + + + + + + + + + + + {/* Ground shadow */} + + + {/* Everything bobs */} + + + {/* Body */} + + + {/* Left arm */} + + + + + {/* Right arm — waves after wink */} + + + + + {/* Head group: drift + tilt + squash */} + + + + {/* Neck shadows */} + + + + + + + + {/* Head circle */} + + + {/* Left eye — blinks periodically after wink */} + + + + + + + + + + + {/* Right eye: open (fades out) → wink (fades in) */} + + + + + + + + + + + {/* Wink right eye — stroke arch */} + + + + + {/* Left cheek */} + + + + + + {/* Right cheek */} + + + + + + {/* Smirk mouth */} + + + + + + {/* end head group */} + + + {/* end bob group */} + + + + ); +}; diff --git a/remotion/src/Root.tsx b/remotion/src/Root.tsx index 90eede34a..02bca28da 100644 --- a/remotion/src/Root.tsx +++ b/remotion/src/Root.tsx @@ -55,6 +55,21 @@ import { NewMascotCupHolding } from "./Mascot/NewMascotCupHolding"; import { NewMascotBobateaHolding } from "./Mascot/NewMascotBobateaHolding"; import { NewMascotSyicSmile } from "./Mascot/NewMascotSyicSmile"; import { NewMascotSyicSmileSlow } from "./Mascot/NewMascotSyicSmileSlow"; +import { BlackMascotIdle } from "./Mascot/black-MascotIdle"; +import { BlackMascotRecording } from "./Mascot/black-MascotRecording"; +import { BlackMascotLoading } from "./Mascot/black-MascotLoading"; +import { BlackMascotPickup } from "./Mascot/black-MascotPickup"; +import { BlackMascotTalking } from "./Mascot/black-MascotTalking"; +import { BlackMascotThinking } from "./Mascot/black-MascotThinking"; +import { BlackMascotSleep } from "./Mascot/black-MascotSleep"; +import { BlackMascotLove } from "./Mascot/black-MascotLove"; +import { BlackMascotWave } from "./Mascot/black-MascotWave"; +import { BlackMascotListening } from "./Mascot/black-MascotListening"; +import { BlackMascotCrying } from "./Mascot/black-MascotCrying"; +import { BlackMascotWink } from "./Mascot/black-MascotWink"; +import { BlackMascotCelebrate } from "./Mascot/black-MascotCelebrate"; +import { BlackMascotHatWithBag } from "./Mascot/black-MascotHatWithBag"; +import { BlackMascotLaughing } from "./Mascot/black-MascotLaughing"; export const RemotionRoot: React.FC = () => { const staticFiles = getStaticFiles(); @@ -395,6 +410,141 @@ export const RemotionRoot: React.FC = () => { height={1080} defaultProps={{}} /> + + + + + + + + + + + + + + + {timelines.map((storyName) => (