← Catalog
Edit Props (live)
IMAGE
TEXT
TEXT
TEXT
config
config
config
config
config
config
Source
298 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", "800", "900"] });
interface BranchNode {
label: string;
logo?: string; // optional logo path
color?: string; // optional bg override
tile_bg?: string; // optional chip bg for monochrome logos
}
interface MindmapExpandProps {
centralLabel: string;
centralLogo?: string;
centralColor?: string; // default "#1a1a1a"
centralTextColor?: string; // default "#ffffff"
branches: BranchNode[]; // 4-8 nodes recommended
backgroundColor?: string; // default "#f2eee4"
lineColor?: string; // default "#111111"
accentColor?: string; // default "#1d9bf0"
captionText?: string;
captionPosition?: "top" | "bottom";
}
const FONT = "'Inter', sans-serif";
const resolveStatic = (s: string) => (s.startsWith("http") ? s : staticFile(s));
export const MindmapExpand: React.FC<MindmapExpandProps> = ({
centralLabel,
centralLogo,
centralColor = "#1a1a1a",
centralTextColor = "#ffffff",
branches,
backgroundColor = "#f2eee4",
lineColor = "#111111",
accentColor = "#1d9bf0",
captionText,
captionPosition = "top",
}) => {
const frame = useCurrentFrame();
const { fps, width, height } = useVideoConfig();
const centerX = width / 2;
const centerY = height / 2;
const centralRadius = 180;
const branchRadius = 130;
const orbitRadius = Math.min(width, height) * 0.38;
// Central node enters first
const centralEnter = spring({
frame,
fps,
config: { mass: 1, damping: 12, stiffness: 120 },
});
const centralScale = interpolate(centralEnter, [0, 1], [0, 1]);
// Subtle breathing on the central node
const t = frame / fps;
const pulse = 1 + Math.sin(t * 1.4) * 0.02;
// Caption
const capOp = interpolate(frame, [4, 16], [0, 1], { extrapolateRight: "clamp" });
const capY = interpolate(frame, [4, 16], [10, 0], {
extrapolateRight: "clamp",
easing: Easing.out(Easing.cubic),
});
// Evenly distribute branches around central node
const branchCount = branches.length;
const branchData = branches.map((b, i) => {
// Start from top, go clockwise
const angle = (i / branchCount) * Math.PI * 2 - Math.PI / 2;
return {
branch: b,
x: centerX + Math.cos(angle) * orbitRadius,
y: centerY + Math.sin(angle) * orbitRadius,
angle,
};
});
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" ? 140 : undefined,
bottom: captionPosition === "bottom" ? 140 : undefined,
textAlign: "center",
fontFamily: FONT,
fontSize: 56,
fontWeight: 600,
fontStyle: "italic",
color: "#111",
letterSpacing: -1,
opacity: capOp,
transform: `translateY(${capY}px)`,
padding: "0 60px",
zIndex: 10,
}}
>
{captionText}
</div>
)}
{/* Connector lines — animated draw-on */}
<svg
style={{ position: "absolute", inset: 0, pointerEvents: "none" }}
width={width}
height={height}
>
{branchData.map((bd, i) => {
const lineDelay = 8 + i * 3;
const lineT = interpolate(frame, [lineDelay, lineDelay + 14], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
easing: Easing.out(Easing.cubic),
});
// Line goes from central edge to branch edge
const cx1 = centerX + Math.cos(bd.angle) * centralRadius;
const cy1 = centerY + Math.sin(bd.angle) * centralRadius;
const cx2 = bd.x - Math.cos(bd.angle) * branchRadius;
const cy2 = bd.y - Math.sin(bd.angle) * branchRadius;
const endX = cx1 + (cx2 - cx1) * lineT;
const endY = cy1 + (cy2 - cy1) * lineT;
return (
<line
key={i}
x1={cx1}
y1={cy1}
x2={endX}
y2={endY}
stroke={lineColor}
strokeWidth={5}
strokeLinecap="round"
strokeDasharray="2 8"
opacity={0.65}
/>
);
})}
</svg>
{/* Branch nodes — spring in after line reaches */}
{branchData.map((bd, i) => {
const nodeStart = 16 + i * 3;
const nodeEnter = spring({
frame: frame - nodeStart,
fps,
config: { mass: 1, damping: 13, stiffness: 140 },
});
const nodeScale = interpolate(nodeEnter, [0, 1], [0, 1]);
const nodeOp = interpolate(nodeEnter, [0, 0.3, 1], [0, 1, 1]);
const branchFloat = Math.sin(t * 1.2 + i * 0.7) * 4;
return (
<Node
key={i}
x={bd.x}
y={bd.y + branchFloat}
scale={nodeScale}
opacity={nodeOp}
radius={branchRadius}
label={bd.branch.label}
logo={bd.branch.logo}
bg={bd.branch.color || "#ffffff"}
tile_bg={bd.branch.tile_bg}
textColor={"#111"}
shadow
/>
);
})}
{/* Central node */}
<Node
x={centerX}
y={centerY}
scale={centralScale * pulse}
opacity={1}
radius={centralRadius}
label={centralLabel}
logo={centralLogo}
bg={centralColor}
textColor={centralTextColor}
big
shadow
/>
</AbsoluteFill>
);
};
const Node: React.FC<{
x: number;
y: number;
scale: number;
opacity: number;
radius: number;
label: string;
logo?: string;
bg: string;
textColor: string;
tile_bg?: string;
big?: boolean;
shadow?: boolean;
}> = ({ x, y, scale, opacity, radius, label, logo, bg, textColor, tile_bg, big, shadow }) => {
const size = radius * 2;
const fontSize = big ? Math.floor(radius * 0.32) : Math.floor(radius * 0.24);
const logoSize = Math.floor(radius * 0.8);
return (
<div
style={{
position: "absolute",
left: x - radius,
top: y - radius,
width: size,
height: size,
borderRadius: "50%",
background: bg,
color: textColor,
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
gap: logo && label ? 6 : 0,
transform: `scale(${scale})`,
opacity,
boxShadow: shadow
? "0 24px 44px rgba(0,0,0,0.18), 0 8px 16px rgba(0,0,0,0.12)"
: "none",
fontFamily: FONT,
padding: 14,
textAlign: "center",
overflow: "hidden",
}}
>
{logo &&
(tile_bg ? (
<div
style={{
width: logoSize,
height: logoSize,
borderRadius: Math.floor(logoSize * 0.22),
background: tile_bg,
display: "flex",
alignItems: "center",
justifyContent: "center",
overflow: "hidden",
}}
>
<Img
src={resolveStatic(logo)}
style={{ width: "70%", height: "70%", objectFit: "contain" }}
/>
</div>
) : (
<Img
src={resolveStatic(logo)}
style={{ width: logoSize, height: logoSize, objectFit: "contain" }}
/>
))}
{label && (
<div
style={{
fontSize,
fontWeight: big ? 900 : 800,
letterSpacing: -1,
lineHeight: 1.05,
}}
>
{label}
</div>
)}
</div>
);
};
Schema (Zod)
// Auto-extracted from Props interface — see source for authoritative types.
// Component: MindmapExpand
// 10 props detected · 4 port(s)
Mindmap Expand
mindmap_expandMindmap Expand — Andy's hand-crafted Remotion scene. 10 props. centralLogo:IMAGE, centralTextColor:TEXT, captionText:TEXT, +1 more.
Workflow Inputs (4)
- Central Logo
centralLogoIMAGE - Central Text Color
centralTextColorTEXT - Caption Text
captionTextTEXT - Caption Position
captionPositionTEXT
Config Fields (6)
brancheslineColoraccentColorcentralColorcentralLabelbackgroundColor
Meta
- Updated
- 4/21/2026