add marketing movies

This commit is contained in:
Trey t
2026-01-26 18:03:51 -06:00
parent 77c28516fa
commit 2828e5eef7
19 changed files with 8467 additions and 10 deletions

View File

@@ -0,0 +1,674 @@
import {
AbsoluteFill,
Img,
staticFile,
useCurrentFrame,
useVideoConfig,
interpolate,
spring,
Sequence,
} from "remotion";
// Shared tiled background component
const TiledIconBackground: React.FC<{ color?: string }> = ({
color = "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
}) => {
const frame = useCurrentFrame();
const { width, height } = useVideoConfig();
const iconSize = 80;
const gap = 40;
const cellSize = iconSize + gap;
const cols = Math.ceil(width / cellSize) + 4;
const rows = Math.ceil(height / cellSize) + 4;
const offsetX = (frame * 0.3) % cellSize;
const offsetY = (frame * 0.2) % cellSize;
return (
<AbsoluteFill
style={{
background: color,
overflow: "hidden",
}}
>
<div
style={{
position: "absolute",
top: -cellSize * 2,
left: -cellSize * 2,
transform: `translate(${offsetX}px, ${offsetY}px)`,
}}
>
{[...Array(rows)].map((_, row) =>
[...Array(cols)].map((_, col) => {
const staggerX = row % 2 === 0 ? 0 : cellSize / 2;
return (
<Img
key={`${row}-${col}`}
src={staticFile("app-icon.png")}
style={{
position: "absolute",
width: iconSize,
height: iconSize,
left: col * cellSize + staggerX,
top: row * cellSize,
opacity: 0.08,
borderRadius: iconSize * 0.22,
}}
/>
);
})
)}
</div>
</AbsoluteFill>
);
};
// Phone frame component
const PhoneFrame: React.FC<{
mediaSrc: string;
width?: number;
rotation?: number;
style?: React.CSSProperties;
}> = ({ mediaSrc, width = 460, rotation = 0, style }) => {
const aspectRatio = 2760 / 1350;
const height = width * aspectRatio;
return (
<div
style={{
position: "relative",
width,
height,
transform: rotation ? `rotate(${rotation}deg)` : undefined,
filter: "drop-shadow(0 40px 80px rgba(0,0,0,0.5))",
...style,
}}
>
<div
style={{
position: "absolute",
top: "2.5%",
left: "5.33%",
right: "5.33%",
bottom: "2.5%",
borderRadius: width * 0.083,
overflow: "hidden",
backgroundColor: "#000",
}}
>
<Img
src={staticFile(mediaSrc)}
style={{
width: "100%",
height: "100%",
objectFit: "cover",
}}
/>
</div>
<Img
src={staticFile("phone.png")}
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
pointerEvents: "none",
}}
/>
</div>
);
};
// Scene 1: Hook - "What if understanding yourself took 5 seconds a day?"
const HookScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const textProgress = spring({ frame, fps, config: { damping: 200 } });
const textOpacity = interpolate(textProgress, [0, 1], [0, 1]);
const textY = interpolate(textProgress, [0, 1], [50, 0]);
const secondLineProgress = spring({
frame: frame - 15,
fps,
config: { damping: 200 },
});
const secondLineOpacity = interpolate(secondLineProgress, [0, 1], [0, 1]);
return (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #1e3a5f 0%, #2d5a87 100%)" />
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
padding: 60,
}}
>
<div
style={{
fontSize: 72,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textAlign: "center",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
lineHeight: 1.2,
transform: `translateY(${-textY}px)`,
opacity: textOpacity,
}}
>
What if understanding yourself
</div>
<div
style={{
fontSize: 72,
fontWeight: 800,
color: "#fbbf24",
fontFamily: "system-ui, -apple-system, sans-serif",
textAlign: "center",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
lineHeight: 1.2,
marginTop: 20,
opacity: secondLineOpacity,
}}
>
took 5 seconds a day?
</div>
</div>
</AbsoluteFill>
);
};
// Scene 2: Demo - Quick-fire mood logging
const DemoScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const widget1Progress = spring({
frame,
fps,
config: { damping: 12, stiffness: 80 },
});
const widget1Scale = interpolate(widget1Progress, [0, 1], [0.5, 1]);
const widget2Progress = spring({
frame: frame - 20,
fps,
config: { damping: 12, stiffness: 80 },
});
const widget2Scale = interpolate(widget2Progress, [0, 1], [0.5, 1]);
const widget3Progress = spring({
frame: frame - 40,
fps,
config: { damping: 12, stiffness: 80 },
});
const widget3Scale = interpolate(widget3Progress, [0, 1], [0.5, 1]);
return (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #059669 0%, #10b981 100%)" />
{/* Title */}
<div
style={{
position: "absolute",
top: 80,
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 56,
fontWeight: 700,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
}}
>
Log anywhere, anytime
</div>
</div>
{/* Three devices */}
<div
style={{
position: "absolute",
bottom: 100,
left: 0,
right: 0,
display: "flex",
justifyContent: "center",
alignItems: "flex-end",
gap: 40,
}}
>
{/* Widget */}
<div
style={{
transform: `scale(${widget1Scale})`,
filter: "drop-shadow(0 20px 40px rgba(0,0,0,0.4))",
}}
>
<Img
src={staticFile("screen2-widget.png")}
style={{
width: 280,
height: 280,
borderRadius: 30,
}}
/>
<div
style={{
fontSize: 24,
fontWeight: 600,
color: "white",
textAlign: "center",
marginTop: 16,
}}
>
Widget
</div>
</div>
{/* Phone */}
<div
style={{
transform: `scale(${widget2Scale})`,
}}
>
<PhoneFrame mediaSrc="screen1-day.png" width={320} />
<div
style={{
fontSize: 24,
fontWeight: 600,
color: "white",
textAlign: "center",
marginTop: 16,
}}
>
App
</div>
</div>
{/* Watch */}
<div
style={{
transform: `scale(${widget3Scale})`,
filter: "drop-shadow(0 20px 40px rgba(0,0,0,0.4))",
}}
>
<div style={{ position: "relative" }}>
<div
style={{
position: "absolute",
top: "22%",
left: "15%",
width: "70%",
height: "42%",
borderRadius: 12,
overflow: "hidden",
zIndex: 1,
}}
>
<Img
src={staticFile("screen2-watch.png")}
style={{
width: "100%",
height: "100%",
objectFit: "cover",
}}
/>
</div>
<Img
src={staticFile("watch-frame.png")}
style={{
width: 180,
height: 288,
position: "relative",
zIndex: 2,
}}
/>
</div>
<div
style={{
fontSize: 24,
fontWeight: 600,
color: "white",
textAlign: "center",
marginTop: 16,
}}
>
Watch
</div>
</div>
</div>
</AbsoluteFill>
);
};
// Scene 3: Payoff - Calendar filling with colors
const PayoffScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const phoneProgress = spring({
frame,
fps,
config: { damping: 12, stiffness: 80 },
});
const phoneScale = interpolate(phoneProgress, [0, 1], [0.8, 1]);
// Simulate cells filling in
const fillProgress = interpolate(frame, [0, fps * 4], [0, 1], {
extrapolateRight: "clamp",
});
return (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #7c3aed 0%, #8b5cf6 100%)" />
{/* Title */}
<div
style={{
position: "absolute",
top: 100,
left: 60,
right: 60,
}}
>
<div
style={{
fontSize: 72,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
lineHeight: 1.1,
}}
>
Watch patterns
<br />
emerge
</div>
</div>
{/* Phone with year view */}
<div
style={{
position: "absolute",
left: "50%",
bottom: -150,
transform: `translateX(-50%) scale(${phoneScale})`,
}}
>
<PhoneFrame mediaSrc="screen1-day.png" width={600} />
</div>
{/* Progress indicator */}
<div
style={{
position: "absolute",
bottom: 120,
left: 60,
right: 60,
}}
>
<div
style={{
height: 8,
borderRadius: 4,
backgroundColor: "rgba(255,255,255,0.3)",
overflow: "hidden",
}}
>
<div
style={{
height: "100%",
width: `${fillProgress * 100}%`,
backgroundColor: "white",
borderRadius: 4,
}}
/>
</div>
<div
style={{
fontSize: 24,
color: "rgba(255,255,255,0.8)",
marginTop: 12,
textAlign: "center",
}}
>
{Math.round(fillProgress * 365)} days tracked
</div>
</div>
</AbsoluteFill>
);
};
// Scene 4: AI Insights
const InsightsScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const cardProgress = spring({
frame,
fps,
config: { damping: 12, stiffness: 80 },
});
const cardScale = interpolate(cardProgress, [0, 1], [0.8, 1]);
const cardOpacity = interpolate(cardProgress, [0, 1], [0, 1]);
const sparkleProgress = spring({
frame: frame - 20,
fps,
config: { damping: 10, stiffness: 100 },
});
return (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #2563eb 0%, #3b82f6 100%)" />
{/* Title */}
<div
style={{
position: "absolute",
top: 100,
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 72,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
}}
>
AI understands you
</div>
</div>
{/* Insight card */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: `translate(-50%, -50%) scale(${cardScale})`,
opacity: cardOpacity,
}}
>
<div
style={{
background: "rgba(255,255,255,0.95)",
borderRadius: 30,
padding: 50,
width: 800,
boxShadow: "0 30px 60px rgba(0,0,0,0.3)",
}}
>
<div style={{ display: "flex", alignItems: "center", gap: 20, marginBottom: 30 }}>
<span
style={{
fontSize: 48,
transform: `scale(${interpolate(sparkleProgress, [0, 1], [0, 1])})`,
}}
>
</span>
<span
style={{
fontSize: 32,
fontWeight: 600,
color: "#1a1a1a",
}}
>
This Month
</span>
</div>
<div
style={{
fontSize: 36,
color: "#374151",
lineHeight: 1.5,
}}
>
"You tend to feel better on weekends. Consider bringing more of that
weekend energy into your weekdays."
</div>
</div>
</div>
</AbsoluteFill>
);
};
// Scene 5: CTA
const CTAScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const logoScale = spring({
frame,
fps,
config: { damping: 10, stiffness: 80 },
});
const textOpacity = spring({
frame: frame - 15,
fps,
config: { damping: 200 },
});
const glowIntensity = interpolate(Math.sin(frame * 0.1), [-1, 1], [0.3, 0.6]);
return (
<AbsoluteFill
style={{
justifyContent: "center",
alignItems: "center",
}}
>
<TiledIconBackground color="linear-gradient(180deg, #1e3a5f 0%, #2d5a87 100%)" />
<div
style={{
position: "absolute",
width: 500,
height: 500,
borderRadius: "50%",
background: `radial-gradient(circle, rgba(255,255,255,${glowIntensity}) 0%, transparent 70%)`,
transform: `scale(${interpolate(logoScale, [0, 1], [0.3, 1])})`,
}}
/>
<div
style={{
textAlign: "center",
transform: `scale(${interpolate(logoScale, [0, 1], [0.5, 1])})`,
}}
>
<Img
src={staticFile("app-icon.png")}
style={{
width: 200,
height: 200,
borderRadius: 200 * 0.22,
marginBottom: 40,
filter: `drop-shadow(0 0 60px rgba(255,255,255,${glowIntensity}))`,
}}
/>
<div
style={{
fontSize: 80,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
}}
>
Feels
</div>
<div
style={{
fontSize: 36,
fontWeight: 500,
color: "#fbbf24",
fontFamily: "system-ui, -apple-system, sans-serif",
marginTop: 30,
opacity: interpolate(textOpacity, [0, 1], [0, 1]),
}}
>
Start today. Understand tomorrow.
</div>
</div>
</AbsoluteFill>
);
};
// Main composition
export const ConceptASelfAwareness: React.FC = () => {
const { fps } = useVideoConfig();
return (
<AbsoluteFill>
<Sequence from={0} durationInFrames={Math.round(5 * fps)}>
<HookScene />
</Sequence>
<Sequence from={Math.round(5 * fps)} durationInFrames={Math.round(7 * fps)}>
<DemoScene />
</Sequence>
<Sequence from={Math.round(12 * fps)} durationInFrames={Math.round(6 * fps)}>
<PayoffScene />
</Sequence>
<Sequence from={Math.round(18 * fps)} durationInFrames={Math.round(7 * fps)}>
<InsightsScene />
</Sequence>
<Sequence from={Math.round(25 * fps)} durationInFrames={Math.round(5 * fps)}>
<CTAScene />
</Sequence>
</AbsoluteFill>
);
};

View File

@@ -0,0 +1,589 @@
import {
AbsoluteFill,
Img,
staticFile,
useCurrentFrame,
useVideoConfig,
interpolate,
spring,
Sequence,
} from "remotion";
// Shared tiled background component
const TiledIconBackground: React.FC<{ color?: string }> = ({
color = "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
}) => {
const frame = useCurrentFrame();
const { width, height } = useVideoConfig();
const iconSize = 80;
const gap = 40;
const cellSize = iconSize + gap;
const cols = Math.ceil(width / cellSize) + 4;
const rows = Math.ceil(height / cellSize) + 4;
const offsetX = (frame * 0.3) % cellSize;
const offsetY = (frame * 0.2) % cellSize;
return (
<AbsoluteFill
style={{
background: color,
overflow: "hidden",
}}
>
<div
style={{
position: "absolute",
top: -cellSize * 2,
left: -cellSize * 2,
transform: `translate(${offsetX}px, ${offsetY}px)`,
}}
>
{[...Array(rows)].map((_, row) =>
[...Array(cols)].map((_, col) => {
const staggerX = row % 2 === 0 ? 0 : cellSize / 2;
return (
<Img
key={`${row}-${col}`}
src={staticFile("app-icon.png")}
style={{
position: "absolute",
width: iconSize,
height: iconSize,
left: col * cellSize + staggerX,
top: row * cellSize,
opacity: 0.08,
borderRadius: iconSize * 0.22,
}}
/>
);
})
)}
</div>
</AbsoluteFill>
);
};
// Scene 1: Problem - "Journaling is hard"
const ProblemScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const textProgress = spring({ frame, fps, config: { damping: 200 } });
const textOpacity = interpolate(textProgress, [0, 1], [0, 1]);
const strikeProgress = interpolate(frame, [fps * 1.5, fps * 2], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
return (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #991b1b 0%, #b91c1c 100%)" />
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
padding: 60,
}}
>
<div style={{ position: "relative" }}>
<div
style={{
fontSize: 96,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textAlign: "center",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
opacity: textOpacity,
}}
>
Journaling is hard
</div>
{/* Strike-through line */}
<div
style={{
position: "absolute",
top: "50%",
left: 0,
height: 12,
backgroundColor: "#fbbf24",
width: `${strikeProgress * 100}%`,
transform: "translateY(-50%) rotate(-2deg)",
borderRadius: 6,
}}
/>
</div>
</div>
</AbsoluteFill>
);
};
// Scene 2: Solution - "Feels is easy"
const SolutionScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const textProgress = spring({ frame, fps, config: { damping: 200 } });
const textOpacity = interpolate(textProgress, [0, 1], [0, 1]);
const textScale = interpolate(textProgress, [0, 1], [0.8, 1]);
const tapProgress = spring({
frame: frame - 20,
fps,
config: { damping: 15, stiffness: 100 },
});
const checkProgress = spring({
frame: frame - 40,
fps,
config: { damping: 10, stiffness: 100 },
});
return (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #059669 0%, #10b981 100%)" />
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
padding: 60,
}}
>
<div
style={{
fontSize: 96,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textAlign: "center",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
opacity: textOpacity,
transform: `scale(${textScale})`,
marginBottom: 60,
}}
>
Feels is easy
</div>
{/* Tap animation sequence */}
<div
style={{
display: "flex",
alignItems: "center",
gap: 30,
opacity: interpolate(tapProgress, [0, 1], [0, 1]),
}}
>
<div
style={{
width: 100,
height: 100,
borderRadius: 25,
backgroundColor: "#fbbf24",
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 48,
boxShadow: "0 10px 30px rgba(0,0,0,0.3)",
}}
>
😊
</div>
<div
style={{
fontSize: 64,
color: "white",
}}
>
</div>
<div
style={{
fontSize: 72,
transform: `scale(${interpolate(checkProgress, [0, 1], [0, 1])})`,
}}
>
</div>
<div
style={{
fontSize: 48,
fontWeight: 600,
color: "white",
opacity: interpolate(checkProgress, [0, 1], [0, 1]),
}}
>
Done!
</div>
</div>
</div>
</AbsoluteFill>
);
};
// Scene 3: Comparison - Complex vs Simple
const ComparisonScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const leftProgress = spring({
frame,
fps,
config: { damping: 12, stiffness: 80 },
});
const rightProgress = spring({
frame: frame - 15,
fps,
config: { damping: 12, stiffness: 80 },
});
const vsProgress = spring({
frame: frame - 30,
fps,
config: { damping: 10, stiffness: 100 },
});
return (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #374151 0%, #4b5563 100%)" />
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
display: "flex",
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
padding: 40,
gap: 40,
}}
>
{/* Left: Other apps */}
<div
style={{
flex: 1,
display: "flex",
flexDirection: "column",
alignItems: "center",
opacity: interpolate(leftProgress, [0, 1], [0, 1]),
transform: `translateX(${interpolate(leftProgress, [0, 1], [-50, 0])}px)`,
}}
>
<div
style={{
backgroundColor: "rgba(0,0,0,0.4)",
borderRadius: 30,
padding: 40,
width: 400,
}}
>
<div
style={{
height: 30,
backgroundColor: "rgba(255,255,255,0.3)",
borderRadius: 8,
marginBottom: 20,
}}
/>
<div
style={{
height: 30,
backgroundColor: "rgba(255,255,255,0.2)",
borderRadius: 8,
marginBottom: 20,
width: "80%",
}}
/>
<div
style={{
height: 100,
backgroundColor: "rgba(255,255,255,0.15)",
borderRadius: 12,
marginBottom: 20,
}}
/>
<div
style={{
height: 30,
backgroundColor: "rgba(255,255,255,0.2)",
borderRadius: 8,
width: "60%",
}}
/>
</div>
<div
style={{
fontSize: 32,
fontWeight: 600,
color: "#ef4444",
marginTop: 30,
}}
>
Other apps: 5+ minutes
</div>
</div>
{/* VS */}
<div
style={{
fontSize: 64,
fontWeight: 800,
color: "white",
transform: `scale(${interpolate(vsProgress, [0, 1], [0, 1])})`,
}}
>
VS
</div>
{/* Right: Feels */}
<div
style={{
flex: 1,
display: "flex",
flexDirection: "column",
alignItems: "center",
opacity: interpolate(rightProgress, [0, 1], [0, 1]),
transform: `translateX(${interpolate(rightProgress, [0, 1], [50, 0])}px)`,
}}
>
<div
style={{
backgroundColor: "#059669",
borderRadius: 30,
padding: 40,
width: 400,
display: "flex",
justifyContent: "center",
gap: 20,
}}
>
{["😢", "😕", "😐", "😊", "😄"].map((emoji, i) => (
<div
key={i}
style={{
width: 60,
height: 60,
borderRadius: 15,
backgroundColor: "rgba(255,255,255,0.2)",
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 32,
}}
>
{emoji}
</div>
))}
</div>
<div
style={{
fontSize: 32,
fontWeight: 600,
color: "#10b981",
marginTop: 30,
}}
>
Feels: 5 seconds
</div>
</div>
</div>
</AbsoluteFill>
);
};
// Scene 4: Benefit
const BenefitScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const textProgress = spring({ frame, fps, config: { damping: 200 } });
const textOpacity = interpolate(textProgress, [0, 1], [0, 1]);
const subProgress = spring({
frame: frame - 15,
fps,
config: { damping: 200 },
});
return (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #7c3aed 0%, #8b5cf6 100%)" />
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
padding: 60,
}}
>
<div
style={{
fontSize: 72,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textAlign: "center",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
opacity: textOpacity,
lineHeight: 1.2,
}}
>
Same insights.
<br />
Fraction of effort.
</div>
<div
style={{
fontSize: 42,
fontWeight: 500,
color: "#fbbf24",
fontFamily: "system-ui, -apple-system, sans-serif",
textAlign: "center",
marginTop: 40,
opacity: interpolate(subProgress, [0, 1], [0, 1]),
}}
>
Finally, tracking that sticks.
</div>
</div>
</AbsoluteFill>
);
};
// Scene 5: CTA
const CTAScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const logoScale = spring({
frame,
fps,
config: { damping: 10, stiffness: 80 },
});
const glowIntensity = interpolate(Math.sin(frame * 0.1), [-1, 1], [0.3, 0.6]);
return (
<AbsoluteFill
style={{
justifyContent: "center",
alignItems: "center",
}}
>
<TiledIconBackground color="linear-gradient(180deg, #059669 0%, #10b981 100%)" />
<div
style={{
position: "absolute",
width: 400,
height: 400,
borderRadius: "50%",
background: `radial-gradient(circle, rgba(255,255,255,${glowIntensity}) 0%, transparent 70%)`,
transform: `scale(${interpolate(logoScale, [0, 1], [0.3, 1])})`,
}}
/>
<div
style={{
textAlign: "center",
transform: `scale(${interpolate(logoScale, [0, 1], [0.5, 1])})`,
}}
>
<Img
src={staticFile("app-icon.png")}
style={{
width: 180,
height: 180,
borderRadius: 180 * 0.22,
marginBottom: 30,
filter: `drop-shadow(0 0 60px rgba(255,255,255,${glowIntensity}))`,
}}
/>
<div
style={{
fontSize: 72,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
}}
>
Feels
</div>
<div
style={{
fontSize: 36,
fontWeight: 600,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
marginTop: 20,
opacity: 0.9,
}}
>
The no-journal journal
</div>
</div>
</AbsoluteFill>
);
};
// Main composition - 20 seconds total
export const ConceptBNoJournalJournal: React.FC = () => {
const { fps } = useVideoConfig();
return (
<AbsoluteFill>
<Sequence from={0} durationInFrames={Math.round(4 * fps)}>
<ProblemScene />
</Sequence>
<Sequence from={Math.round(4 * fps)} durationInFrames={Math.round(4 * fps)}>
<SolutionScene />
</Sequence>
<Sequence from={Math.round(8 * fps)} durationInFrames={Math.round(5 * fps)}>
<ComparisonScene />
</Sequence>
<Sequence from={Math.round(13 * fps)} durationInFrames={Math.round(4 * fps)}>
<BenefitScene />
</Sequence>
<Sequence from={Math.round(17 * fps)} durationInFrames={Math.round(3 * fps)}>
<CTAScene />
</Sequence>
</AbsoluteFill>
);
};

View File

@@ -0,0 +1,410 @@
import {
AbsoluteFill,
Img,
staticFile,
useCurrentFrame,
useVideoConfig,
interpolate,
spring,
Sequence,
} from "remotion";
// Shared tiled background component
const TiledIconBackground: React.FC<{ color?: string }> = ({
color = "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
}) => {
const frame = useCurrentFrame();
const { width, height } = useVideoConfig();
const iconSize = 80;
const gap = 40;
const cellSize = iconSize + gap;
const cols = Math.ceil(width / cellSize) + 4;
const rows = Math.ceil(height / cellSize) + 4;
const offsetX = (frame * 0.3) % cellSize;
const offsetY = (frame * 0.2) % cellSize;
return (
<AbsoluteFill
style={{
background: color,
overflow: "hidden",
}}
>
<div
style={{
position: "absolute",
top: -cellSize * 2,
left: -cellSize * 2,
transform: `translate(${offsetX}px, ${offsetY}px)`,
}}
>
{[...Array(rows)].map((_, row) =>
[...Array(cols)].map((_, col) => {
const staggerX = row % 2 === 0 ? 0 : cellSize / 2;
return (
<Img
key={`${row}-${col}`}
src={staticFile("app-icon.png")}
style={{
position: "absolute",
width: iconSize,
height: iconSize,
left: col * cellSize + staggerX,
top: row * cellSize,
opacity: 0.08,
borderRadius: iconSize * 0.22,
}}
/>
);
})
)}
</div>
</AbsoluteFill>
);
};
// Mood colors for the heatmap
const MOOD_COLORS = ["#ef4444", "#f97316", "#fbbf24", "#22c55e", "#10b981"];
// Scene 1: Heatmap filling in
const HeatmapScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
// Generate a consistent grid of mood values
const gridCols = 12;
const gridRows = 31;
const fillProgress = interpolate(frame, [0, fps * 6], [0, 1], {
extrapolateRight: "clamp",
});
const totalCells = gridCols * gridRows;
const filledCells = Math.floor(fillProgress * totalCells);
// Title animation
const titleOpacity = interpolate(frame, [0, fps * 0.5], [0, 1], {
extrapolateRight: "clamp",
});
return (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #1e1b4b 0%, #312e81 100%)" />
{/* Title */}
<div
style={{
position: "absolute",
top: 80,
left: 0,
right: 0,
textAlign: "center",
opacity: titleOpacity,
}}
>
<div
style={{
fontSize: 64,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
}}
>
Your Year in Feelings
</div>
</div>
{/* Heatmap grid */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
display: "grid",
gridTemplateColumns: `repeat(${gridCols}, 60px)`,
gap: 6,
padding: 30,
backgroundColor: "rgba(0,0,0,0.3)",
borderRadius: 30,
}}
>
{[...Array(totalCells)].map((_, i) => {
const isFilled = i < filledCells;
const moodIndex = Math.floor(Math.random() * 5);
const color = isFilled ? MOOD_COLORS[moodIndex] : "rgba(255,255,255,0.1)";
// Stagger the scale animation
const cellDelay = (i / totalCells) * fps * 5;
const cellProgress = spring({
frame: frame - cellDelay,
fps,
config: { damping: 15, stiffness: 150 },
});
return (
<div
key={i}
style={{
width: 60,
height: 16,
borderRadius: 4,
backgroundColor: isFilled ? color : "rgba(255,255,255,0.1)",
transform: `scaleX(${isFilled ? interpolate(cellProgress, [0, 1], [0, 1]) : 1})`,
transformOrigin: "left",
}}
/>
);
})}
</div>
{/* Year label */}
<div
style={{
position: "absolute",
bottom: 100,
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 120,
fontWeight: 800,
color: "rgba(255,255,255,0.1)",
fontFamily: "system-ui, -apple-system, sans-serif",
}}
>
2025
</div>
</div>
</AbsoluteFill>
);
};
// Scene 2: Share card appearing
const ShareScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const cardProgress = spring({
frame,
fps,
config: { damping: 12, stiffness: 80 },
});
const cardScale = interpolate(cardProgress, [0, 1], [0.7, 1]);
const cardOpacity = interpolate(cardProgress, [0, 1], [0, 1]);
const shareProgress = spring({
frame: frame - 30,
fps,
config: { damping: 10, stiffness: 100 },
});
return (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #7c3aed 0%, #a855f7 100%)" />
{/* Share card */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: `translate(-50%, -50%) scale(${cardScale})`,
opacity: cardOpacity,
}}
>
<div
style={{
backgroundColor: "white",
borderRadius: 40,
padding: 60,
width: 700,
boxShadow: "0 40px 80px rgba(0,0,0,0.4)",
}}
>
<div
style={{
fontSize: 48,
fontWeight: 800,
color: "#1a1a1a",
marginBottom: 20,
}}
>
2025
</div>
<div
style={{
fontSize: 28,
color: "#6b7280",
marginBottom: 40,
}}
>
Year in Review
</div>
{/* Mini heatmap */}
<div
style={{
display: "grid",
gridTemplateColumns: "repeat(12, 1fr)",
gap: 4,
marginBottom: 40,
}}
>
{[...Array(60)].map((_, i) => (
<div
key={i}
style={{
height: 12,
borderRadius: 3,
backgroundColor: MOOD_COLORS[Math.floor(Math.random() * 5)],
}}
/>
))}
</div>
{/* Stats */}
<div style={{ display: "flex", justifyContent: "space-around" }}>
<div style={{ textAlign: "center" }}>
<div style={{ fontSize: 48, fontWeight: 700, color: "#10b981" }}>
312
</div>
<div style={{ fontSize: 20, color: "#6b7280" }}>Days Tracked</div>
</div>
<div style={{ textAlign: "center" }}>
<div style={{ fontSize: 48 }}>😊</div>
<div style={{ fontSize: 20, color: "#6b7280" }}>Top Mood</div>
</div>
</div>
{/* Feels branding */}
<div
style={{
marginTop: 40,
textAlign: "center",
fontSize: 24,
color: "#9ca3af",
}}
>
ifeel
</div>
</div>
</div>
{/* Share button */}
<div
style={{
position: "absolute",
bottom: 150,
left: "50%",
transform: `translateX(-50%) scale(${interpolate(shareProgress, [0, 1], [0, 1])})`,
}}
>
<div
style={{
backgroundColor: "white",
borderRadius: 50,
padding: "20px 50px",
display: "flex",
alignItems: "center",
gap: 15,
boxShadow: "0 10px 30px rgba(0,0,0,0.3)",
}}
>
<span style={{ fontSize: 32 }}>📤</span>
<span
style={{
fontSize: 28,
fontWeight: 600,
color: "#1a1a1a",
}}
>
Share Your Year
</span>
</div>
</div>
</AbsoluteFill>
);
};
// Scene 3: CTA
const CTAScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const textProgress = spring({ frame, fps, config: { damping: 200 } });
return (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #1e1b4b 0%, #312e81 100%)" />
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
padding: 60,
}}
>
<Img
src={staticFile("app-icon.png")}
style={{
width: 160,
height: 160,
borderRadius: 160 * 0.22,
marginBottom: 40,
filter: "drop-shadow(0 20px 40px rgba(0,0,0,0.4))",
transform: `scale(${interpolate(textProgress, [0, 1], [0.5, 1])})`,
}}
/>
<div
style={{
fontSize: 56,
fontWeight: 800,
color: "#fbbf24",
fontFamily: "system-ui, -apple-system, sans-serif",
textAlign: "center",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
opacity: interpolate(textProgress, [0, 1], [0, 1]),
}}
>
What will YOUR year look like?
</div>
</div>
</AbsoluteFill>
);
};
// Main composition - 15 seconds total
export const ConceptCYearInFeelings: React.FC = () => {
const { fps } = useVideoConfig();
return (
<AbsoluteFill>
<Sequence from={0} durationInFrames={Math.round(7 * fps)}>
<HeatmapScene />
</Sequence>
<Sequence from={Math.round(7 * fps)} durationInFrames={Math.round(5 * fps)}>
<ShareScene />
</Sequence>
<Sequence from={Math.round(12 * fps)} durationInFrames={Math.round(3 * fps)}>
<CTAScene />
</Sequence>
</AbsoluteFill>
);
};

View File

@@ -0,0 +1,645 @@
import {
AbsoluteFill,
Img,
staticFile,
useCurrentFrame,
useVideoConfig,
interpolate,
spring,
Sequence,
} from "remotion";
// Shared tiled background component
const TiledIconBackground: React.FC<{ color?: string }> = ({
color = "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
}) => {
const frame = useCurrentFrame();
const { width, height } = useVideoConfig();
const iconSize = 80;
const gap = 40;
const cellSize = iconSize + gap;
const cols = Math.ceil(width / cellSize) + 4;
const rows = Math.ceil(height / cellSize) + 4;
const offsetX = (frame * 0.3) % cellSize;
const offsetY = (frame * 0.2) % cellSize;
return (
<AbsoluteFill
style={{
background: color,
overflow: "hidden",
}}
>
<div
style={{
position: "absolute",
top: -cellSize * 2,
left: -cellSize * 2,
transform: `translate(${offsetX}px, ${offsetY}px)`,
}}
>
{[...Array(rows)].map((_, row) =>
[...Array(cols)].map((_, col) => {
const staggerX = row % 2 === 0 ? 0 : cellSize / 2;
return (
<Img
key={`${row}-${col}`}
src={staticFile("app-icon.png")}
style={{
position: "absolute",
width: iconSize,
height: iconSize,
left: col * cellSize + staggerX,
top: row * cellSize,
opacity: 0.08,
borderRadius: iconSize * 0.22,
}}
/>
);
})
)}
</div>
</AbsoluteFill>
);
};
// Phone frame component
const PhoneFrame: React.FC<{
mediaSrc: string;
width?: number;
rotation?: number;
style?: React.CSSProperties;
}> = ({ mediaSrc, width = 460, rotation = 0, style }) => {
const aspectRatio = 2760 / 1350;
const height = width * aspectRatio;
return (
<div
style={{
position: "relative",
width,
height,
transform: rotation ? `rotate(${rotation}deg)` : undefined,
filter: "drop-shadow(0 40px 80px rgba(0,0,0,0.5))",
...style,
}}
>
<div
style={{
position: "absolute",
top: "2.5%",
left: "5.33%",
right: "5.33%",
bottom: "2.5%",
borderRadius: width * 0.083,
overflow: "hidden",
backgroundColor: "#000",
}}
>
<Img
src={staticFile(mediaSrc)}
style={{
width: "100%",
height: "100%",
objectFit: "cover",
}}
/>
</div>
<Img
src={staticFile("phone.png")}
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
pointerEvents: "none",
}}
/>
</div>
);
};
// Scene 1: Morning - Widget tap while coffee
const MorningScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const widgetProgress = spring({
frame,
fps,
config: { damping: 12, stiffness: 80 },
});
const widgetScale = interpolate(widgetProgress, [0, 1], [0.8, 1]);
const labelProgress = spring({
frame: frame - 15,
fps,
config: { damping: 200 },
});
return (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #f59e0b 0%, #fbbf24 100%)" />
{/* Time label */}
<div
style={{
position: "absolute",
top: 80,
left: 60,
opacity: interpolate(labelProgress, [0, 1], [0, 1]),
}}
>
<div
style={{
fontSize: 32,
fontWeight: 600,
color: "rgba(255,255,255,0.8)",
fontFamily: "system-ui, -apple-system, sans-serif",
}}
>
7:30 AM
</div>
<div
style={{
fontSize: 64,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.2)",
}}
>
Morning coffee
</div>
</div>
{/* Widget */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: `translate(-50%, -50%) scale(${widgetScale})`,
filter: "drop-shadow(0 30px 60px rgba(0,0,0,0.3))",
}}
>
<Img
src={staticFile("screen2-widget.png")}
style={{
width: 500,
height: 500,
borderRadius: 50,
}}
/>
</div>
{/* Tap indicator */}
<div
style={{
position: "absolute",
bottom: 200,
left: "50%",
transform: "translateX(-50%)",
display: "flex",
alignItems: "center",
gap: 15,
}}
>
<span style={{ fontSize: 40 }}></span>
<span
style={{
fontSize: 32,
color: "white",
fontWeight: 600,
}}
>
One tap from home screen
</span>
</div>
</AbsoluteFill>
);
};
// Scene 2: Midday - Apple Watch at work
const MiddayScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const watchProgress = spring({
frame,
fps,
config: { damping: 12, stiffness: 80 },
});
const watchScale = interpolate(watchProgress, [0, 1], [0.8, 1]);
const labelProgress = spring({
frame: frame - 15,
fps,
config: { damping: 200 },
});
return (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #3b82f6 0%, #60a5fa 100%)" />
{/* Time label */}
<div
style={{
position: "absolute",
top: 80,
right: 60,
textAlign: "right",
opacity: interpolate(labelProgress, [0, 1], [0, 1]),
}}
>
<div
style={{
fontSize: 32,
fontWeight: 600,
color: "rgba(255,255,255,0.8)",
fontFamily: "system-ui, -apple-system, sans-serif",
}}
>
12:45 PM
</div>
<div
style={{
fontSize: 64,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.2)",
}}
>
Lunch break
</div>
</div>
{/* Watch */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: `translate(-50%, -50%) scale(${watchScale})`,
filter: "drop-shadow(0 30px 60px rgba(0,0,0,0.3))",
}}
>
<div style={{ position: "relative" }}>
<div
style={{
position: "absolute",
top: "22%",
left: "15%",
width: "70%",
height: "42%",
borderRadius: 25,
overflow: "hidden",
zIndex: 1,
}}
>
<Img
src={staticFile("screen2-watch.png")}
style={{
width: "100%",
height: "100%",
objectFit: "cover",
}}
/>
</div>
<Img
src={staticFile("watch-frame.png")}
style={{
width: 400,
height: 640,
position: "relative",
zIndex: 2,
}}
/>
</div>
</div>
{/* Label */}
<div
style={{
position: "absolute",
bottom: 200,
left: "50%",
transform: "translateX(-50%)",
display: "flex",
alignItems: "center",
gap: 15,
}}
>
<span style={{ fontSize: 40 }}></span>
<span
style={{
fontSize: 32,
color: "white",
fontWeight: 600,
}}
>
Quick glance from your wrist
</span>
</div>
</AbsoluteFill>
);
};
// Scene 3: Evening - Lock Screen Live Activity
const EveningScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const phoneProgress = spring({
frame,
fps,
config: { damping: 12, stiffness: 80 },
});
const phoneScale = interpolate(phoneProgress, [0, 1], [0.8, 1]);
const labelProgress = spring({
frame: frame - 15,
fps,
config: { damping: 200 },
});
return (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #8b5cf6 0%, #a78bfa 100%)" />
{/* Time label */}
<div
style={{
position: "absolute",
top: 80,
left: 60,
opacity: interpolate(labelProgress, [0, 1], [0, 1]),
}}
>
<div
style={{
fontSize: 32,
fontWeight: 600,
color: "rgba(255,255,255,0.8)",
fontFamily: "system-ui, -apple-system, sans-serif",
}}
>
6:30 PM
</div>
<div
style={{
fontSize: 64,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.2)",
}}
>
Evening wind-down
</div>
</div>
{/* Phone with lock screen */}
<div
style={{
position: "absolute",
right: -50,
bottom: -150,
transform: `scale(${phoneScale})`,
}}
>
<PhoneFrame mediaSrc="screen7-notifications.png" width={550} rotation={-5} />
</div>
{/* Label */}
<div
style={{
position: "absolute",
bottom: 200,
left: 60,
}}
>
<span
style={{
fontSize: 32,
color: "white",
fontWeight: 600,
}}
>
🔔 Gentle reminder on your Lock Screen
</span>
</div>
</AbsoluteFill>
);
};
// Scene 4: Night - Quick note before bed
const NightScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const phoneProgress = spring({
frame,
fps,
config: { damping: 12, stiffness: 80 },
});
const phoneScale = interpolate(phoneProgress, [0, 1], [0.8, 1]);
const labelProgress = spring({
frame: frame - 15,
fps,
config: { damping: 200 },
});
return (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #1e3a5f 0%, #1e293b 100%)" />
{/* Time label */}
<div
style={{
position: "absolute",
top: 80,
right: 60,
textAlign: "right",
opacity: interpolate(labelProgress, [0, 1], [0, 1]),
}}
>
<div
style={{
fontSize: 32,
fontWeight: 600,
color: "rgba(255,255,255,0.8)",
fontFamily: "system-ui, -apple-system, sans-serif",
}}
>
10:15 PM
</div>
<div
style={{
fontSize: 64,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.2)",
}}
>
Before sleep
</div>
</div>
{/* Phone with journal */}
<div
style={{
position: "absolute",
left: "50%",
bottom: -150,
transform: `translateX(-50%) scale(${phoneScale})`,
}}
>
<PhoneFrame mediaSrc="screen3-journal.png" width={550} />
</div>
{/* Label */}
<div
style={{
position: "absolute",
bottom: 200,
left: "50%",
transform: "translateX(-50%)",
display: "flex",
alignItems: "center",
gap: 15,
}}
>
<span style={{ fontSize: 40 }}>🌙</span>
<span
style={{
fontSize: 32,
color: "white",
fontWeight: 600,
}}
>
Add a note to remember the day
</span>
</div>
</AbsoluteFill>
);
};
// Scene 5: CTA
const CTAScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const logoScale = spring({
frame,
fps,
config: { damping: 10, stiffness: 80 },
});
const textOpacity = spring({
frame: frame - 15,
fps,
config: { damping: 200 },
});
const glowIntensity = interpolate(Math.sin(frame * 0.1), [-1, 1], [0.3, 0.6]);
return (
<AbsoluteFill
style={{
justifyContent: "center",
alignItems: "center",
}}
>
<TiledIconBackground color="linear-gradient(180deg, #059669 0%, #10b981 100%)" />
<div
style={{
position: "absolute",
width: 400,
height: 400,
borderRadius: "50%",
background: `radial-gradient(circle, rgba(255,255,255,${glowIntensity}) 0%, transparent 70%)`,
transform: `scale(${interpolate(logoScale, [0, 1], [0.3, 1])})`,
}}
/>
<div
style={{
textAlign: "center",
transform: `scale(${interpolate(logoScale, [0, 1], [0.5, 1])})`,
}}
>
<Img
src={staticFile("app-icon.png")}
style={{
width: 180,
height: 180,
borderRadius: 180 * 0.22,
marginBottom: 40,
filter: `drop-shadow(0 0 60px rgba(255,255,255,${glowIntensity}))`,
}}
/>
<div
style={{
fontSize: 72,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
}}
>
Feels
</div>
<div
style={{
fontSize: 36,
fontWeight: 600,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
marginTop: 20,
opacity: interpolate(textOpacity, [0, 1], [0, 1]),
}}
>
Your mood tracker, wherever you are
</div>
</div>
</AbsoluteFill>
);
};
// Main composition - 25 seconds total
export const ConceptDAlwaysThere: React.FC = () => {
const { fps } = useVideoConfig();
return (
<AbsoluteFill>
<Sequence from={0} durationInFrames={Math.round(5 * fps)}>
<MorningScene />
</Sequence>
<Sequence from={Math.round(5 * fps)} durationInFrames={Math.round(5 * fps)}>
<MiddayScene />
</Sequence>
<Sequence from={Math.round(10 * fps)} durationInFrames={Math.round(5 * fps)}>
<EveningScene />
</Sequence>
<Sequence from={Math.round(15 * fps)} durationInFrames={Math.round(5 * fps)}>
<NightScene />
</Sequence>
<Sequence from={Math.round(20 * fps)} durationInFrames={Math.round(5 * fps)}>
<CTAScene />
</Sequence>
</AbsoluteFill>
);
};

View File

@@ -0,0 +1,664 @@
import {
AbsoluteFill,
Img,
staticFile,
useCurrentFrame,
useVideoConfig,
interpolate,
spring,
Sequence,
} from "remotion";
// Theme colors for montage
const THEMES = [
{ name: "Forest", colors: ["#1a472a", "#2d5a3d"] },
{ name: "Ocean", colors: ["#1e3a5f", "#2563eb"] },
{ name: "Sunset", colors: ["#f59e0b", "#ef4444"] },
{ name: "Lavender", colors: ["#7c3aed", "#a855f7"] },
{ name: "Rose", colors: ["#e11d48", "#f43f5e"] },
{ name: "Mint", colors: ["#059669", "#10b981"] },
{ name: "Neon", colors: ["#0ea5e9", "#06b6d4"] },
{ name: "Charcoal", colors: ["#1f2937", "#374151"] },
];
// Shared tiled background component
const TiledIconBackground: React.FC<{ color?: string }> = ({
color = "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
}) => {
const frame = useCurrentFrame();
const { width, height } = useVideoConfig();
const iconSize = 80;
const gap = 40;
const cellSize = iconSize + gap;
const cols = Math.ceil(width / cellSize) + 4;
const rows = Math.ceil(height / cellSize) + 4;
const offsetX = (frame * 0.3) % cellSize;
const offsetY = (frame * 0.2) % cellSize;
return (
<AbsoluteFill
style={{
background: color,
overflow: "hidden",
}}
>
<div
style={{
position: "absolute",
top: -cellSize * 2,
left: -cellSize * 2,
transform: `translate(${offsetX}px, ${offsetY}px)`,
}}
>
{[...Array(rows)].map((_, row) =>
[...Array(cols)].map((_, col) => {
const staggerX = row % 2 === 0 ? 0 : cellSize / 2;
return (
<Img
key={`${row}-${col}`}
src={staticFile("app-icon.png")}
style={{
position: "absolute",
width: iconSize,
height: iconSize,
left: col * cellSize + staggerX,
top: row * cellSize,
opacity: 0.08,
borderRadius: iconSize * 0.22,
}}
/>
);
})
)}
</div>
</AbsoluteFill>
);
};
// Phone frame component
const PhoneFrame: React.FC<{
mediaSrc: string;
width?: number;
rotation?: number;
style?: React.CSSProperties;
}> = ({ mediaSrc, width = 460, rotation = 0, style }) => {
const aspectRatio = 2760 / 1350;
const height = width * aspectRatio;
return (
<div
style={{
position: "relative",
width,
height,
transform: rotation ? `rotate(${rotation}deg)` : undefined,
filter: "drop-shadow(0 40px 80px rgba(0,0,0,0.5))",
...style,
}}
>
<div
style={{
position: "absolute",
top: "2.5%",
left: "5.33%",
right: "5.33%",
bottom: "2.5%",
borderRadius: width * 0.083,
overflow: "hidden",
backgroundColor: "#000",
}}
>
<Img
src={staticFile(mediaSrc)}
style={{
width: "100%",
height: "100%",
objectFit: "cover",
}}
/>
</div>
<Img
src={staticFile("phone.png")}
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
pointerEvents: "none",
}}
/>
</div>
);
};
// Scene 1: Rapid theme montage
const ThemeMontageScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
// Cycle through themes every 0.4 seconds
const themeDuration = Math.round(0.4 * fps);
const currentThemeIndex = Math.floor(frame / themeDuration) % THEMES.length;
const theme = THEMES[currentThemeIndex];
const titleProgress = spring({ frame, fps, config: { damping: 200 } });
// Calculate progress within current theme for animation
const themeProgress = (frame % themeDuration) / themeDuration;
return (
<AbsoluteFill style={{ overflow: "hidden" }}>
<AbsoluteFill
style={{
background: `linear-gradient(180deg, ${theme.colors[0]} 0%, ${theme.colors[1]} 100%)`,
transition: "background 0.1s ease-out",
}}
/>
{/* Title */}
<div
style={{
position: "absolute",
top: 100,
left: 0,
right: 0,
textAlign: "center",
opacity: interpolate(titleProgress, [0, 1], [0, 1]),
}}
>
<div
style={{
fontSize: 72,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
}}
>
Make it yours
</div>
</div>
{/* Theme name */}
<div
style={{
position: "absolute",
top: "45%",
left: "50%",
transform: `translate(-50%, -50%) scale(${interpolate(themeProgress, [0, 0.5, 1], [0.8, 1, 0.8])})`,
opacity: interpolate(themeProgress, [0, 0.3, 0.7, 1], [0, 1, 1, 0]),
}}
>
<div
style={{
fontSize: 120,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 60px rgba(0,0,0,0.5)",
}}
>
{theme.name}
</div>
</div>
{/* Mood icons row */}
<div
style={{
position: "absolute",
bottom: 200,
left: 0,
right: 0,
display: "flex",
justifyContent: "center",
gap: 30,
}}
>
{["😢", "😕", "😐", "😊", "😄"].map((emoji, i) => (
<div
key={i}
style={{
width: 80,
height: 80,
borderRadius: 20,
backgroundColor: "rgba(255,255,255,0.2)",
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 48,
}}
>
{emoji}
</div>
))}
</div>
</AbsoluteFill>
);
};
// Scene 2: Theme picker detail
const ThemeDetailScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const phoneProgress = spring({
frame,
fps,
config: { damping: 12, stiffness: 80 },
});
const phoneScale = interpolate(phoneProgress, [0, 1], [0.8, 1]);
return (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #1f2937 0%, #374151 100%)" />
{/* Title */}
<div
style={{
position: "absolute",
top: 100,
left: 60,
right: 60,
}}
>
<div
style={{
fontSize: 56,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
}}
>
12 thoughtful themes
</div>
<div
style={{
fontSize: 32,
fontWeight: 500,
color: "rgba(255,255,255,0.7)",
fontFamily: "system-ui, -apple-system, sans-serif",
marginTop: 15,
}}
>
Light, dark, and everything in between
</div>
</div>
{/* Phone with theme picker */}
<div
style={{
position: "absolute",
left: "50%",
bottom: -150,
transform: `translateX(-50%) scale(${phoneScale})`,
}}
>
<PhoneFrame mediaSrc="screen6-themes.png" width={550} />
</div>
</AbsoluteFill>
);
};
// Scene 3: Icon packs
const IconPacksScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const titleProgress = spring({ frame, fps, config: { damping: 200 } });
// Cycle through icon sets
const iconSets = [
["😢", "😕", "😐", "😊", "😄"],
["💔", "😤", "😑", "🙂", "😁"],
["🌧️", "☁️", "⛅", "🌤️", "☀️"],
["1", "2", "3", "4", "5"],
];
const setDuration = Math.round(0.8 * fps);
const currentSetIndex = Math.floor(frame / setDuration) % iconSets.length;
const iconSet = iconSets[currentSetIndex];
const setProgress = (frame % setDuration) / setDuration;
return (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #7c3aed 0%, #a855f7 100%)" />
{/* Title */}
<div
style={{
position: "absolute",
top: 100,
left: 0,
right: 0,
textAlign: "center",
opacity: interpolate(titleProgress, [0, 1], [0, 1]),
}}
>
<div
style={{
fontSize: 64,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
}}
>
Choose your icons
</div>
</div>
{/* Icon set display */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: `translate(-50%, -50%) scale(${interpolate(setProgress, [0, 0.5, 1], [0.9, 1, 0.9])})`,
opacity: interpolate(setProgress, [0, 0.2, 0.8, 1], [0, 1, 1, 0]),
}}
>
<div
style={{
display: "flex",
gap: 30,
backgroundColor: "rgba(0,0,0,0.3)",
padding: 50,
borderRadius: 40,
}}
>
{iconSet.map((icon, i) => (
<div
key={i}
style={{
width: 120,
height: 120,
borderRadius: 30,
backgroundColor: `hsl(${i * 30 + 0}, 70%, 50%)`,
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 64,
color: "white",
fontWeight: 700,
}}
>
{icon}
</div>
))}
</div>
</div>
{/* Label */}
<div
style={{
position: "absolute",
bottom: 200,
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 32,
color: "rgba(255,255,255,0.8)",
fontWeight: 500,
}}
>
Multiple icon packs to match your style
</div>
</div>
</AbsoluteFill>
);
};
// Scene 4: Widgets
const WidgetsScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const widget1Progress = spring({
frame,
fps,
config: { damping: 12, stiffness: 80 },
});
const widget2Progress = spring({
frame: frame - 10,
fps,
config: { damping: 12, stiffness: 80 },
});
return (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #059669 0%, #10b981 100%)" />
{/* Title */}
<div
style={{
position: "absolute",
top: 100,
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 64,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
}}
>
Custom widgets
</div>
</div>
{/* Widgets */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
display: "flex",
gap: 40,
alignItems: "center",
}}
>
{/* Large widget */}
<div
style={{
transform: `scale(${interpolate(widget1Progress, [0, 1], [0.5, 1])})`,
opacity: interpolate(widget1Progress, [0, 1], [0, 1]),
}}
>
<Img
src={staticFile("screen2-widget.png")}
style={{
width: 400,
height: 400,
borderRadius: 40,
filter: "drop-shadow(0 30px 60px rgba(0,0,0,0.4))",
}}
/>
</div>
{/* Small widgets */}
<div
style={{
display: "flex",
flexDirection: "column",
gap: 20,
transform: `scale(${interpolate(widget2Progress, [0, 1], [0.5, 1])})`,
opacity: interpolate(widget2Progress, [0, 1], [0, 1]),
}}
>
<div
style={{
width: 200,
height: 200,
borderRadius: 30,
backgroundColor: "rgba(255,255,255,0.2)",
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 80,
}}
>
😊
</div>
<div
style={{
width: 200,
height: 90,
borderRadius: 20,
backgroundColor: "rgba(255,255,255,0.2)",
display: "flex",
alignItems: "center",
justifyContent: "center",
padding: "0 20px",
}}
>
<span style={{ fontSize: 28, color: "white", fontWeight: 600 }}>
🔥 15 day streak
</span>
</div>
</div>
</div>
{/* Label */}
<div
style={{
position: "absolute",
bottom: 150,
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 32,
color: "rgba(255,255,255,0.8)",
fontWeight: 500,
}}
>
Small, medium, and large sizes
</div>
</div>
</AbsoluteFill>
);
};
// Scene 5: CTA
const CTAScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const logoScale = spring({
frame,
fps,
config: { damping: 10, stiffness: 80 },
});
const glowIntensity = interpolate(Math.sin(frame * 0.1), [-1, 1], [0.3, 0.6]);
return (
<AbsoluteFill
style={{
justifyContent: "center",
alignItems: "center",
}}
>
<TiledIconBackground color="linear-gradient(180deg, #7c3aed 0%, #a855f7 100%)" />
<div
style={{
position: "absolute",
width: 400,
height: 400,
borderRadius: "50%",
background: `radial-gradient(circle, rgba(255,255,255,${glowIntensity}) 0%, transparent 70%)`,
transform: `scale(${interpolate(logoScale, [0, 1], [0.3, 1])})`,
}}
/>
<div
style={{
textAlign: "center",
transform: `scale(${interpolate(logoScale, [0, 1], [0.5, 1])})`,
}}
>
<Img
src={staticFile("app-icon.png")}
style={{
width: 180,
height: 180,
borderRadius: 180 * 0.22,
marginBottom: 40,
filter: `drop-shadow(0 0 60px rgba(255,255,255,${glowIntensity}))`,
}}
/>
<div
style={{
fontSize: 72,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
}}
>
Feels
</div>
<div
style={{
fontSize: 42,
fontWeight: 700,
color: "#fbbf24",
fontFamily: "system-ui, -apple-system, sans-serif",
marginTop: 20,
}}
>
12 themes. Unlimited you.
</div>
</div>
</AbsoluteFill>
);
};
// Main composition - 20 seconds total
export const ConceptEMakeItYours: React.FC = () => {
const { fps } = useVideoConfig();
return (
<AbsoluteFill>
<Sequence from={0} durationInFrames={Math.round(5 * fps)}>
<ThemeMontageScene />
</Sequence>
<Sequence from={Math.round(5 * fps)} durationInFrames={Math.round(4 * fps)}>
<ThemeDetailScene />
</Sequence>
<Sequence from={Math.round(9 * fps)} durationInFrames={Math.round(4 * fps)}>
<IconPacksScene />
</Sequence>
<Sequence from={Math.round(13 * fps)} durationInFrames={Math.round(4 * fps)}>
<WidgetsScene />
</Sequence>
<Sequence from={Math.round(17 * fps)} durationInFrames={Math.round(3 * fps)}>
<CTAScene />
</Sequence>
</AbsoluteFill>
);
};

View File

@@ -0,0 +1,549 @@
import {
AbsoluteFill,
Img,
staticFile,
useCurrentFrame,
useVideoConfig,
interpolate,
spring,
Sequence,
} from "remotion";
// Shared tiled background component
const TiledIconBackground: React.FC<{ color?: string }> = ({
color = "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
}) => {
const frame = useCurrentFrame();
const { width, height } = useVideoConfig();
const iconSize = 80;
const gap = 40;
const cellSize = iconSize + gap;
const cols = Math.ceil(width / cellSize) + 4;
const rows = Math.ceil(height / cellSize) + 4;
const offsetX = (frame * 0.3) % cellSize;
const offsetY = (frame * 0.2) % cellSize;
return (
<AbsoluteFill
style={{
background: color,
overflow: "hidden",
}}
>
<div
style={{
position: "absolute",
top: -cellSize * 2,
left: -cellSize * 2,
transform: `translate(${offsetX}px, ${offsetY}px)`,
}}
>
{[...Array(rows)].map((_, row) =>
[...Array(cols)].map((_, col) => {
const staggerX = row % 2 === 0 ? 0 : cellSize / 2;
return (
<Img
key={`${row}-${col}`}
src={staticFile("app-icon.png")}
style={{
position: "absolute",
width: iconSize,
height: iconSize,
left: col * cellSize + staggerX,
top: row * cellSize,
opacity: 0.08,
borderRadius: iconSize * 0.22,
}}
/>
);
})
)}
</div>
</AbsoluteFill>
);
};
// Scene 1: Statement - "Your feelings are personal"
const StatementScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const textProgress = spring({ frame, fps, config: { damping: 200 } });
const textOpacity = interpolate(textProgress, [0, 1], [0, 1]);
const textScale = interpolate(textProgress, [0, 1], [0.9, 1]);
const lockProgress = spring({
frame: frame - 20,
fps,
config: { damping: 10, stiffness: 100 },
});
return (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #1e3a5f 0%, #1e293b 100%)" />
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
padding: 60,
}}
>
{/* Lock icon */}
<div
style={{
fontSize: 120,
marginBottom: 40,
transform: `scale(${interpolate(lockProgress, [0, 1], [0, 1])})`,
}}
>
🔒
</div>
<div
style={{
fontSize: 80,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textAlign: "center",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
opacity: textOpacity,
transform: `scale(${textScale})`,
lineHeight: 1.2,
}}
>
Your feelings
<br />
are personal
</div>
</div>
</AbsoluteFill>
);
};
// Scene 2: iCloud visualization
const iCloudScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
// Data flowing animation
const flowProgress = interpolate(frame, [0, fps * 4], [0, 1], {
extrapolateRight: "clamp",
});
const titleProgress = spring({ frame, fps, config: { damping: 200 } });
// Create particles for data flow
const particles = [...Array(12)].map((_, i) => {
const delay = i * 5;
const particleProgress = interpolate(
frame - delay,
[0, fps * 1.5],
[0, 1],
{ extrapolateLeft: "clamp", extrapolateRight: "clamp" }
);
return {
x: interpolate(particleProgress, [0, 1], [0, 300]),
y: interpolate(particleProgress, [0, 0.5, 1], [0, -30, 0]) + i * 25,
opacity: interpolate(particleProgress, [0, 0.2, 0.8, 1], [0, 1, 1, 0]),
scale: interpolate(particleProgress, [0, 0.5, 1], [0.5, 1, 0.5]),
};
});
return (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #059669 0%, #10b981 100%)" />
{/* Title */}
<div
style={{
position: "absolute",
top: 100,
left: 0,
right: 0,
textAlign: "center",
opacity: interpolate(titleProgress, [0, 1], [0, 1]),
}}
>
<div
style={{
fontSize: 56,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
}}
>
Encrypted & synced
</div>
</div>
{/* Data flow visualization */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
display: "flex",
alignItems: "center",
gap: 80,
}}
>
{/* Phone icon */}
<div
style={{
width: 200,
height: 350,
backgroundColor: "rgba(255,255,255,0.15)",
borderRadius: 40,
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 100,
position: "relative",
}}
>
📱
{/* Particles emanating */}
{particles.map((p, i) => (
<div
key={i}
style={{
position: "absolute",
right: -p.x - 50,
top: p.y + 100,
width: 20,
height: 20,
borderRadius: 10,
backgroundColor: "rgba(255,255,255,0.8)",
opacity: p.opacity,
transform: `scale(${p.scale})`,
}}
/>
))}
</div>
{/* Arrow */}
<div
style={{
fontSize: 80,
color: "white",
opacity: 0.5,
}}
>
</div>
{/* iCloud icon */}
<div
style={{
width: 250,
height: 250,
backgroundColor: "rgba(255,255,255,0.2)",
borderRadius: 50,
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
gap: 15,
}}
>
<span style={{ fontSize: 80 }}></span>
<span
style={{
fontSize: 28,
fontWeight: 600,
color: "white",
}}
>
iCloud
</span>
</div>
</div>
{/* Labels */}
<div
style={{
position: "absolute",
bottom: 150,
left: 0,
right: 0,
display: "flex",
justifyContent: "center",
gap: 60,
}}
>
<div
style={{
display: "flex",
alignItems: "center",
gap: 12,
backgroundColor: "rgba(0,0,0,0.3)",
padding: "15px 25px",
borderRadius: 30,
}}
>
<span style={{ fontSize: 28 }}>🔐</span>
<span style={{ fontSize: 24, color: "white", fontWeight: 500 }}>
End-to-end encrypted
</span>
</div>
<div
style={{
display: "flex",
alignItems: "center",
gap: 12,
backgroundColor: "rgba(0,0,0,0.3)",
padding: "15px 25px",
borderRadius: 30,
}}
>
<span style={{ fontSize: 28 }}>🔄</span>
<span style={{ fontSize: 24, color: "white", fontWeight: 500 }}>
Syncs across devices
</span>
</div>
</div>
</AbsoluteFill>
);
};
// Scene 3: Apple Health
const HealthScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const badgeProgress = spring({
frame,
fps,
config: { damping: 12, stiffness: 80 },
});
const titleProgress = spring({
frame: frame - 15,
fps,
config: { damping: 200 },
});
return (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #dc2626 0%, #ef4444 100%)" />
{/* Title */}
<div
style={{
position: "absolute",
top: 100,
left: 0,
right: 0,
textAlign: "center",
opacity: interpolate(titleProgress, [0, 1], [0, 1]),
}}
>
<div
style={{
fontSize: 56,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
}}
>
Works with Apple Health
</div>
</div>
{/* Health badge */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: `translate(-50%, -50%) scale(${interpolate(badgeProgress, [0, 1], [0.5, 1])})`,
opacity: interpolate(badgeProgress, [0, 1], [0, 1]),
}}
>
<div
style={{
backgroundColor: "white",
borderRadius: 60,
padding: 60,
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: 30,
boxShadow: "0 30px 60px rgba(0,0,0,0.3)",
}}
>
{/* Apple Health icon approximation */}
<div
style={{
width: 140,
height: 140,
borderRadius: 35,
background: "linear-gradient(180deg, #ff6b6b 0%, #ff4757 100%)",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<span style={{ fontSize: 80 }}></span>
</div>
<div
style={{
fontSize: 36,
fontWeight: 700,
color: "#1a1a1a",
}}
>
Apple Health
</div>
<div
style={{
fontSize: 24,
color: "#6b7280",
textAlign: "center",
maxWidth: 400,
}}
>
Your mood data syncs with State of Mind
</div>
</div>
</div>
</AbsoluteFill>
);
};
// Scene 4: CTA
const CTAScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const logoScale = spring({
frame,
fps,
config: { damping: 10, stiffness: 80 },
});
const textProgress = spring({
frame: frame - 10,
fps,
config: { damping: 200 },
});
const glowIntensity = interpolate(Math.sin(frame * 0.1), [-1, 1], [0.3, 0.6]);
return (
<AbsoluteFill
style={{
justifyContent: "center",
alignItems: "center",
}}
>
<TiledIconBackground color="linear-gradient(180deg, #1e3a5f 0%, #1e293b 100%)" />
<div
style={{
position: "absolute",
width: 400,
height: 400,
borderRadius: "50%",
background: `radial-gradient(circle, rgba(255,255,255,${glowIntensity}) 0%, transparent 70%)`,
transform: `scale(${interpolate(logoScale, [0, 1], [0.3, 1])})`,
}}
/>
<div
style={{
textAlign: "center",
transform: `scale(${interpolate(logoScale, [0, 1], [0.5, 1])})`,
}}
>
{/* Trust badges */}
<div
style={{
display: "flex",
gap: 30,
marginBottom: 50,
opacity: interpolate(textProgress, [0, 1], [0, 1]),
}}
>
<span style={{ fontSize: 60 }}>🔒</span>
<span style={{ fontSize: 60 }}></span>
<span style={{ fontSize: 60 }}></span>
</div>
<Img
src={staticFile("app-icon.png")}
style={{
width: 160,
height: 160,
borderRadius: 160 * 0.22,
marginBottom: 30,
filter: `drop-shadow(0 0 60px rgba(255,255,255,${glowIntensity}))`,
}}
/>
<div
style={{
fontSize: 64,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
}}
>
Feels
</div>
<div
style={{
fontSize: 42,
fontWeight: 700,
color: "#10b981",
fontFamily: "system-ui, -apple-system, sans-serif",
marginTop: 20,
opacity: interpolate(textProgress, [0, 1], [0, 1]),
}}
>
Private. Secure. Yours.
</div>
</div>
</AbsoluteFill>
);
};
// Main composition - 15 seconds total
export const ConceptFPrivacyFirst: React.FC = () => {
const { fps } = useVideoConfig();
return (
<AbsoluteFill>
<Sequence from={0} durationInFrames={Math.round(3.5 * fps)}>
<StatementScene />
</Sequence>
<Sequence from={Math.round(3.5 * fps)} durationInFrames={Math.round(4.5 * fps)}>
<iCloudScene />
</Sequence>
<Sequence from={Math.round(8 * fps)} durationInFrames={Math.round(4 * fps)}>
<HealthScene />
</Sequence>
<Sequence from={Math.round(12 * fps)} durationInFrames={Math.round(3 * fps)}>
<CTAScene />
</Sequence>
</AbsoluteFill>
);
};

View File

@@ -0,0 +1,571 @@
import {
AbsoluteFill,
Img,
staticFile,
useCurrentFrame,
useVideoConfig,
interpolate,
spring,
Sequence,
Easing,
} from "remotion";
// Shared tiled background component
const TiledIconBackground: React.FC<{ color?: string }> = ({
color = "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
}) => {
const frame = useCurrentFrame();
const { width, height } = useVideoConfig();
const iconSize = 80;
const gap = 40;
const cellSize = iconSize + gap;
const cols = Math.ceil(width / cellSize) + 4;
const rows = Math.ceil(height / cellSize) + 4;
const offsetX = (frame * 0.3) % cellSize;
const offsetY = (frame * 0.2) % cellSize;
return (
<AbsoluteFill
style={{
background: color,
overflow: "hidden",
}}
>
<div
style={{
position: "absolute",
top: -cellSize * 2,
left: -cellSize * 2,
transform: `translate(${offsetX}px, ${offsetY}px)`,
}}
>
{[...Array(rows)].map((_, row) =>
[...Array(cols)].map((_, col) => {
const staggerX = row % 2 === 0 ? 0 : cellSize / 2;
return (
<Img
key={`${row}-${col}`}
src={staticFile("app-icon.png")}
style={{
position: "absolute",
width: iconSize,
height: iconSize,
left: col * cellSize + staggerX,
top: row * cellSize,
opacity: 0.08,
borderRadius: iconSize * 0.22,
}}
/>
);
})
)}
</div>
</AbsoluteFill>
);
};
// Confetti particle component
const Confetti: React.FC<{ count: number; startFrame: number }> = ({
count,
startFrame,
}) => {
const frame = useCurrentFrame();
const { fps, width, height } = useVideoConfig();
const localFrame = frame - startFrame;
if (localFrame < 0) return null;
const colors = ["#fbbf24", "#ef4444", "#10b981", "#3b82f6", "#8b5cf6", "#ec4899"];
return (
<>
{[...Array(count)].map((_, i) => {
const seed = i * 1234.5678;
const startX = (Math.sin(seed) * 0.5 + 0.5) * width;
const startY = -50;
const endX = startX + (Math.sin(seed * 2) * 200);
const endY = height + 100;
const progress = interpolate(localFrame, [0, fps * 3], [0, 1], {
extrapolateRight: "clamp",
easing: Easing.out(Easing.quad),
});
const x = interpolate(progress, [0, 1], [startX, endX]);
const y = interpolate(progress, [0, 1], [startY, endY]);
const rotation = localFrame * (5 + (i % 10));
const opacity = interpolate(progress, [0, 0.7, 1], [1, 1, 0]);
const scale = 0.5 + (i % 5) * 0.2;
return (
<div
key={i}
style={{
position: "absolute",
left: x,
top: y,
width: 20,
height: 20,
backgroundColor: colors[i % colors.length],
borderRadius: i % 2 === 0 ? "50%" : 4,
transform: `rotate(${rotation}deg) scale(${scale})`,
opacity,
}}
/>
);
})}
</>
);
};
// Scene 1: Streak counter building up
const StreakBuildScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
// Count up from 0 to target
const targetStreak = 30;
const countProgress = interpolate(frame, [fps * 0.5, fps * 4], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
easing: Easing.out(Easing.cubic),
});
const currentCount = Math.round(countProgress * targetStreak);
const titleProgress = spring({ frame, fps, config: { damping: 200 } });
// Fire emoji scale on each increment
const fireScale = interpolate(
Math.sin(frame * 0.5),
[-1, 1],
[0.9, 1.1]
);
return (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #f59e0b 0%, #ef4444 100%)" />
{/* Title */}
<div
style={{
position: "absolute",
top: 100,
left: 0,
right: 0,
textAlign: "center",
opacity: interpolate(titleProgress, [0, 1], [0, 1]),
}}
>
<div
style={{
fontSize: 56,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
}}
>
Build your streak
</div>
</div>
{/* Streak counter */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
textAlign: "center",
}}
>
<div
style={{
fontSize: 120,
transform: `scale(${fireScale})`,
marginBottom: 20,
}}
>
🔥
</div>
<div
style={{
fontSize: 200,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 60px rgba(0,0,0,0.3)",
lineHeight: 1,
}}
>
{currentCount}
</div>
<div
style={{
fontSize: 48,
fontWeight: 600,
color: "rgba(255,255,255,0.9)",
fontFamily: "system-ui, -apple-system, sans-serif",
marginTop: 10,
}}
>
day streak
</div>
</div>
{/* Progress bar */}
<div
style={{
position: "absolute",
bottom: 200,
left: 100,
right: 100,
}}
>
<div
style={{
height: 20,
borderRadius: 10,
backgroundColor: "rgba(255,255,255,0.3)",
overflow: "hidden",
}}
>
<div
style={{
height: "100%",
width: `${countProgress * 100}%`,
backgroundColor: "white",
borderRadius: 10,
}}
/>
</div>
</div>
</AbsoluteFill>
);
};
// Scene 2: Calendar filling
const CalendarScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const titleProgress = spring({ frame, fps, config: { damping: 200 } });
// Calendar grid
const days = 35;
const cols = 7;
const fillProgress = interpolate(frame, [0, fps * 4], [0, 1], {
extrapolateRight: "clamp",
});
const filledDays = Math.floor(fillProgress * 30);
const MOOD_COLORS = ["#10b981", "#22c55e", "#fbbf24", "#10b981", "#22c55e"];
return (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #059669 0%, #10b981 100%)" />
{/* Title */}
<div
style={{
position: "absolute",
top: 100,
left: 0,
right: 0,
textAlign: "center",
opacity: interpolate(titleProgress, [0, 1], [0, 1]),
}}
>
<div
style={{
fontSize: 56,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
}}
>
Watch it grow
</div>
</div>
{/* Calendar grid */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
display: "grid",
gridTemplateColumns: `repeat(${cols}, 100px)`,
gap: 15,
padding: 40,
backgroundColor: "rgba(0,0,0,0.2)",
borderRadius: 30,
}}
>
{/* Day labels */}
{["S", "M", "T", "W", "T", "F", "S"].map((day, i) => (
<div
key={`label-${i}`}
style={{
fontSize: 24,
fontWeight: 600,
color: "rgba(255,255,255,0.6)",
textAlign: "center",
marginBottom: 10,
}}
>
{day}
</div>
))}
{/* Days */}
{[...Array(days)].map((_, i) => {
const isFilled = i < filledDays;
const cellProgress = spring({
frame: frame - i * 2,
fps,
config: { damping: 15, stiffness: 150 },
});
return (
<div
key={i}
style={{
width: 100,
height: 100,
borderRadius: 20,
backgroundColor: isFilled
? MOOD_COLORS[i % MOOD_COLORS.length]
: "rgba(255,255,255,0.1)",
transform: `scale(${isFilled ? interpolate(cellProgress, [0, 1], [0.5, 1]) : 1})`,
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 32,
fontWeight: 600,
color: isFilled ? "white" : "rgba(255,255,255,0.3)",
}}
>
{i + 1}
</div>
);
})}
</div>
</AbsoluteFill>
);
};
// Scene 3: Milestone celebration
const CelebrationScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const badgeProgress = spring({
frame,
fps,
config: { damping: 10, stiffness: 80 },
});
const textProgress = spring({
frame: frame - 15,
fps,
config: { damping: 200 },
});
return (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #7c3aed 0%, #a855f7 100%)" />
{/* Confetti */}
<Confetti count={50} startFrame={10} />
{/* Achievement badge */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: `translate(-50%, -50%) scale(${interpolate(badgeProgress, [0, 1], [0, 1])})`,
}}
>
<div
style={{
backgroundColor: "white",
borderRadius: 60,
padding: 60,
textAlign: "center",
boxShadow: "0 30px 60px rgba(0,0,0,0.3)",
}}
>
<div style={{ fontSize: 100, marginBottom: 20 }}>🏆</div>
<div
style={{
fontSize: 48,
fontWeight: 800,
color: "#1a1a1a",
marginBottom: 15,
}}
>
30 Day Streak!
</div>
<div
style={{
fontSize: 28,
color: "#6b7280",
}}
>
You're on fire!
</div>
</div>
</div>
{/* Subtitle */}
<div
style={{
position: "absolute",
bottom: 150,
left: 0,
right: 0,
textAlign: "center",
opacity: interpolate(textProgress, [0, 1], [0, 1]),
}}
>
<div
style={{
fontSize: 36,
fontWeight: 600,
color: "white",
}}
>
Celebrate every milestone
</div>
</div>
</AbsoluteFill>
);
};
// Scene 4: CTA
const CTAScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const logoScale = spring({
frame,
fps,
config: { damping: 10, stiffness: 80 },
});
const textProgress = spring({
frame: frame - 15,
fps,
config: { damping: 200 },
});
const glowIntensity = interpolate(Math.sin(frame * 0.1), [-1, 1], [0.3, 0.6]);
// Pulsing fire
const fireScale = interpolate(Math.sin(frame * 0.15), [-1, 1], [0.9, 1.1]);
return (
<AbsoluteFill
style={{
justifyContent: "center",
alignItems: "center",
}}
>
<TiledIconBackground color="linear-gradient(180deg, #f59e0b 0%, #ef4444 100%)" />
<div
style={{
position: "absolute",
width: 400,
height: 400,
borderRadius: "50%",
background: `radial-gradient(circle, rgba(255,255,255,${glowIntensity}) 0%, transparent 70%)`,
transform: `scale(${interpolate(logoScale, [0, 1], [0.3, 1])})`,
}}
/>
<div
style={{
textAlign: "center",
transform: `scale(${interpolate(logoScale, [0, 1], [0.5, 1])})`,
}}
>
<div
style={{
fontSize: 100,
marginBottom: 30,
transform: `scale(${fireScale})`,
}}
>
🔥
</div>
<Img
src={staticFile("app-icon.png")}
style={{
width: 160,
height: 160,
borderRadius: 160 * 0.22,
marginBottom: 30,
filter: `drop-shadow(0 0 60px rgba(255,255,255,${glowIntensity}))`,
}}
/>
<div
style={{
fontSize: 64,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
}}
>
Feels
</div>
<div
style={{
fontSize: 42,
fontWeight: 700,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
marginTop: 20,
opacity: interpolate(textProgress, [0, 1], [0, 1]),
}}
>
How long can you go?
</div>
</div>
</AbsoluteFill>
);
};
// Main composition - 20 seconds total
export const ConceptGStreakEffect: React.FC = () => {
const { fps } = useVideoConfig();
return (
<AbsoluteFill>
<Sequence from={0} durationInFrames={Math.round(5 * fps)}>
<StreakBuildScene />
</Sequence>
<Sequence from={Math.round(5 * fps)} durationInFrames={Math.round(6 * fps)}>
<CalendarScene />
</Sequence>
<Sequence from={Math.round(11 * fps)} durationInFrames={Math.round(5 * fps)}>
<CelebrationScene />
</Sequence>
<Sequence from={Math.round(16 * fps)} durationInFrames={Math.round(4 * fps)}>
<CTAScene />
</Sequence>
</AbsoluteFill>
);
};

View File

@@ -0,0 +1,788 @@
import {
AbsoluteFill,
Img,
staticFile,
useCurrentFrame,
useVideoConfig,
interpolate,
spring,
Sequence,
Easing,
} from "remotion";
// Cinematic letterbox bars
const LetterBox: React.FC = () => (
<>
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
height: 120,
background: "black",
zIndex: 100,
}}
/>
<div
style={{
position: "absolute",
bottom: 0,
left: 0,
right: 0,
height: 120,
background: "black",
zIndex: 100,
}}
/>
</>
);
// Scan lines for that heist movie feel
const ScanLines: React.FC<{ opacity?: number }> = ({ opacity = 0.1 }) => {
const frame = useCurrentFrame();
const offset = (frame * 2) % 4;
return (
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
background: `repeating-linear-gradient(
0deg,
transparent,
transparent 2px,
rgba(0,0,0,${opacity}) 2px,
rgba(0,0,0,${opacity}) 4px
)`,
transform: `translateY(${offset}px)`,
pointerEvents: "none",
zIndex: 50,
}}
/>
);
};
// Glitch effect text
const GlitchText: React.FC<{
children: string;
style?: React.CSSProperties;
}> = ({ children, style }) => {
const frame = useCurrentFrame();
const glitchOffset = Math.sin(frame * 0.5) * 2;
const shouldGlitch = frame % 30 < 3;
return (
<div style={{ position: "relative", ...style }}>
{shouldGlitch && (
<>
<span
style={{
position: "absolute",
left: glitchOffset,
color: "#ff0000",
opacity: 0.8,
clipPath: "inset(10% 0 60% 0)",
}}
>
{children}
</span>
<span
style={{
position: "absolute",
left: -glitchOffset,
color: "#00ffff",
opacity: 0.8,
clipPath: "inset(60% 0 10% 0)",
}}
>
{children}
</span>
</>
)}
<span style={{ position: "relative" }}>{children}</span>
</div>
);
};
// Scene 1: The Setup - "They took something from you"
const SetupScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const fadeIn = interpolate(frame, [0, fps * 0.5], [0, 1], {
extrapolateRight: "clamp",
});
const textReveal = interpolate(frame, [fps * 0.5, fps * 2], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const typewriterLength = Math.floor(textReveal * 28);
const fullText = "They took something from you";
const displayText = fullText.slice(0, typewriterLength);
// Flicker effect
const flicker = frame % 60 < 2 ? 0.3 : 1;
return (
<AbsoluteFill
style={{
backgroundColor: "#0a0a0a",
opacity: fadeIn * flicker,
}}
>
<ScanLines opacity={0.15} />
<LetterBox />
{/* Dramatic spotlight */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
width: 800,
height: 800,
transform: "translate(-50%, -50%)",
background:
"radial-gradient(circle, rgba(255,255,255,0.05) 0%, transparent 70%)",
}}
/>
{/* Main text */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
textAlign: "center",
}}
>
<GlitchText
style={{
fontSize: 72,
fontWeight: 300,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
letterSpacing: 8,
textTransform: "uppercase",
}}
>
{displayText}
</GlitchText>
{/* Blinking cursor */}
{typewriterLength < fullText.length && (
<span
style={{
opacity: frame % 30 < 15 ? 1 : 0,
color: "#ef4444",
fontSize: 72,
fontWeight: 300,
}}
>
_
</span>
)}
</div>
{/* Bottom text */}
<div
style={{
position: "absolute",
bottom: 180,
left: 0,
right: 0,
textAlign: "center",
opacity: interpolate(frame, [fps * 3, fps * 4], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
}),
}}
>
<div
style={{
fontSize: 24,
color: "#ef4444",
fontFamily: "monospace",
letterSpacing: 4,
}}
>
YOUR EMOTIONS
</div>
</div>
</AbsoluteFill>
);
};
// Scene 2: The Crew - Mood emojis as heist team
const CrewScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const crew = [
{ emoji: "😊", name: "THE OPTIMIST", color: "#10b981", role: "INFILTRATION" },
{ emoji: "😤", name: "THE MUSCLE", color: "#ef4444", role: "FIREPOWER" },
{ emoji: "🤔", name: "THE BRAINS", color: "#3b82f6", role: "STRATEGY" },
{ emoji: "😌", name: "THE COOL", color: "#8b5cf6", role: "EXTRACTION" },
];
return (
<AbsoluteFill style={{ backgroundColor: "#0a0a0a" }}>
<ScanLines opacity={0.1} />
<LetterBox />
{/* Title */}
<div
style={{
position: "absolute",
top: 180,
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 32,
color: "#fbbf24",
fontFamily: "monospace",
letterSpacing: 8,
opacity: interpolate(frame, [0, fps * 0.5], [0, 1]),
}}
>
ASSEMBLING THE CREW
</div>
</div>
{/* Crew grid */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
display: "grid",
gridTemplateColumns: "repeat(2, 1fr)",
gap: 60,
}}
>
{crew.map((member, i) => {
const delay = i * 8;
const memberProgress = spring({
frame: frame - delay,
fps,
config: { damping: 12, stiffness: 100 },
});
return (
<div
key={member.name}
style={{
textAlign: "center",
opacity: interpolate(memberProgress, [0, 1], [0, 1]),
transform: `translateY(${interpolate(memberProgress, [0, 1], [50, 0])}px)`,
}}
>
<div
style={{
fontSize: 100,
marginBottom: 15,
filter: `drop-shadow(0 0 20px ${member.color})`,
}}
>
{member.emoji}
</div>
<div
style={{
fontSize: 20,
color: member.color,
fontFamily: "monospace",
fontWeight: 700,
letterSpacing: 2,
}}
>
{member.name}
</div>
<div
style={{
fontSize: 14,
color: "#666",
fontFamily: "monospace",
marginTop: 5,
}}
>
{member.role}
</div>
</div>
);
})}
</div>
</AbsoluteFill>
);
};
// Scene 3: The Plan - Blueprint style
const PlanScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps, width, height } = useVideoConfig();
// Grid lines drawing animation
const gridProgress = interpolate(frame, [0, fps * 2], [0, 1], {
extrapolateRight: "clamp",
});
return (
<AbsoluteFill style={{ backgroundColor: "#0a1628" }}>
<ScanLines opacity={0.05} />
<LetterBox />
{/* Blueprint grid */}
<svg
style={{ position: "absolute", top: 0, left: 0, width: "100%", height: "100%" }}
>
{/* Vertical lines */}
{[...Array(20)].map((_, i) => (
<line
key={`v-${i}`}
x1={(i * width) / 20}
y1={0}
x2={(i * width) / 20}
y2={height * gridProgress}
stroke="#1e40af"
strokeWidth={0.5}
opacity={0.3}
/>
))}
{/* Horizontal lines */}
{[...Array(30)].map((_, i) => (
<line
key={`h-${i}`}
x1={0}
y1={(i * height) / 30}
x2={width * gridProgress}
y2={(i * height) / 30}
stroke="#1e40af"
strokeWidth={0.5}
opacity={0.3}
/>
))}
</svg>
{/* Phone blueprint */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
opacity: interpolate(frame, [fps, fps * 2], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
}),
}}
>
<div
style={{
width: 300,
height: 600,
border: "2px solid #3b82f6",
borderRadius: 40,
position: "relative",
}}
>
{/* Target markers */}
<div
style={{
position: "absolute",
top: "30%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 100,
height: 100,
border: "2px dashed #ef4444",
borderRadius: "50%",
}}
>
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
fontSize: 14,
color: "#ef4444",
fontFamily: "monospace",
}}
>
TARGET
</div>
</div>
{/* Entry point */}
<div
style={{
position: "absolute",
bottom: 80,
left: "50%",
transform: "translateX(-50%)",
fontSize: 12,
color: "#10b981",
fontFamily: "monospace",
}}
>
ENTRY POINT
</div>
</div>
</div>
{/* Title */}
<div
style={{
position: "absolute",
top: 180,
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 32,
color: "#3b82f6",
fontFamily: "monospace",
letterSpacing: 8,
}}
>
THE PLAN
</div>
</div>
{/* Steps */}
<div
style={{
position: "absolute",
bottom: 200,
left: 60,
right: 60,
display: "flex",
justifyContent: "space-around",
}}
>
{["DOWNLOAD", "TAP MOOD", "REPEAT"].map((step, i) => (
<div
key={step}
style={{
opacity: interpolate(
frame,
[fps * 2 + i * 10, fps * 2.5 + i * 10],
[0, 1],
{ extrapolateLeft: "clamp", extrapolateRight: "clamp" }
),
}}
>
<div
style={{
fontSize: 48,
color: "#fbbf24",
fontFamily: "monospace",
fontWeight: 700,
}}
>
{i + 1}
</div>
<div
style={{
fontSize: 16,
color: "#94a3b8",
fontFamily: "monospace",
}}
>
{step}
</div>
</div>
))}
</div>
</AbsoluteFill>
);
};
// Scene 4: The Execution - Dramatic mood selection
const ExecutionScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const moods = ["😢", "😕", "😐", "🙂", "😊"];
const selectedIndex = 4; // Great mood
// Dramatic slow reveal
const revealProgress = interpolate(frame, [0, fps * 2], [0, 1], {
extrapolateRight: "clamp",
easing: Easing.out(Easing.cubic),
});
// Finger approaching
const fingerProgress = interpolate(frame, [fps * 2, fps * 3], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
// Selection flash
const selectFlash = frame > fps * 3 && frame < fps * 3.5;
return (
<AbsoluteFill style={{ backgroundColor: "#0a0a0a" }}>
<ScanLines opacity={0.1} />
<LetterBox />
{/* Dramatic lighting */}
{selectFlash && (
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: "rgba(16, 185, 129, 0.3)",
zIndex: 10,
}}
/>
)}
{/* Mood buttons */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
display: "flex",
gap: 30,
}}
>
{moods.map((mood, i) => {
const isSelected = i === selectedIndex && frame > fps * 3;
const buttonProgress = interpolate(
revealProgress,
[i * 0.15, i * 0.15 + 0.3],
[0, 1],
{ extrapolateLeft: "clamp", extrapolateRight: "clamp" }
);
return (
<div
key={i}
style={{
width: 120,
height: 120,
borderRadius: 30,
backgroundColor: isSelected ? "#10b981" : "rgba(255,255,255,0.1)",
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 60,
opacity: buttonProgress,
transform: `scale(${isSelected ? 1.2 : 1}) translateY(${interpolate(buttonProgress, [0, 1], [50, 0])}px)`,
boxShadow: isSelected ? "0 0 60px #10b981" : "none",
transition: "all 0.3s",
}}
>
{mood}
</div>
);
})}
</div>
{/* Approaching finger/cursor */}
<div
style={{
position: "absolute",
top: "60%",
left: `${50 + 20 - fingerProgress * 8}%`,
fontSize: 80,
transform: `translateX(-50%) rotate(-30deg)`,
opacity: fingerProgress < 1 ? fingerProgress : 0,
}}
>
👆
</div>
{/* "ACQUIRED" text */}
{frame > fps * 3.2 && (
<div
style={{
position: "absolute",
bottom: 250,
left: 0,
right: 0,
textAlign: "center",
}}
>
<GlitchText
style={{
fontSize: 48,
color: "#10b981",
fontFamily: "monospace",
letterSpacing: 16,
fontWeight: 700,
}}
>
MOOD ACQUIRED
</GlitchText>
</div>
)}
</AbsoluteFill>
);
};
// Scene 5: The Getaway - Success celebration
const GetawayScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const logoProgress = spring({
frame,
fps,
config: { damping: 10, stiffness: 80 },
});
// Vault door opening effect
const vaultOpen = interpolate(frame, [0, fps], [0, 1], {
extrapolateRight: "clamp",
easing: Easing.out(Easing.cubic),
});
return (
<AbsoluteFill style={{ backgroundColor: "#0a0a0a" }}>
<ScanLines opacity={0.05} />
<LetterBox />
{/* Vault doors */}
<div
style={{
position: "absolute",
top: 0,
left: 0,
width: "50%",
height: "100%",
backgroundColor: "#1a1a1a",
transform: `translateX(${-vaultOpen * 100}%)`,
borderRight: "4px solid #333",
zIndex: 20,
}}
/>
<div
style={{
position: "absolute",
top: 0,
right: 0,
width: "50%",
height: "100%",
backgroundColor: "#1a1a1a",
transform: `translateX(${vaultOpen * 100}%)`,
borderLeft: "4px solid #333",
zIndex: 20,
}}
/>
{/* Bright light behind vault */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 600,
height: 600,
background: `radial-gradient(circle, rgba(251,191,36,${vaultOpen * 0.5}) 0%, transparent 70%)`,
}}
/>
{/* App icon revealed */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: `translate(-50%, -50%) scale(${interpolate(logoProgress, [0, 1], [0.5, 1])})`,
textAlign: "center",
opacity: vaultOpen,
}}
>
<Img
src={staticFile("app-icon.png")}
style={{
width: 200,
height: 200,
borderRadius: 200 * 0.22,
marginBottom: 40,
filter: `drop-shadow(0 0 60px rgba(251,191,36,0.8))`,
}}
/>
<div
style={{
fontSize: 80,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 0 40px rgba(255,255,255,0.5)",
}}
>
Feels
</div>
<div
style={{
fontSize: 28,
color: "#fbbf24",
fontFamily: "monospace",
marginTop: 20,
letterSpacing: 4,
opacity: interpolate(frame, [fps * 2, fps * 2.5], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
}),
}}
>
TAKE BACK YOUR EMOTIONS
</div>
</div>
</AbsoluteFill>
);
};
// Main composition - 25 seconds total
export const ConceptHMoodHeist: React.FC = () => {
const { fps } = useVideoConfig();
return (
<AbsoluteFill>
<Sequence from={0} durationInFrames={Math.round(5 * fps)}>
<SetupScene />
</Sequence>
<Sequence from={Math.round(5 * fps)} durationInFrames={Math.round(5 * fps)}>
<CrewScene />
</Sequence>
<Sequence from={Math.round(10 * fps)} durationInFrames={Math.round(5 * fps)}>
<PlanScene />
</Sequence>
<Sequence from={Math.round(15 * fps)} durationInFrames={Math.round(5 * fps)}>
<ExecutionScene />
</Sequence>
<Sequence from={Math.round(20 * fps)} durationInFrames={Math.round(5 * fps)}>
<GetawayScene />
</Sequence>
</AbsoluteFill>
);
};

View File

@@ -0,0 +1,833 @@
import {
AbsoluteFill,
Img,
staticFile,
useCurrentFrame,
useVideoConfig,
interpolate,
spring,
Sequence,
Easing,
} from "remotion";
// Pixel font style helper
const pixelText: React.CSSProperties = {
fontFamily: "'Press Start 2P', 'Courier New', monospace",
imageRendering: "pixelated",
textShadow: "4px 4px 0 #000",
};
// CRT screen effect
const CRTEffect: React.FC = () => {
const frame = useCurrentFrame();
return (
<>
{/* Scan lines */}
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
background: `repeating-linear-gradient(
0deg,
transparent,
transparent 2px,
rgba(0,0,0,0.3) 2px,
rgba(0,0,0,0.3) 4px
)`,
pointerEvents: "none",
zIndex: 100,
}}
/>
{/* Screen flicker */}
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: "rgba(255,255,255,0.02)",
opacity: Math.sin(frame * 0.5) > 0.9 ? 1 : 0,
pointerEvents: "none",
zIndex: 101,
}}
/>
{/* Vignette */}
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
background:
"radial-gradient(ellipse at center, transparent 50%, rgba(0,0,0,0.5) 100%)",
pointerEvents: "none",
zIndex: 99,
}}
/>
</>
);
};
// Pixel art mood character
const MoodSprite: React.FC<{
mood: number;
x: number;
y: number;
size?: number;
bounce?: boolean;
}> = ({ mood, x, y, size = 80, bounce = true }) => {
const frame = useCurrentFrame();
const colors = ["#ef4444", "#f97316", "#fbbf24", "#22c55e", "#10b981"];
const faces = [":(", ":/", ":|", ":)", ":D"];
const bounceY = bounce ? Math.sin(frame * 0.3) * 5 : 0;
return (
<div
style={{
position: "absolute",
left: x,
top: y + bounceY,
width: size,
height: size,
backgroundColor: colors[mood],
borderRadius: 8,
display: "flex",
alignItems: "center",
justifyContent: "center",
border: "4px solid #000",
boxShadow: "4px 4px 0 #000",
}}
>
<span
style={{
fontSize: size * 0.4,
...pixelText,
color: "#000",
textShadow: "none",
}}
>
{faces[mood]}
</span>
</div>
);
};
// Scene 1: Insert Coin
const InsertCoinScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const blink = frame % 30 < 20;
const titleDrop = spring({
frame,
fps,
config: { damping: 8, stiffness: 100 },
});
return (
<AbsoluteFill style={{ backgroundColor: "#1a1a2e" }}>
<CRTEffect />
{/* Starfield background */}
{[...Array(50)].map((_, i) => {
const x = (i * 137.5) % 100;
const y = (i * 73.3 + frame * 0.5) % 100;
const size = (i % 3) + 1;
return (
<div
key={i}
style={{
position: "absolute",
left: `${x}%`,
top: `${y}%`,
width: size * 2,
height: size * 2,
backgroundColor: "#fff",
opacity: 0.5 + (i % 5) * 0.1,
}}
/>
);
})}
{/* Title */}
<div
style={{
position: "absolute",
top: 200,
left: 0,
right: 0,
textAlign: "center",
transform: `translateY(${interpolate(titleDrop, [0, 1], [-200, 0])}px)`,
}}
>
<div
style={{
fontSize: 80,
color: "#fbbf24",
...pixelText,
marginBottom: 20,
}}
>
FEELS
</div>
<div
style={{
fontSize: 28,
color: "#10b981",
...pixelText,
}}
>
THE GAME
</div>
</div>
{/* Mood characters parade */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
display: "flex",
gap: 20,
}}
>
{[0, 1, 2, 3, 4].map((mood, i) => {
const delay = i * 5;
const appearProgress = spring({
frame: frame - delay - 15,
fps,
config: { damping: 12 },
});
return (
<div
key={mood}
style={{
transform: `scale(${interpolate(appearProgress, [0, 1], [0, 1])})`,
}}
>
<MoodSprite mood={mood} x={0} y={0} size={100} />
</div>
);
})}
</div>
{/* Insert Coin */}
<div
style={{
position: "absolute",
bottom: 300,
left: 0,
right: 0,
textAlign: "center",
opacity: blink ? 1 : 0,
}}
>
<div
style={{
fontSize: 24,
color: "#fff",
...pixelText,
}}
>
INSERT COIN
</div>
</div>
{/* Credits */}
<div
style={{
position: "absolute",
bottom: 200,
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 16,
color: "#666",
...pixelText,
}}
>
© 2025 FEELS CORP
</div>
</div>
</AbsoluteFill>
);
};
// Scene 2: Gameplay - Catching moods
const GameplayScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps, width, height } = useVideoConfig();
// Player position (bottom of screen)
const playerX = width / 2 - 50 + Math.sin(frame * 0.1) * 200;
// Falling moods
const fallingMoods = [
{ mood: 4, startX: 200, delay: 0 },
{ mood: 3, startX: 500, delay: 20 },
{ mood: 2, startX: 800, delay: 40 },
{ mood: 4, startX: 350, delay: 60 },
{ mood: 3, startX: 650, delay: 80 },
];
// Score counter
const score = Math.floor(interpolate(frame, [0, fps * 4], [0, 9999], {
extrapolateRight: "clamp",
}));
return (
<AbsoluteFill style={{ backgroundColor: "#16213e" }}>
<CRTEffect />
{/* HUD */}
<div
style={{
position: "absolute",
top: 40,
left: 40,
right: 40,
display: "flex",
justifyContent: "space-between",
}}
>
<div style={{ fontSize: 20, color: "#fff", ...pixelText }}>
SCORE: {score.toString().padStart(5, "0")}
</div>
<div style={{ fontSize: 20, color: "#ef4444", ...pixelText }}>
</div>
<div style={{ fontSize: 20, color: "#fbbf24", ...pixelText }}>
STREAK: 7
</div>
</div>
{/* Falling moods */}
{fallingMoods.map((item, i) => {
const fallProgress = interpolate(
frame - item.delay,
[0, fps * 2],
[0, 1],
{ extrapolateLeft: "clamp", extrapolateRight: "clamp" }
);
const y = interpolate(fallProgress, [0, 1], [-100, height - 300]);
return (
<MoodSprite
key={i}
mood={item.mood}
x={item.startX}
y={y}
size={80}
bounce={false}
/>
);
})}
{/* Player basket */}
<div
style={{
position: "absolute",
left: playerX,
bottom: 150,
width: 100,
height: 80,
backgroundColor: "#3b82f6",
border: "4px solid #000",
borderRadius: "0 0 20px 20px",
boxShadow: "4px 4px 0 #000",
}}
>
<div
style={{
position: "absolute",
top: -30,
left: "50%",
transform: "translateX(-50%)",
fontSize: 40,
}}
>
🧺
</div>
</div>
{/* Ground */}
<div
style={{
position: "absolute",
bottom: 0,
left: 0,
right: 0,
height: 100,
backgroundColor: "#2d3748",
borderTop: "4px solid #4a5568",
}}
>
{/* Ground pattern */}
{[...Array(20)].map((_, i) => (
<div
key={i}
style={{
position: "absolute",
left: i * 60,
top: 20,
width: 40,
height: 20,
backgroundColor: "#3d4a5c",
}}
/>
))}
</div>
{/* "CATCH THE GOOD VIBES" */}
<div
style={{
position: "absolute",
top: 150,
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 28,
color: "#10b981",
...pixelText,
}}
>
CATCH THE GOOD VIBES!
</div>
</div>
</AbsoluteFill>
);
};
// Scene 3: Power Up - Streak bonus
const PowerUpScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const powerUpPulse = Math.sin(frame * 0.3) * 0.2 + 1;
const rotateSpeed = frame * 3;
const streakNumber = Math.floor(
interpolate(frame, [0, fps * 2], [7, 30], {
extrapolateRight: "clamp",
easing: Easing.out(Easing.cubic),
})
);
return (
<AbsoluteFill style={{ backgroundColor: "#0f0f23" }}>
<CRTEffect />
{/* Explosion rays */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: `translate(-50%, -50%) rotate(${rotateSpeed}deg)`,
}}
>
{[...Array(12)].map((_, i) => (
<div
key={i}
style={{
position: "absolute",
width: 20,
height: 600,
backgroundColor: i % 2 === 0 ? "#fbbf24" : "#f97316",
transform: `rotate(${i * 30}deg)`,
transformOrigin: "center center",
opacity: 0.3,
}}
/>
))}
</div>
{/* Power up text */}
<div
style={{
position: "absolute",
top: 250,
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 48,
color: "#fbbf24",
...pixelText,
transform: `scale(${powerUpPulse})`,
}}
>
POWER UP!
</div>
</div>
{/* Giant streak number */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: `translate(-50%, -50%) scale(${powerUpPulse})`,
textAlign: "center",
}}
>
<div
style={{
fontSize: 200,
color: "#fff",
...pixelText,
}}
>
{streakNumber}
</div>
<div
style={{
fontSize: 32,
color: "#10b981",
...pixelText,
}}
>
DAY STREAK!
</div>
</div>
{/* Bonus items floating */}
{["🔥", "⭐", "💎", "🏆"].map((emoji, i) => {
const angle = (frame * 2 + i * 90) * (Math.PI / 180);
const radius = 300;
const x = Math.cos(angle) * radius;
const y = Math.sin(angle) * radius * 0.5;
return (
<div
key={i}
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: `translate(calc(-50% + ${x}px), calc(-50% + ${y}px))`,
fontSize: 60,
}}
>
{emoji}
</div>
);
})}
{/* XP bonus */}
<div
style={{
position: "absolute",
bottom: 300,
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 24,
color: "#a78bfa",
...pixelText,
}}
>
+500 XP BONUS!
</div>
</div>
</AbsoluteFill>
);
};
// Scene 4: High Score - Leaderboard
const HighScoreScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const scores = [
{ rank: 1, name: "YOU", score: 99999, isPlayer: true },
{ rank: 2, name: "PRO", score: 88420, isPlayer: false },
{ rank: 3, name: "ACE", score: 77350, isPlayer: false },
{ rank: 4, name: "ZEN", score: 66100, isPlayer: false },
{ rank: 5, name: "JOY", score: 55000, isPlayer: false },
];
return (
<AbsoluteFill style={{ backgroundColor: "#1a1a2e" }}>
<CRTEffect />
{/* Title */}
<div
style={{
position: "absolute",
top: 150,
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 48,
color: "#fbbf24",
...pixelText,
}}
>
HIGH SCORES
</div>
</div>
{/* Leaderboard */}
<div
style={{
position: "absolute",
top: 350,
left: "50%",
transform: "translateX(-50%)",
}}
>
{scores.map((entry, i) => {
const delay = i * 8;
const rowProgress = spring({
frame: frame - delay,
fps,
config: { damping: 12 },
});
return (
<div
key={i}
style={{
display: "flex",
gap: 40,
marginBottom: 30,
opacity: interpolate(rowProgress, [0, 1], [0, 1]),
transform: `translateX(${interpolate(rowProgress, [0, 1], [-100, 0])}px)`,
}}
>
<div
style={{
fontSize: 28,
color: entry.rank === 1 ? "#fbbf24" : "#666",
...pixelText,
width: 60,
}}
>
{entry.rank}.
</div>
<div
style={{
fontSize: 28,
color: entry.isPlayer ? "#10b981" : "#fff",
...pixelText,
width: 150,
}}
>
{entry.name}
</div>
<div
style={{
fontSize: 28,
color: entry.isPlayer ? "#10b981" : "#fff",
...pixelText,
}}
>
{entry.score.toLocaleString()}
</div>
{entry.isPlayer && (
<span style={{ fontSize: 28, marginLeft: 10 }}>👑</span>
)}
</div>
);
})}
</div>
{/* New high score flash */}
<div
style={{
position: "absolute",
bottom: 250,
left: 0,
right: 0,
textAlign: "center",
opacity: frame % 20 < 15 ? 1 : 0,
}}
>
<div
style={{
fontSize: 32,
color: "#ef4444",
...pixelText,
}}
>
NEW HIGH SCORE
</div>
</div>
</AbsoluteFill>
);
};
// Scene 5: Game Over - CTA
const GameOverScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const logoProgress = spring({
frame,
fps,
config: { damping: 10, stiffness: 80 },
});
const blink = frame % 40 < 30;
return (
<AbsoluteFill style={{ backgroundColor: "#0a0a0a" }}>
<CRTEffect />
{/* Pixelated app icon */}
<div
style={{
position: "absolute",
top: "40%",
left: "50%",
transform: `translate(-50%, -50%) scale(${interpolate(logoProgress, [0, 1], [0.5, 1])})`,
textAlign: "center",
}}
>
<Img
src={staticFile("app-icon.png")}
style={{
width: 180,
height: 180,
borderRadius: 180 * 0.22,
border: "6px solid #fff",
boxShadow: "8px 8px 0 #000",
imageRendering: "pixelated",
}}
/>
</div>
{/* Title */}
<div
style={{
position: "absolute",
top: "60%",
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 64,
color: "#10b981",
...pixelText,
marginBottom: 20,
}}
>
FEELS
</div>
<div
style={{
fontSize: 24,
color: "#fbbf24",
...pixelText,
}}
>
LEVEL UP YOUR MOOD
</div>
</div>
{/* Press Start */}
<div
style={{
position: "absolute",
bottom: 300,
left: 0,
right: 0,
textAlign: "center",
opacity: blink ? 1 : 0,
}}
>
<div
style={{
fontSize: 24,
color: "#fff",
...pixelText,
}}
>
PRESS START
</div>
</div>
{/* Download prompt */}
<div
style={{
position: "absolute",
bottom: 200,
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 16,
color: "#666",
...pixelText,
}}
>
DOWNLOAD NOW ON APP STORE
</div>
</div>
</AbsoluteFill>
);
};
// Main composition - 20 seconds total
export const ConceptIRetroArcade: React.FC = () => {
const { fps } = useVideoConfig();
return (
<AbsoluteFill>
<Sequence from={0} durationInFrames={Math.round(4 * fps)}>
<InsertCoinScene />
</Sequence>
<Sequence from={Math.round(4 * fps)} durationInFrames={Math.round(5 * fps)}>
<GameplayScene />
</Sequence>
<Sequence from={Math.round(9 * fps)} durationInFrames={Math.round(4 * fps)}>
<PowerUpScene />
</Sequence>
<Sequence from={Math.round(13 * fps)} durationInFrames={Math.round(3 * fps)}>
<HighScoreScene />
</Sequence>
<Sequence from={Math.round(16 * fps)} durationInFrames={Math.round(4 * fps)}>
<GameOverScene />
</Sequence>
</AbsoluteFill>
);
};

View File

@@ -0,0 +1,609 @@
import {
AbsoluteFill,
Img,
staticFile,
useCurrentFrame,
useVideoConfig,
interpolate,
spring,
Sequence,
Easing,
} from "remotion";
// Film grain effect
const FilmGrain: React.FC<{ intensity?: number }> = ({ intensity = 0.15 }) => {
const frame = useCurrentFrame();
return (
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
opacity: intensity,
background: `url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' seed='${frame}' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E")`,
pointerEvents: "none",
zIndex: 200,
}}
/>
);
};
// Redacted text bar
const Redacted: React.FC<{ width: number }> = ({ width }) => (
<span
style={{
display: "inline-block",
width,
height: "1em",
backgroundColor: "#000",
marginLeft: 8,
marginRight: 8,
verticalAlign: "middle",
}}
/>
);
// Glitch/static overlay
const StaticOverlay: React.FC<{ active: boolean }> = ({ active }) => {
const frame = useCurrentFrame();
if (!active) return null;
return (
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: "#fff",
opacity: 0.1 + Math.random() * 0.2,
zIndex: 150,
}}
/>
);
};
// Document stamp
const ClassifiedStamp: React.FC<{ delay?: number }> = ({ delay = 0 }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const stampProgress = spring({
frame: frame - delay,
fps,
config: { damping: 8, stiffness: 200 },
});
return (
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: `translate(-50%, -50%) rotate(-15deg) scale(${interpolate(stampProgress, [0, 1], [3, 1])})`,
opacity: interpolate(stampProgress, [0, 0.5, 1], [0, 1, 1]),
border: "8px solid #ef4444",
padding: "20px 40px",
borderRadius: 8,
}}
>
<div
style={{
fontSize: 48,
fontWeight: 900,
color: "#ef4444",
fontFamily: "Impact, sans-serif",
letterSpacing: 8,
}}
>
CLASSIFIED
</div>
</div>
);
};
// Scene 1: The Hook - "They don't want you to know"
const HookScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const staticActive = frame < fps * 0.5 || (frame > fps * 2 && frame < fps * 2.3);
const textOpacity = interpolate(frame, [fps * 0.5, fps * 1], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const zoomIn = interpolate(frame, [0, fps * 4], [1, 1.1], {
extrapolateRight: "clamp",
});
return (
<AbsoluteFill style={{ backgroundColor: "#0a0a0a" }}>
<FilmGrain intensity={0.2} />
<StaticOverlay active={staticActive} />
{/* Dark vignette */}
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
background:
"radial-gradient(ellipse at center, transparent 30%, rgba(0,0,0,0.8) 100%)",
}}
/>
{/* Ominous text */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: `translate(-50%, -50%) scale(${zoomIn})`,
textAlign: "center",
opacity: textOpacity,
}}
>
<div
style={{
fontSize: 28,
color: "#666",
fontFamily: "Georgia, serif",
fontStyle: "italic",
marginBottom: 40,
letterSpacing: 4,
}}
>
WHAT IF EVERYTHING YOU KNEW
</div>
<div
style={{
fontSize: 72,
color: "#fff",
fontWeight: 900,
fontFamily: "system-ui, sans-serif",
letterSpacing: 2,
textTransform: "uppercase",
}}
>
WAS A LIE?
</div>
</div>
{/* Timestamp */}
<div
style={{
position: "absolute",
bottom: 100,
right: 60,
fontSize: 14,
color: "#ef4444",
fontFamily: "monospace",
}}
>
DOC-7X-{frame.toString().padStart(4, "0")}
</div>
</AbsoluteFill>
);
};
// Scene 2: The Evidence - Redacted documents
const EvidenceScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const docSlide = interpolate(frame, [0, fps], [100, 0], {
extrapolateRight: "clamp",
easing: Easing.out(Easing.cubic),
});
return (
<AbsoluteFill style={{ backgroundColor: "#1a1a1a" }}>
<FilmGrain intensity={0.15} />
{/* Cork board background */}
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: "#8B4513",
opacity: 0.3,
}}
/>
{/* Document */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: `translate(-50%, -50%) translateY(${docSlide}px) rotate(-2deg)`,
width: 700,
backgroundColor: "#f5f5dc",
padding: 60,
boxShadow: "0 20px 60px rgba(0,0,0,0.5)",
}}
>
{/* Header */}
<div
style={{
fontSize: 14,
color: "#333",
fontFamily: "Courier New, monospace",
marginBottom: 20,
borderBottom: "1px solid #999",
paddingBottom: 10,
}}
>
INTERNAL MEMO - RESTRICTED ACCESS
</div>
{/* Body text with redactions */}
<div
style={{
fontSize: 18,
color: "#222",
fontFamily: "Georgia, serif",
lineHeight: 2,
}}
>
Subject: Emotional Awareness Initiative
<br />
<br />
The <Redacted width={120} /> has determined that widespread
<Redacted width={180} /> of emotional patterns could lead to
<Redacted width={80} /> self-improvement. This is
<Redacted width={100} /> to our interests.
<br />
<br />
Recommendation: Continue suppression of <Redacted width={150} />
tracking tools.
</div>
{/* Classified stamp */}
<ClassifiedStamp delay={fps} />
</div>
{/* Red strings connecting */}
<svg
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
pointerEvents: "none",
}}
>
<line
x1="100"
y1="200"
x2="400"
y2="400"
stroke="#ef4444"
strokeWidth="2"
opacity="0.6"
/>
<line
x1="900"
y1="300"
x2="650"
y2="500"
stroke="#ef4444"
strokeWidth="2"
opacity="0.6"
/>
</svg>
{/* Narration text */}
<div
style={{
position: "absolute",
bottom: 150,
left: 0,
right: 0,
textAlign: "center",
opacity: interpolate(frame, [fps * 2, fps * 2.5], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
}),
}}
>
<div
style={{
fontSize: 24,
color: "#fff",
fontFamily: "Georgia, serif",
fontStyle: "italic",
}}
>
"They don't want you to understand yourself."
</div>
</div>
</AbsoluteFill>
);
};
// Scene 3: The Truth - Revelation
const TruthScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const revealProgress = interpolate(frame, [0, fps * 2], [0, 1], {
extrapolateRight: "clamp",
});
const glitchActive = frame % 45 < 3;
return (
<AbsoluteFill style={{ backgroundColor: "#000" }}>
<FilmGrain intensity={0.25} />
<StaticOverlay active={glitchActive} />
{/* Dramatic light rays */}
<div
style={{
position: "absolute",
top: 0,
left: "50%",
width: 400,
height: "100%",
background:
"linear-gradient(90deg, transparent 0%, rgba(255,255,255,0.05) 50%, transparent 100%)",
transform: "translateX(-50%)",
}}
/>
{/* The truth revealed */}
<div
style={{
position: "absolute",
top: "30%",
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 24,
color: "#ef4444",
fontFamily: "monospace",
letterSpacing: 8,
marginBottom: 40,
opacity: interpolate(revealProgress, [0, 0.3], [0, 1]),
}}
>
THE TRUTH IS
</div>
<div
style={{
fontSize: 64,
color: "#fff",
fontWeight: 900,
fontFamily: "system-ui, sans-serif",
opacity: interpolate(revealProgress, [0.3, 0.6], [0, 1]),
}}
>
YOUR EMOTIONS
</div>
<div
style={{
fontSize: 64,
color: "#10b981",
fontWeight: 900,
fontFamily: "system-ui, sans-serif",
marginTop: 20,
opacity: interpolate(revealProgress, [0.6, 1], [0, 1]),
}}
>
MATTER
</div>
</div>
{/* Bottom text */}
<div
style={{
position: "absolute",
bottom: 200,
left: 0,
right: 0,
textAlign: "center",
opacity: interpolate(frame, [fps * 3, fps * 3.5], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
}),
}}
>
<div
style={{
fontSize: 28,
color: "#666",
fontFamily: "Georgia, serif",
fontStyle: "italic",
}}
>
And there's an app that knows it.
</div>
</div>
</AbsoluteFill>
);
};
// Scene 4: The Solution - Feels reveal
const SolutionScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const logoProgress = spring({
frame,
fps,
config: { damping: 12, stiffness: 80 },
});
// Typing effect for tagline
const tagline = "Track your truth.";
const typedLength = Math.floor(
interpolate(frame, [fps * 1.5, fps * 3], [0, tagline.length], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
})
);
return (
<AbsoluteFill style={{ backgroundColor: "#0a0a0a" }}>
<FilmGrain intensity={0.1} />
{/* Spotlight effect */}
<div
style={{
position: "absolute",
top: "40%",
left: "50%",
width: 600,
height: 600,
transform: "translate(-50%, -50%)",
background:
"radial-gradient(circle, rgba(16,185,129,0.2) 0%, transparent 70%)",
}}
/>
{/* App icon */}
<div
style={{
position: "absolute",
top: "35%",
left: "50%",
transform: `translate(-50%, -50%) scale(${interpolate(logoProgress, [0, 1], [0.5, 1])})`,
textAlign: "center",
}}
>
<Img
src={staticFile("app-icon.png")}
style={{
width: 200,
height: 200,
borderRadius: 200 * 0.22,
boxShadow: "0 0 100px rgba(16,185,129,0.5)",
}}
/>
</div>
{/* App name */}
<div
style={{
position: "absolute",
top: "58%",
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 80,
fontWeight: 800,
color: "#fff",
fontFamily: "system-ui, sans-serif",
opacity: interpolate(logoProgress, [0, 1], [0, 1]),
}}
>
Feels
</div>
{/* Typed tagline */}
<div
style={{
fontSize: 32,
color: "#10b981",
fontFamily: "Georgia, serif",
fontStyle: "italic",
marginTop: 30,
minHeight: 50,
}}
>
{tagline.slice(0, typedLength)}
<span style={{ opacity: frame % 30 < 15 ? 1 : 0 }}>|</span>
</div>
</div>
{/* "Wake up" text */}
<div
style={{
position: "absolute",
bottom: 200,
left: 0,
right: 0,
textAlign: "center",
opacity: interpolate(frame, [fps * 3.5, fps * 4], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
}),
}}
>
<div
style={{
fontSize: 20,
color: "#ef4444",
fontFamily: "monospace",
letterSpacing: 8,
}}
>
WAKE UP. DOWNLOAD NOW.
</div>
</div>
{/* File number */}
<div
style={{
position: "absolute",
bottom: 100,
right: 60,
fontSize: 12,
color: "#333",
fontFamily: "monospace",
}}
>
CASE CLOSED
</div>
</AbsoluteFill>
);
};
// Main composition - 20 seconds total
export const ConceptJConspiracy: React.FC = () => {
const { fps } = useVideoConfig();
return (
<AbsoluteFill>
<Sequence from={0} durationInFrames={Math.round(4.5 * fps)}>
<HookScene />
</Sequence>
<Sequence from={Math.round(4.5 * fps)} durationInFrames={Math.round(5 * fps)}>
<EvidenceScene />
</Sequence>
<Sequence from={Math.round(9.5 * fps)} durationInFrames={Math.round(5 * fps)}>
<TruthScene />
</Sequence>
<Sequence from={Math.round(14.5 * fps)} durationInFrames={Math.round(5.5 * fps)}>
<SolutionScene />
</Sequence>
</AbsoluteFill>
);
};

View File

@@ -0,0 +1,701 @@
import {
AbsoluteFill,
Img,
staticFile,
useCurrentFrame,
useVideoConfig,
interpolate,
spring,
Sequence,
Easing,
} from "remotion";
// ESPN-style colors
const ESPN_RED = "#cc0000";
const ESPN_DARK = "#1a1a1a";
const ESPN_YELLOW = "#ffc629";
// Breaking news ticker
const NewsTicker: React.FC = () => {
const frame = useCurrentFrame();
const { width } = useVideoConfig();
const headlines = [
"BREAKING: Local user hits 30-day streak, experts baffled",
"MOOD ALERT: Wednesday showing signs of improvement league-wide",
"TRADE DEADLINE: Bad vibes traded for good vibes in blockbuster deal",
"INJURY REPORT: Monday motivation OUT indefinitely",
"STATS: Average mood up 15% since app download",
];
const tickerText = headlines.join(" • ");
const tickerWidth = tickerText.length * 14;
const offset = (frame * 3) % (tickerWidth + width);
return (
<div
style={{
position: "absolute",
bottom: 0,
left: 0,
right: 0,
height: 60,
backgroundColor: ESPN_RED,
display: "flex",
alignItems: "center",
overflow: "hidden",
zIndex: 100,
}}
>
<div
style={{
position: "absolute",
left: 0,
width: 200,
height: "100%",
backgroundColor: ESPN_DARK,
display: "flex",
alignItems: "center",
justifyContent: "center",
zIndex: 2,
}}
>
<span
style={{
fontSize: 18,
fontWeight: 900,
color: "#fff",
fontFamily: "system-ui, sans-serif",
}}
>
FEELS CENTER
</span>
</div>
<div
style={{
position: "absolute",
left: 200,
whiteSpace: "nowrap",
fontSize: 20,
color: "#fff",
fontWeight: 600,
fontFamily: "system-ui, sans-serif",
transform: `translateX(${width - offset}px)`,
}}
>
{tickerText}
</div>
</div>
);
};
// Score bug / mood tracker
const MoodScoreBug: React.FC<{ mood: string; score: number; streak: number }> = ({
mood,
score,
streak,
}) => {
return (
<div
style={{
position: "absolute",
top: 40,
left: 40,
backgroundColor: "rgba(0,0,0,0.8)",
borderLeft: `4px solid ${ESPN_RED}`,
padding: "15px 25px",
display: "flex",
gap: 30,
alignItems: "center",
}}
>
<div>
<div style={{ fontSize: 14, color: "#888", fontWeight: 600 }}>TODAY</div>
<div style={{ fontSize: 36, color: "#fff" }}>{mood}</div>
</div>
<div style={{ borderLeft: "1px solid #444", paddingLeft: 30 }}>
<div style={{ fontSize: 14, color: "#888", fontWeight: 600 }}>SCORE</div>
<div style={{ fontSize: 36, color: ESPN_YELLOW, fontWeight: 700 }}>
{score}
</div>
</div>
<div style={{ borderLeft: "1px solid #444", paddingLeft: 30 }}>
<div style={{ fontSize: 14, color: "#888", fontWeight: 600 }}>STREAK</div>
<div style={{ fontSize: 36, color: "#10b981", fontWeight: 700 }}>
{streak}🔥
</div>
</div>
</div>
);
};
// Scene 1: Opening - "Welcome to Feels Center"
const OpeningScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const logoSlam = spring({
frame,
fps,
config: { damping: 8, stiffness: 150 },
});
const textReveal = interpolate(frame, [fps * 0.5, fps * 1.5], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
return (
<AbsoluteFill style={{ backgroundColor: ESPN_DARK }}>
{/* Dramatic spotlight */}
<div
style={{
position: "absolute",
top: 0,
left: "50%",
width: 800,
height: "100%",
background: `linear-gradient(90deg, transparent 0%, rgba(204,0,0,0.1) 50%, transparent 100%)`,
transform: "translateX(-50%)",
}}
/>
{/* Logo slam */}
<div
style={{
position: "absolute",
top: "35%",
left: "50%",
transform: `translate(-50%, -50%) scale(${interpolate(logoSlam, [0, 1], [3, 1])})`,
textAlign: "center",
}}
>
<div
style={{
fontSize: 120,
fontWeight: 900,
color: "#fff",
fontFamily: "system-ui, sans-serif",
textShadow: `0 0 60px ${ESPN_RED}`,
}}
>
FEELS
</div>
<div
style={{
fontSize: 48,
fontWeight: 700,
color: ESPN_RED,
fontFamily: "system-ui, sans-serif",
letterSpacing: 16,
}}
>
CENTER
</div>
</div>
{/* Subtitle */}
<div
style={{
position: "absolute",
top: "60%",
left: 0,
right: 0,
textAlign: "center",
opacity: textReveal,
}}
>
<div
style={{
fontSize: 28,
color: "#888",
fontFamily: "system-ui, sans-serif",
fontWeight: 600,
}}
>
YOUR EMOTIONAL HIGHLIGHTS
</div>
</div>
{/* ESPN-style corner graphics */}
<div
style={{
position: "absolute",
bottom: 100,
right: 40,
fontSize: 16,
color: "#444",
fontFamily: "monospace",
}}
>
LIVE
<span
style={{
display: "inline-block",
width: 10,
height: 10,
backgroundColor: ESPN_RED,
borderRadius: "50%",
marginLeft: 10,
animation: "blink 1s infinite",
}}
/>
</div>
<NewsTicker />
</AbsoluteFill>
);
};
// Scene 2: Play of the Day - Mood selection replay
const PlayOfDayScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const replayProgress = interpolate(frame, [fps * 0.5, fps * 3], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
// Slow-mo finger movement
const fingerX = interpolate(replayProgress, [0, 0.8, 1], [0, 200, 200]);
const fingerY = interpolate(replayProgress, [0, 0.8, 0.9, 1], [100, 0, -20, 0]);
// Impact flash
const impactFlash = replayProgress > 0.85 && replayProgress < 0.95;
return (
<AbsoluteFill style={{ backgroundColor: ESPN_DARK }}>
{/* "PLAY OF THE DAY" banner */}
<div
style={{
position: "absolute",
top: 40,
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
display: "inline-block",
backgroundColor: ESPN_YELLOW,
padding: "10px 40px",
transform: "skewX(-10deg)",
}}
>
<span
style={{
fontSize: 32,
fontWeight: 900,
color: "#000",
fontFamily: "system-ui, sans-serif",
transform: "skewX(10deg)",
display: "inline-block",
}}
>
🏆 PLAY OF THE DAY
</span>
</div>
</div>
{/* Replay frame */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
border: "4px solid #333",
borderRadius: 20,
padding: 40,
backgroundColor: "rgba(0,0,0,0.5)",
}}
>
{/* Mood buttons */}
<div style={{ display: "flex", gap: 20 }}>
{["😢", "😕", "😐", "🙂", "😊"].map((emoji, i) => {
const isTarget = i === 4;
const isSelected = isTarget && replayProgress > 0.85;
return (
<div
key={i}
style={{
width: 100,
height: 100,
borderRadius: 25,
backgroundColor: isSelected ? "#10b981" : "rgba(255,255,255,0.1)",
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 50,
boxShadow: isSelected ? `0 0 40px #10b981` : "none",
transform: isSelected ? "scale(1.1)" : "scale(1)",
}}
>
{emoji}
</div>
);
})}
</div>
{/* Slow-mo finger */}
<div
style={{
position: "absolute",
left: 40 + fingerX,
bottom: -50 + fingerY,
fontSize: 60,
transform: "rotate(-30deg)",
opacity: replayProgress < 0.9 ? 1 : 0,
}}
>
👆
</div>
{/* Impact lines */}
{impactFlash && (
<>
{[...Array(8)].map((_, i) => (
<div
key={i}
style={{
position: "absolute",
right: 0,
top: "50%",
width: 60,
height: 4,
backgroundColor: "#fff",
transform: `rotate(${i * 45}deg)`,
transformOrigin: "right center",
}}
/>
))}
</>
)}
</div>
{/* Slow-mo indicator */}
<div
style={{
position: "absolute",
bottom: 150,
left: 40,
display: "flex",
alignItems: "center",
gap: 10,
}}
>
<div
style={{
fontSize: 18,
color: ESPN_YELLOW,
fontWeight: 700,
}}
>
SLOW-MO REPLAY
</div>
<div style={{ fontSize: 24 }}>🎬</div>
</div>
{/* Commentary */}
<div
style={{
position: "absolute",
bottom: 200,
left: 0,
right: 0,
textAlign: "center",
opacity: replayProgress > 0.9 ? 1 : 0,
}}
>
<div
style={{
fontSize: 36,
color: "#fff",
fontWeight: 700,
fontFamily: "system-ui, sans-serif",
}}
>
"WHAT A SELECTION! ABSOLUTELY CLINICAL!"
</div>
</div>
<NewsTicker />
</AbsoluteFill>
);
};
// Scene 3: Stats breakdown
const StatsScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const stats = [
{ label: "DAYS TRACKED", value: 127, color: "#10b981" },
{ label: "CURRENT STREAK", value: 23, color: ESPN_YELLOW },
{ label: "BEST STREAK", value: 45, color: ESPN_RED },
{ label: "MOOD AVERAGE", value: "4.2", color: "#3b82f6" },
];
return (
<AbsoluteFill style={{ backgroundColor: ESPN_DARK }}>
{/* Stats title */}
<div
style={{
position: "absolute",
top: 80,
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 36,
fontWeight: 900,
color: "#fff",
fontFamily: "system-ui, sans-serif",
}}
>
📊 SEASON STATS
</div>
</div>
{/* Stats grid */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
display: "grid",
gridTemplateColumns: "repeat(2, 1fr)",
gap: 40,
}}
>
{stats.map((stat, i) => {
const delay = i * 8;
const statProgress = spring({
frame: frame - delay,
fps,
config: { damping: 12 },
});
const countUp = Math.floor(
interpolate(statProgress, [0, 1], [0, typeof stat.value === "number" ? stat.value : parseFloat(stat.value)])
);
return (
<div
key={stat.label}
style={{
backgroundColor: "rgba(255,255,255,0.05)",
borderLeft: `4px solid ${stat.color}`,
padding: "30px 50px",
opacity: interpolate(statProgress, [0, 1], [0, 1]),
transform: `translateY(${interpolate(statProgress, [0, 1], [30, 0])}px)`,
}}
>
<div
style={{
fontSize: 16,
color: "#888",
fontWeight: 600,
marginBottom: 10,
}}
>
{stat.label}
</div>
<div
style={{
fontSize: 64,
color: stat.color,
fontWeight: 900,
fontFamily: "system-ui, sans-serif",
}}
>
{typeof stat.value === "number" ? countUp : stat.value}
</div>
</div>
);
})}
</div>
<MoodScoreBug mood="😊" score={85} streak={23} />
<NewsTicker />
</AbsoluteFill>
);
};
// Scene 4: CTA - "Download to join the league"
const CTAScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const logoProgress = spring({
frame,
fps,
config: { damping: 10, stiffness: 80 },
});
return (
<AbsoluteFill style={{ backgroundColor: ESPN_DARK }}>
{/* Dramatic background */}
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
background: `radial-gradient(ellipse at center, rgba(204,0,0,0.2) 0%, transparent 70%)`,
}}
/>
{/* App icon */}
<div
style={{
position: "absolute",
top: "35%",
left: "50%",
transform: `translate(-50%, -50%) scale(${interpolate(logoProgress, [0, 1], [0.5, 1])})`,
textAlign: "center",
}}
>
<Img
src={staticFile("app-icon.png")}
style={{
width: 180,
height: 180,
borderRadius: 180 * 0.22,
border: `4px solid ${ESPN_RED}`,
boxShadow: `0 0 60px ${ESPN_RED}`,
}}
/>
</div>
{/* CTA text */}
<div
style={{
position: "absolute",
top: "58%",
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 72,
fontWeight: 900,
color: "#fff",
fontFamily: "system-ui, sans-serif",
}}
>
Feels
</div>
<div
style={{
fontSize: 32,
color: ESPN_YELLOW,
fontWeight: 700,
marginTop: 20,
opacity: interpolate(frame, [fps, fps * 1.5], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
}),
}}
>
JOIN THE LEAGUE
</div>
</div>
{/* ESPN-style "Download" button */}
<div
style={{
position: "absolute",
bottom: 200,
left: "50%",
transform: `translateX(-50%) scale(${interpolate(frame, [fps * 2, fps * 2.5], [0.8, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
})})`,
}}
>
<div
style={{
backgroundColor: ESPN_RED,
padding: "20px 60px",
borderRadius: 8,
transform: "skewX(-5deg)",
}}
>
<span
style={{
fontSize: 28,
fontWeight: 900,
color: "#fff",
fontFamily: "system-ui, sans-serif",
transform: "skewX(5deg)",
display: "inline-block",
}}
>
DOWNLOAD NOW
</span>
</div>
</div>
{/* "THIS HAS BEEN" outro */}
<div
style={{
position: "absolute",
bottom: 100,
left: 0,
right: 0,
textAlign: "center",
opacity: interpolate(frame, [fps * 3, fps * 3.5], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
}),
}}
>
<div
style={{
fontSize: 18,
color: "#666",
fontFamily: "system-ui, sans-serif",
}}
>
This has been FEELS CENTER. Track responsibly.
</div>
</div>
</AbsoluteFill>
);
};
// Main composition - 20 seconds total
export const ConceptKSportsCenter: React.FC = () => {
const { fps } = useVideoConfig();
return (
<AbsoluteFill>
<Sequence from={0} durationInFrames={Math.round(4 * fps)}>
<OpeningScene />
</Sequence>
<Sequence from={Math.round(4 * fps)} durationInFrames={Math.round(6 * fps)}>
<PlayOfDayScene />
</Sequence>
<Sequence from={Math.round(10 * fps)} durationInFrames={Math.round(5 * fps)}>
<StatsScene />
</Sequence>
<Sequence from={Math.round(15 * fps)} durationInFrames={Math.round(5 * fps)}>
<CTAScene />
</Sequence>
</AbsoluteFill>
);
};

View File

@@ -0,0 +1,724 @@
import {
AbsoluteFill,
Img,
staticFile,
useCurrentFrame,
useVideoConfig,
interpolate,
spring,
Sequence,
Easing,
} from "remotion";
// Stage curtain component
const Curtain: React.FC<{ side: "left" | "right"; open: number }> = ({
side,
open,
}) => {
const offset = interpolate(open, [0, 1], [0, 100]);
return (
<div
style={{
position: "absolute",
top: 0,
[side]: 0,
width: "55%",
height: "100%",
background: "linear-gradient(180deg, #8B0000 0%, #4a0000 100%)",
transform: `translateX(${side === "left" ? -offset : offset}%)`,
zIndex: 50,
boxShadow:
side === "left"
? "10px 0 30px rgba(0,0,0,0.5)"
: "-10px 0 30px rgba(0,0,0,0.5)",
}}
>
{/* Curtain folds */}
{[...Array(8)].map((_, i) => (
<div
key={i}
style={{
position: "absolute",
top: 0,
left: `${i * 12.5}%`,
width: "12.5%",
height: "100%",
background: `linear-gradient(90deg, rgba(0,0,0,0.2) 0%, transparent 50%, rgba(0,0,0,0.1) 100%)`,
}}
/>
))}
</div>
);
};
// Spotlight effect
const Spotlight: React.FC<{
x: number;
y: number;
size?: number;
color?: string;
intensity?: number;
}> = ({ x, y, size = 400, color = "#fff", intensity = 0.6 }) => (
<div
style={{
position: "absolute",
left: x - size / 2,
top: y - size / 2,
width: size,
height: size,
background: `radial-gradient(circle, ${color}${Math.floor(intensity * 255).toString(16).padStart(2, "0")} 0%, transparent 70%)`,
pointerEvents: "none",
}}
/>
);
// Dancing emoji character
const DancingEmoji: React.FC<{
emoji: string;
x: number;
y: number;
size?: number;
delay?: number;
danceStyle?: "bounce" | "spin" | "sway";
}> = ({ emoji, x, y, size = 100, delay = 0, danceStyle = "bounce" }) => {
const frame = useCurrentFrame();
const localFrame = frame - delay;
let transform = "";
switch (danceStyle) {
case "bounce":
const bounceY = Math.abs(Math.sin(localFrame * 0.2)) * 30;
transform = `translateY(${-bounceY}px)`;
break;
case "spin":
transform = `rotate(${localFrame * 5}deg)`;
break;
case "sway":
const swayX = Math.sin(localFrame * 0.15) * 20;
const swayRotate = Math.sin(localFrame * 0.15) * 10;
transform = `translateX(${swayX}px) rotate(${swayRotate}deg)`;
break;
}
return (
<div
style={{
position: "absolute",
left: x,
top: y,
fontSize: size,
transform,
filter: "drop-shadow(0 10px 20px rgba(0,0,0,0.5))",
}}
>
{emoji}
</div>
);
};
// Stage lights at top
const StageLights: React.FC = () => {
const frame = useCurrentFrame();
const colors = ["#ff6b6b", "#feca57", "#48dbfb", "#ff9ff3", "#1dd1a1"];
return (
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
height: 80,
backgroundColor: "#1a1a1a",
display: "flex",
justifyContent: "space-around",
alignItems: "center",
zIndex: 60,
}}
>
{colors.map((color, i) => {
const pulse = Math.sin(frame * 0.1 + i) * 0.3 + 0.7;
return (
<div
key={i}
style={{
width: 40,
height: 50,
backgroundColor: "#333",
borderRadius: "50% 50% 0 0",
display: "flex",
alignItems: "flex-end",
justifyContent: "center",
paddingBottom: 5,
}}
>
<div
style={{
width: 30,
height: 30,
borderRadius: "50%",
backgroundColor: color,
opacity: pulse,
boxShadow: `0 0 30px ${color}, 0 20px 60px ${color}`,
}}
/>
</div>
);
})}
</div>
);
};
// Scene 1: Curtain Rise - "Welcome to the show"
const CurtainRiseScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const curtainOpen = interpolate(frame, [fps * 1, fps * 3], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
easing: Easing.out(Easing.cubic),
});
const titleProgress = spring({
frame: frame - fps * 2,
fps,
config: { damping: 12, stiffness: 80 },
});
return (
<AbsoluteFill style={{ backgroundColor: "#0a0a0a" }}>
{/* Stage floor */}
<div
style={{
position: "absolute",
bottom: 0,
left: 0,
right: 0,
height: 400,
background: "linear-gradient(180deg, #2d2d2d 0%, #1a1a1a 100%)",
}}
/>
{/* Spotlight */}
<Spotlight x={540} y={600} size={600} intensity={0.4} />
{/* Title card */}
<div
style={{
position: "absolute",
top: "45%",
left: "50%",
transform: `translate(-50%, -50%) scale(${interpolate(titleProgress, [0, 1], [0.5, 1])})`,
textAlign: "center",
opacity: interpolate(titleProgress, [0, 1], [0, 1]),
zIndex: 40,
}}
>
<div
style={{
fontSize: 32,
color: "#feca57",
fontFamily: "Georgia, serif",
fontStyle: "italic",
marginBottom: 20,
}}
>
Welcome to
</div>
<div
style={{
fontSize: 100,
fontWeight: 900,
color: "#fff",
fontFamily: "Georgia, serif",
textShadow: "0 0 60px rgba(255,200,100,0.5)",
letterSpacing: 8,
}}
>
FEELS
</div>
<div
style={{
fontSize: 48,
color: "#ff6b6b",
fontFamily: "Georgia, serif",
fontStyle: "italic",
marginTop: 10,
}}
>
The Musical
</div>
</div>
<StageLights />
<Curtain side="left" open={curtainOpen} />
<Curtain side="right" open={curtainOpen} />
</AbsoluteFill>
);
};
// Scene 2: "The Feelings Song" - Dancing mood emojis
const FeelingSongScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps, width, height } = useVideoConfig();
const emojis = [
{ emoji: "😢", color: "#3b82f6", name: "SADNESS" },
{ emoji: "😤", color: "#ef4444", name: "ANGER" },
{ emoji: "😊", color: "#10b981", name: "JOY" },
{ emoji: "😰", color: "#8b5cf6", name: "ANXIETY" },
{ emoji: "🥰", color: "#ec4899", name: "LOVE" },
];
// Choreographed positions
const getPosition = (index: number, progress: number) => {
const centerX = width / 2;
const baseY = height * 0.5;
// Dance formation - V shape to line to circle
const phase = Math.floor(progress * 3) % 3;
if (phase === 0) {
// V formation
const vX = centerX + (index - 2) * 150;
const vY = baseY + Math.abs(index - 2) * 80;
return { x: vX - 50, y: vY };
} else if (phase === 1) {
// Line formation
const lineX = centerX + (index - 2) * 180;
return { x: lineX - 50, y: baseY };
} else {
// Circle formation
const angle = (index / 5) * Math.PI * 2 + progress * 2;
const radius = 250;
return {
x: centerX + Math.cos(angle) * radius - 50,
y: baseY + Math.sin(angle) * radius * 0.5,
};
}
};
const danceProgress = interpolate(frame, [0, fps * 5], [0, 1], {
extrapolateRight: "clamp",
});
return (
<AbsoluteFill style={{ backgroundColor: "#1a1a1a" }}>
{/* Stage floor with reflection */}
<div
style={{
position: "absolute",
bottom: 0,
left: 0,
right: 0,
height: 500,
background:
"linear-gradient(180deg, #2d2d2d 0%, #1a1a1a 50%, #0a0a0a 100%)",
}}
/>
{/* Multiple spotlights */}
{emojis.map((e, i) => {
const pos = getPosition(i, danceProgress);
return (
<Spotlight
key={i}
x={pos.x + 50}
y={pos.y + 50}
size={300}
color={e.color}
intensity={0.3}
/>
);
})}
{/* "EVERYBODY FEEL!" banner */}
<div
style={{
position: "absolute",
top: 150,
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 48,
fontWeight: 900,
color: "#feca57",
fontFamily: "Georgia, serif",
textShadow: "0 0 30px rgba(254,202,87,0.5)",
transform: `scale(${1 + Math.sin(frame * 0.2) * 0.05})`,
}}
>
EVERYBODY FEEL!
</div>
</div>
{/* Dancing emojis */}
{emojis.map((e, i) => {
const pos = getPosition(i, danceProgress);
const delay = i * 3;
return (
<DancingEmoji
key={i}
emoji={e.emoji}
x={pos.x}
y={pos.y}
size={100}
delay={delay}
danceStyle={i % 3 === 0 ? "bounce" : i % 3 === 1 ? "sway" : "spin"}
/>
);
})}
{/* Musical notes floating */}
{["♪", "♫", "♬", "♩"].map((note, i) => {
const noteX = (frame * 2 + i * 200) % (width + 100) - 50;
const noteY = 200 + Math.sin(frame * 0.1 + i) * 100;
return (
<div
key={i}
style={{
position: "absolute",
left: noteX,
top: noteY,
fontSize: 60,
color: "#feca57",
opacity: 0.6,
transform: `rotate(${Math.sin(frame * 0.1 + i) * 20}deg)`,
}}
>
{note}
</div>
);
})}
<StageLights />
</AbsoluteFill>
);
};
// Scene 3: Solo number - Joy takes center stage
const SoloNumberScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps, width, height } = useVideoConfig();
const spotlightPulse = Math.sin(frame * 0.1) * 0.2 + 0.8;
// Joy's dramatic entrance
const joyEnter = spring({
frame,
fps,
config: { damping: 12, stiffness: 60 },
});
const joyY = interpolate(joyEnter, [0, 1], [height, height * 0.4]);
// Star burst behind Joy
const starRotation = frame * 0.5;
return (
<AbsoluteFill style={{ backgroundColor: "#0a0a0a" }}>
{/* Dramatic backdrop */}
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
background:
"radial-gradient(ellipse at center, #1a472a 0%, #0a0a0a 70%)",
}}
/>
{/* Star burst */}
<div
style={{
position: "absolute",
top: joyY - 100,
left: width / 2,
transform: `translate(-50%, -50%) rotate(${starRotation}deg)`,
}}
>
{[...Array(12)].map((_, i) => (
<div
key={i}
style={{
position: "absolute",
width: 8,
height: 300,
background: `linear-gradient(180deg, #feca57 0%, transparent 100%)`,
transform: `rotate(${i * 30}deg)`,
transformOrigin: "center bottom",
opacity: 0.3,
}}
/>
))}
</div>
{/* Giant spotlight on Joy */}
<Spotlight
x={width / 2}
y={joyY}
size={800}
color="#10b981"
intensity={spotlightPulse * 0.5}
/>
{/* Joy - center stage */}
<div
style={{
position: "absolute",
left: width / 2 - 100,
top: joyY - 100,
fontSize: 200,
filter: "drop-shadow(0 20px 40px rgba(16,185,129,0.5))",
transform: `scale(${1 + Math.sin(frame * 0.15) * 0.1})`,
}}
>
😊
</div>
{/* Lyric text */}
<div
style={{
position: "absolute",
bottom: 300,
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 40,
color: "#fff",
fontFamily: "Georgia, serif",
fontStyle: "italic",
opacity: interpolate(frame, [fps, fps * 1.5], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
}),
}}
>
"One tap is all it takes..."
</div>
<div
style={{
fontSize: 40,
color: "#10b981",
fontFamily: "Georgia, serif",
fontStyle: "italic",
marginTop: 20,
opacity: interpolate(frame, [fps * 2, fps * 2.5], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
}),
}}
>
"To know how your heart feels today!"
</div>
</div>
<StageLights />
</AbsoluteFill>
);
};
// Scene 4: Finale - All together + app reveal
const FinaleScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps, width, height } = useVideoConfig();
const emojis = ["😢", "😕", "😐", "🙂", "😊"];
const logoProgress = spring({
frame: frame - fps * 1,
fps,
config: { damping: 10, stiffness: 80 },
});
// Confetti
const confettiColors = ["#ff6b6b", "#feca57", "#48dbfb", "#ff9ff3", "#1dd1a1"];
return (
<AbsoluteFill style={{ backgroundColor: "#0a0a0a" }}>
{/* Grand finale lighting */}
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
background: `
radial-gradient(ellipse at 50% 30%, rgba(254,202,87,0.3) 0%, transparent 50%),
radial-gradient(ellipse at 30% 70%, rgba(236,72,153,0.2) 0%, transparent 40%),
radial-gradient(ellipse at 70% 70%, rgba(72,219,251,0.2) 0%, transparent 40%)
`,
}}
/>
{/* Line of emojis at top */}
<div
style={{
position: "absolute",
top: 200,
left: 0,
right: 0,
display: "flex",
justifyContent: "center",
gap: 40,
}}
>
{emojis.map((emoji, i) => {
const delay = i * 3;
const emojiProgress = spring({
frame: frame - delay,
fps,
config: { damping: 12 },
});
return (
<div
key={i}
style={{
fontSize: 80,
transform: `translateY(${interpolate(emojiProgress, [0, 1], [100, 0])}px) scale(${interpolate(emojiProgress, [0, 1], [0.5, 1])})`,
opacity: interpolate(emojiProgress, [0, 1], [0, 1]),
}}
>
{emoji}
</div>
);
})}
</div>
{/* App icon center stage */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: `translate(-50%, -50%) scale(${interpolate(logoProgress, [0, 1], [0, 1])})`,
textAlign: "center",
}}
>
<Img
src={staticFile("app-icon.png")}
style={{
width: 200,
height: 200,
borderRadius: 200 * 0.22,
boxShadow: "0 0 100px rgba(254,202,87,0.5)",
}}
/>
</div>
{/* App name */}
<div
style={{
position: "absolute",
top: "68%",
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 80,
fontWeight: 900,
color: "#fff",
fontFamily: "Georgia, serif",
textShadow: "0 0 40px rgba(255,255,255,0.5)",
opacity: interpolate(logoProgress, [0, 1], [0, 1]),
}}
>
Feels
</div>
<div
style={{
fontSize: 32,
color: "#feca57",
fontFamily: "Georgia, serif",
fontStyle: "italic",
marginTop: 20,
opacity: interpolate(frame, [fps * 2.5, fps * 3], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
}),
}}
>
Download Today
</div>
</div>
{/* Confetti */}
{[...Array(40)].map((_, i) => {
const seed = i * 137.5;
const x = (Math.sin(seed) * 0.5 + 0.5) * width;
const fallProgress = interpolate(
frame,
[fps * 1 + i * 2, fps * 5],
[0, 1],
{ extrapolateLeft: "clamp", extrapolateRight: "clamp" }
);
const y = interpolate(fallProgress, [0, 1], [-50, height + 50]);
return (
<div
key={i}
style={{
position: "absolute",
left: x,
top: y,
width: 15,
height: 15,
backgroundColor: confettiColors[i % confettiColors.length],
borderRadius: i % 2 === 0 ? "50%" : 2,
transform: `rotate(${frame * (3 + (i % 5))}deg)`,
opacity: interpolate(fallProgress, [0, 0.8, 1], [1, 1, 0]),
}}
/>
);
})}
<StageLights />
</AbsoluteFill>
);
};
// Main composition - 25 seconds total
export const ConceptLMusical: React.FC = () => {
const { fps } = useVideoConfig();
return (
<AbsoluteFill>
<Sequence from={0} durationInFrames={Math.round(5 * fps)}>
<CurtainRiseScene />
</Sequence>
<Sequence from={Math.round(5 * fps)} durationInFrames={Math.round(7 * fps)}>
<FeelingSongScene />
</Sequence>
<Sequence from={Math.round(12 * fps)} durationInFrames={Math.round(6 * fps)}>
<SoloNumberScene />
</Sequence>
<Sequence from={Math.round(18 * fps)} durationInFrames={Math.round(7 * fps)}>
<FinaleScene />
</Sequence>
</AbsoluteFill>
);
};

View File

@@ -1,23 +1,166 @@
import { Composition } from "remotion";
import { FeelsPromoV1 } from "./FeelsPromo";
import { ConceptASelfAwareness } from "./ConceptA-SelfAwareness";
import { ConceptBNoJournalJournal } from "./ConceptB-NoJournalJournal";
import { ConceptCYearInFeelings } from "./ConceptC-YearInFeelings";
import { ConceptDAlwaysThere } from "./ConceptD-AlwaysThere";
import { ConceptEMakeItYours } from "./ConceptE-MakeItYours";
import { ConceptFPrivacyFirst } from "./ConceptF-PrivacyFirst";
import { ConceptGStreakEffect } from "./ConceptG-StreakEffect";
// Wild concepts
import { ConceptHMoodHeist } from "./ConceptH-MoodHeist";
import { ConceptIRetroArcade } from "./ConceptI-RetroArcade";
import { ConceptJConspiracy } from "./ConceptJ-Conspiracy";
import { ConceptKSportsCenter } from "./ConceptK-SportsCenter";
import { ConceptLMusical } from "./ConceptL-Musical";
export const RemotionRoot: React.FC = () => {
const fps = 30;
const sceneDuration = 3.5 * fps; // 3.5 seconds per scene
const transitionDuration = Math.round(0.6 * fps); // 0.6 second transitions
const outroDuration = Math.round(2.5 * fps);
// Calculate total duration accounting for transition overlaps
// 7 scenes + outro - 7 transitions
const totalDuration =
// V1 calculations
const sceneDuration = 3.5 * fps;
const transitionDuration = Math.round(0.6 * fps);
const outroDuration = Math.round(2.5 * fps);
const v1TotalDuration =
sceneDuration * 7 + outroDuration - transitionDuration * 7;
return (
<>
{/* ═══════════════════════════════════════════════════════════════
ORIGINAL PROMO
═══════════════════════════════════════════════════════════════ */}
<Composition
id="FeelsPromoV1"
component={FeelsPromoV1}
durationInFrames={totalDuration}
durationInFrames={v1TotalDuration}
fps={fps}
width={1080}
height={1920}
/>
{/* ═══════════════════════════════════════════════════════════════
STANDARD CONCEPTS (A-G)
═══════════════════════════════════════════════════════════════ */}
{/* Concept A: 30 Seconds to Self-Awareness */}
<Composition
id="ConceptA-SelfAwareness"
component={ConceptASelfAwareness}
durationInFrames={Math.round(30 * fps)}
fps={fps}
width={1080}
height={1920}
/>
{/* Concept B: The No-Journal Journal (20s) */}
<Composition
id="ConceptB-NoJournalJournal"
component={ConceptBNoJournalJournal}
durationInFrames={Math.round(20 * fps)}
fps={fps}
width={1080}
height={1920}
/>
{/* Concept C: Your Year in Feelings (15s) */}
<Composition
id="ConceptC-YearInFeelings"
component={ConceptCYearInFeelings}
durationInFrames={Math.round(15 * fps)}
fps={fps}
width={1080}
height={1920}
/>
{/* Concept D: Always There (25s) */}
<Composition
id="ConceptD-AlwaysThere"
component={ConceptDAlwaysThere}
durationInFrames={Math.round(25 * fps)}
fps={fps}
width={1080}
height={1920}
/>
{/* Concept E: Make It Yours (20s) */}
<Composition
id="ConceptE-MakeItYours"
component={ConceptEMakeItYours}
durationInFrames={Math.round(20 * fps)}
fps={fps}
width={1080}
height={1920}
/>
{/* Concept F: Privacy First (15s) */}
<Composition
id="ConceptF-PrivacyFirst"
component={ConceptFPrivacyFirst}
durationInFrames={Math.round(15 * fps)}
fps={fps}
width={1080}
height={1920}
/>
{/* Concept G: The Streak Effect (20s) */}
<Composition
id="ConceptG-StreakEffect"
component={ConceptGStreakEffect}
durationInFrames={Math.round(20 * fps)}
fps={fps}
width={1080}
height={1920}
/>
{/* ═══════════════════════════════════════════════════════════════
WILD CONCEPTS (H-L) - OFF THE WALL CREATIVE
═══════════════════════════════════════════════════════════════ */}
{/* Concept H: The Mood Heist (25s) - Ocean's Eleven style thriller */}
<Composition
id="ConceptH-MoodHeist"
component={ConceptHMoodHeist}
durationInFrames={Math.round(25 * fps)}
fps={fps}
width={1080}
height={1920}
/>
{/* Concept I: Retro Arcade Feels (20s) - 8-bit video game */}
<Composition
id="ConceptI-RetroArcade"
component={ConceptIRetroArcade}
durationInFrames={Math.round(20 * fps)}
fps={fps}
width={1080}
height={1920}
/>
{/* Concept J: The Feels Conspiracy (20s) - Dark documentary thriller */}
<Composition
id="ConceptJ-Conspiracy"
component={ConceptJConspiracy}
durationInFrames={Math.round(20 * fps)}
fps={fps}
width={1080}
height={1920}
/>
{/* Concept K: Sports Center Emotions (20s) - ESPN broadcast parody */}
<Composition
id="ConceptK-SportsCenter"
component={ConceptKSportsCenter}
durationInFrames={Math.round(20 * fps)}
fps={fps}
width={1080}
height={1920}
/>
{/* Concept L: Feelings The Musical (25s) - Broadway musical number */}
<Composition
id="ConceptL-Musical"
component={ConceptLMusical}
durationInFrames={Math.round(25 * fps)}
fps={fps}
width={1080}
height={1920}