feat(remotion): mortgage broker demo video for Alex Ferrari

- Add SquareMCPBrokerDemo composition (1920x1080, 45s)
- 5 scenes: title, email inbox (47 unread → 5 replies), rate blast (200 prospects),
  WhatsApp auto-response, Facebook page reply
- Outro: SquareMCP logo + tagline
- Uses existing brand colors and spring animations
This commit is contained in:
Garfield
2026-05-15 06:19:28 -04:00
parent fd7ea2fee5
commit 05b4a30759
2 changed files with 288 additions and 0 deletions

View File

@@ -3,6 +3,7 @@ import { Composition } from "remotion";
import { SquareMCPLinkedIn } from "./SquareMCPLinkedIn"; import { SquareMCPLinkedIn } from "./SquareMCPLinkedIn";
import { SquareMCPHeroLoop } from "./SquareMCPHeroLoop"; import { SquareMCPHeroLoop } from "./SquareMCPHeroLoop";
import { YCAppVideo } from "./YCAppVideo"; import { YCAppVideo } from "./YCAppVideo";
import { SquareMCPBrokerDemo } from "./SquareMCPBrokerDemo";
import { import {
SquareMCPTikTokCTA, SquareMCPTikTokCTA,
SquareMCPTikTokDemo, SquareMCPTikTokDemo,
@@ -105,6 +106,14 @@ export const RemotionRoot = () => {
width={1920} width={1920}
height={1080} height={1080}
/> />
<Composition
id="SquareMCPBrokerDemo"
component={SquareMCPBrokerDemo}
durationInFrames={45 * 30}
fps={30}
width={1920}
height={1080}
/>
</> </>
); );
}; };

View File

@@ -0,0 +1,279 @@
import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig } from "remotion";
import { COLORS, FONT, SPRING_CFG, SPRING_SOFT } from "./styles";
// ─── SCENE TIMINGS ───────────────────────────────────────────────────
const SCENES = {
title: { start: 0, duration: 3 }, // 0:000:03
emailIn: { start: 3, duration: 10 }, // 0:030:13
rateBlast:{ start: 13, duration: 10 }, // 0:130:23
whatsapp: { start: 23, duration: 8 }, // 0:230:31
facebook: { start: 31, duration: 8 }, // 0:310:39
outro: { start: 39, duration: 6 }, // 0:390:45
} as const;
// ─── HELPERS ─────────────────────────────────────────────────────────
function useSceneProgress(sceneStart: number, sceneDuration: number) {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const startFrame = sceneStart * fps;
const endFrame = (sceneStart + sceneDuration) * fps;
const progress = interpolate(frame, [startFrame, endFrame], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const fadeIn = interpolate(frame, [startFrame, startFrame + 0.5 * fps], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const fadeOut = interpolate(frame, [endFrame - 0.5 * fps, endFrame], [1, 0], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
return { progress, opacity: fadeIn * fadeOut, startFrame, endFrame };
}
// ─── TITLE CARD ──────────────────────────────────────────────────────
const TitleScene = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const { opacity } = useSceneProgress(SCENES.title.start, SCENES.title.duration);
const titleY = spring({ frame: frame - SCENES.title.start * fps, fps, config: SPRING_CFG, from: 40, to: 0 });
const subtitleY = spring({ frame: frame - SCENES.title.start * fps, fps, config: SPRING_CFG, from: 30, to: 0, delay: 5 });
return (
<AbsoluteFill style={{ opacity, background: COLORS.bg, fontFamily: FONT, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center" }}>
<div style={{ width: 400, height: 400, borderRadius: "50%", background: `radial-gradient(circle, ${COLORS.accentGlow} 0%, transparent 70%)`, position: "absolute", top: "35%", left: "50%", transform: "translate(-50%, -50%)" }} />
<div style={{ fontSize: 64, fontWeight: 800, color: COLORS.text, letterSpacing: "-0.02em", transform: `translateY(${titleY}px)`, textShadow: `0 0 60px ${COLORS.accentGlow}` }}>
A Day in the Life
</div>
<div style={{ fontSize: 36, fontWeight: 700, color: COLORS.accentLight, marginTop: 16, transform: `translateY(${subtitleY}px)`, display: "flex", alignItems: "center", gap: 12 }}>
<span style={{ color: COLORS.accent }}></span>
Mortgage Broker
</div>
<div style={{ fontSize: 20, color: COLORS.textSecondary, marginTop: 32, opacity: interpolate(frame, [1.5 * fps, 2 * fps], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" }) }}>
Powered by SquareMCP
</div>
</AbsoluteFill>
);
};
// ─── EMAIL INBOX SCENE ───────────────────────────────────────────────
const EmailInboxScene = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const { opacity } = useSceneProgress(SCENES.emailIn.start, SCENES.emailIn.duration);
const localFrame = frame - SCENES.emailIn.start * fps;
const emails = [
{ from: "Sarah J.", subject: "Pre-approval letter?", time: "8:14 AM", unread: true },
{ from: "Mike T.", subject: "Rate lock expiration", time: "8:22 AM", unread: true },
{ from: "Linda R.", subject: "Refinance question", time: "8:31 AM", unread: true },
{ from: "David K.", subject: "Closing docs status", time: "8:45 AM", unread: true },
{ from: "Jessica M.", subject: "Down payment help", time: "9:02 AM", unread: true },
];
return (
<AbsoluteFill style={{ opacity, background: COLORS.bg, fontFamily: FONT, padding: 60 }}>
<div style={{ fontSize: 32, fontWeight: 700, color: COLORS.text, marginBottom: 32 }}>
8:00 AM 47 unread emails
</div>
<div style={{ display: "flex", gap: 40 }}>
{/* Inbox */}
<div style={{ flex: 1, background: COLORS.bgCard, borderRadius: 12, border: `1px solid ${COLORS.border}`, overflow: "hidden" }}>
{emails.map((e, i) => {
const y = spring({ frame: localFrame, fps, config: SPRING_SOFT, from: 30, to: 0, delay: i * 4 });
const op = interpolate(localFrame, [i * 4, i * 4 + 8], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
return (
<div key={i} style={{ padding: "14px 20px", borderBottom: `1px solid ${COLORS.border}`, display: "flex", alignItems: "center", gap: 12, opacity: op, transform: `translateY(${y}px)` }}>
<div style={{ width: 8, height: 8, borderRadius: "50%", background: e.unread ? COLORS.accent : "transparent", flexShrink: 0 }} />
<div style={{ fontSize: 15, fontWeight: 600, color: COLORS.text, width: 120 }}>{e.from}</div>
<div style={{ fontSize: 15, color: COLORS.textSecondary, flex: 1 }}>{e.subject}</div>
<div style={{ fontSize: 13, color: COLORS.textSecondary }}>{e.time}</div>
</div>
);
})}
</div>
{/* SquareMCP overlay */}
<div style={{ width: 320, opacity: interpolate(localFrame, [3 * fps, 4 * fps], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" }) }}>
<div style={{ background: COLORS.bgCard, borderRadius: 12, border: `1px solid ${COLORS.borderBlue}`, padding: 20 }}>
<div style={{ fontSize: 14, fontWeight: 700, color: COLORS.accentLight, marginBottom: 12 }}> SquareMCP</div>
<div style={{ fontSize: 14, color: COLORS.textSecondary, lineHeight: 1.6 }}>
Reading 47 emails...{"\n"}
Drafting responses...{"\n"}
<span style={{ color: COLORS.success, fontWeight: 600 }}> 5 replies queued</span>
</div>
</div>
</div>
</div>
</AbsoluteFill>
);
};
// ─── RATE BLAST SCENE ────────────────────────────────────────────────
const RateBlastScene = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const { opacity } = useSceneProgress(SCENES.rateBlast.start, SCENES.rateBlast.duration);
const localFrame = frame - SCENES.rateBlast.start * fps;
const count = Math.floor(interpolate(localFrame, [2 * fps, 6 * fps], [0, 200], { extrapolateLeft: "clamp", extrapolateRight: "clamp" }));
const barWidth = interpolate(localFrame, [2 * fps, 6 * fps], [0, 100], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
return (
<AbsoluteFill style={{ opacity, background: COLORS.bg, fontFamily: FONT, padding: 60, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center" }}>
<div style={{ fontSize: 32, fontWeight: 700, color: COLORS.text, marginBottom: 40 }}>
10:30 AM Daily Rate Update
</div>
<div style={{ width: 600, background: COLORS.bgCard, borderRadius: 12, border: `1px solid ${COLORS.border}`, padding: 32 }}>
<div style={{ fontSize: 14, color: COLORS.textSecondary, marginBottom: 8 }}>API Call</div>
<div style={{ fontSize: 16, fontFamily: "monospace", color: COLORS.accentLight, background: "rgba(14,99,246,0.1)", padding: 12, borderRadius: 6, marginBottom: 24 }}>
POST /api/email/send{"\n"}
to: 200 prospects{"\n"}
subject: "Today's Rates - May 14"{"\n"}
body: rate_table + personalized_note
</div>
<div style={{ display: "flex", alignItems: "center", gap: 16 }}>
<div style={{ flex: 1, height: 8, background: COLORS.border, borderRadius: 4, overflow: "hidden" }}>
<div style={{ width: `${barWidth}%`, height: "100%", background: COLORS.success, borderRadius: 4 }} />
</div>
<div style={{ fontSize: 24, fontWeight: 800, color: COLORS.success, minWidth: 80, textAlign: "right" }}>
{count}/200
</div>
</div>
<div style={{ fontSize: 14, color: COLORS.textSecondary, marginTop: 12, textAlign: "center" }}>
{count >= 200 ? <span style={{ color: COLORS.success, fontWeight: 600 }}> All 200 prospects notified in 3.2s</span> : "Sending..."}
</div>
</div>
</AbsoluteFill>
);
};
// ─── WHATSAPP SCENE ──────────────────────────────────────────────────
const WhatsAppScene = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const { opacity } = useSceneProgress(SCENES.whatsapp.start, SCENES.whatsapp.duration);
const localFrame = frame - SCENES.whatsapp.start * fps;
const msgOp = interpolate(localFrame, [1 * fps, 2 * fps], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
const replyOp = interpolate(localFrame, [3 * fps, 4 * fps], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
return (
<AbsoluteFill style={{ opacity, background: COLORS.bg, fontFamily: FONT, padding: 60, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center" }}>
<div style={{ fontSize: 32, fontWeight: 700, color: COLORS.text, marginBottom: 40 }}>
2:15 PM Client on WhatsApp
</div>
<div style={{ width: 420, background: COLORS.bgCard, borderRadius: 16, border: `1px solid ${COLORS.border}`, padding: 24 }}>
{/* Client message */}
<div style={{ opacity: msgOp, display: "flex", justifyContent: "flex-start", marginBottom: 16 }}>
<div style={{ background: "#2a2a3e", padding: "12px 16px", borderRadius: "18px 18px 18px 4px", maxWidth: "80%" }}>
<div style={{ fontSize: 15, color: COLORS.text }}>Hey Alex, did rates go up today?</div>
<div style={{ fontSize: 11, color: COLORS.textSecondary, marginTop: 4, textAlign: "right" }}>2:15 PM</div>
</div>
</div>
{/* SquareMCP reply */}
<div style={{ opacity: replyOp, display: "flex", justifyContent: "flex-end", marginBottom: 8 }}>
<div style={{ background: COLORS.accent, padding: "12px 16px", borderRadius: "18px 18px 4px 18px", maxWidth: "80%" }}>
<div style={{ fontSize: 15, color: "#fff" }}>Hi! Yes, 30-year fixed ticked up to 6.875%. Want me to lock you in?</div>
<div style={{ fontSize: 11, color: "rgba(255,255,255,0.7)", marginTop: 4, textAlign: "right" }}>2:15 PM · SquareMCP</div>
</div>
</div>
{/* Label */}
<div style={{ opacity: replyOp, textAlign: "center", marginTop: 12 }}>
<span style={{ fontSize: 12, color: COLORS.accentLight, fontWeight: 600, background: "rgba(14,99,246,0.15)", padding: "4px 10px", borderRadius: 12 }}>
Auto-responded via SquareMCP
</span>
</div>
</div>
</AbsoluteFill>
);
};
// ─── FACEBOOK SCENE ──────────────────────────────────────────────────
const FacebookScene = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const { opacity } = useSceneProgress(SCENES.facebook.start, SCENES.facebook.duration);
const localFrame = frame - SCENES.facebook.start * fps;
const commentOp = interpolate(localFrame, [1 * fps, 2 * fps], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
const replyOp = interpolate(localFrame, [3 * fps, 4 * fps], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
return (
<AbsoluteFill style={{ opacity, background: COLORS.bg, fontFamily: FONT, padding: 60, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center" }}>
<div style={{ fontSize: 32, fontWeight: 700, color: COLORS.text, marginBottom: 40 }}>
4:00 PM Facebook Page
</div>
<div style={{ width: 520, background: COLORS.bgCard, borderRadius: 12, border: `1px solid ${COLORS.border}`, padding: 24 }}>
{/* Post */}
<div style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 16 }}>
<div style={{ width: 40, height: 40, borderRadius: "50%", background: COLORS.accent, display: "flex", alignItems: "center", justifyContent: "center", fontSize: 18, fontWeight: 800, color: "#fff" }}>F</div>
<div>
<div style={{ fontSize: 15, fontWeight: 700, color: COLORS.text }}>Ferrari Lending</div>
<div style={{ fontSize: 13, color: COLORS.textSecondary }}>2 hrs ago</div>
</div>
</div>
<div style={{ fontSize: 15, color: COLORS.textSecondary, marginBottom: 20, lineHeight: 1.5 }}>
🏠 Thinking about buying your first home? DM us for a free rate quote no credit pull required.
</div>
{/* Comment */}
<div style={{ opacity: commentOp, borderTop: `1px solid ${COLORS.border}`, paddingTop: 16 }}>
<div style={{ display: "flex", gap: 12 }}>
<div style={{ width: 32, height: 32, borderRadius: "50%", background: "#3e3e5e", flexShrink: 0 }} />
<div>
<div style={{ fontSize: 14, fontWeight: 600, color: COLORS.text }}>Carlos V.</div>
<div style={{ fontSize: 14, color: COLORS.textSecondary, marginTop: 2 }}>Do you work with FHA loans?</div>
</div>
</div>
</div>
{/* Reply */}
<div style={{ opacity: replyOp, marginTop: 12, paddingLeft: 44 }}>
<div style={{ display: "flex", gap: 12 }}>
<div style={{ width: 32, height: 32, borderRadius: "50%", background: COLORS.accent, flexShrink: 0, display: "flex", alignItems: "center", justifyContent: "center", fontSize: 12, fontWeight: 800, color: "#fff" }}>F</div>
<div>
<div style={{ fontSize: 14, fontWeight: 600, color: COLORS.text }}>Ferrari Lending</div>
<div style={{ fontSize: 14, color: COLORS.textSecondary, marginTop: 2 }}>Yes! FHA, VA, and conventional. Send us a DM and we'll get you pre-qualified today. 🎯</div>
<div style={{ fontSize: 12, color: COLORS.accentLight, marginTop: 4 }}> Replied via SquareMCP</div>
</div>
</div>
</div>
</div>
</AbsoluteFill>
);
};
// ─── OUTRO ───────────────────────────────────────────────────────────
const OutroScene = () => {
const { opacity } = useSceneProgress(SCENES.outro.start, SCENES.outro.duration);
const localFrame = useCurrentFrame() - SCENES.outro.start * 30;
const scale = spring({ frame: localFrame, fps: 30, config: SPRING_CFG, from: 0.9, to: 1 });
return (
<AbsoluteFill style={{ opacity, background: COLORS.bg, fontFamily: FONT, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center" }}>
<div style={{ transform: `scale(${scale})`, display: "flex", flexDirection: "column", alignItems: "center" }}>
<div style={{ width: 64, height: 64, borderRadius: 16, background: COLORS.accent, display: "flex", alignItems: "center", justifyContent: "center", fontSize: 28, fontWeight: 800, color: "#fff" }}></div>
<div style={{ fontSize: 48, fontWeight: 800, color: COLORS.text, marginTop: 24, letterSpacing: "-0.02em" }}>SquareMCP</div>
<div style={{ fontSize: 22, color: COLORS.textSecondary, marginTop: 12 }}>One API. Every Platform.</div>
<div style={{ fontSize: 16, color: COLORS.accentLight, marginTop: 32, fontWeight: 600 }}>squaremcp.com</div>
</div>
</AbsoluteFill>
);
};
// ─── MAIN COMPOSITION ────────────────────────────────────────────────
export const SquareMCPBrokerDemo = () => {
return (
<AbsoluteFill style={{ background: COLORS.bg }}>
<TitleScene />
<EmailInboxScene />
<RateBlastScene />
<WhatsAppScene />
<FacebookScene />
<OutroScene />
</AbsoluteFill>
);
};