feat(remotion): WhatsApp template management demo video for Meta app review

16s landscape (1920x1080): shows SquareMCP chat prompt triggering an
animated cURL call to the Business Management API creating a message
template (HEADER/BODY/FOOTER components with variables), then the right
panel renders a WhatsApp phone UI previewing the template bubble with
variable placeholders highlighted and a PENDING status badge.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Garfield
2026-05-14 20:38:35 -04:00
parent 195ad0b3d1
commit fd7ea2fee5
4 changed files with 401 additions and 0 deletions

Binary file not shown.

View File

@@ -12,6 +12,7 @@ import {
SquareMCPTikTokProof,
} from "./SquareMCPTikTok";
import { SquareMCPWhatsApp } from "./SquareMCPWhatsApp";
import { SquareMCPWhatsAppTemplate } from "./SquareMCPWhatsAppTemplate";
export const RemotionRoot = () => {
return (
@@ -96,6 +97,14 @@ export const RemotionRoot = () => {
width={1920}
height={1080}
/>
<Composition
id="SquareMCPWhatsAppTemplate"
component={SquareMCPWhatsAppTemplate}
durationInFrames={16 * 30}
fps={30}
width={1920}
height={1080}
/>
</>
);
};

View File

@@ -0,0 +1,44 @@
import { AbsoluteFill, Sequence, useVideoConfig } from "remotion";
import { WhatsAppBackground } from "./scenes/whatsapp/WhatsAppBackground";
import { WhatsAppTemplateScreen } from "./scenes/whatsapp/WhatsAppTemplateScreen";
import { FONT, SPRING_CFG } from "./styles";
import { spring, useCurrentFrame } from "remotion";
const WA_GREEN = "#25D366";
const Intro = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const logoIn = spring({ fps, frame, config: SPRING_CFG });
const textIn = spring({ fps, frame: Math.max(0, frame - 18), config: SPRING_CFG });
const subIn = spring({ fps, frame: Math.max(0, frame - 36), config: SPRING_CFG });
return (
<AbsoluteFill style={{ alignItems: "center", justifyContent: "center", display: "flex", flexDirection: "column", gap: 28 }}>
<div style={{ opacity: logoIn, transform: `scale(${0.7 + logoIn * 0.3})`, fontSize: 80 }}>📋</div>
<div style={{ fontFamily: FONT, color: "#ffffff", fontSize: 60, fontWeight: 800, textAlign: "center", lineHeight: 1.1, opacity: textIn, transform: `translateY(${(1 - textIn) * 36}px)` }}>
WhatsApp Template Management
</div>
<div style={{ fontFamily: FONT, color: WA_GREEN, fontSize: 30, fontWeight: 600, opacity: subIn, transform: `translateY(${(1 - subIn) * 20}px)` }}>
Creating message templates via the Business API
</div>
</AbsoluteFill>
);
};
// Total: 3s intro + 13s template screen = 16s @ 30fps = 480 frames
export const SquareMCPWhatsAppTemplate = () => {
const { fps } = useVideoConfig();
return (
<AbsoluteFill>
<WhatsAppBackground />
<Sequence durationInFrames={3 * fps}>
<Intro />
</Sequence>
<Sequence from={3 * fps}>
<WhatsAppTemplateScreen />
</Sequence>
</AbsoluteFill>
);
};

View File

@@ -0,0 +1,348 @@
import {
AbsoluteFill,
interpolate,
spring,
useCurrentFrame,
useVideoConfig,
} from "remotion";
import { FONT, SPRING_CFG, SPRING_SOFT } from "../../styles";
const WA_GREEN = "#25D366";
const WA_DARK = "#111b21";
const WA_HEADER = "#202c33";
const TEMPLATE_REQUEST_LINES = [
"curl -X POST \\",
" https://graph.facebook.com/v18.0/ \\",
" {BUSINESS_ACCOUNT_ID}/message_templates \\",
" -H 'Authorization: Bearer {TOKEN}' \\",
" -H 'Content-Type: application/json' \\",
" -d '{",
' "name": "pilot_request_alert",',
' "language": "en_US",',
' "category": "MARKETING",',
' "components": [',
' { "type": "HEADER", "format": "TEXT",',
' "text": "New Pilot Request 🚀" },',
' { "type": "BODY",',
' "text": "{{1}} from {{2}} wants to connect.\\nUse case: {{3}}" },',
' { "type": "FOOTER",',
' "text": "Sent via SquareMCP" }',
" ]",
" }'",
];
const RESPONSE_LINES = [
"{",
' "id": "1234567890123456",',
' "status": "PENDING",',
' "category": "MARKETING"',
"}",
];
// ── Left: API call panel ──────────────────────────────────────────────────────
const LeftPanel = ({ frame, fps }: { frame: number; fps: number }) => {
const panelIn = spring({ fps, frame, config: SPRING_SOFT });
const chatIn = spring({ fps, frame: Math.max(0, frame - 10), config: SPRING_CFG });
const termIn = spring({ fps, frame: Math.max(0, frame - 30), config: SPRING_CFG });
const reqProgress = interpolate(frame, [40, 170], [0, TEMPLATE_REQUEST_LINES.length], { extrapolateRight: "clamp" });
const resProgress = interpolate(frame, [185, 230], [0, RESPONSE_LINES.length], { extrapolateRight: "clamp" });
return (
<div
style={{
flex: 1,
padding: "40px 32px 40px 48px",
display: "flex",
flexDirection: "column",
gap: 24,
opacity: panelIn,
transform: `translateX(${(1 - panelIn) * -60}px)`,
}}
>
<div style={{ fontFamily: FONT, color: "rgba(255,255,255,0.4)", fontSize: 20, fontWeight: 600, letterSpacing: 2, textTransform: "uppercase" }}>
SquareMCP Creating message template
</div>
{/* Chat prompt */}
<div
style={{
borderRadius: 20,
border: "1px solid rgba(14,99,246,0.35)",
background: "rgba(9,12,22,0.94)",
padding: "20px 24px",
opacity: chatIn,
transform: `translateY(${(1 - chatIn) * 30}px)`,
}}
>
<div style={{ fontFamily: FONT, color: "rgba(255,255,255,0.4)", fontSize: 16, marginBottom: 10 }}>Chat</div>
<div
style={{
marginLeft: "auto",
borderRadius: 18,
background: "linear-gradient(135deg, rgba(14,99,246,0.95), rgba(125,182,255,0.9))",
padding: "16px 20px",
color: "#08111f",
fontFamily: FONT,
fontSize: 22,
fontWeight: 700,
lineHeight: 1.3,
maxWidth: "90%",
}}
>
Create a WhatsApp message template<br />
for new pilot request alerts.
</div>
</div>
{/* Terminal */}
<div
style={{
borderRadius: 16,
background: "#0d1117",
border: "1px solid rgba(37,211,102,0.2)",
padding: "18px 20px",
flex: 1,
opacity: termIn,
transform: `translateY(${(1 - termIn) * 30}px)`,
overflow: "hidden",
}}
>
<div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 14 }}>
{["#ff5f57", "#febc2e", "#28c840"].map((c) => (
<div key={c} style={{ width: 12, height: 12, borderRadius: 999, background: c }} />
))}
<div style={{ marginLeft: 8, fontFamily: "ui-monospace, monospace", color: "rgba(255,255,255,0.3)", fontSize: 14 }}>
terminal curl
</div>
</div>
<div style={{ fontFamily: "ui-monospace, monospace", color: "#7ee787", fontSize: 15, marginBottom: 4 }}>$</div>
{TEMPLATE_REQUEST_LINES.slice(0, Math.ceil(reqProgress)).map((line, i) => (
<div
key={i}
style={{
fontFamily: "ui-monospace, monospace",
color: line.includes('"type"') || line.includes('"text"') || line.includes('"format"') || line.includes('"category"') || line.includes('"name"') || line.includes('"language"')
? "#79c0ff"
: line.includes("HEADER") || line.includes("BODY") || line.includes("FOOTER") || line.includes("MARKETING")
? "#ffa657"
: "#e6edf3",
fontSize: 14,
lineHeight: 1.55,
opacity: i < reqProgress - 1 ? 1 : reqProgress - i,
}}
>
{line}
</div>
))}
{resProgress > 0 && (
<div style={{ marginTop: 10 }}>
<div style={{ fontFamily: "ui-monospace, monospace", color: WA_GREEN, fontSize: 13, marginBottom: 3 }}>
# Response (200 OK) template submitted for review
</div>
{RESPONSE_LINES.slice(0, Math.ceil(resProgress)).map((line, i) => (
<div
key={i}
style={{
fontFamily: "ui-monospace, monospace",
color: line.includes("PENDING") ? "#ffa657" : line.includes("id") ? WA_GREEN : "#e6edf3",
fontSize: 14,
lineHeight: 1.55,
opacity: i < resProgress - 1 ? 1 : resProgress - i,
}}
>
{line}
</div>
))}
</div>
)}
</div>
</div>
);
};
// ── Right: template preview panel ────────────────────────────────────────────
const RightPanel = ({ frame, fps }: { frame: number; fps: number }) => {
const panelIn = spring({ fps, frame, config: SPRING_SOFT });
const labelIn = spring({ fps, frame: Math.max(0, frame - 15), config: SPRING_CFG });
const headerIn = spring({ fps, frame: Math.max(0, frame - 220), config: SPRING_CFG });
const bodyIn = spring({ fps, frame: Math.max(0, frame - 245), config: SPRING_CFG });
const footerIn = spring({ fps, frame: Math.max(0, frame - 265), config: SPRING_CFG });
const badgeIn = spring({ fps, frame: Math.max(0, frame - 290), config: SPRING_CFG });
return (
<div
style={{
width: 460,
padding: "40px 48px 40px 24px",
display: "flex",
flexDirection: "column",
gap: 24,
opacity: panelIn,
transform: `translateX(${(1 - panelIn) * 60}px)`,
}}
>
<div
style={{
fontFamily: FONT,
color: "rgba(255,255,255,0.4)",
fontSize: 20,
fontWeight: 600,
letterSpacing: 2,
textTransform: "uppercase",
opacity: labelIn,
}}
>
Template Preview
</div>
{/* Phone showing template */}
<div
style={{
borderRadius: 28,
border: "6px solid #2a2a2a",
background: WA_DARK,
overflow: "hidden",
boxShadow: "0 32px 80px rgba(0,0,0,0.7)",
flex: 1,
display: "flex",
flexDirection: "column",
}}
>
{/* Header bar */}
<div style={{ background: WA_HEADER, padding: "12px 16px", display: "flex", alignItems: "center", gap: 10 }}>
<div style={{ width: 32, height: 32, borderRadius: 999, background: WA_GREEN, display: "flex", alignItems: "center", justifyContent: "center" }}>
<div style={{ fontFamily: FONT, color: "white", fontSize: 14, fontWeight: 700 }}>S</div>
</div>
<div>
<div style={{ fontFamily: FONT, color: "white", fontSize: 13, fontWeight: 600 }}>SquareMCP</div>
<div style={{ fontFamily: FONT, color: "rgba(255,255,255,0.45)", fontSize: 11 }}>Business Account</div>
</div>
</div>
{/* Chat body */}
<div style={{ flex: 1, background: "#0b141a", padding: "16px 12px", display: "flex", flexDirection: "column", justifyContent: "flex-end", gap: 8 }}>
{/* Template message bubble */}
<div style={{ alignSelf: "flex-end", maxWidth: "90%" }}>
{/* Header component */}
<div
style={{
background: "#005c4b",
borderRadius: "16px 16px 0 0",
padding: "10px 14px 8px",
opacity: headerIn,
transform: `translateY(${(1 - headerIn) * 20}px)`,
}}
>
<div style={{ fontFamily: FONT, color: "rgba(255,255,255,0.55)", fontSize: 10, marginBottom: 4, textTransform: "uppercase", letterSpacing: 1 }}>
Header
</div>
<div style={{ fontFamily: FONT, color: "white", fontSize: 15, fontWeight: 700 }}>
New Pilot Request 🚀
</div>
</div>
{/* Body component */}
<div
style={{
background: "#005c4b",
padding: "8px 14px",
borderTop: "1px solid rgba(255,255,255,0.08)",
opacity: bodyIn,
transform: `translateY(${(1 - bodyIn) * 16}px)`,
}}
>
<div style={{ fontFamily: FONT, color: "rgba(255,255,255,0.55)", fontSize: 10, marginBottom: 4, textTransform: "uppercase", letterSpacing: 1 }}>
Body
</div>
<div style={{ fontFamily: FONT, color: "white", fontSize: 14, lineHeight: 1.5 }}>
<span style={{ background: "rgba(37,211,102,0.2)", borderRadius: 4, padding: "0 4px" }}>{"{{1}}"}</span>
{" from "}
<span style={{ background: "rgba(37,211,102,0.2)", borderRadius: 4, padding: "0 4px" }}>{"{{2}}"}</span>
{" wants to connect.\nUse case: "}
<span style={{ background: "rgba(37,211,102,0.2)", borderRadius: 4, padding: "0 4px" }}>{"{{3}}"}</span>
</div>
</div>
{/* Footer component */}
<div
style={{
background: "#005c4b",
borderRadius: "0 0 4px 16px",
padding: "6px 14px 20px",
borderTop: "1px solid rgba(255,255,255,0.08)",
position: "relative",
opacity: footerIn,
transform: `translateY(${(1 - footerIn) * 12}px)`,
}}
>
<div style={{ fontFamily: FONT, color: "rgba(255,255,255,0.55)", fontSize: 10, marginBottom: 3, textTransform: "uppercase", letterSpacing: 1 }}>
Footer
</div>
<div style={{ fontFamily: FONT, color: "rgba(255,255,255,0.5)", fontSize: 12 }}>
Sent via SquareMCP
</div>
<div style={{ position: "absolute", bottom: 6, right: 10, fontFamily: FONT, color: "rgba(255,255,255,0.4)", fontSize: 11 }}>
9:41 AM
</div>
</div>
</div>
</div>
{/* Input bar */}
<div style={{ background: WA_HEADER, padding: "8px 12px", display: "flex", alignItems: "center", gap: 8 }}>
<div style={{ flex: 1, background: "#2a3942", borderRadius: 24, padding: "8px 16px", fontFamily: FONT, color: "rgba(255,255,255,0.3)", fontSize: 13 }}>
Message
</div>
<div style={{ width: 36, height: 36, borderRadius: 999, background: WA_GREEN, display: "flex", alignItems: "center", justifyContent: "center" }}>
<svg width="16" height="16" viewBox="0 0 24 24" fill="white"><path d="M2 21l21-9L2 3v7l15 2-15 2v7z" /></svg>
</div>
</div>
</div>
{/* Status badge */}
<div
style={{
borderRadius: 16,
border: "1px solid rgba(255,166,87,0.4)",
background: "rgba(255,166,87,0.08)",
padding: "14px 20px",
display: "flex",
alignItems: "center",
gap: 12,
opacity: badgeIn,
transform: `translateY(${(1 - badgeIn) * 16}px)`,
}}
>
<div style={{ width: 10, height: 10, borderRadius: 999, background: "#ffa657", flexShrink: 0 }} />
<div style={{ fontFamily: FONT, color: "#ffa657", fontSize: 14, fontWeight: 600 }}>
Status: PENDING submitted to Meta for review
</div>
</div>
</div>
);
};
const Divider = () => (
<div style={{ width: 1, alignSelf: "stretch", background: "linear-gradient(to bottom, transparent, rgba(37,211,102,0.25), transparent)", margin: "40px 0" }} />
);
export const WhatsAppTemplateScreen = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
return (
<AbsoluteFill style={{ display: "flex", flexDirection: "row", alignItems: "stretch" }}>
<LeftPanel frame={frame} fps={fps} />
<Divider />
<RightPanel frame={frame} fps={fps} />
</AbsoluteFill>
);
};