feat: add asset preferences, video research, and Remotion ad assets
- Add thumbs-down feedback modal and preference API endpoint - Add AI UGC video platforms research doc - Add ReflectAd Remotion composition with public flow assets - Add gemini-ad-designer and poster-ad-designer pipeline skills - Add research_reflect_v1.1 pipeline script Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,581 @@
|
||||
import {
|
||||
AbsoluteFill,
|
||||
Img,
|
||||
interpolate,
|
||||
spring,
|
||||
useCurrentFrame,
|
||||
useVideoConfig,
|
||||
staticFile,
|
||||
} from "remotion";
|
||||
|
||||
export interface ReflectAdProps {
|
||||
platform: "instagram" | "tiktok";
|
||||
hookText: string;
|
||||
bodyText: string;
|
||||
ctaText: string;
|
||||
proofText: string;
|
||||
screenshotSrc: string;
|
||||
}
|
||||
|
||||
const COLORS = {
|
||||
sage: "#3d5a4c",
|
||||
bronze: "#c4956b",
|
||||
cream: "#f5f1eb",
|
||||
dark: "#141210",
|
||||
white: "#ffffff",
|
||||
sageLight: "#5a7d6a",
|
||||
bronzeLight: "#d4ad85",
|
||||
};
|
||||
|
||||
export const ReflectAd: React.FC<ReflectAdProps> = ({
|
||||
platform,
|
||||
hookText,
|
||||
bodyText,
|
||||
ctaText,
|
||||
proofText,
|
||||
screenshotSrc,
|
||||
}) => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps, durationInFrames, width, height } = useVideoConfig();
|
||||
|
||||
const isPolished = platform === "instagram";
|
||||
|
||||
// Scene boundaries (4 scenes per the brief)
|
||||
// Hook: 0 → ~20% | Phone reveal: ~20% → ~60% | Proof: ~60% → ~78% | CTA: ~78% → end
|
||||
const hookEnd = Math.floor(durationInFrames * 0.2);
|
||||
const phoneStart = hookEnd;
|
||||
const phoneEnd = Math.floor(durationInFrames * 0.6);
|
||||
const proofStart = phoneEnd;
|
||||
const proofEnd = Math.floor(durationInFrames * 0.78);
|
||||
const ctaStart = proofEnd;
|
||||
|
||||
// === HOOK SCENE ===
|
||||
const hookFadeIn = interpolate(frame, [0, 12], [0, 1], {
|
||||
extrapolateRight: "clamp",
|
||||
});
|
||||
const hookFadeOut = interpolate(
|
||||
frame,
|
||||
[hookEnd - 10, hookEnd],
|
||||
[1, 0],
|
||||
{ extrapolateLeft: "clamp", extrapolateRight: "clamp" }
|
||||
);
|
||||
const hookOpacity = Math.min(hookFadeIn, hookFadeOut);
|
||||
const hookY = isPolished
|
||||
? interpolate(frame, [0, 18], [30, 0], { extrapolateRight: "clamp" })
|
||||
: 0;
|
||||
// TikTok: pop-in scale
|
||||
const hookScale = isPolished
|
||||
? 1
|
||||
: spring({
|
||||
frame,
|
||||
fps,
|
||||
config: { damping: 14, stiffness: 120 },
|
||||
});
|
||||
|
||||
// === PHONE REVEAL SCENE ===
|
||||
const phoneSpring = spring({
|
||||
frame: Math.max(0, frame - phoneStart),
|
||||
fps,
|
||||
config: { damping: 16, stiffness: 60 },
|
||||
});
|
||||
const phoneOpacity = interpolate(
|
||||
frame,
|
||||
[phoneStart, phoneStart + 12, phoneEnd - 10, phoneEnd],
|
||||
[0, 1, 1, 0],
|
||||
{ extrapolateLeft: "clamp", extrapolateRight: "clamp" }
|
||||
);
|
||||
const bodyTextOpacity = interpolate(
|
||||
frame,
|
||||
[phoneStart + 18, phoneStart + 30],
|
||||
[0, 1],
|
||||
{ extrapolateLeft: "clamp", extrapolateRight: "clamp" }
|
||||
);
|
||||
const bodyTextFadeOut = interpolate(
|
||||
frame,
|
||||
[phoneEnd - 10, phoneEnd],
|
||||
[1, 0],
|
||||
{ extrapolateLeft: "clamp", extrapolateRight: "clamp" }
|
||||
);
|
||||
|
||||
// === PROOF SCENE ===
|
||||
const proofOpacity = interpolate(
|
||||
frame,
|
||||
[proofStart, proofStart + 12, proofEnd - 8, proofEnd],
|
||||
[0, 1, 1, 0],
|
||||
{ extrapolateLeft: "clamp", extrapolateRight: "clamp" }
|
||||
);
|
||||
const proofScale = spring({
|
||||
frame: Math.max(0, frame - proofStart),
|
||||
fps,
|
||||
config: { damping: 14 },
|
||||
});
|
||||
|
||||
// === CTA SCENE ===
|
||||
const ctaOpacity = interpolate(frame, [ctaStart, ctaStart + 14], [0, 1], {
|
||||
extrapolateLeft: "clamp",
|
||||
extrapolateRight: "clamp",
|
||||
});
|
||||
const ctaScale = spring({
|
||||
frame: Math.max(0, frame - ctaStart),
|
||||
fps,
|
||||
config: { damping: 12, stiffness: 80 },
|
||||
});
|
||||
const ctaPulse =
|
||||
frame > ctaStart + 24
|
||||
? 1 + 0.025 * Math.sin((frame - ctaStart - 24) * 0.12)
|
||||
: 1;
|
||||
|
||||
const phoneWidth = width * 0.52;
|
||||
const phoneHeight = phoneWidth * 2.05;
|
||||
|
||||
// Subtle background grain animation
|
||||
const grainOpacity = isPolished ? 0.03 : 0.06;
|
||||
|
||||
if (isPolished) {
|
||||
// ============================
|
||||
// POLISHED — Instagram Reels
|
||||
// Warm cream or dark bg, elegant serif, smooth fades
|
||||
// ============================
|
||||
return (
|
||||
<AbsoluteFill
|
||||
style={{
|
||||
backgroundColor: COLORS.dark,
|
||||
fontFamily: '"Cormorant Garamond", "Georgia", serif',
|
||||
}}
|
||||
>
|
||||
{/* Warm radial gradient */}
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
inset: 0,
|
||||
background: `radial-gradient(ellipse at 50% 25%, ${COLORS.sage}20 0%, transparent 55%), radial-gradient(ellipse at 50% 75%, ${COLORS.bronze}15 0%, transparent 50%)`,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Subtle grain texture */}
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
inset: 0,
|
||||
opacity: grainOpacity,
|
||||
backgroundImage: `url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='1'/%3E%3C/svg%3E")`,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* === HOOK === */}
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: "38%",
|
||||
left: "50%",
|
||||
transform: `translate(-50%, -50%) translateY(${hookY}px)`,
|
||||
opacity: hookOpacity,
|
||||
width: "82%",
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
fontSize: 62,
|
||||
fontWeight: 400,
|
||||
fontStyle: "italic",
|
||||
color: COLORS.cream,
|
||||
lineHeight: 1.25,
|
||||
letterSpacing: 0.5,
|
||||
}}
|
||||
>
|
||||
{hookText}
|
||||
</div>
|
||||
{/* Decorative line */}
|
||||
<div
|
||||
style={{
|
||||
width: 60,
|
||||
height: 2,
|
||||
backgroundColor: COLORS.bronze,
|
||||
margin: "28px auto 0",
|
||||
opacity: hookFadeIn,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* === PHONE REVEAL + BODY TEXT === */}
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: "6%",
|
||||
left: "50%",
|
||||
transform: `translateX(-50%) scale(${phoneSpring})`,
|
||||
opacity: phoneOpacity,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
gap: 24,
|
||||
}}
|
||||
>
|
||||
{/* Body text above phone */}
|
||||
<div
|
||||
style={{
|
||||
opacity: Math.min(bodyTextOpacity, bodyTextFadeOut),
|
||||
fontSize: 32,
|
||||
fontWeight: 300,
|
||||
color: COLORS.cream,
|
||||
textAlign: "center",
|
||||
maxWidth: width * 0.78,
|
||||
lineHeight: 1.4,
|
||||
fontFamily: '"Outfit", "Inter", sans-serif',
|
||||
letterSpacing: 0.3,
|
||||
}}
|
||||
>
|
||||
{bodyText}
|
||||
</div>
|
||||
|
||||
{/* Phone mockup */}
|
||||
<div
|
||||
style={{
|
||||
width: phoneWidth,
|
||||
height: phoneHeight,
|
||||
position: "relative",
|
||||
filter: "drop-shadow(0 24px 48px rgba(0,0,0,0.45))",
|
||||
}}
|
||||
>
|
||||
{/* Screenshot behind the frame */}
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: "3.2%",
|
||||
left: "4.2%",
|
||||
width: "91.6%",
|
||||
height: "93.6%",
|
||||
borderRadius: phoneWidth * 0.065,
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
<Img
|
||||
src={screenshotSrc}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
objectFit: "cover",
|
||||
objectPosition: "top center",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/* Phone frame on top */}
|
||||
<Img
|
||||
src={staticFile("phone.png")}
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
objectFit: "contain",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* === PROOF === */}
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: "42%",
|
||||
left: "50%",
|
||||
transform: `translate(-50%, -50%) scale(${proofScale})`,
|
||||
opacity: proofOpacity,
|
||||
textAlign: "center",
|
||||
width: "80%",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
fontSize: 40,
|
||||
fontWeight: 400,
|
||||
fontStyle: "italic",
|
||||
color: COLORS.bronze,
|
||||
lineHeight: 1.3,
|
||||
}}
|
||||
>
|
||||
{proofText}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: 26,
|
||||
fontWeight: 300,
|
||||
color: "rgba(245, 241, 235, 0.6)",
|
||||
marginTop: 16,
|
||||
fontFamily: '"Outfit", "Inter", sans-serif',
|
||||
}}
|
||||
>
|
||||
Loved by thousands of mindful users
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* === CTA === */}
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
inset: 0,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
opacity: ctaOpacity,
|
||||
transform: `scale(${ctaScale})`,
|
||||
gap: 36,
|
||||
}}
|
||||
>
|
||||
{/* App name */}
|
||||
<div
|
||||
style={{
|
||||
fontSize: 52,
|
||||
fontWeight: 400,
|
||||
fontStyle: "italic",
|
||||
color: COLORS.cream,
|
||||
letterSpacing: 2,
|
||||
}}
|
||||
>
|
||||
Reflect
|
||||
</div>
|
||||
|
||||
{/* Tagline */}
|
||||
<div
|
||||
style={{
|
||||
fontSize: 26,
|
||||
fontWeight: 300,
|
||||
color: "rgba(245, 241, 235, 0.7)",
|
||||
fontFamily: '"Outfit", "Inter", sans-serif',
|
||||
marginTop: -12,
|
||||
}}
|
||||
>
|
||||
A quiet space for your inner world
|
||||
</div>
|
||||
|
||||
{/* CTA button */}
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: COLORS.sage,
|
||||
color: COLORS.cream,
|
||||
fontSize: 32,
|
||||
fontWeight: 500,
|
||||
padding: "22px 64px",
|
||||
borderRadius: 28,
|
||||
textAlign: "center",
|
||||
boxShadow: `0 8px 32px ${COLORS.sage}60`,
|
||||
transform: `scale(${ctaPulse})`,
|
||||
fontFamily: '"Outfit", "Inter", sans-serif',
|
||||
}}
|
||||
>
|
||||
{ctaText}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Small logo watermark bottom-right */}
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: 40,
|
||||
right: 40,
|
||||
opacity: interpolate(frame, [0, durationInFrames * 0.15], [0, 0.4], {
|
||||
extrapolateRight: "clamp",
|
||||
}),
|
||||
fontSize: 18,
|
||||
fontWeight: 300,
|
||||
color: COLORS.cream,
|
||||
fontFamily: '"Outfit", "Inter", sans-serif',
|
||||
letterSpacing: 1,
|
||||
}}
|
||||
>
|
||||
reflect
|
||||
</div>
|
||||
</AbsoluteFill>
|
||||
);
|
||||
}
|
||||
|
||||
// ============================
|
||||
// AUTHENTIC — TikTok
|
||||
// Dark bg, bold text, quick cuts, max 6 words per frame, native feel
|
||||
// ============================
|
||||
return (
|
||||
<AbsoluteFill
|
||||
style={{
|
||||
backgroundColor: "#0a0a0a",
|
||||
fontFamily: '"Inter", "SF Pro Display", system-ui, sans-serif',
|
||||
}}
|
||||
>
|
||||
{/* Subtle warm vignette */}
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
inset: 0,
|
||||
background: `radial-gradient(ellipse at 50% 50%, transparent 40%, rgba(0,0,0,0.4) 100%)`,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* === HOOK === */}
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: "40%",
|
||||
left: "50%",
|
||||
transform: `translate(-50%, -50%) scale(${hookScale})`,
|
||||
opacity: hookOpacity,
|
||||
width: "88%",
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
fontSize: 72,
|
||||
fontWeight: 800,
|
||||
color: COLORS.white,
|
||||
lineHeight: 1.15,
|
||||
letterSpacing: -1,
|
||||
textShadow: "0 4px 20px rgba(0,0,0,0.5)",
|
||||
}}
|
||||
>
|
||||
{hookText}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* === PHONE REVEAL + BODY TEXT === */}
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: "5%",
|
||||
left: "50%",
|
||||
transform: `translateX(-50%) scale(${phoneSpring})`,
|
||||
opacity: phoneOpacity,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
gap: 20,
|
||||
}}
|
||||
>
|
||||
{/* Body text — bold, short */}
|
||||
<div
|
||||
style={{
|
||||
opacity: Math.min(bodyTextOpacity, bodyTextFadeOut),
|
||||
fontSize: 38,
|
||||
fontWeight: 700,
|
||||
color: COLORS.white,
|
||||
textAlign: "center",
|
||||
maxWidth: width * 0.85,
|
||||
lineHeight: 1.25,
|
||||
textShadow: "0 2px 12px rgba(0,0,0,0.4)",
|
||||
}}
|
||||
>
|
||||
{bodyText}
|
||||
</div>
|
||||
|
||||
{/* Phone mockup */}
|
||||
<div
|
||||
style={{
|
||||
width: phoneWidth,
|
||||
height: phoneHeight,
|
||||
position: "relative",
|
||||
filter: "drop-shadow(0 16px 40px rgba(0,0,0,0.6))",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: "3.2%",
|
||||
left: "4.2%",
|
||||
width: "91.6%",
|
||||
height: "93.6%",
|
||||
borderRadius: phoneWidth * 0.065,
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
<Img
|
||||
src={screenshotSrc}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
objectFit: "cover",
|
||||
objectPosition: "top center",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Img
|
||||
src={staticFile("phone.png")}
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
objectFit: "contain",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* === PROOF === */}
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: "42%",
|
||||
left: "50%",
|
||||
transform: `translate(-50%, -50%) scale(${proofScale})`,
|
||||
opacity: proofOpacity,
|
||||
textAlign: "center",
|
||||
width: "85%",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
fontSize: 48,
|
||||
fontWeight: 800,
|
||||
color: COLORS.bronze,
|
||||
lineHeight: 1.2,
|
||||
textShadow: "0 2px 16px rgba(0,0,0,0.4)",
|
||||
}}
|
||||
>
|
||||
{proofText}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* === CTA === */}
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
inset: 0,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
opacity: ctaOpacity,
|
||||
transform: `scale(${ctaScale})`,
|
||||
gap: 28,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
fontSize: 58,
|
||||
fontWeight: 800,
|
||||
color: COLORS.white,
|
||||
letterSpacing: -1,
|
||||
}}
|
||||
>
|
||||
Reflect
|
||||
</div>
|
||||
|
||||
{/* CTA text (no button — native TikTok feel) */}
|
||||
<div
|
||||
style={{
|
||||
fontSize: 36,
|
||||
fontWeight: 700,
|
||||
color: COLORS.bronze,
|
||||
textAlign: "center",
|
||||
textShadow: "0 2px 12px rgba(0,0,0,0.3)",
|
||||
transform: `scale(${ctaPulse})`,
|
||||
}}
|
||||
>
|
||||
{ctaText}
|
||||
</div>
|
||||
</div>
|
||||
</AbsoluteFill>
|
||||
);
|
||||
};
|
||||
@@ -1,20 +1,176 @@
|
||||
import { Composition, staticFile } from "remotion";
|
||||
import { HoneyDueAd } from "./HoneyDueAd";
|
||||
import { ReflectAd } from "./ReflectAd";
|
||||
|
||||
const SCREENSHOT = staticFile("tasks_overdue.png");
|
||||
const HONEYDUESCREEN = staticFile("tasks_overdue.png");
|
||||
|
||||
// 15s @ 30fps for Instagram, 12s for TikTok
|
||||
const IG_FRAMES = 450;
|
||||
const TT_FRAMES = 360;
|
||||
// 15s @ 30fps for Instagram, various for TikTok
|
||||
const IG_FRAMES = 450; // 15s
|
||||
const TT_13_FRAMES = 390; // 13s
|
||||
const TT_12_FRAMES = 360; // 12s
|
||||
|
||||
export const RemotionRoot: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
{/* === GEMINI AD VIDEOS === */}
|
||||
{/* ============================================ */}
|
||||
{/* === REFLECT v1.1 — GEMINI AD VIDEOS ======= */}
|
||||
{/* ============================================ */}
|
||||
|
||||
{/* Gemini IG Hook 2 — "My therapist asked me to track my moods" */}
|
||||
<Composition
|
||||
id="Reflect-Gemini-IG-Hook2"
|
||||
component={ReflectAd}
|
||||
durationInFrames={IG_FRAMES}
|
||||
fps={30}
|
||||
width={1080}
|
||||
height={1920}
|
||||
defaultProps={{
|
||||
platform: "instagram" as const,
|
||||
hookText: "My therapist asked me\nto track my moods.",
|
||||
bodyText: "She didn't expect an AI-powered mood report.",
|
||||
ctaText: "Begin reflecting",
|
||||
proofText: "Your check-ins become patterns\nyou can share with your therapist",
|
||||
screenshotSrc: staticFile("flow_07_detail_after.png"),
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Gemini IG Hook 4 — "Two minutes of reflection" */}
|
||||
<Composition
|
||||
id="Reflect-Gemini-IG-Hook4"
|
||||
component={ReflectAd}
|
||||
durationInFrames={420}
|
||||
fps={30}
|
||||
width={1080}
|
||||
height={1920}
|
||||
defaultProps={{
|
||||
platform: "instagram" as const,
|
||||
hookText: "Two minutes of reflection.\nZero blank pages.",
|
||||
bodyText: "Guided questions that adapt to your mood.",
|
||||
ctaText: "Start your journal",
|
||||
proofText: "AI-powered reports turn daily moments\ninto real understanding",
|
||||
screenshotSrc: staticFile("flow_03_q1.png"),
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Gemini TT Hook 1 — "I stopped journaling" */}
|
||||
<Composition
|
||||
id="Reflect-Gemini-TT-Hook1"
|
||||
component={ReflectAd}
|
||||
durationInFrames={TT_13_FRAMES}
|
||||
fps={30}
|
||||
width={1080}
|
||||
height={1920}
|
||||
defaultProps={{
|
||||
platform: "tiktok" as const,
|
||||
hookText: "I stopped journaling.",
|
||||
bodyText: "Then I found one that\nasks the questions for you.",
|
||||
ctaText: "Try Reflect free",
|
||||
proofText: "No blank pages.\nJust the right question.",
|
||||
screenshotSrc: staticFile("flow_q2_typing.png"),
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Gemini TT Hook 3 — "POV: the app asks exactly what you needed" */}
|
||||
<Composition
|
||||
id="Reflect-Gemini-TT-Hook3"
|
||||
component={ReflectAd}
|
||||
durationInFrames={TT_12_FRAMES}
|
||||
fps={30}
|
||||
width={1080}
|
||||
height={1920}
|
||||
defaultProps={{
|
||||
platform: "tiktok" as const,
|
||||
hookText: "POV: it asks exactly\nwhat you needed.",
|
||||
bodyText: "Questions based on how you feel.\nNot random. Not generic.",
|
||||
ctaText: "Download Reflect free",
|
||||
proofText: "The one that makes\nyou pause and think.",
|
||||
screenshotSrc: staticFile("flow_05_q3_scrolled.png"),
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* ============================================ */}
|
||||
{/* === REFLECT v1.1 — POSTER AD VIDEOS ======= */}
|
||||
{/* ============================================ */}
|
||||
|
||||
{/* Poster IG Hook 2 — "My therapist asked me to track my moods" */}
|
||||
<Composition
|
||||
id="Reflect-Poster-IG-Hook2"
|
||||
component={ReflectAd}
|
||||
durationInFrames={IG_FRAMES}
|
||||
fps={30}
|
||||
width={1080}
|
||||
height={1920}
|
||||
defaultProps={{
|
||||
platform: "instagram" as const,
|
||||
hookText: "My therapist asked me\nto track my moods.",
|
||||
bodyText: "She didn't expect an AI-powered mood report.",
|
||||
ctaText: "Begin reflecting",
|
||||
proofText: "Patterns you can understand\nand share with your therapist",
|
||||
screenshotSrc: staticFile("flow_06_q4.png"),
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Poster IG Hook 5 — "What if your phone helped you feel" */}
|
||||
<Composition
|
||||
id="Reflect-Poster-IG-Hook5"
|
||||
component={ReflectAd}
|
||||
durationInFrames={IG_FRAMES}
|
||||
fps={30}
|
||||
width={1080}
|
||||
height={1920}
|
||||
defaultProps={{
|
||||
platform: "instagram" as const,
|
||||
hookText: "What if your phone\nhelped you feel\ninstead of just scroll?",
|
||||
bodyText: "Guided reflection that adapts to your mood.",
|
||||
ctaText: "Begin reflecting",
|
||||
proofText: "AI-powered insights reveal\npatterns you can't see alone",
|
||||
screenshotSrc: staticFile("flow_07_detail_after.png"),
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Poster TT Hook 1 — "I stopped journaling" */}
|
||||
<Composition
|
||||
id="Reflect-Poster-TT-Hook1"
|
||||
component={ReflectAd}
|
||||
durationInFrames={TT_13_FRAMES}
|
||||
fps={30}
|
||||
width={1080}
|
||||
height={1920}
|
||||
defaultProps={{
|
||||
platform: "tiktok" as const,
|
||||
hookText: "I stopped journaling.",
|
||||
bodyText: "Tap your mood.\nIt asks the right question.",
|
||||
ctaText: "Try Reflect free",
|
||||
proofText: "No pressure.\nJust the right question.",
|
||||
screenshotSrc: staticFile("flow_03_q1.png"),
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Poster TT Hook 3 — "POV: the app asks exactly what you needed" */}
|
||||
<Composition
|
||||
id="Reflect-Poster-TT-Hook3"
|
||||
component={ReflectAd}
|
||||
durationInFrames={TT_12_FRAMES}
|
||||
fps={30}
|
||||
width={1080}
|
||||
height={1920}
|
||||
defaultProps={{
|
||||
platform: "tiktok" as const,
|
||||
hookText: "POV: it asks exactly\nwhat you needed.",
|
||||
bodyText: "Log your mood.\nGet a guided question.",
|
||||
ctaText: "Download Reflect free",
|
||||
proofText: "Not random.\nThe one you needed.",
|
||||
screenshotSrc: staticFile("flow_06_q4.png"),
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* ============================================ */}
|
||||
{/* === HONEYDUE (LEGACY COMPOSITIONS) ========= */}
|
||||
{/* ============================================ */}
|
||||
<Composition
|
||||
id="Gemini-IG-Feed-Cost"
|
||||
component={HoneyDueAd}
|
||||
durationInFrames={IG_FRAMES}
|
||||
durationInFrames={450}
|
||||
fps={30}
|
||||
width={1080}
|
||||
height={1920}
|
||||
@@ -24,13 +180,13 @@ export const RemotionRoot: React.FC = () => {
|
||||
bodyText: "honeyDue tracks every task so you never skip the small stuff.",
|
||||
ctaText: "Download Free",
|
||||
proofText: "Trusted by thousands of homeowners",
|
||||
screenshotSrc: SCREENSHOT,
|
||||
screenshotSrc: HONEYDUESCREEN,
|
||||
}}
|
||||
/>
|
||||
<Composition
|
||||
id="Gemini-IG-Stories-FirstTimer"
|
||||
component={HoneyDueAd}
|
||||
durationInFrames={IG_FRAMES}
|
||||
durationInFrames={450}
|
||||
fps={30}
|
||||
width={1080}
|
||||
height={1920}
|
||||
@@ -40,13 +196,13 @@ export const RemotionRoot: React.FC = () => {
|
||||
bodyText: "This app tells you what to fix and when.",
|
||||
ctaText: "Try honeyDue",
|
||||
proofText: "First-time homeowners love this",
|
||||
screenshotSrc: SCREENSHOT,
|
||||
screenshotSrc: HONEYDUESCREEN,
|
||||
}}
|
||||
/>
|
||||
<Composition
|
||||
id="Gemini-TT-SilentTodo"
|
||||
component={HoneyDueAd}
|
||||
durationInFrames={TT_FRAMES}
|
||||
durationInFrames={360}
|
||||
fps={30}
|
||||
width={1080}
|
||||
height={1920}
|
||||
@@ -56,13 +212,13 @@ export const RemotionRoot: React.FC = () => {
|
||||
bodyText: "HVAC filters. Gutters. Water heater. honeyDue sees it all.",
|
||||
ctaText: "Get Started Free",
|
||||
proofText: "Never miss maintenance again",
|
||||
screenshotSrc: SCREENSHOT,
|
||||
screenshotSrc: HONEYDUESCREEN,
|
||||
}}
|
||||
/>
|
||||
<Composition
|
||||
id="Gemini-TT-Forgetter"
|
||||
component={HoneyDueAd}
|
||||
durationInFrames={TT_FRAMES}
|
||||
durationInFrames={360}
|
||||
fps={30}
|
||||
width={1080}
|
||||
height={1920}
|
||||
@@ -72,15 +228,13 @@ export const RemotionRoot: React.FC = () => {
|
||||
bodyText: "honeyDue would have reminded me 8 times by now.",
|
||||
ctaText: "Download Free",
|
||||
proofText: "Your home maintenance safety net",
|
||||
screenshotSrc: SCREENSHOT,
|
||||
screenshotSrc: HONEYDUESCREEN,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* === CANVAS POSTER VIDEOS === */}
|
||||
<Composition
|
||||
id="Poster-IG-Feed-Cost"
|
||||
component={HoneyDueAd}
|
||||
durationInFrames={IG_FRAMES}
|
||||
durationInFrames={450}
|
||||
fps={30}
|
||||
width={1080}
|
||||
height={1920}
|
||||
@@ -90,13 +244,13 @@ export const RemotionRoot: React.FC = () => {
|
||||
bodyText: "The difference between a reminder and a disaster.",
|
||||
ctaText: "Download Free",
|
||||
proofText: "Join thousands of organized homeowners",
|
||||
screenshotSrc: SCREENSHOT,
|
||||
screenshotSrc: HONEYDUESCREEN,
|
||||
}}
|
||||
/>
|
||||
<Composition
|
||||
id="Poster-IG-Stories-FirstHouse"
|
||||
component={HoneyDueAd}
|
||||
durationInFrames={IG_FRAMES}
|
||||
durationInFrames={450}
|
||||
fps={30}
|
||||
width={1080}
|
||||
height={1920}
|
||||
@@ -106,13 +260,13 @@ export const RemotionRoot: React.FC = () => {
|
||||
bodyText: "100+ maintenance tasks. One app to track them all.",
|
||||
ctaText: "Try honeyDue",
|
||||
proofText: "Built for first-time homeowners",
|
||||
screenshotSrc: SCREENSHOT,
|
||||
screenshotSrc: HONEYDUESCREEN,
|
||||
}}
|
||||
/>
|
||||
<Composition
|
||||
id="Poster-TT-HiddenTodo"
|
||||
component={HoneyDueAd}
|
||||
durationInFrames={TT_FRAMES}
|
||||
durationInFrames={360}
|
||||
fps={30}
|
||||
width={1080}
|
||||
height={1920}
|
||||
@@ -122,13 +276,13 @@ export const RemotionRoot: React.FC = () => {
|
||||
bodyText: "Filters. Gutters. Drains. Vents. honeyDue tracks all of it.",
|
||||
ctaText: "Get Started Free",
|
||||
proofText: "See what you've been missing",
|
||||
screenshotSrc: SCREENSHOT,
|
||||
screenshotSrc: HONEYDUESCREEN,
|
||||
}}
|
||||
/>
|
||||
<Composition
|
||||
id="Poster-TT-HVAC2Years"
|
||||
component={HoneyDueAd}
|
||||
durationInFrames={TT_FRAMES}
|
||||
durationInFrames={360}
|
||||
fps={30}
|
||||
width={1080}
|
||||
height={1920}
|
||||
@@ -138,7 +292,7 @@ export const RemotionRoot: React.FC = () => {
|
||||
bodyText: "That's 8 missed reminders honeyDue would have sent.",
|
||||
ctaText: "Download Free",
|
||||
proofText: "Never forget again",
|
||||
screenshotSrc: SCREENSHOT,
|
||||
screenshotSrc: HONEYDUESCREEN,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user