import React from "react";
import {
AbsoluteFill,
Img,
interpolate,
spring,
useCurrentFrame,
useVideoConfig,
Series,
staticFile,
} from "remotion";
// ============================================
// SPRING CONFIGS (from best practices)
// ============================================
const SPRING_SMOOTH = { damping: 200 };
const SPRING_SNAPPY = { damping: 20, stiffness: 200 };
const SPRING_BOUNCY = { damping: 12 };
// ============================================
// CAMERA - Simple wrapper, ONE motion at a time
// ============================================
const Camera: React.FC<{
children: React.ReactNode;
zoom?: number;
x?: number;
y?: number;
}> = ({ children, zoom = 1, x = 0, y = 0 }) => (
{children}
);
// ============================================
// KINETIC TEXT - Word by word reveal
// ============================================
const KineticText: React.FC<{
text: string;
delay?: number;
stagger?: number;
style?: React.CSSProperties;
}> = ({ text, delay = 0, stagger = 4, style = {} }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const words = text.split(" ");
return (
{words.map((word, i) => {
const wordDelay = delay + i * stagger;
const progress = spring({
frame: frame - wordDelay,
fps,
config: SPRING_SNAPPY,
});
return (
{word}
);
})}
);
};
// ============================================
// BROWSER MOCKUP
// ============================================
const BrowserMockup: React.FC<{
src: string;
width?: number;
}> = ({ src, width = 1000 }) => {
const height = width * 0.5625;
return (
);
};
// ============================================
// STEP LABEL
// ============================================
const StepLabel: React.FC<{
step: number;
text: string;
color?: string;
}> = ({ step, text, color = "#6366f1" }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const progress = spring({ frame: frame - 10, fps, config: SPRING_SNAPPY });
return (
);
};
// ============================================
// CONTACT CARD
// ============================================
const ContactCard: React.FC<{
type: "phone" | "email";
value: string;
source?: string;
delay: number;
}> = ({ type, value, source, delay }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const progress = spring({ frame: frame - delay, fps, config: SPRING_SNAPPY });
return (
{type === "phone" ? "📞" : "📧"}
{value}
{source &&
{source}
}
);
};
// ============================================
// ANIMATED STAT
// ============================================
const AnimatedStat: React.FC<{
value: number | string;
label: string;
color: string;
delay: number;
}> = ({ value, label, color, delay }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const progress = spring({ frame: frame - delay, fps, config: SPRING_SMOOTH });
const numericValue =
typeof value === "number" ? Math.round(interpolate(progress, [0, 1], [0, value])) : value;
return (
);
};
// ============================================
// SCENES
// ============================================
const SceneIntro: React.FC = () => {
const frame = useCurrentFrame();
// Slow, elegant zoom settle: 1.12 -> 1.0 over longer duration
const zoom = interpolate(frame, [0, 120], [1.12, 1.0], {
extrapolateRight: "clamp",
});
return (
);
};
const SceneLogin: React.FC = () => {
const frame = useCurrentFrame();
// Slow push in toward login area: 0.92 -> 1.08 over 140 frames
const zoom = interpolate(frame, [0, 140], [0.92, 1.08], {
extrapolateRight: "clamp",
});
return (
);
};
const SceneSearch: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
// Slower settle: start wide, ease in over 100 frames
const zoom = interpolate(frame, [0, 100], [0.88, 1.0], {
extrapolateRight: "clamp",
});
const filterProgress1 = spring({ frame: frame - 50, fps, config: SPRING_SNAPPY });
const filterProgress2 = spring({ frame: frame - 70, fps, config: SPRING_SNAPPY });
return (
{/* Filter badges - staggered */}
);
};
const SceneProperty: React.FC = () => {
const frame = useCurrentFrame();
// Slow, cinematic pan down over 120 frames
const y = interpolate(frame, [0, 120], [-25, 15], {
extrapolateRight: "clamp",
});
return (
);
};
const SceneOwner: React.FC = () => {
const frame = useCurrentFrame();
// Slow zoom in to owner section over 120 frames
const zoom = interpolate(frame, [0, 120], [1.0, 1.12], {
extrapolateRight: "clamp",
});
return (
);
};
const SceneModal: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
// Slower pop in then settle over longer duration
const zoom = interpolate(frame, [0, 50, 100], [0.85, 1.04, 1.0], {
extrapolateRight: "clamp",
});
const labelProgress = spring({ frame: frame - 70, fps, config: SPRING_BOUNCY });
return (
{/* Success label */}
);
};
const SceneResults: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
// Slow, elegant zoom out over 150 frames
const zoom = interpolate(frame, [0, 150], [1.06, 1.0], {
extrapolateRight: "clamp",
});
const phones = [
{ value: "919-469-9553", source: "Greystone Property Mgmt" },
{ value: "727-341-0186", source: "Seaside Villas" },
{ value: "903-566-9506", source: "Apartment Income Reit" },
{ value: "407-671-2400", source: "E R Management" },
{ value: "407-382-2683", source: "Bellagio Apartments" },
];
const emails = [
{ value: "berrizoro@gmail.com" },
{ value: "aberriz@hotmail.com" },
{ value: "jasonhitch1@gmail.com" },
{ value: "albert@annarborusa.org" },
{ value: "albertb@sterlinghousing.com" },
];
const headerProgress = spring({ frame: frame - 10, fps, config: SPRING_SMOOTH });
const phoneHeaderProgress = spring({ frame: frame - 60, fps, config: SPRING_SMOOTH });
const emailHeaderProgress = spring({ frame: frame - 70, fps, config: SPRING_SMOOTH });
return (
{/* Title */}
CRE Leads Delivered Direct to Your CRM
… On Demand 🚀
{/* Contact columns */}
📞 Phone Numbers
{phones.map((p, i) => (
))}
📧 Email Addresses
{emails.map((e, i) => (
))}
{/* Stats footer */}
);
};
// ============================================
// MAIN COMPOSITION
// ============================================
export const ReonomyDemo: React.FC = () => {
const frame = useCurrentFrame();
const { durationInFrames } = useVideoConfig();
// Global fade in/out (20 frames as recommended)
const fadeIn = interpolate(frame, [0, 20], [0, 1], { extrapolateRight: "clamp" });
const fadeOut = interpolate(frame, [durationInFrames - 20, durationInFrames], [1, 0], {
extrapolateLeft: "clamp",
});
return (
);
};