Files
Reflect/feels-promo/src/ConceptH-MoodHeist.tsx
2026-01-26 18:03:51 -06:00

789 lines
19 KiB
TypeScript

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>
);
};