diff --git a/marketing/product-film/src/components/Cursor.tsx b/marketing/product-film/src/components/Cursor.tsx new file mode 100644 index 0000000..4a4f042 --- /dev/null +++ b/marketing/product-film/src/components/Cursor.tsx @@ -0,0 +1,57 @@ +import { useCurrentFrame, useVideoConfig, spring, interpolate, AbsoluteFill } from "remotion"; +import { theme } from "../theme"; + +type Pt = { x: number; y: number }; // pixels in the 1920x1080 canvas + +export const Cursor: React.FC<{ + from: Pt; + to: Pt; + moveStartFrame: number; + moveDurationFrames?: number; + clickAtFrame?: number; +}> = ({ from, to, moveStartFrame, moveDurationFrames = 22, clickAtFrame }) => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + const p = spring({ + frame: frame - moveStartFrame, + fps, + durationInFrames: moveDurationFrames, + config: { damping: 200 }, + }); + const x = interpolate(p, [0, 1], [from.x, to.x]); + const y = interpolate(p, [0, 1], [from.y, to.y]); + + let pulse = 0; + if (clickAtFrame !== undefined) { + pulse = interpolate(frame - clickAtFrame, [0, 8], [1, 0], { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + }); + } + + return ( + +
+ {pulse > 0 && ( +
+ )} + + + +
+ + ); +}; diff --git a/marketing/product-film/src/components/TypeReveal.tsx b/marketing/product-film/src/components/TypeReveal.tsx new file mode 100644 index 0000000..dd3b989 --- /dev/null +++ b/marketing/product-film/src/components/TypeReveal.tsx @@ -0,0 +1,20 @@ +import { useCurrentFrame } from "remotion"; +import { theme } from "../theme"; + +export const TypeReveal: React.FC<{ + text: string; + startFrame: number; + framesPerChar?: number; + style?: React.CSSProperties; +}> = ({ text, startFrame, framesPerChar = 1.6, style }) => { + const frame = useCurrentFrame(); + const shown = Math.max(0, Math.floor((frame - startFrame) / framesPerChar)); + const visible = text.slice(0, shown); + const blinkOn = Math.floor(frame / 15) % 2 === 0; + return ( + + {visible} + {shown < text.length && blinkOn ? | : null} + + ); +};