feat(videos): add X Launch scenes 1-4 (Hook, Terminal, ToolGrid, AI)

This commit is contained in:
SnapOtter
2026-05-08 16:43:54 +08:00
parent 85acce7e16
commit ccf4653b96
4 changed files with 262 additions and 0 deletions
@@ -0,0 +1,142 @@
import type React from "react";
import { AbsoluteFill, interpolate, Sequence, useCurrentFrame } from "remotion";
import { BeforeAfter } from "@/components/BeforeAfter";
import { ClipReveal } from "@/components/ClipReveal";
import { PhotoPlaceholder } from "@/components/PhotoPlaceholder";
import { COLOR } from "@/lib/colors";
import { FONT } from "@/lib/fonts";
const Checkerboard: React.FC<{ width: number; height: number }> = ({ width, height }) => (
<div
style={{
width,
height,
backgroundImage:
"linear-gradient(45deg, #808080 25%, transparent 25%), linear-gradient(-45deg, #808080 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #808080 75%), linear-gradient(-45deg, transparent 75%, #808080 75%)",
backgroundSize: "16px 16px",
backgroundPosition: "0 0, 0 8px, 8px -8px, -8px 0px",
opacity: 0.3,
}}
/>
);
const BgRemovalDemo: React.FC = () => (
<BeforeAfter
before={<PhotoPlaceholder width={400} height={300} hue={30} />}
after={
<div style={{ position: "relative", width: 400, height: 300 }}>
<Checkerboard width={400} height={300} />
<div
style={{
position: "absolute",
top: 60,
left: 120,
width: 160,
height: 200,
borderRadius: "50% 50% 45% 45%",
background: "linear-gradient(135deg, #e8a87c, #d4856b)",
}}
/>
</div>
}
scanStartFrame={5}
scanDuration={40}
width={400}
height={300}
/>
);
const UpscaleDemo: React.FC = () => {
const frame = useCurrentFrame();
return (
<div style={{ display: "flex", alignItems: "center", gap: 30 }}>
<div
style={{
width: 80,
height: 80,
display: "grid",
gridTemplateColumns: "repeat(4, 1fr)",
gap: 2,
borderRadius: 4,
overflow: "hidden",
}}
>
{Array.from({ length: 16 }).map((_, i) => (
<div
key={`px-${i}`}
style={{
backgroundColor: `hsl(${(i * 23) % 360}, 60%, ${50 + (i % 3) * 10}%)`,
}}
/>
))}
</div>
<span
style={{ color: COLOR.accent, fontFamily: FONT.heading, fontWeight: 800, fontSize: 36 }}
>
4x
</span>
<PhotoPlaceholder width={160} height={160} hue={200} />
</div>
);
};
export const AiShowcaseScene: React.FC = () => {
return (
<AbsoluteFill>
<Sequence from={0} durationInFrames={60}>
<AbsoluteFill style={{ justifyContent: "center", alignItems: "center" }}>
<BgRemovalDemo />
</AbsoluteFill>
</Sequence>
<Sequence from={60} durationInFrames={60}>
<AbsoluteFill style={{ justifyContent: "center", alignItems: "center" }}>
<UpscaleDemo />
</AbsoluteFill>
</Sequence>
<Sequence from={120} durationInFrames={60}>
<AbsoluteFill style={{ justifyContent: "center", alignItems: "center" }}>
<BeforeAfter
before={
<div style={{ position: "relative", width: 400, height: 300 }}>
<PhotoPlaceholder width={400} height={300} hue={180} />
<div
style={{
position: "absolute",
top: 80,
left: 150,
width: 100,
height: 100,
border: `2px dashed ${COLOR.accent}`,
borderRadius: 8,
}}
/>
</div>
}
after={<PhotoPlaceholder width={400} height={300} hue={180} />}
scanStartFrame={5}
scanDuration={40}
width={400}
height={300}
/>
</AbsoluteFill>
</Sequence>
<div
style={{
position: "absolute",
bottom: 40,
width: "100%",
textAlign: "center",
fontFamily: FONT.body,
fontWeight: 600,
fontSize: 24,
color: "rgba(255,255,255,0.8)",
}}
>
15 AI models. Your hardware. No cloud.
</div>
</AbsoluteFill>
);
};
@@ -0,0 +1,36 @@
import type React from "react";
import { AbsoluteFill, interpolate, useCurrentFrame } from "remotion";
import { ClipReveal } from "@/components/ClipReveal";
import { COLOR } from "@/lib/colors";
import { TEXT } from "@/lib/fonts";
export const HookScene: React.FC = () => {
const frame = useCurrentFrame();
const glowOpacity = Math.sin(frame * 0.08) * 0.03 + 0.08;
return (
<AbsoluteFill style={{ justifyContent: "center", alignItems: "center" }}>
<div
style={{
position: "absolute",
width: 600,
height: 200,
borderRadius: "50%",
background: `radial-gradient(ellipse, ${COLOR.accent} 0%, transparent 70%)`,
opacity: glowOpacity,
filter: "blur(60px)",
}}
/>
<div style={{ textAlign: "center" }}>
<ClipReveal startFrame={10} duration={20}>
<span style={{ ...TEXT.heroHeadline }}>Your images.</span>
</ClipReveal>
<div style={{ height: 12 }} />
<ClipReveal startFrame={40} duration={20}>
<span style={{ ...TEXT.heroHeadline }}>Stay yours.</span>
</ClipReveal>
</div>
</AbsoluteFill>
);
};
@@ -0,0 +1,37 @@
import type React from "react";
import { AbsoluteFill } from "remotion";
import { Terminal } from "@/components/Terminal";
const COMMAND = [
{ text: "docker", color: "#ff7b72" },
{ text: " run ", color: "#e6edf3" },
{ text: "-p ", color: "#79c0ff" },
{ text: "3000:3000", color: "#a5d6ff" },
{ text: " ", color: "#e6edf3" },
{ text: "snapotter/snapotter", color: "#7ee787" },
];
const OUTPUT = [
{ text: "v2.4.0", color: "#f59e0b", delay: 0 },
{ text: "49 tools loaded", color: "#7ee787", delay: 3 },
{ text: "15 AI models ready", color: "#7ee787", delay: 6 },
{ text: "✓ Server running on :3000", color: "#3fb950", delay: 9 },
];
export const TerminalInstallScene: React.FC = () => {
return (
<AbsoluteFill style={{ justifyContent: "center", alignItems: "center" }}>
<Terminal
command={COMMAND}
commandStartFrame={15}
commandSpeed={2}
outputLines={OUTPUT}
outputStartFrame={95}
outputStagger={8}
width={800}
height={350}
enterFrame={0}
/>
</AbsoluteFill>
);
};
@@ -0,0 +1,47 @@
import type React from "react";
import { AbsoluteFill, interpolate, useCurrentFrame } from "remotion";
import { Counter } from "@/components/Counter";
import { ToolGrid } from "@/components/ToolGrid";
import { COLOR } from "@/lib/colors";
import { TEXT } from "@/lib/fonts";
export const ToolGridRevealScene: React.FC = () => {
const frame = useCurrentFrame();
const gridOpacity = interpolate(frame, [0, 20], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
return (
<AbsoluteFill>
<div
style={{
position: "absolute",
top: 60,
left: 40,
right: 40,
opacity: gridOpacity,
transform: "scale(0.85)",
transformOrigin: "top center",
}}
>
<ToolGrid startFrame={10} cellWidth={110} cellHeight={26} gap={4} />
</div>
<div style={{ position: "absolute", bottom: 60, right: 80 }}>
<Counter
from={0}
to={49}
startFrame={10}
duration={90}
style={{
...TEXT.counter,
fontSize: 64,
color: COLOR.accent,
}}
/>
</div>
</AbsoluteFill>
);
};