feat(videos): add all 5 Promo Teaser scene components

This commit is contained in:
SnapOtter
2026-05-08 16:41:04 +08:00
parent 7ff96a6515
commit 210bf9cded
5 changed files with 207 additions and 0 deletions
@@ -0,0 +1,58 @@
import type React from "react";
import { AbsoluteFill, interpolate, useCurrentFrame } from "remotion";
import { GradientBlob } from "@/components/GradientBlob";
const BLOBS = [
{
color: "#f59e0b",
radius: 200,
cx: 300,
cy: 400,
a: 1,
b: 2,
phaseX: 0,
phaseY: 0,
amplitudeX: 60,
amplitudeY: 40,
},
{
color: "#f97316",
radius: 150,
cx: 750,
cy: 250,
a: 2,
b: 1,
phaseX: 1.2,
phaseY: 0.8,
amplitudeX: 50,
amplitudeY: 60,
},
{
color: "#d97706",
radius: 180,
cx: 540,
cy: 700,
a: 1,
b: 1,
phaseX: 2.4,
phaseY: 1.6,
amplitudeX: 70,
amplitudeY: 50,
},
];
export const AmbientOpenScene: React.FC = () => {
const frame = useCurrentFrame();
const fadeIn = interpolate(frame, [0, 45], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
return (
<AbsoluteFill style={{ opacity: fadeIn }}>
{BLOBS.map((blob, i) => (
<GradientBlob key={`ambient-${i}`} config={blob} duration={600} />
))}
</AbsoluteFill>
);
};
@@ -0,0 +1,60 @@
import type React from "react";
import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig } from "remotion";
import { COLOR } from "@/lib/colors";
import { FONT } from "@/lib/fonts";
import { SPRING } from "@/lib/motion";
export const CTAScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const pillScale = spring({ frame, fps, config: SPRING.popIn });
const urlOpacity = interpolate(frame, [15, 25], [0, 0.7], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const fadeOut = interpolate(frame, [60, 90], [1, 0], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const glowOpacity = interpolate(Math.sin(frame * 0.1), [-1, 1], [0.3, 0.6]);
return (
<AbsoluteFill
style={{
justifyContent: "center",
alignItems: "center",
opacity: fadeOut,
}}
>
<div
style={{
transform: `scale(${pillScale})`,
padding: "14px 36px",
borderRadius: 50,
background: `linear-gradient(135deg, ${COLOR.accent}, ${COLOR.accentHover})`,
boxShadow: `0 0 30px rgba(245, 158, 11, ${glowOpacity})`,
color: "white",
fontFamily: FONT.heading,
fontWeight: 700,
fontSize: 20,
}}
>
Get it free
</div>
<div style={{ height: 16 }} />
<span
style={{
fontFamily: FONT.mono,
fontSize: 16,
color: "white",
opacity: urlOpacity,
}}
>
snapotter.com
</span>
</AbsoluteFill>
);
};
@@ -0,0 +1,18 @@
import type React from "react";
import { AbsoluteFill } from "remotion";
import { LogoReveal } from "@/components/LogoReveal";
export const LogoRevealScene: React.FC = () => {
return (
<AbsoluteFill>
<LogoReveal
convergeFrame={0}
burstFrame={30}
logoFrame={30}
textFrame={50}
taglineFrame={70}
logoSize={80}
/>
</AbsoluteFill>
);
};
@@ -0,0 +1,45 @@
import type React from "react";
import { AbsoluteFill, interpolate, useCurrentFrame } from "remotion";
import { NumberPunch } from "@/components/NumberPunch";
import { COLOR } from "@/lib/colors";
const PUNCHES = [
{ number: "49", descriptor: "tools", frame: 0, size: 120, shake: 2 },
{ number: "15", descriptor: "AI models", frame: 37, size: 120, shake: 2 },
{ number: "55+", descriptor: "formats", frame: 74, size: 120, shake: 2 },
{ number: "1", descriptor: "container", frame: 111, size: 144, shake: 3 },
];
export const NumberPunchScene: React.FC = () => {
const frame = useCurrentFrame();
return (
<AbsoluteFill style={{ justifyContent: "center", alignItems: "center" }}>
{PUNCHES.map((p, i) => {
const visible = frame >= p.frame && frame < p.frame + 37;
const flashStart = p.frame - 2;
const flashOpacity =
i > 0 && frame >= flashStart && frame < flashStart + 2
? interpolate(frame, [flashStart, flashStart + 2], [1, 0])
: 0;
return (
<AbsoluteFill key={p.number} style={{ justifyContent: "center", alignItems: "center" }}>
{i > 0 && (
<AbsoluteFill style={{ backgroundColor: COLOR.dark, opacity: flashOpacity }} />
)}
{visible && (
<NumberPunch
number={p.number}
descriptor={p.descriptor}
enterFrame={p.frame}
numberSize={p.size}
shakeIntensity={p.shake}
/>
)}
</AbsoluteFill>
);
})}
</AbsoluteFill>
);
};
@@ -0,0 +1,26 @@
import type React from "react";
import { AbsoluteFill } from "remotion";
import { RotatingTaglines } from "@/components/RotatingTaglines";
const TAGLINES = [
"No signups.",
"No uploads.",
"No limits.",
"Free forever.",
"Open source.",
"Fully offline.",
];
export const TaglineCascadeScene: React.FC = () => {
return (
<AbsoluteFill>
<RotatingTaglines
lines={TAGLINES}
startFrame={0}
framesPerLine={25}
fontSize={40}
color="white"
/>
</AbsoluteFill>
);
};