← Catalog
Edit Props (live)
TEXT
config
config
config
config
config
Source
418 linesimport React from "react";
import {
AbsoluteFill,
useCurrentFrame,
useVideoConfig,
interpolate,
spring,
Easing,
} from "remotion";
import { loadFont as loadInter } from "@remotion/google-fonts/Inter";
loadInter("normal", { weights: ["400", "500", "700", "900"] });
const FONT = "'Inter', sans-serif";
interface BrandFlowProps {
brandName?: string;
accentPalette?: string[];
fontSamples?: string[];
outputSrc?: string;
backgroundColor?: string;
captionText?: string;
}
const DEFAULT_PALETTE = ["#509ADB", "#57CEB2", "#FFB080", "#FFD166", "#FF6B6B"];
const DEFAULT_FONTS = ["Aa", "Aa"];
const hex2rgba = (hex: string, a: number) => {
const h = hex.replace("#", "");
const r = parseInt(h.slice(0, 2), 16);
const g = parseInt(h.slice(2, 4), 16);
const b = parseInt(h.slice(4, 6), 16);
return `rgba(${r},${g},${b},${a})`;
};
export const BrandFlow: React.FC<BrandFlowProps> = ({
brandName,
accentPalette,
fontSamples,
outputSrc,
backgroundColor = "#509ADB",
captionText,
}) => {
const frame = useCurrentFrame();
const { fps, width, height } = useVideoConfig();
const palette = (accentPalette && accentPalette.length >= 2) ? accentPalette : DEFAULT_PALETTE;
const fonts = (fontSamples && fontSamples.length > 0) ? fontSamples : DEFAULT_FONTS;
// Phase timing (frames @ 30fps; remotion passes fps via hook but these are
// absolute frame anchors — good for a 3-4s scene).
const F_FILE_IN = 4; // file icon springs in
const F_FILE_SET = 16; // file fully visible, about to open
const F_SWATCH_START = 18; // swatches + fonts begin flying out
const F_SWATCH_END = 42;
const F_OUTPUT_IN = 44; // styled mockup fades up
const F_OUTPUT_SET = 60;
// FILE ICON — springs in from nothing
const fileEnter = spring({
frame: frame - F_FILE_IN,
fps,
config: { mass: 1, damping: 12, stiffness: 140 },
});
const fileScale = interpolate(fileEnter, [0, 1], [0.2, 1]);
const fileOp = interpolate(frame, [F_FILE_IN, F_FILE_IN + 10], [0, 1], {
extrapolateRight: "clamp",
});
// Slight gentle bob during hold
const bob = Math.sin((frame - F_FILE_SET) / 10) * 4;
// Icon itself fades out as elements fly from it
const fileExit = interpolate(frame, [F_SWATCH_END - 4, F_OUTPUT_SET], [1, 0.35], {
extrapolateLeft: "clamp", extrapolateRight: "clamp",
});
// Output mockup fade-up
const outputEnter = spring({
frame: frame - F_OUTPUT_IN,
fps,
config: { mass: 1, damping: 14, stiffness: 120 },
});
const outputScale = interpolate(outputEnter, [0, 1], [0.7, 1]);
const outputOp = interpolate(frame, [F_OUTPUT_IN, F_OUTPUT_IN + 14], [0, 1], {
extrapolateRight: "clamp",
});
// Caption
const capOp = interpolate(frame, [8, 22], [0, 1], { extrapolateRight: "clamp" });
const capY = interpolate(frame, [8, 22], [14, 0], {
extrapolateRight: "clamp",
easing: Easing.out(Easing.cubic),
});
// Center anchor
const cx = width / 2;
const cy = height / 2;
// FILE ICON SIZE — big enough to read without dominating
const iconW = Math.floor(width * 0.34);
const iconH = Math.floor(iconW * 1.15);
// SWATCH POSITIONS — arc above the file, evenly spaced
const swatchSize = Math.floor(width * 0.11);
const swatchRadiusX = Math.floor(width * 0.28);
const swatchRadiusY = Math.floor(height * 0.14);
const nSwatches = Math.min(palette.length, 5);
// FONT SAMPLE POSITIONS — below the file, two sides
const fontSize = Math.floor(width * 0.11);
return (
<AbsoluteFill style={{ backgroundColor, overflow: "hidden" }}>
{/* Subtle radial glow for depth */}
<div
style={{
position: "absolute",
top: `${50}%`,
left: "50%",
width: width * 1.2,
height: height * 0.6,
transform: "translate(-50%, -50%)",
background: "transparent",
pointerEvents: "none",
}}
/>
{/* CAPTION */}
{captionText ? (
<div
style={{
position: "absolute",
top: 120,
left: 0,
right: 0,
textAlign: "center",
fontFamily: FONT,
fontWeight: 800,
fontSize: 56,
color: "#ffffff",
letterSpacing: -1,
padding: "0 50px",
opacity: capOp,
transform: `translateY(${capY}px)`,
textShadow: "0 4px 20px rgba(0,0,0,0.3)",
zIndex: 10,
}}
>
{captionText}
</div>
) : null}
{/* BRAND NAME chip, optional */}
{brandName ? (
<div
style={{
position: "absolute",
bottom: 140,
left: 0,
right: 0,
textAlign: "center",
fontFamily: FONT,
fontWeight: 700,
fontSize: 36,
color: "#ffffff",
letterSpacing: 2,
textTransform: "uppercase",
opacity: outputOp,
}}
>
{brandName}
</div>
) : null}
{/* FILE ICON — brand.zip-style folder */}
<div
style={{
position: "absolute",
left: cx - iconW / 2,
top: cy - iconH / 2 + bob,
width: iconW,
height: iconH,
opacity: fileOp * fileExit,
transform: `scale(${fileScale})`,
transformOrigin: "center center",
zIndex: 5,
}}
>
{/* Folder body */}
<div
style={{
position: "absolute",
inset: 0,
borderRadius: Math.floor(iconW * 0.08),
background: "linear-gradient(160deg, #ffffff 0%, #e5e8ee 100%)",
boxShadow:
"0 30px 70px rgba(0,0,0,0.35), 0 10px 24px rgba(0,0,0,0.2)",
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
padding: Math.floor(iconW * 0.08),
}}
>
{/* Header tab */}
<div
style={{
position: "absolute",
top: Math.floor(iconH * 0.08),
left: Math.floor(iconW * 0.10),
width: Math.floor(iconW * 0.32),
height: Math.floor(iconH * 0.07),
borderRadius: Math.floor(iconW * 0.025),
background: palette[0],
}}
/>
{/* File content lines */}
<div style={{ width: "80%", marginTop: Math.floor(iconH * 0.14) }}>
{[0.88, 0.72, 0.94, 0.58, 0.8].map((w, i) => (
<div
key={i}
style={{
width: `${w * 100}%`,
height: Math.floor(iconH * 0.04),
margin: `${Math.floor(iconH * 0.025)}px 0`,
borderRadius: 4,
background: "rgba(0,0,0,0.14)",
}}
/>
))}
</div>
{/* Extension label */}
<div
style={{
position: "absolute",
bottom: Math.floor(iconH * 0.08),
right: Math.floor(iconW * 0.10),
fontFamily: FONT,
fontWeight: 800,
fontSize: Math.floor(iconH * 0.07),
color: palette[0],
letterSpacing: 1,
}}
>
BRAND
</div>
</div>
</div>
{/* COLOR SWATCHES — fly out from the file in an upward arc */}
{palette.slice(0, nSwatches).map((color, i) => {
// Stagger: swatch i starts 2 frames after swatch i-1
const startFrame = F_SWATCH_START + i * 2;
const endFrame = startFrame + 22;
const progress = interpolate(frame, [startFrame, endFrame], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
easing: Easing.out(Easing.cubic),
});
// Angle: spread swatches along a 160° arc above center
const angleDeg = -80 + (160 / (nSwatches - 1 || 1)) * i;
const angleRad = (angleDeg * Math.PI) / 180;
const targetX = cx + Math.sin(angleRad) * swatchRadiusX - swatchSize / 2;
const targetY = cy - swatchRadiusY - Math.abs(Math.cos(angleRad)) * swatchRadiusY * 0.5 - swatchSize / 2;
const startX = cx - swatchSize / 2;
const startY = cy - swatchSize / 2;
const x = interpolate(progress, [0, 1], [startX, targetX]);
const y = interpolate(progress, [0, 1], [startY, targetY]);
const scale = interpolate(progress, [0, 1], [0.3, 1]);
const fade = interpolate(frame, [F_OUTPUT_IN + 8, F_OUTPUT_SET], [1, 0.25], {
extrapolateLeft: "clamp", extrapolateRight: "clamp",
});
return (
<div
key={`sw-${i}`}
style={{
position: "absolute",
left: x,
top: y,
width: swatchSize,
height: swatchSize,
borderRadius: "50%",
background: color,
transform: `scale(${scale})`,
opacity: progress * fade,
boxShadow: `0 12px 30px ${hex2rgba(color, 0.45)}, 0 2px 8px rgba(0,0,0,0.25)`,
border: "3px solid rgba(255,255,255,0.85)",
zIndex: 6,
}}
/>
);
})}
{/* FONT SAMPLES — slide in left/right below the file */}
{fonts.slice(0, 2).map((sample, i) => {
const startFrame = F_SWATCH_START + 6 + i * 3;
const progress = interpolate(frame, [startFrame, startFrame + 22], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
easing: Easing.out(Easing.cubic),
});
const dir = i === 0 ? -1 : 1;
const offsetX = dir * width * 0.25;
const targetX = cx + offsetX - fontSize * 0.9;
const targetY = cy + iconH * 0.55;
const startX = cx - fontSize * 0.9;
const x = interpolate(progress, [0, 1], [startX, targetX]);
const y = interpolate(progress, [0, 1], [cy, targetY]);
const fade = interpolate(frame, [F_OUTPUT_IN + 8, F_OUTPUT_SET], [1, 0.25], {
extrapolateLeft: "clamp", extrapolateRight: "clamp",
});
return (
<div
key={`fn-${i}`}
style={{
position: "absolute",
left: x,
top: y,
fontFamily: i === 0 ? FONT : "Georgia, serif",
fontWeight: i === 0 ? 900 : 400,
fontSize,
color: "#ffffff",
letterSpacing: -1,
textShadow: "0 4px 16px rgba(0,0,0,0.35)",
opacity: progress * fade,
zIndex: 6,
}}
>
{sample}
</div>
);
})}
{/* STYLED OUTPUT — a constructed brand-identity poster using the
extracted palette + name. We deliberately do NOT show outputSrc here:
a real product screenshot mid-animation reads as "random screenshot
replaced the swatches." Keeping this synthetic keeps the metaphor
(palette + fonts → branded artifact) intact. */}
<BrandIdentityCard
palette={palette}
brandName={brandName || "Brand"}
opacity={outputOp}
scale={outputScale}
cx={cx}
cy={cy}
width={width}
height={height}
/>
</AbsoluteFill>
);
};
const BrandIdentityCard: React.FC<{
palette: string[];
brandName: string;
opacity: number;
scale: number;
cx: number;
cy: number;
width: number;
height: number;
}> = ({ palette, brandName, opacity, scale, cx, cy, width, height }) => {
const w = Math.floor(width * 0.7);
const h = Math.floor(height * 0.5);
const accent = palette[0];
const accent2 = palette[1] || palette[0];
const accent3 = palette[2] || palette[0];
return (
<div
style={{
position: "absolute",
left: cx - w / 2,
top: cy - h / 2,
width: w,
height: h,
borderRadius: 32,
overflow: "hidden",
boxShadow: "0 50px 110px rgba(0,0,0,0.45), 0 16px 40px rgba(0,0,0,0.28)",
transform: `scale(${scale})`,
opacity,
transformOrigin: "center center",
zIndex: 8,
background: "#ffffff",
fontFamily: FONT,
display: "flex",
flexDirection: "column",
}}
>
{/* Top accent stripe */}
<div style={{ height: Math.floor(h * 0.16), background: accent, display: "flex", alignItems: "center", padding: "0 36px" }}>
<div style={{ width: 18, height: 18, borderRadius: 6, background: "#fff", marginRight: 14 }} />
<div style={{ color: "#fff", fontWeight: 800, fontSize: 28, letterSpacing: 2, textTransform: "uppercase" }}>
{brandName}
</div>
</div>
{/* Big serif headline */}
<div style={{ padding: "32px 40px 8px 40px", fontFamily: "Georgia, serif", fontWeight: 700, fontSize: 78, color: "#1d1d1f", letterSpacing: -1.5, lineHeight: 1 }}>
Made with<br />
<span style={{ color: accent }}>Claude</span>.
</div>
{/* Subtitle */}
<div style={{ padding: "0 40px", fontWeight: 500, fontSize: 26, color: "#666", marginTop: 10 }}>
On-brand, every export.
</div>
{/* Palette swatches */}
<div style={{ flex: 1 }} />
<div style={{ display: "flex", padding: "0 40px 36px 40px", gap: 14 }}>
{palette.slice(0, 4).map((c, i) => (
<div key={i} style={{ flex: 1, height: 70, borderRadius: 14, background: c, boxShadow: "0 4px 12px rgba(0,0,0,0.10)" }} />
))}
</div>
{/* Bottom underscore accent */}
<div style={{ height: 8, background: `linear-gradient(90deg, ${accent} 0%, ${accent2} 50%, ${accent3} 100%)` }} />
</div>
);
};
Schema (Zod)
// Auto-extracted from Props interface — see source for authoritative types.
// Component: BrandFlow
// 6 props detected · 1 port(s)
Brand Flow
brand_flowBrand Flow — Andy's hand-crafted Remotion scene. 6 props. captionText:TEXT.
Workflow Inputs (1)
- Caption Text
captionTextTEXT
Config Fields (5)
brandNameoutputSrcfontSamplesaccentPalettebackgroundColor
Meta
- Updated
- 4/21/2026