Remotion Medium
Event Countdown (Remotion)
An animated event countdown with flip-style digits, blinking separators, and glowing background — rendered with Remotion at 1280×720 30fps.
remotion react typescript
Targets: TS React
Preview
Code
import {
AbsoluteFill,
Composition,
interpolate,
spring,
useCurrentFrame,
useVideoConfig,
Easing,
} from "remotion";
const EVENT_NAME = "Product Launch";
const EVENT_DATE = "March 30, 2026";
const ACCENT = "#ef4444";
const TOTAL_SECONDS = 7;
const Digit: React.FC<{
value: number;
label: string;
frame: number;
fps: number;
delay: number;
}> = ({ value, label, frame, fps, delay }) => {
const f = Math.max(0, frame - delay);
const scale = spring({ frame: f, fps, from: 0, to: 1, config: { damping: 12, stiffness: 100 } });
const display = String(value).padStart(2, "0");
// Pulse when value changes
const pulseFrame = frame % 30;
const pulse =
pulseFrame < 5
? interpolate(pulseFrame, [0, 3, 5], [1, 1.05, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
})
: 1;
return (
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: 12,
transform: `scale(${scale})`,
}}
>
<div
style={{
position: "relative",
width: 140,
height: 140,
borderRadius: 16,
background: "rgba(255,255,255,0.04)",
border: "1px solid rgba(255,255,255,0.08)",
display: "flex",
alignItems: "center",
justifyContent: "center",
transform: `scale(${pulse})`,
}}
>
<span
style={{
fontFamily: "ui-monospace, monospace",
fontWeight: 800,
fontSize: 64,
color: "#ffffff",
letterSpacing: -2,
}}
>
{display}
</span>
{/* Divider line */}
<div
style={{
position: "absolute",
left: 0,
right: 0,
top: "50%",
height: 1,
backgroundColor: "rgba(255,255,255,0.06)",
}}
/>
</div>
<span
style={{
fontFamily: "system-ui, sans-serif",
fontWeight: 500,
fontSize: 14,
color: "rgba(255,255,255,0.4)",
textTransform: "uppercase",
letterSpacing: 2,
}}
>
{label}
</span>
</div>
);
};
const Separator: React.FC<{ frame: number; delay: number }> = ({ frame, delay }) => {
const opacity = interpolate(Math.max(0, frame - delay), [0, 10], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const blink = frame % 30 < 15 ? 1 : 0.3;
return (
<div
style={{
display: "flex",
flexDirection: "column",
gap: 16,
alignItems: "center",
opacity: opacity * blink,
paddingTop: 20,
}}
>
<div style={{ width: 8, height: 8, borderRadius: "50%", backgroundColor: ACCENT }} />
<div style={{ width: 8, height: 8, borderRadius: "50%", backgroundColor: ACCENT }} />
</div>
);
};
export const CountdownVideo: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const remainingSeconds = Math.max(0, TOTAL_SECONDS - frame / fps);
const days = Math.floor(remainingSeconds / 86400);
const hours = Math.floor((remainingSeconds % 86400) / 3600);
const minutes = Math.floor((remainingSeconds % 3600) / 60);
const seconds = Math.floor(remainingSeconds % 60);
// Use fixed display values for visual effect
const displayDays = 12;
const displayHours = 8;
const displayMinutes = 45;
const displaySeconds = Math.max(0, 30 - Math.floor(frame / fps));
const titleOpacity = interpolate(frame, [0, 20], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const titleY = spring({ frame, fps, from: -30, to: 0, config: { damping: 14, stiffness: 80 } });
const dateOpacity = interpolate(frame, [25, 45], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const glowOpacity = interpolate(frame, [0, 40], [0, 0.3], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
easing: Easing.out(Easing.quad),
});
return (
<AbsoluteFill style={{ backgroundColor: "#0a0a0f" }}>
{/* Background glow */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
width: 900,
height: 400,
borderRadius: "50%",
background: `radial-gradient(ellipse, ${ACCENT}25 0%, transparent 70%)`,
transform: "translate(-50%, -50%)",
opacity: glowOpacity,
}}
/>
{/* Title */}
<div
style={{
position: "absolute",
top: 80,
left: 0,
right: 0,
textAlign: "center",
opacity: titleOpacity,
transform: `translateY(${titleY}px)`,
}}
>
<div
style={{
fontFamily: "system-ui, sans-serif",
fontWeight: 800,
fontSize: 48,
color: "#ffffff",
letterSpacing: -1,
}}
>
{EVENT_NAME}
</div>
<div
style={{
fontFamily: "system-ui, sans-serif",
fontWeight: 400,
fontSize: 20,
color: ACCENT,
marginTop: 8,
opacity: dateOpacity,
}}
>
{EVENT_DATE}
</div>
</div>
{/* Countdown digits */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
display: "flex",
alignItems: "flex-start",
gap: 24,
marginTop: 30,
}}
>
<Digit value={displayDays} label="Days" frame={frame} fps={fps} delay={10} />
<Separator frame={frame} delay={14} />
<Digit value={displayHours} label="Hours" frame={frame} fps={fps} delay={16} />
<Separator frame={frame} delay={20} />
<Digit value={displayMinutes} label="Minutes" frame={frame} fps={fps} delay={22} />
<Separator frame={frame} delay={26} />
<Digit value={displaySeconds} label="Seconds" frame={frame} fps={fps} delay={28} />
</div>
</AbsoluteFill>
);
};
export const RemotionRoot: React.FC = () => (
<Composition
id="CountdownVideo"
component={CountdownVideo}
durationInFrames={210}
fps={30}
width={1280}
height={720}
/>
);Event countdown timer showing days, hours, minutes, seconds with spring-animated digit cards, blinking colon separators, and a pulsing red glow. Customize EVENT_NAME, EVENT_DATE, and ACCENT color.