feat: add Remotion video project + SquareMCP brand compositions
- Scaffold videos/remotion-demo with Remotion 4.0.459 - SquareMCPLinkedIn: 90s 1080×1080 LinkedIn explainer (6 scenes, spring animations) - SquareMCPHeroLoop: 30s 1920×1080 seamless hero loop (node network + MCP layer + logo) - Brand tokens from squaremcp-logo.svg (#0E63F6/#7DB6FF), Inter via @remotion/google-fonts - Both compositions rendered to out/ and type-checked clean - Update .gitignore to exclude videos/remotion-demo/node_modules and out/ Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -14,3 +14,7 @@ hermes.log
|
||||
.claude/
|
||||
.codex
|
||||
opencode.json
|
||||
|
||||
# Remotion demo project
|
||||
videos/remotion-demo/node_modules
|
||||
videos/remotion-demo/out
|
||||
|
||||
BIN
videos/.DS_Store
vendored
Normal file
BIN
videos/.DS_Store
vendored
Normal file
Binary file not shown.
7
videos/remotion-demo/.gitignore
vendored
Normal file
7
videos/remotion-demo/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
node_modules
|
||||
dist
|
||||
.DS_Store
|
||||
.env
|
||||
|
||||
# Ignore the output video from Git but not videos you import into src/.
|
||||
out
|
||||
5
videos/remotion-demo/.prettierrc
Normal file
5
videos/remotion-demo/.prettierrc
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"useTabs": false,
|
||||
"bracketSpacing": true,
|
||||
"tabWidth": 2
|
||||
}
|
||||
54
videos/remotion-demo/README.md
Normal file
54
videos/remotion-demo/README.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# Remotion video
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/remotion-dev/logo">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/remotion-dev/logo/raw/main/animated-logo-banner-dark.apng">
|
||||
<img alt="Animated Remotion Logo" src="https://github.com/remotion-dev/logo/raw/main/animated-logo-banner-light.gif">
|
||||
</picture>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
Welcome to your Remotion project!
|
||||
|
||||
## Commands
|
||||
|
||||
**Install Dependencies**
|
||||
|
||||
```console
|
||||
npm i
|
||||
```
|
||||
|
||||
**Start Preview**
|
||||
|
||||
```console
|
||||
npm run dev
|
||||
```
|
||||
|
||||
**Render video**
|
||||
|
||||
```console
|
||||
npx remotion render
|
||||
```
|
||||
|
||||
**Upgrade Remotion**
|
||||
|
||||
```console
|
||||
npx remotion upgrade
|
||||
```
|
||||
|
||||
## Docs
|
||||
|
||||
Get started with Remotion by reading the [fundamentals page](https://www.remotion.dev/docs/the-fundamentals).
|
||||
|
||||
## Help
|
||||
|
||||
We provide help on our [Discord server](https://discord.gg/6VzzNDwUwV).
|
||||
|
||||
## Issues
|
||||
|
||||
Found an issue with Remotion? [File an issue here](https://github.com/remotion-dev/remotion/issues/new).
|
||||
|
||||
## License
|
||||
|
||||
Note that for some entities a company license is needed. [Read the terms here](https://github.com/remotion-dev/remotion/blob/main/LICENSE.md).
|
||||
3
videos/remotion-demo/eslint.config.mjs
Normal file
3
videos/remotion-demo/eslint.config.mjs
Normal file
@@ -0,0 +1,3 @@
|
||||
import { config } from "@remotion/eslint-config-flat";
|
||||
|
||||
export default config;
|
||||
5005
videos/remotion-demo/package-lock.json
generated
Normal file
5005
videos/remotion-demo/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
34
videos/remotion-demo/package.json
Normal file
34
videos/remotion-demo/package.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "remotion-demo",
|
||||
"version": "1.0.0",
|
||||
"description": "My Remotion video",
|
||||
"repository": {},
|
||||
"license": "UNLICENSED",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@remotion/cli": "4.0.459",
|
||||
"@remotion/google-fonts": "^4.0.459",
|
||||
"@remotion/tailwind-v4": "4.0.459",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"remotion": "4.0.459",
|
||||
"tailwindcss": "4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@remotion/eslint-config-flat": "4.0.459",
|
||||
"@types/react": "19.2.7",
|
||||
"@types/web": "0.0.166",
|
||||
"eslint": "9.19.0",
|
||||
"prettier": "3.8.1",
|
||||
"typescript": "5.9.3"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "remotion studio",
|
||||
"build": "remotion bundle",
|
||||
"upgrade": "remotion upgrade",
|
||||
"lint": "eslint src && tsc"
|
||||
},
|
||||
"sideEffects": [
|
||||
"*.css"
|
||||
]
|
||||
}
|
||||
18
videos/remotion-demo/public/squaremcp-logo.svg
Normal file
18
videos/remotion-demo/public/squaremcp-logo.svg
Normal file
@@ -0,0 +1,18 @@
|
||||
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<title>SquareMCP logo</title>
|
||||
<defs>
|
||||
<linearGradient id="squaremcp-logo-gradient" x1="10" y1="10" x2="54" y2="54" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#7DB6FF"/>
|
||||
<stop offset="1" stop-color="#0E63F6"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path
|
||||
d="M10 12C10 10.8954 10.8954 10 12 10H31V17H17V31H10V12ZM33 10H52C53.1046 10 54 10.8954 54 12V31H47V17H33V10ZM10 33H17V47H31V54H12C10.8954 54 10 53.1046 10 52V33ZM47 33H54V52C54 53.1046 53.1046 54 52 54H33V47H47V33Z"
|
||||
fill="url(#squaremcp-logo-gradient)"
|
||||
/>
|
||||
<path
|
||||
d="M24 24H33V31H40V40H31V33H24V24Z"
|
||||
fill="#0E63F6"
|
||||
opacity="0.92"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 736 B |
13
videos/remotion-demo/remotion.config.ts
Normal file
13
videos/remotion-demo/remotion.config.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Note: When using the Node.JS APIs, the config file
|
||||
* doesn't apply. Instead, pass options directly to the APIs.
|
||||
*
|
||||
* All configuration options: https://remotion.dev/docs/config
|
||||
*/
|
||||
|
||||
import { Config } from "@remotion/cli/config";
|
||||
import { enableTailwind } from '@remotion/tailwind-v4';
|
||||
|
||||
Config.setVideoImageFormat("jpeg");
|
||||
Config.setOverwriteOutput(true);
|
||||
Config.overrideWebpackConfig(enableTailwind);
|
||||
3
videos/remotion-demo/src/Composition.tsx
Normal file
3
videos/remotion-demo/src/Composition.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
export const MyComposition = () => {
|
||||
return null;
|
||||
};
|
||||
27
videos/remotion-demo/src/Root.tsx
Normal file
27
videos/remotion-demo/src/Root.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import "./index.css";
|
||||
import { Composition } from "remotion";
|
||||
import { SquareMCPLinkedIn } from "./SquareMCPLinkedIn";
|
||||
import { SquareMCPHeroLoop } from "./SquareMCPHeroLoop";
|
||||
|
||||
export const RemotionRoot = () => {
|
||||
return (
|
||||
<>
|
||||
<Composition
|
||||
id="SquareMCPLinkedIn"
|
||||
component={SquareMCPLinkedIn}
|
||||
durationInFrames={90 * 30}
|
||||
fps={30}
|
||||
width={1080}
|
||||
height={1080}
|
||||
/>
|
||||
<Composition
|
||||
id="SquareMCPHeroLoop"
|
||||
component={SquareMCPHeroLoop}
|
||||
durationInFrames={30 * 30}
|
||||
fps={30}
|
||||
width={1920}
|
||||
height={1080}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
47
videos/remotion-demo/src/SquareMCPHeroLoop.tsx
Normal file
47
videos/remotion-demo/src/SquareMCPHeroLoop.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import { AbsoluteFill, interpolate, Sequence, useCurrentFrame, useVideoConfig } from "remotion";
|
||||
import { COLORS } from "./styles";
|
||||
import { HeroNetwork } from "./scenes/HeroNetwork";
|
||||
import { HeroMCPLayer } from "./scenes/HeroMCPLayer";
|
||||
import { HeroLogoReveal } from "./scenes/HeroLogoReveal";
|
||||
|
||||
export const SquareMCPHeroLoop = () => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps } = useVideoConfig();
|
||||
|
||||
// MCP overlay: fade in 8–10s, hold until 16s, fade out 16–18s
|
||||
const mcpOpacity = interpolate(
|
||||
frame,
|
||||
[8 * fps, 10 * fps, 16 * fps, 18 * fps],
|
||||
[0, 1, 1, 0],
|
||||
{ extrapolateLeft: "clamp", extrapolateRight: "clamp" }
|
||||
);
|
||||
|
||||
// Logo overlay: fade in 18–20s, hold until 26s, fade out 26–29s
|
||||
const logoOpacity = interpolate(
|
||||
frame,
|
||||
[18 * fps, 20 * fps, 26 * fps, 29 * fps],
|
||||
[0, 1, 1, 0],
|
||||
{ extrapolateLeft: "clamp", extrapolateRight: "clamp" }
|
||||
);
|
||||
|
||||
return (
|
||||
<AbsoluteFill style={{ background: COLORS.bg }}>
|
||||
{/* Network runs on the global frame — periods 90 and 180 divide evenly into 900, so the loop is seamless */}
|
||||
<HeroNetwork />
|
||||
|
||||
{/* MCP layer: Sequence resets frame to 0 at global frame 240 so springs start fresh */}
|
||||
<AbsoluteFill style={{ opacity: mcpOpacity }}>
|
||||
<Sequence from={8 * fps} durationInFrames={10 * fps}>
|
||||
<HeroMCPLayer />
|
||||
</Sequence>
|
||||
</AbsoluteFill>
|
||||
|
||||
{/* Logo reveal: Sequence resets frame to 0 at global frame 540 */}
|
||||
<AbsoluteFill style={{ opacity: logoOpacity }}>
|
||||
<Sequence from={18 * fps} durationInFrames={11 * fps}>
|
||||
<HeroLogoReveal />
|
||||
</Sequence>
|
||||
</AbsoluteFill>
|
||||
</AbsoluteFill>
|
||||
);
|
||||
};
|
||||
46
videos/remotion-demo/src/SquareMCPLinkedIn.tsx
Normal file
46
videos/remotion-demo/src/SquareMCPLinkedIn.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { AbsoluteFill, Sequence, useVideoConfig } from "remotion";
|
||||
import { COLORS } from "./styles";
|
||||
import { Scene1Hook } from "./scenes/Scene1Hook";
|
||||
import { Scene2Problem } from "./scenes/Scene2Problem";
|
||||
import { Scene3Solution } from "./scenes/Scene3Solution";
|
||||
import { Scene4Architecture } from "./scenes/Scene4Architecture";
|
||||
import { Scene5Benefits } from "./scenes/Scene5Benefits";
|
||||
import { Scene6CTA } from "./scenes/Scene6CTA";
|
||||
|
||||
export const SquareMCPLinkedIn = () => {
|
||||
const { fps } = useVideoConfig();
|
||||
|
||||
return (
|
||||
<AbsoluteFill style={{ background: COLORS.bg }}>
|
||||
{/* Scene 1: Hook (0–4s) */}
|
||||
<Sequence durationInFrames={4 * fps}>
|
||||
<Scene1Hook />
|
||||
</Sequence>
|
||||
|
||||
{/* Scene 2: Problem (4–14s) */}
|
||||
<Sequence from={4 * fps} durationInFrames={10 * fps}>
|
||||
<Scene2Problem />
|
||||
</Sequence>
|
||||
|
||||
{/* Scene 3: Solution intro (14–22s) */}
|
||||
<Sequence from={14 * fps} durationInFrames={8 * fps}>
|
||||
<Scene3Solution />
|
||||
</Sequence>
|
||||
|
||||
{/* Scene 4: Architecture (22–55s) */}
|
||||
<Sequence from={22 * fps} durationInFrames={33 * fps}>
|
||||
<Scene4Architecture />
|
||||
</Sequence>
|
||||
|
||||
{/* Scene 5: Benefits (55–75s) */}
|
||||
<Sequence from={55 * fps} durationInFrames={20 * fps}>
|
||||
<Scene5Benefits />
|
||||
</Sequence>
|
||||
|
||||
{/* Scene 6: CTA (75–90s) */}
|
||||
<Sequence from={75 * fps} durationInFrames={15 * fps}>
|
||||
<Scene6CTA />
|
||||
</Sequence>
|
||||
</AbsoluteFill>
|
||||
);
|
||||
};
|
||||
1
videos/remotion-demo/src/index.css
Normal file
1
videos/remotion-demo/src/index.css
Normal file
@@ -0,0 +1 @@
|
||||
@import "tailwindcss";
|
||||
4
videos/remotion-demo/src/index.ts
Normal file
4
videos/remotion-demo/src/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { registerRoot } from "remotion";
|
||||
import { RemotionRoot } from "./Root";
|
||||
|
||||
registerRoot(RemotionRoot);
|
||||
62
videos/remotion-demo/src/scenes/HeroLogoReveal.tsx
Normal file
62
videos/remotion-demo/src/scenes/HeroLogoReveal.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import { AbsoluteFill, Img, spring, staticFile, useCurrentFrame, useVideoConfig } from "remotion";
|
||||
import { COLORS, FONT, SPRING_CFG, SPRING_SOFT } from "../styles";
|
||||
|
||||
export const HeroLogoReveal = () => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps } = useVideoConfig();
|
||||
|
||||
const logoP = spring({ fps, frame, config: SPRING_SOFT, delay: 0 });
|
||||
const nameP = spring({ fps, frame, config: SPRING_CFG, delay: 18 });
|
||||
const tagP = spring({ fps, frame, config: SPRING_CFG, delay: 32 });
|
||||
|
||||
const glowPulse = (Math.sin((frame / 45) * Math.PI) + 1) / 2;
|
||||
const glowSize = 30 + glowPulse * 40;
|
||||
const glowAlpha = 0.35 + glowPulse * 0.3;
|
||||
|
||||
return (
|
||||
<AbsoluteFill
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
gap: 28,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
opacity: logoP,
|
||||
transform: `scale(${0.4 + 0.6 * logoP})`,
|
||||
filter: `drop-shadow(0 0 ${glowSize}px rgba(14, 99, 246, ${glowAlpha}))`,
|
||||
}}
|
||||
>
|
||||
<Img src={staticFile("squaremcp-logo.svg")} style={{ width: 140, height: 140 }} />
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: FONT,
|
||||
fontSize: 80,
|
||||
fontWeight: 800,
|
||||
color: "#fff",
|
||||
letterSpacing: -2,
|
||||
opacity: nameP,
|
||||
transform: `translateY(${(1 - nameP) * 30}px)`,
|
||||
}}
|
||||
>
|
||||
SquareMCP
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: FONT,
|
||||
fontSize: 30,
|
||||
color: COLORS.accentLight,
|
||||
textAlign: "center",
|
||||
opacity: tagP,
|
||||
transform: `translateY(${(1 - tagP) * 20}px)`,
|
||||
}}
|
||||
>
|
||||
Managed MCP Infrastructure for Internal Tools
|
||||
</div>
|
||||
</AbsoluteFill>
|
||||
);
|
||||
};
|
||||
135
videos/remotion-demo/src/scenes/HeroMCPLayer.tsx
Normal file
135
videos/remotion-demo/src/scenes/HeroMCPLayer.tsx
Normal file
@@ -0,0 +1,135 @@
|
||||
import { AbsoluteFill, spring, useCurrentFrame, useVideoConfig } from "remotion";
|
||||
import { COLORS, FONT, SPRING_CFG } from "../styles";
|
||||
|
||||
const ShieldIcon = () => (
|
||||
<svg width={52} height={52} viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 10.99h7c-.53 4.12-3.28 7.79-7 8.94V12H5V6.3l7-3.11v8.8z"
|
||||
fill="#0E63F6"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
const AuditLine = ({ frame, delay }: { frame: number; delay: number }) => {
|
||||
const w = ((Math.sin(((frame + delay) / 90) * 2 * Math.PI) + 1) / 2) * 110 + 70;
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
height: 3,
|
||||
width: w,
|
||||
background: "rgba(125, 182, 255, 0.28)",
|
||||
borderRadius: 2,
|
||||
marginBottom: 7,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const HeroMCPLayer = () => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps } = useVideoConfig();
|
||||
|
||||
const revealP = spring({ fps, frame, config: SPRING_CFG, delay: 0 });
|
||||
const shieldScale = 1 + Math.sin((frame / 45) * Math.PI) * 0.04;
|
||||
|
||||
return (
|
||||
<AbsoluteFill
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
padding: "0 240px",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
opacity: revealP,
|
||||
transform: `translateY(${(1 - revealP) * 30}px)`,
|
||||
}}
|
||||
>
|
||||
{/* Top: AI Agent */}
|
||||
<div
|
||||
style={{
|
||||
background: "rgba(18, 18, 31, 0.9)",
|
||||
border: "1px solid rgba(125, 182, 255, 0.2)",
|
||||
borderRadius: 14,
|
||||
padding: "20px 40px",
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
<div style={{ fontFamily: FONT, fontSize: 22, color: COLORS.accentLight, fontWeight: 600 }}>
|
||||
Your AI Agent
|
||||
</div>
|
||||
<div style={{ fontFamily: FONT, fontSize: 15, color: COLORS.textSecondary, marginTop: 4 }}>
|
||||
Claude · Codex · GPT · Any Model
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: "flex", justifyContent: "center", padding: "10px 0" }}>
|
||||
<div style={{ color: COLORS.accent, fontSize: 22 }}>↓</div>
|
||||
</div>
|
||||
|
||||
{/* Center: Control Layer */}
|
||||
<div
|
||||
style={{
|
||||
background: "rgba(14, 99, 246, 0.13)",
|
||||
border: "2px solid rgba(14, 99, 246, 0.45)",
|
||||
borderRadius: 20,
|
||||
padding: "26px 40px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
transform: `scale(${shieldScale})`,
|
||||
}}
|
||||
>
|
||||
<div style={{ display: "flex", flexDirection: "column" }}>
|
||||
{[0, 30, 60, 90].map((d) => (
|
||||
<AuditLine key={d} frame={frame} delay={d} />
|
||||
))}
|
||||
</div>
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<div style={{ display: "flex", justifyContent: "center", marginBottom: 8 }}>
|
||||
<ShieldIcon />
|
||||
</div>
|
||||
<div style={{ fontFamily: FONT, fontSize: 24, fontWeight: 700, color: "#fff" }}>
|
||||
SquareMCP
|
||||
</div>
|
||||
<div style={{ fontFamily: FONT, fontSize: 13, color: COLORS.accent, marginTop: 5, letterSpacing: 1 }}>
|
||||
Auth · Permissions · Audit · Revocation
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: "flex", flexDirection: "column", alignItems: "flex-end" }}>
|
||||
{[15, 45, 75, 105].map((d) => (
|
||||
<AuditLine key={d} frame={frame} delay={d} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: "flex", justifyContent: "center", padding: "10px 0" }}>
|
||||
<div style={{ color: COLORS.accentLight, fontSize: 22 }}>↓</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom: Internal Tools */}
|
||||
<div
|
||||
style={{
|
||||
background: "rgba(18, 18, 31, 0.9)",
|
||||
border: "1px solid rgba(125, 182, 255, 0.2)",
|
||||
borderRadius: 14,
|
||||
padding: "20px 40px",
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
<div style={{ fontFamily: FONT, fontSize: 22, color: COLORS.accentLight, fontWeight: 600 }}>
|
||||
Your Internal Tools
|
||||
</div>
|
||||
<div style={{ fontFamily: FONT, fontSize: 15, color: COLORS.textSecondary, marginTop: 4 }}>
|
||||
Email · Knowledge Base · CRM · Ops Systems
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AbsoluteFill>
|
||||
);
|
||||
};
|
||||
73
videos/remotion-demo/src/scenes/HeroNetwork.tsx
Normal file
73
videos/remotion-demo/src/scenes/HeroNetwork.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import { AbsoluteFill, useCurrentFrame } from "remotion";
|
||||
|
||||
const NODES = [
|
||||
{ x: 180, y: 150 }, { x: 320, y: 220 }, { x: 520, y: 100 },
|
||||
{ x: 680, y: 200 }, { x: 820, y: 120 }, { x: 960, y: 80 },
|
||||
{ x: 1200, y: 150 }, { x: 1350, y: 280 }, { x: 1500, y: 180 },
|
||||
{ x: 1650, y: 320 }, { x: 1750, y: 200 }, { x: 240, y: 360 },
|
||||
{ x: 120, y: 450 }, { x: 650, y: 420 }, { x: 800, y: 380 },
|
||||
{ x: 960, y: 300 }, { x: 960, y: 480 }, { x: 820, y: 580 },
|
||||
{ x: 1100, y: 440 }, { x: 1300, y: 520 }, { x: 1550, y: 480 },
|
||||
{ x: 1750, y: 560 }, { x: 350, y: 750 }, { x: 550, y: 820 },
|
||||
{ x: 750, y: 760 }, { x: 1050, y: 800 }, { x: 1250, y: 700 },
|
||||
{ x: 1450, y: 820 }, { x: 1650, y: 750 }, { x: 100, y: 700 },
|
||||
];
|
||||
|
||||
type Connection = { from: number; to: number };
|
||||
|
||||
const CONNECTIONS: Connection[] = [];
|
||||
for (let i = 0; i < NODES.length; i++) {
|
||||
for (let j = i + 1; j < NODES.length; j++) {
|
||||
const dx = NODES[i].x - NODES[j].x;
|
||||
const dy = NODES[i].y - NODES[j].y;
|
||||
if (dx * dx + dy * dy < 370 * 370) {
|
||||
CONNECTIONS.push({ from: i, to: j });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Periods 90 and 180 both divide evenly into 900 (total frames), ensuring seamless loop.
|
||||
export const HeroNetwork = () => {
|
||||
const frame = useCurrentFrame();
|
||||
|
||||
return (
|
||||
<AbsoluteFill>
|
||||
<svg
|
||||
width={1920}
|
||||
height={1080}
|
||||
style={{ position: "absolute", top: 0, left: 0 }}
|
||||
>
|
||||
{CONNECTIONS.map((conn, i) => {
|
||||
const phase = (i * 137) % 180;
|
||||
const opacity =
|
||||
((Math.sin(((frame + phase) / 180) * 2 * Math.PI) + 1) / 2) * 0.5 + 0.06;
|
||||
return (
|
||||
<line
|
||||
key={i}
|
||||
x1={NODES[conn.from].x}
|
||||
y1={NODES[conn.from].y}
|
||||
x2={NODES[conn.to].x}
|
||||
y2={NODES[conn.to].y}
|
||||
stroke="#7DB6FF"
|
||||
strokeOpacity={opacity}
|
||||
strokeWidth={1.2}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{NODES.map((node, i) => {
|
||||
const phase = (i * 113) % 90;
|
||||
const pulse = (Math.sin(((frame + phase) / 90) * 2 * Math.PI) + 1) / 2;
|
||||
const r = 5 + pulse * 2.5;
|
||||
const glowR = 18 + pulse * 10;
|
||||
const glowAlpha = 0.07 + pulse * 0.12;
|
||||
return (
|
||||
<g key={i}>
|
||||
<circle cx={node.x} cy={node.y} r={glowR} fill="#0E63F6" opacity={glowAlpha} />
|
||||
<circle cx={node.x} cy={node.y} r={r} fill="#7DB6FF" opacity={0.85} />
|
||||
</g>
|
||||
);
|
||||
})}
|
||||
</svg>
|
||||
</AbsoluteFill>
|
||||
);
|
||||
};
|
||||
57
videos/remotion-demo/src/scenes/Scene1Hook.tsx
Normal file
57
videos/remotion-demo/src/scenes/Scene1Hook.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig } from "remotion";
|
||||
import { COLORS, FONT, SPRING_CFG } from "../styles";
|
||||
|
||||
export const Scene1Hook = () => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps } = useVideoConfig();
|
||||
|
||||
const fadeIn = interpolate(frame, [0, 12], [0, 1], { extrapolateRight: "clamp" });
|
||||
const line1 = spring({ fps, frame, config: SPRING_CFG, delay: 5 });
|
||||
const line2 = spring({ fps, frame, config: SPRING_CFG, delay: 28 });
|
||||
|
||||
return (
|
||||
<AbsoluteFill
|
||||
style={{
|
||||
background: COLORS.bg,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
padding: "0 80px",
|
||||
opacity: fadeIn,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: FONT,
|
||||
fontSize: 72,
|
||||
fontWeight: 800,
|
||||
color: COLORS.text,
|
||||
textAlign: "center",
|
||||
lineHeight: 1.15,
|
||||
marginBottom: 32,
|
||||
opacity: line1,
|
||||
transform: `translateY(${(1 - line1) * 60}px)`,
|
||||
}}
|
||||
>
|
||||
Your AI agent is powerful.
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: FONT,
|
||||
fontSize: 50,
|
||||
fontWeight: 600,
|
||||
color: COLORS.accentLight,
|
||||
textAlign: "center",
|
||||
lineHeight: 1.35,
|
||||
opacity: line2,
|
||||
transform: `translateY(${(1 - line2) * 60}px)`,
|
||||
}}
|
||||
>
|
||||
But can it safely touch
|
||||
<br />
|
||||
your internal tools?
|
||||
</div>
|
||||
</AbsoluteFill>
|
||||
);
|
||||
};
|
||||
85
videos/remotion-demo/src/scenes/Scene2Problem.tsx
Normal file
85
videos/remotion-demo/src/scenes/Scene2Problem.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig } from "remotion";
|
||||
import { COLORS, FONT, SPRING_CFG } from "../styles";
|
||||
|
||||
const WarningIcon = () => (
|
||||
<svg width={44} height={44} viewBox="0 0 24 24" style={{ flexShrink: 0 }}>
|
||||
<path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z" fill="#ef4444" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const CARDS = [
|
||||
{ text: "No access controls", delay: 8 },
|
||||
{ text: "No audit logs", delay: 28 },
|
||||
{ text: "No revocation", delay: 48 },
|
||||
];
|
||||
|
||||
export const Scene2Problem = () => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps } = useVideoConfig();
|
||||
|
||||
const fadeIn = interpolate(frame, [0, 12], [0, 1], { extrapolateRight: "clamp" });
|
||||
const subtitleP = spring({ fps, frame, config: SPRING_CFG, delay: 75 });
|
||||
|
||||
return (
|
||||
<AbsoluteFill
|
||||
style={{
|
||||
background: COLORS.bg,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
padding: "0 80px",
|
||||
gap: 28,
|
||||
opacity: fadeIn,
|
||||
}}
|
||||
>
|
||||
{CARDS.map((card, i) => {
|
||||
const p = spring({ fps, frame, config: SPRING_CFG, delay: card.delay });
|
||||
return (
|
||||
<div
|
||||
key={i}
|
||||
style={{
|
||||
background: "rgba(239, 68, 68, 0.07)",
|
||||
border: "1px solid rgba(239, 68, 68, 0.25)",
|
||||
borderRadius: 18,
|
||||
padding: "28px 44px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 24,
|
||||
width: 780,
|
||||
opacity: p,
|
||||
transform: `translateX(${(1 - p) * -80}px)`,
|
||||
}}
|
||||
>
|
||||
<WarningIcon />
|
||||
<span
|
||||
style={{
|
||||
fontFamily: FONT,
|
||||
fontSize: 44,
|
||||
fontWeight: 700,
|
||||
color: COLORS.text,
|
||||
}}
|
||||
>
|
||||
{card.text}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<div
|
||||
style={{
|
||||
fontFamily: FONT,
|
||||
fontSize: 26,
|
||||
color: COLORS.textSecondary,
|
||||
textAlign: "center",
|
||||
marginTop: 8,
|
||||
maxWidth: 700,
|
||||
lineHeight: 1.55,
|
||||
opacity: subtitleP,
|
||||
transform: `translateY(${(1 - subtitleP) * 20}px)`,
|
||||
}}
|
||||
>
|
||||
Most teams bolt AI onto internal systems with zero governance.
|
||||
</div>
|
||||
</AbsoluteFill>
|
||||
);
|
||||
};
|
||||
78
videos/remotion-demo/src/scenes/Scene3Solution.tsx
Normal file
78
videos/remotion-demo/src/scenes/Scene3Solution.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
import { AbsoluteFill, Img, interpolate, spring, staticFile, useCurrentFrame, useVideoConfig } from "remotion";
|
||||
import { COLORS, FONT, SPRING_CFG, SPRING_SOFT } from "../styles";
|
||||
|
||||
export const Scene3Solution = () => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps } = useVideoConfig();
|
||||
|
||||
const fadeIn = interpolate(frame, [0, 12], [0, 1], { extrapolateRight: "clamp" });
|
||||
const logoP = spring({ fps, frame, config: SPRING_SOFT, delay: 0 });
|
||||
const titleP = spring({ fps, frame, config: SPRING_CFG, delay: 18 });
|
||||
const subtitleP = spring({ fps, frame, config: SPRING_CFG, delay: 34 });
|
||||
const bodyP = spring({ fps, frame, config: SPRING_CFG, delay: 50 });
|
||||
|
||||
return (
|
||||
<AbsoluteFill
|
||||
style={{
|
||||
background: COLORS.bg,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
padding: "0 80px",
|
||||
gap: 24,
|
||||
opacity: fadeIn,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
opacity: logoP,
|
||||
transform: `scale(${0.4 + 0.6 * logoP})`,
|
||||
filter: `drop-shadow(0 0 40px ${COLORS.accentGlow})`,
|
||||
}}
|
||||
>
|
||||
<Img src={staticFile("squaremcp-logo.svg")} style={{ width: 120, height: 120 }} />
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: FONT,
|
||||
fontSize: 72,
|
||||
fontWeight: 800,
|
||||
color: COLORS.text,
|
||||
opacity: titleP,
|
||||
transform: `translateY(${(1 - titleP) * 40}px)`,
|
||||
}}
|
||||
>
|
||||
SquareMCP
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: FONT,
|
||||
fontSize: 34,
|
||||
fontWeight: 600,
|
||||
color: COLORS.accentLight,
|
||||
textAlign: "center",
|
||||
lineHeight: 1.35,
|
||||
opacity: subtitleP,
|
||||
transform: `translateY(${(1 - subtitleP) * 30}px)`,
|
||||
}}
|
||||
>
|
||||
Managed MCP Infrastructure for Internal Tools
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: FONT,
|
||||
fontSize: 26,
|
||||
color: COLORS.textSecondary,
|
||||
textAlign: "center",
|
||||
lineHeight: 1.55,
|
||||
marginTop: 4,
|
||||
opacity: bodyP,
|
||||
transform: `translateY(${(1 - bodyP) * 20}px)`,
|
||||
}}
|
||||
>
|
||||
Connect your internal tools to AI agents safely. In a day.
|
||||
</div>
|
||||
</AbsoluteFill>
|
||||
);
|
||||
};
|
||||
173
videos/remotion-demo/src/scenes/Scene4Architecture.tsx
Normal file
173
videos/remotion-demo/src/scenes/Scene4Architecture.tsx
Normal file
@@ -0,0 +1,173 @@
|
||||
import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig } from "remotion";
|
||||
import { COLORS, FONT, SPRING_CFG } from "../styles";
|
||||
|
||||
const CheckIcon = () => (
|
||||
<svg width={22} height={22} viewBox="0 0 24 24" style={{ flexShrink: 0 }}>
|
||||
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z" fill="#22c55e" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const FEATURES = ["Auth & Identity", "Permissions", "Audit Logs", "Revocation"];
|
||||
const FEATURE_DELAYS = [65, 80, 95, 110];
|
||||
|
||||
export const Scene4Architecture = () => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps } = useVideoConfig();
|
||||
|
||||
const fadeIn = interpolate(frame, [0, 12], [0, 1], { extrapolateRight: "clamp" });
|
||||
const titleP = spring({ fps, frame, config: SPRING_CFG, delay: 0 });
|
||||
const layer1P = spring({ fps, frame, config: SPRING_CFG, delay: 12 });
|
||||
const arrow1P = spring({ fps, frame, config: { damping: 20, stiffness: 200 }, delay: 32 });
|
||||
const layer2P = spring({ fps, frame, config: SPRING_CFG, delay: 46 });
|
||||
const featureProgress = FEATURE_DELAYS.map((d) =>
|
||||
spring({ fps, frame, config: SPRING_CFG, delay: d })
|
||||
);
|
||||
const arrow2P = spring({ fps, frame, config: { damping: 20, stiffness: 200 }, delay: 128 });
|
||||
const layer3P = spring({ fps, frame, config: SPRING_CFG, delay: 142 });
|
||||
|
||||
return (
|
||||
<AbsoluteFill
|
||||
style={{
|
||||
background: COLORS.bg,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
padding: "28px 60px",
|
||||
gap: 0,
|
||||
opacity: fadeIn,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: FONT,
|
||||
fontSize: 42,
|
||||
fontWeight: 700,
|
||||
color: COLORS.text,
|
||||
marginBottom: 24,
|
||||
opacity: titleP,
|
||||
transform: `translateY(${(1 - titleP) * -20}px)`,
|
||||
}}
|
||||
>
|
||||
How It Works
|
||||
</div>
|
||||
|
||||
{/* Layer 1: AI Agent */}
|
||||
<div
|
||||
style={{
|
||||
background: COLORS.bgCard,
|
||||
border: `1px solid ${COLORS.border}`,
|
||||
borderRadius: 16,
|
||||
padding: "22px 40px",
|
||||
width: 880,
|
||||
textAlign: "center",
|
||||
opacity: layer1P,
|
||||
transform: `translateY(${(1 - layer1P) * -30}px)`,
|
||||
}}
|
||||
>
|
||||
<div style={{ fontFamily: FONT, fontSize: 12, color: COLORS.textSecondary, textTransform: "uppercase", letterSpacing: 2, marginBottom: 6 }}>
|
||||
Layer 1
|
||||
</div>
|
||||
<div style={{ fontFamily: FONT, fontSize: 34, fontWeight: 700, color: COLORS.text }}>
|
||||
Your AI Agent
|
||||
</div>
|
||||
<div style={{ fontFamily: FONT, fontSize: 18, color: COLORS.textSecondary, marginTop: 6 }}>
|
||||
Claude · Codex · GPT · Any Model
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Arrow 1 */}
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
padding: "6px 0",
|
||||
opacity: arrow1P,
|
||||
}}
|
||||
>
|
||||
<div style={{ fontFamily: FONT, fontSize: 12, color: COLORS.accent, fontWeight: 600, letterSpacing: 1 }}>
|
||||
MCP
|
||||
</div>
|
||||
<div style={{ color: COLORS.accent, fontSize: 26, lineHeight: 1.1 }}>↓</div>
|
||||
</div>
|
||||
|
||||
{/* Layer 2: SquareMCP Control Plane */}
|
||||
<div
|
||||
style={{
|
||||
background: `linear-gradient(135deg, rgba(14,99,246,0.14), rgba(14,99,246,0.06))`,
|
||||
border: `1.5px solid ${COLORS.borderBlue}`,
|
||||
borderRadius: 20,
|
||||
padding: "22px 40px",
|
||||
width: 880,
|
||||
opacity: layer2P,
|
||||
transform: `scale(${0.96 + 0.04 * layer2P})`,
|
||||
}}
|
||||
>
|
||||
<div style={{ fontFamily: FONT, fontSize: 12, color: COLORS.accent, textTransform: "uppercase", letterSpacing: 2, marginBottom: 6 }}>
|
||||
Layer 2 — Control Plane
|
||||
</div>
|
||||
<div style={{ fontFamily: FONT, fontSize: 30, fontWeight: 700, color: COLORS.text, marginBottom: 16 }}>
|
||||
SquareMCP
|
||||
</div>
|
||||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "10px 20px" }}>
|
||||
{FEATURES.map((f, i) => (
|
||||
<div
|
||||
key={i}
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 10,
|
||||
opacity: featureProgress[i],
|
||||
transform: `translateX(${(1 - featureProgress[i]) * 24}px)`,
|
||||
}}
|
||||
>
|
||||
<CheckIcon />
|
||||
<span style={{ fontFamily: FONT, fontSize: 20, color: COLORS.text }}>{f}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Arrow 2 */}
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
padding: "6px 0",
|
||||
opacity: arrow2P,
|
||||
}}
|
||||
>
|
||||
<div style={{ fontFamily: FONT, fontSize: 12, color: COLORS.accentLight, fontWeight: 600, letterSpacing: 1 }}>
|
||||
Controlled Access
|
||||
</div>
|
||||
<div style={{ color: COLORS.accentLight, fontSize: 26, lineHeight: 1.1 }}>↓</div>
|
||||
</div>
|
||||
|
||||
{/* Layer 3: Internal Tools */}
|
||||
<div
|
||||
style={{
|
||||
background: COLORS.bgCard,
|
||||
border: `1px solid ${COLORS.border}`,
|
||||
borderRadius: 16,
|
||||
padding: "22px 40px",
|
||||
width: 880,
|
||||
textAlign: "center",
|
||||
opacity: layer3P,
|
||||
transform: `translateY(${(1 - layer3P) * 30}px)`,
|
||||
}}
|
||||
>
|
||||
<div style={{ fontFamily: FONT, fontSize: 12, color: COLORS.textSecondary, textTransform: "uppercase", letterSpacing: 2, marginBottom: 6 }}>
|
||||
Layer 3
|
||||
</div>
|
||||
<div style={{ fontFamily: FONT, fontSize: 34, fontWeight: 700, color: COLORS.text }}>
|
||||
Your Internal Tools
|
||||
</div>
|
||||
<div style={{ fontFamily: FONT, fontSize: 18, color: COLORS.textSecondary, marginTop: 6 }}>
|
||||
Email · Knowledge Base · CRM · Ops Systems
|
||||
</div>
|
||||
</div>
|
||||
</AbsoluteFill>
|
||||
);
|
||||
};
|
||||
119
videos/remotion-demo/src/scenes/Scene5Benefits.tsx
Normal file
119
videos/remotion-demo/src/scenes/Scene5Benefits.tsx
Normal file
@@ -0,0 +1,119 @@
|
||||
import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig } from "remotion";
|
||||
import { COLORS, FONT, SPRING_CFG } from "../styles";
|
||||
|
||||
const LightningIcon = () => (
|
||||
<svg width={48} height={48} viewBox="0 0 24 24" style={{ flexShrink: 0 }}>
|
||||
<path d="M13 3L4 14h7v7l9-11h-7V3z" fill="#0E63F6" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const LockIcon = () => (
|
||||
<svg width={48} height={48} viewBox="0 0 24 24" style={{ flexShrink: 0 }}>
|
||||
<path
|
||||
d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zM12 17c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z"
|
||||
fill="#0E63F6"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
const ListIcon = () => (
|
||||
<svg width={48} height={48} viewBox="0 0 24 24" style={{ flexShrink: 0 }}>
|
||||
<path d="M3 13h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm4 4h14v-2H7v2zm0 4h14v-2H7v2zM7 7v2h14V7H7z" fill="#0E63F6" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const BENEFITS = [
|
||||
{
|
||||
Icon: LightningIcon,
|
||||
title: "Deploy in a day — not months",
|
||||
caption: "Pre-built connectors, one-line config, production-ready from day one.",
|
||||
delay: 15,
|
||||
},
|
||||
{
|
||||
Icon: LockIcon,
|
||||
title: "Full tool-level permission controls",
|
||||
caption: "Grant, restrict, and revoke access to individual tools per agent.",
|
||||
delay: 35,
|
||||
},
|
||||
{
|
||||
Icon: ListIcon,
|
||||
title: "Complete audit log of every agent action",
|
||||
caption: "Know exactly what your AI did, when, and why. Always.",
|
||||
delay: 55,
|
||||
},
|
||||
];
|
||||
|
||||
export const Scene5Benefits = () => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps } = useVideoConfig();
|
||||
|
||||
const fadeIn = interpolate(frame, [0, 12], [0, 1], { extrapolateRight: "clamp" });
|
||||
const titleP = spring({ fps, frame, config: SPRING_CFG, delay: 0 });
|
||||
|
||||
return (
|
||||
<AbsoluteFill
|
||||
style={{
|
||||
background: COLORS.bg,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
padding: "0 80px",
|
||||
gap: 24,
|
||||
opacity: fadeIn,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: FONT,
|
||||
fontSize: 50,
|
||||
fontWeight: 800,
|
||||
color: COLORS.text,
|
||||
marginBottom: 4,
|
||||
opacity: titleP,
|
||||
transform: `translateY(${(1 - titleP) * -20}px)`,
|
||||
}}
|
||||
>
|
||||
Why SquareMCP?
|
||||
</div>
|
||||
{BENEFITS.map(({ Icon, title, caption, delay }, i) => {
|
||||
const p = spring({ fps, frame, config: SPRING_CFG, delay });
|
||||
return (
|
||||
<div
|
||||
key={i}
|
||||
style={{
|
||||
background: COLORS.bgCard,
|
||||
border: `1px solid ${COLORS.border}`,
|
||||
borderRadius: 20,
|
||||
padding: "26px 36px",
|
||||
width: 880,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 28,
|
||||
opacity: p,
|
||||
transform: `translateX(${(1 - p) * 60}px)`,
|
||||
}}
|
||||
>
|
||||
<Icon />
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: FONT,
|
||||
fontSize: 28,
|
||||
fontWeight: 700,
|
||||
color: COLORS.text,
|
||||
marginBottom: 6,
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</div>
|
||||
<div style={{ fontFamily: FONT, fontSize: 19, color: COLORS.textSecondary }}>
|
||||
{caption}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</AbsoluteFill>
|
||||
);
|
||||
};
|
||||
67
videos/remotion-demo/src/scenes/Scene6CTA.tsx
Normal file
67
videos/remotion-demo/src/scenes/Scene6CTA.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import { AbsoluteFill, Img, interpolate, spring, staticFile, useCurrentFrame, useVideoConfig } from "remotion";
|
||||
import { COLORS, FONT, SPRING_SOFT } from "../styles";
|
||||
|
||||
export const Scene6CTA = () => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps } = useVideoConfig();
|
||||
|
||||
const fadeIn = interpolate(frame, [0, 15], [0, 1], { extrapolateRight: "clamp" });
|
||||
const logoP = spring({ fps, frame, config: SPRING_SOFT, delay: 0 });
|
||||
const urlP = spring({ fps, frame, config: { damping: 16, stiffness: 120 }, delay: 22 });
|
||||
const subP = spring({ fps, frame, config: { damping: 16, stiffness: 120 }, delay: 38 });
|
||||
|
||||
const glowPulse = (Math.sin((frame / 45) * Math.PI) + 1) / 2;
|
||||
const glowSize = 24 + glowPulse * 32;
|
||||
const glowAlpha = 0.3 + glowPulse * 0.45;
|
||||
|
||||
return (
|
||||
<AbsoluteFill
|
||||
style={{
|
||||
background: COLORS.bg,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
gap: 28,
|
||||
opacity: fadeIn,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
opacity: logoP,
|
||||
transform: `scale(${0.5 + 0.5 * logoP})`,
|
||||
filter: `drop-shadow(0 0 ${glowSize}px rgba(14, 99, 246, ${glowAlpha}))`,
|
||||
}}
|
||||
>
|
||||
<Img src={staticFile("squaremcp-logo.svg")} style={{ width: 160, height: 160 }} />
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: FONT,
|
||||
fontSize: 68,
|
||||
fontWeight: 800,
|
||||
color: COLORS.text,
|
||||
letterSpacing: -1,
|
||||
opacity: urlP,
|
||||
transform: `translateY(${(1 - urlP) * 30}px)`,
|
||||
}}
|
||||
>
|
||||
squaremcp.com
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: FONT,
|
||||
fontSize: 24,
|
||||
color: COLORS.textSecondary,
|
||||
textAlign: "center",
|
||||
maxWidth: 680,
|
||||
lineHeight: 1.6,
|
||||
opacity: subP,
|
||||
transform: `translateY(${(1 - subP) * 20}px)`,
|
||||
}}
|
||||
>
|
||||
Managed MCP infrastructure for teams building with AI.
|
||||
</div>
|
||||
</AbsoluteFill>
|
||||
);
|
||||
};
|
||||
25
videos/remotion-demo/src/styles.ts
Normal file
25
videos/remotion-demo/src/styles.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { loadFont } from "@remotion/google-fonts/Inter";
|
||||
|
||||
const { fontFamily } = loadFont("normal", {
|
||||
weights: ["400", "600", "700", "800"],
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
export const FONT = fontFamily;
|
||||
|
||||
export const COLORS = {
|
||||
bg: "#09090f",
|
||||
bgCard: "#12121f",
|
||||
accent: "#0E63F6",
|
||||
accentLight: "#7DB6FF",
|
||||
accentGlow: "rgba(14, 99, 246, 0.45)",
|
||||
text: "#ffffff",
|
||||
textSecondary: "#94a3b8",
|
||||
success: "#22c55e",
|
||||
warning: "#ef4444",
|
||||
border: "#1e1e2e",
|
||||
borderBlue: "rgba(14, 99, 246, 0.35)",
|
||||
} as const;
|
||||
|
||||
export const SPRING_CFG = { damping: 14, mass: 0.8, stiffness: 120 } as const;
|
||||
export const SPRING_SOFT = { damping: 12, mass: 1, stiffness: 80 } as const;
|
||||
15
videos/remotion-demo/tsconfig.json
Normal file
15
videos/remotion-demo/tsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2018",
|
||||
"module": "commonjs",
|
||||
"jsx": "react-jsx",
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"lib": ["es2015"],
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noUnusedLocals": true
|
||||
},
|
||||
"exclude": ["remotion.config.ts"]
|
||||
}
|
||||
Reference in New Issue
Block a user