Catalog

Edit Props (live)

TEXT
TEXT
config
config
config
config
config

Source

146 lines
import 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_zoom

Face Closeup Zoom — Andy's hand-crafted Remotion scene. 7 props. captionText:TEXT, captionStyle:TEXT.

Workflow Inputs (2)

  • Caption Text
    captionText
    TEXT
  • Caption Style
    captionStyle
    TEXT

Config Fields (5)

  • src
  • focusX
  • focusY
  • zoomTo
  • zoomFrom

Meta

Updated
4/21/2026