feat(videos): add OneCommand hero composition with terminal-to-app morph

Restore missing landing page compositions (FloatingTools, BrandGradient,
FormatUniverse) lost during branch merge, and fix the @/ path alias in
remotion webpack config to support all composition imports.
This commit is contained in:
SnapOtter
2026-05-08 17:15:28 +08:00
parent 736c2de057
commit 9a4be31154
4 changed files with 339 additions and 1 deletions
@@ -0,0 +1,79 @@
import type React from "react";
import { AbsoluteFill } from "remotion";
import { GradientBlob } from "@/components/GradientBlob";
import { GrainOverlay } from "@/components/GrainOverlay";
import { COLOR } from "@/lib/colors";
const DURATION = 150;
const BLOBS = [
{
color: "#f59e0b",
radius: 180,
cx: 200,
cy: 150,
a: 2,
b: 3,
phaseX: 0,
phaseY: 0,
amplitudeX: 80,
amplitudeY: 60,
},
{
color: "#f97316",
radius: 150,
cx: 500,
cy: 350,
a: 3,
b: 2,
phaseX: 1.2,
phaseY: 0.8,
amplitudeX: 70,
amplitudeY: 90,
},
{
color: "#3b82f6",
radius: 200,
cx: 600,
cy: 150,
a: 2,
b: 1,
phaseX: 2.4,
phaseY: 1.6,
amplitudeX: 90,
amplitudeY: 50,
},
{
color: "#14b8a6",
radius: 140,
cx: 350,
cy: 400,
a: 1,
b: 2,
phaseX: 3.6,
phaseY: 2.4,
amplitudeX: 60,
amplitudeY: 80,
},
{
color: "#8b5cf6",
radius: 160,
cx: 150,
cy: 350,
a: 3,
b: 1,
phaseX: 4.8,
phaseY: 3.2,
amplitudeX: 50,
amplitudeY: 70,
},
];
export const BrandGradient: React.FC = () => (
<AbsoluteFill style={{ backgroundColor: COLOR.dark, overflow: "hidden" }}>
{BLOBS.map((blob) => (
<GradientBlob key={blob.color} config={blob} duration={DURATION} />
))}
<GrainOverlay opacity={0.04} />
</AbsoluteFill>
);
@@ -0,0 +1,68 @@
import type React from "react";
import { useState } from "react";
import { AbsoluteFill, random, useCurrentFrame, useVideoConfig } from "remotion";
import { GrainOverlay } from "@/components/GrainOverlay";
import { COLOR } from "@/lib/colors";
import { FONT } from "@/lib/fonts";
import { TOOLS } from "@/lib/tools";
const LAYERS = [
{ speed: 0.8, fontSize: 14, opacity: 0.06, count: 10 },
{ speed: 1.5, fontSize: 18, opacity: 0.12, count: 9 },
{ speed: 2.5, fontSize: 24, opacity: 0.2, count: 6 },
];
const X_FREQ = (2 * Math.PI) / 120;
export const FloatingTools: React.FC = () => {
const frame = useCurrentFrame();
const { width, height } = useVideoConfig();
const wrapH = height + 100;
const [items] = useState(() => {
let toolIdx = 0;
return LAYERS.flatMap((layer, li) =>
Array.from({ length: layer.count }, (_, i) => {
const tool = TOOLS[toolIdx % TOOLS.length];
toolIdx++;
return {
tool,
layer,
x: random(`float-x-${li}-${i}`) * width,
startY: random(`float-y-${li}-${i}`) * wrapH,
phase: random(`float-phase-${li}-${i}`) * Math.PI * 2,
};
}),
);
});
return (
<AbsoluteFill style={{ backgroundColor: COLOR.dark, overflow: "hidden" }}>
{items.map((item, idx) => {
const rawY = item.startY - frame * item.layer.speed;
const y = (((rawY % wrapH) + wrapH) % wrapH) - 50;
const xOffset = Math.sin(frame * X_FREQ + item.phase) * 3;
return (
<span
key={idx}
style={{
position: "absolute",
left: item.x + xOffset,
top: y,
fontFamily: FONT.body,
fontSize: item.layer.fontSize,
fontWeight: 500,
color: COLOR.category[item.tool.category] ?? COLOR.accent,
opacity: item.layer.opacity,
whiteSpace: "nowrap",
}}
>
{item.tool.name}
</span>
);
})}
<GrainOverlay opacity={0.02} />
</AbsoluteFill>
);
};