원클릭으로
remotion-real-estate-videos
Create real estate marketing videos with Remotion - property showcases, interior reveals, floor plan animations
메뉴
Create real estate marketing videos with Remotion - property showcases, interior reveals, floor plan animations
Delegate coding to Claude Code CLI (features, PRs).
Delegate coding to OpenAI Codex CLI (features, PRs).
Configure, extend, or contribute to Hermes Agent.
Systematically power up Hermes Agent with maximum capabilities — audit, install tools, configure MCP servers, set up cron fleet, enable Ollama delegation, and create monitoring dashboard. Use when the user wants to make their AI agent more powerful.
Navigate and query the full Hermes Agent documentation (2.2MB) stored locally.
Evaluate, configure, and run open-source GitHub tools in the local environment. Covers real dependency auditing, hidden system requirements, API configuration, and zero-cost local LLM integration.
| name | remotion-real-estate-videos |
| description | Create real estate marketing videos with Remotion - property showcases, interior reveals, floor plan animations |
| category | creative |
| metadata | {"tags":"remotion, real-estate, video, marketing, property, motion-graphics"} |
Reusable Remotion components and patterns for creating professional real estate marketing videos programmatically.
Use this skill when creating:
my-real-estate-video/
├── src/
│ ├── index.jsx # Entry point - calls registerRoot()
│ ├── Root.jsx # Defines Composition(s)
│ └── YourComponent.jsx # Video component
├── public/ # Images, videos, data files
│ ├── floorplan.jpg
│ ├── interior1.jpg
│ └── subtitles.json
├── tsconfig.json
├── package.json
└── output/
src/index.jsx:
import { registerRoot } from 'remotion';
import { RemotionRoot } from './Root';
registerRoot(RemotionRoot);
src/Root.jsx:
import { Composition } from 'remotion';
import { YourComponent } from './YourComponent';
export const RemotionRoot = () => (
<Composition
id="YourComposition"
component={YourComponent}
durationInFrames={180} // 6 seconds @ 30fps
fps={30}
width={1080}
height={1920} // Vertical for Shorts/Reels
/>
);
tsconfig.json:
{
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"jsx": "react-jsx",
"module": "ESNext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"allowJs": true,
"checkJs": false,
"strict": false,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*", "public/**/*"]
}
If you see version mismatch warnings, pin all remotion packages to the same version:
npm install remotion@4.0.450 @remotion/cli@4.0.450 @remotion/media-utils@4.0.450 --legacy-peer-deps
Remove the ^ prefix from versions in package.json.
If you get "does not contain registerRoot", your entry file must call registerRoot():
import { registerRoot } from 'remotion';
import { RemotionRoot } from './Root';
registerRoot(RemotionRoot);
Features: zoom from map view, animated boundary lines, street labels, property stats.
const BoundaryLine = ({ delay = 0, color = '#00d4ff' }) => {
const frame = useCurrentFrame();
const progress = interpolate(frame - delay, [0, 60], [0, 100], { extrapolateRight: 'clamp' });
return (
<svg style={{ position: 'absolute', width: '100%', height: '100%', pointerEvents: 'none' }}>
<defs>
<filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="4" result="coloredBlur"/>
<feMerge><feMergeNode in="coloredBlur"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
{/* Draw each side with progress-based opacity */}
<line x1="15%" y1="25%" x2={`${15 + (progress * 0.7)}%`} y2="25%"
stroke={color} strokeWidth="4" filter="url(#glow)" strokeLinecap="round" />
{/* Corner dots with pulse animation */}
<circle cx="15%" cy="25%" r="8" fill={color} filter="url(#glow)">
<animate attributeName="r" values="6;10;6" dur="1.5s" repeatCount="indefinite" />
</circle>
</svg>
);
};
const StreetLabel = ({ text, x, y, delay = 0 }) => {
const frame = useCurrentFrame();
const opacity = interpolate(frame - delay, [0, 30], [0, 1], { extrapolateRight: 'clamp' });
const translateY = interpolate(frame - delay, [0, 30], [20, 0], {
extrapolateRight: 'clamp', easing: Easing.out(Easing.cubic)
});
return (
<div style={{ position: 'absolute', left: x, top: y, transform: `translateY(${translateY}px)`, opacity }}>
<div style={{
backgroundColor: 'rgba(0, 0, 0, 0.7)',
padding: '8px 16px',
borderRadius: '4px',
border: '1px solid rgba(255, 255, 255, 0.3)',
}}>
<span style={{ color: 'white', fontSize: '18px', fontWeight: 500, textTransform: 'uppercase', letterSpacing: '1px' }}>
{text}
</span>
</div>
</div>
);
};
<div style={{
width: '20px',
height: '20px',
backgroundColor: '#00d4ff',
borderRadius: '50%',
boxShadow: '0 0 0 0 rgba(0, 212, 255, 0.7)',
animation: 'pulse 2s infinite',
}} />
<style>{`
@keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(0, 212, 255, 0.7); }
70% { box-shadow: 0 0 0 20px rgba(0, 212, 255, 0); }
100% { box-shadow: 0 0 0 0 rgba(0, 212, 255, 0); }
}
`}</style>
Features: glowing text overlays, animated measurement lines, spring physics, image transitions.
import { spring } from 'remotion';
const GlowText = ({ text, subtext, x, y, delay = 0 }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const opacity = interpolate(frame - delay, [0, 20], [0, 1], { extrapolateRight: 'clamp' });
const scale = spring({ frame: frame - delay, fps, config: { damping: 12, stiffness: 100, mass: 0.8 } });
return (
<div style={{ position: 'absolute', left: x, top: y, transform: `scale(${scale})`, opacity }}>
<div style={{
background: 'linear-gradient(135deg, rgba(0,0,0,0.85) 0%, rgba(0,0,0,0.6) 100%)',
padding: '16px 28px',
borderRadius: '12px',
border: '2px solid rgba(0, 212, 255, 0.7)',
boxShadow: '0 0 40px rgba(0, 212, 255, 0.5)',
}}>
<p style={{ color: '#00d4ff', fontSize: '36px', fontWeight: 700, margin: 0, textShadow: '0 0 20px rgba(0, 212, 255, 0.8)', textTransform: 'uppercase' }}>
{text}
</p>
{subtext && <p style={{ color: 'white', fontSize: '20px', margin: '4px 0 0 0', opacity: 0.9 }}>{subtext}</p>}
</div>
</div>
);
};
const MeasureLine = ({ x1, y1, x2, y2, label, delay = 0 }) => {
const frame = useCurrentFrame();
const progress = interpolate(frame - delay, [0, 30], [0, 1], {
extrapolateRight: 'clamp', easing: Easing.out(Easing.cubic)
});
const labelOpacity = interpolate(frame - delay, [15, 35], [0, 1], { extrapolateRight: 'clamp' });
const length = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
const currentLength = length * progress;
const angle = Math.atan2(y2 - y1, x2 - x1);
return (
<svg style={{ position: 'absolute', width: '100%', height: '100%', pointerEvents: 'none' }}>
<line
x1={x1} y1={y1}
x2={x1 + Math.cos(angle) * currentLength}
y2={y1 + Math.sin(angle) * currentLength}
stroke="#00d4ff" strokeWidth="3" strokeLinecap="round"
filter="url(#glow)"
/>
</svg>
);
};
Features: room-by-room animated borders, progressive labeling, floor indicator.
const RoomReveal = ({ label, area, x, y, width, height, delay = 0, color = '#00d4ff' }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const revealProgress = interpolate(frame - delay, [0, 40], [0, 1], {
extrapolateRight: 'clamp', easing: Easing.out(Easing.cubic)
});
const textOpacity = interpolate(frame - delay, [20, 50], [0, 1], { extrapolateRight: 'clamp' });
const scale = spring({ frame: frame - delay, fps, config: { damping: 15, stiffness: 100 } });
return (
<div style={{ position: 'absolute', left: x, top: y, width, height, pointerEvents: 'none' }}>
<svg style={{ position: 'absolute', width: '100%', height: '100%', opacity: revealProgress }}>
<defs>
<filter id={`glow-${delay}`} x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="4" result="coloredBlur"/>
<feMerge><feMergeNode in="coloredBlur"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
<rect
x="2" y="2" width={parseInt(width) - 4} height={parseInt(height) - 4}
fill={`${color}15`} /* 15 = ~8% opacity hex */
stroke={color} strokeWidth="3"
strokeDasharray={`${revealProgress * 1000}`}
filter={`url(#glow-${delay})`}
rx="8"
/>
</svg>
<div style={{
position: 'absolute', top: '50%', left: '50%',
transform: `translate(-50%, -50%) scale(${scale})`,
textAlign: 'center', opacity: textOpacity,
}}>
<div style={{
backgroundColor: 'rgba(0, 0, 0, 0.8)',
padding: '10px 18px', borderRadius: '8px',
border: `1px solid ${color}`,
boxShadow: `0 0 20px ${color}40`,
}}>
<p style={{ color: 'white', fontSize: '18px', fontWeight: 600, margin: 0, whiteSpace: 'nowrap' }}>{label}</p>
<p style={{ color, fontSize: '14px', margin: '2px 0 0 0' }}>{area}</p>
</div>
</div>
</div>
);
};
const FloorIndicator = ({ floor, delay = 0 }) => {
const frame = useCurrentFrame();
const opacity = interpolate(frame - delay, [0, 20], [0, 1], { extrapolateRight: 'clamp' });
const translateX = interpolate(frame - delay, [0, 20], [-100, 30], {
extrapolateRight: 'clamp', easing: Easing.out(Easing.cubic)
});
return (
<div style={{ position: 'absolute', top: '8%', left: translateX, opacity, zIndex: 20 }}>
<div style={{
background: 'linear-gradient(135deg, rgba(0,212,255,0.2) 0%, rgba(0,0,0,0.8) 100%)',
padding: '16px 32px', borderRadius: '0 12px 12px 0',
border: '2px solid rgba(0, 212, 255, 0.5)', borderLeft: '4px solid #00d4ff',
}}>
<p style={{ color: '#00d4ff', fontSize: '14px', textTransform: 'uppercase', letterSpacing: '3px', margin: 0 }}>Mặt bằng</p>
<p style={{ color: 'white', fontSize: '36px', fontWeight: 700, margin: '4px 0 0 0' }}>{floor}</p>
</div>
</div>
);
};
npx remotion render src/index.jsx CompositionName --output=output/video.mp4
interpolate with extrapolateRight: 'clamp' to prevent animation from running past its rangespring() function) feels more premium than linear interpolation for text pop-insfeGaussianBlur) are GPU-accelerated and perform well in Remotion