← Catalog
Edit Props (live)
TEXT
TEXT
config
config
config
config
config
Source
146 linesimport React from "react";
import {
AbsoluteFill,
Img,
OffthreadVideo,
useCurrentFrame,
useVideoConfig,
interpolate,
Easing,
staticFile,
} from "remotion";
import { loadFont as loadInter } from "@remotion/google-fonts/Inter";
loadInter("normal", { weights: ["700", "900"] });
interface FaceCloseupZoomProps {
src: string;
zoomFrom?: number;
zoomTo?: number;
focusX?: number; // 0..1, default 0.5
focusY?: number; // 0..1, default 0.38
captionText?: string;
captionStyle?: "chip" | "magenta_outline";
}
const FONT = "'Inter', sans-serif";
const isVideo = (s: string) => /\.(mp4|mov|webm)(\?|$)/i.test(s);
export const FaceCloseupZoom: React.FC<FaceCloseupZoomProps> = ({
src,
zoomFrom = 1.4,
zoomTo = 2.2,
focusX = 0.5,
focusY = 0.38,
captionText,
captionStyle = "magenta_outline",
}) => {
const frame = useCurrentFrame();
const { durationInFrames } = useVideoConfig();
const zoomT = interpolate(frame, [0, durationInFrames], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
easing: Easing.inOut(Easing.cubic),
});
const scale = zoomFrom + (zoomTo - zoomFrom) * zoomT;
// Micro shake for tension
const shake = Math.sin(frame * 0.8) * 2;
const shakeY = Math.cos(frame * 0.9) * 1.5;
const resolved = src.startsWith("http") ? src : staticFile(src);
const video = isVideo(src);
const captionOp = interpolate(frame, [14, 26], [0, 1], { extrapolateRight: "clamp" });
const captionScale = interpolate(frame, [14, 26], [0.85, 1], {
extrapolateRight: "clamp",
easing: Easing.out(Easing.back(1.6)),
});
return (
<AbsoluteFill style={{ backgroundColor: "#000", overflow: "hidden" }}>
<div
style={{
position: "absolute",
inset: 0,
transform: `translate(${shake}px, ${shakeY}px) scale(${scale})`,
transformOrigin: `${focusX * 100}% ${focusY * 100}%`,
}}
>
{video ? (
<OffthreadVideo
src={resolved}
muted
style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }}
/>
) : (
<Img
src={resolved}
style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }}
/>
)}
</div>
{/* Vignette */}
<div
style={{
position: "absolute",
inset: 0,
background: "radial-gradient(ellipse at center, transparent 40%, rgba(0,0,0,0.65) 100%)",
pointerEvents: "none",
}}
/>
{/* Caption */}
{captionText && (
<div
style={{
position: "absolute",
bottom: 280,
left: 0,
right: 0,
display: "flex",
justifyContent: "center",
opacity: captionOp,
transform: `scale(${captionScale})`,
zIndex: 10,
}}
>
{captionStyle === "magenta_outline" ? (
<div
style={{
fontFamily: FONT,
fontSize: 98,
fontWeight: 900,
color: "#ff3bd1",
WebkitTextStroke: "5px #000",
letterSpacing: -2,
textShadow:
"4px 4px 0 #000, -4px 4px 0 #000, 4px -4px 0 #000, -4px -4px 0 #000, 0 6px 0 #000",
}}
>
{captionText}
</div>
) : (
<div
style={{
background: "rgba(0,0,0,0.85)",
color: "#fff",
padding: "20px 36px",
borderRadius: 18,
fontFamily: FONT,
fontSize: 58,
fontWeight: 800,
letterSpacing: -0.6,
}}
>
{captionText}
</div>
)}
</div>
)}
</AbsoluteFill>
);
};
Schema (Zod)
// Auto-extracted from Props interface — see source for authoritative types.
// Component: FaceCloseupZoom
// 7 props detected · 2 port(s)
Face Closeup Zoom
face_closeup_zoomFace Closeup Zoom — Andy's hand-crafted Remotion scene. 7 props. captionText:TEXT, captionStyle:TEXT.
Workflow Inputs (2)
- Caption Text
captionTextTEXT - Caption Style
captionStyleTEXT
Config Fields (5)
srcfocusXfocusYzoomTozoomFrom
Meta
- Updated
- 4/21/2026