mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
feat(videos): add X Launch scenes 1-4 (Hook, Terminal, ToolGrid, AI)
This commit is contained in:
@@ -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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user