← Catalog
Edit Props (live)
TEXT
TEXT
TEXT
config
config
config
config
config
config
Source
217 linesimport React from "react";
import {
AbsoluteFill,
Img,
useCurrentFrame,
useVideoConfig,
interpolate,
Easing,
spring,
staticFile,
} from "remotion";
import { loadFont as loadInter } from "@remotion/google-fonts/Inter";
loadInter("normal", { weights: ["400", "500", "700", "900"] });
interface LogoRow {
// File path of logo image (e.g. "logos/openai.png")
logo: string;
// Wordmark text displayed next to the logo.
wordmark: string;
// Optional backdrop color behind the logo — needed for monochrome or
// transparent logos that would disappear on the scene's background.
// e.g. "#1a1a1a" for a dark chip behind a white SVG.
tile_bg?: string;
}
interface LogoListRevealProps {
rows: LogoRow[];
staggerS?: number; // seconds between each row appearing (default 0.35)
backgroundColor?: string; // default "#f2eee4"
captionText?: string;
captionStyle?: "italic" | "bold";
captionPosition?: "top" | "bottom";
fontColor?: string; // wordmark color, default "#111"
highlightLast?: boolean; // if true, last row gets a yellow highlighter (default false)
highlightColor?: string; // default "#ffe94a"
}
const FONT = "'Inter', sans-serif";
export const LogoListReveal: React.FC<LogoListRevealProps> = ({
rows,
staggerS = 0.35,
backgroundColor = "#f2eee4",
captionText,
captionStyle = "italic",
captionPosition = "bottom",
fontColor = "#111",
highlightLast = false,
highlightColor = "#ffe94a",
}) => {
const frame = useCurrentFrame();
const { fps, durationInFrames, width, height } = useVideoConfig();
const staggerFrames = Math.max(1, Math.floor(staggerS * fps));
const rowCount = rows.length;
// Font sizing: shrink as row count grows
const wordmarkFontSize = rowCount <= 3 ? 130 : rowCount <= 4 ? 110 : 92;
const iconSize = Math.floor(wordmarkFontSize * 1.05);
const gap = Math.floor(wordmarkFontSize * 0.32);
const listHeight = rowCount * iconSize + (rowCount - 1) * gap;
const listTop = Math.max(260, Math.floor((height - listHeight) / 2) - 60);
// Caption
const capOp = interpolate(frame, [4, 18], [0, 1], { extrapolateRight: "clamp" });
const capY = interpolate(frame, [4, 18], [12, 0], {
extrapolateRight: "clamp",
easing: Easing.out(Easing.cubic),
});
return (
<AbsoluteFill style={{ backgroundColor, overflow: "hidden" }}>
{/* Soft radial wash */}
<div
style={{
position: "absolute",
inset: 0,
background: "transparent",
pointerEvents: "none",
}}
/>
{/* Caption */}
{captionText && (
<div
style={{
position: "absolute",
left: 0,
right: 0,
top: captionPosition === "top" ? 120 : undefined,
bottom: captionPosition === "bottom" ? 200 : undefined,
textAlign: "center",
fontFamily: FONT,
fontStyle: captionStyle === "italic" ? "italic" : "normal",
fontWeight: captionStyle === "italic" ? 500 : 800,
fontSize: 66,
color: fontColor,
letterSpacing: -1.2,
opacity: capOp,
transform: `translateY(${capY}px)`,
padding: "0 50px",
zIndex: 5,
}}
>
{captionText}
</div>
)}
{/* Logo list */}
<div
style={{
position: "absolute",
top: listTop,
left: 0,
right: 0,
display: "flex",
flexDirection: "column",
alignItems: "center",
gap,
}}
>
{rows.map((row, i) => {
const startFrame = i * staggerFrames + 4;
const enter = spring({
frame: frame - startFrame,
fps,
config: { mass: 1, damping: 15, stiffness: 130 },
});
const translateY = interpolate(enter, [0, 1], [80, 0]);
const opacity = interpolate(enter, [0, 0.25, 1], [0, 1, 1]);
const text = row.wordmark;
const resolvedSrc = row.logo.startsWith("http") ? row.logo : staticFile(row.logo);
const isLast = i === rows.length - 1;
const showHighlight = highlightLast && isLast;
return (
<div
key={i}
style={{
display: "flex",
alignItems: "center",
gap: Math.floor(wordmarkFontSize * 0.3),
transform: `translateY(${translateY}px)`,
opacity,
}}
>
{row.tile_bg ? (
<div
style={{
width: iconSize,
height: iconSize,
borderRadius: Math.floor(iconSize * 0.24),
background: row.tile_bg,
display: "flex",
alignItems: "center",
justifyContent: "center",
overflow: "hidden",
flexShrink: 0,
boxShadow: "0 6px 14px rgba(0,0,0,0.16)",
}}
>
<Img
src={resolvedSrc}
style={{
width: "68%",
height: "68%",
objectFit: "contain",
}}
/>
</div>
) : (
<Img
src={resolvedSrc}
style={{
width: iconSize,
height: iconSize,
objectFit: "contain",
flexShrink: 0,
}}
/>
)}
<div
style={{
position: "relative",
fontFamily: FONT,
fontSize: wordmarkFontSize,
fontWeight: 800,
color: fontColor,
letterSpacing: -2.5,
lineHeight: 1,
padding: showHighlight ? "4px 18px" : 0,
}}
>
{showHighlight && (
<div
style={{
position: "absolute",
inset: 0,
background: highlightColor,
borderRadius: 8,
transform: "skewX(-4deg)",
zIndex: -1,
opacity: 0.85,
}}
/>
)}
{text}
</div>
</div>
);
})}
</div>
</AbsoluteFill>
);
};
Schema (Zod)
// Auto-extracted from Props interface — see source for authoritative types.
// Component: LogoListReveal
// 9 props detected · 3 port(s)
Logo List Reveal
logo_list_revealLogo List Reveal — Andy's hand-crafted Remotion scene. 9 props. captionText:TEXT, captionStyle:TEXT, captionPosition:TEXT.
Workflow Inputs (3)
- Caption Text
captionTextTEXT - Caption Style
captionStyleTEXT - Caption Position
captionPositionTEXT
Config Fields (6)
rowsstaggerSfontColorhighlightLasthighlightColorbackgroundColor
Meta
- Updated
- 4/21/2026