Flash Sale Countdown Ad (Remotion)
A high-urgency 5-second flash sale ad rendered in Remotion at 1280x720 30fps — features a pulsing FLASH SALE header that oscillates in scale, a live HH:MM:SS countdown timer starting at 01:30:00 with per-digit color flashes on change, a deal text slide-in animation, and an animated stock bar draining from 100% to 35% in amber-red palette on a deep dark background.
Preview
Code
import React from "react";
import {
AbsoluteFill,
Composition,
interpolate,
spring,
useCurrentFrame,
useVideoConfig,
Easing,
} from "remotion";
// ── Config constants (customize here) ─────────────────────────────────────────
const SALE_LABEL = "FLASH SALE";
const DEAL_TEXT = "Up to 70% OFF Electronics";
const DEAL_SUB = "Premium headphones, laptops, smartwatches & more";
const BADGE_TEXT = "LIMITED TIME OFFER";
const STOCK_LEFT = 12;
const STOCK_TOTAL = 60;
const START_HOURS = 1;
const START_MINUTES = 30;
const START_SECONDS = 0;
const RED = "#dc2626";
const RED_LIGHT = "#ef4444";
const AMBER = "#f59e0b";
const AMBER_LIGHT = "#fbbf24";
const BG = "#0a0a0f";
const SURFACE = "rgba(255,255,255,0.05)";
const BORDER = "rgba(255,255,255,0.09)";
// ── Helpers ────────────────────────────────────────────────────────────────────
function padTwo(n: number): string {
return String(Math.max(0, Math.floor(n))).padStart(2, "0");
}
function getCountdown(frame: number, fps: number) {
const totalStartSecs = START_HOURS * 3600 + START_MINUTES * 60 + START_SECONDS;
const elapsed = frame / fps;
const remaining = Math.max(0, totalStartSecs - elapsed);
const h = Math.floor(remaining / 3600);
const m = Math.floor((remaining % 3600) / 60);
const s = Math.floor(remaining % 60);
return { h, m, s };
}
// ── BackgroundLayer ────────────────────────────────────────────────────────────
const BackgroundLayer: React.FC<{ frame: number }> = ({ frame }) => {
const redGlowOpacity = interpolate(frame, [0, 30], [0, 0.55], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
easing: Easing.out(Easing.quad),
});
const amberGlowOpacity = interpolate(frame, [10, 45], [0, 0.35], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
easing: Easing.out(Easing.quad),
});
// Slow ambient drift
const drift = interpolate(frame, [0, 150], [0, 18], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
return (
<>
{/* Top-center red glow — behind header */}
<div
style={{
position: "absolute",
top: -120,
left: "50%",
width: 900,
height: 500,
borderRadius: "50%",
background: `radial-gradient(ellipse, ${RED}55 0%, transparent 65%)`,
transform: `translateX(-50%) translateY(${drift * 0.4}px)`,
opacity: redGlowOpacity,
pointerEvents: "none",
}}
/>
{/* Bottom-center amber glow — behind timer */}
<div
style={{
position: "absolute",
bottom: -80,
left: "50%",
width: 1100,
height: 450,
borderRadius: "50%",
background: `radial-gradient(ellipse, ${AMBER}30 0%, transparent 65%)`,
transform: `translateX(-50%) translateY(${-drift * 0.3}px)`,
opacity: amberGlowOpacity,
pointerEvents: "none",
}}
/>
{/* Subtle grid lines */}
<div
style={{
position: "absolute",
inset: 0,
backgroundImage:
"linear-gradient(rgba(255,255,255,0.015) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,0.015) 1px, transparent 1px)",
backgroundSize: "80px 80px",
pointerEvents: "none",
}}
/>
</>
);
};
// ── SaleBadge — pulsing FLASH SALE header ─────────────────────────────────────
const SaleBadge: React.FC<{ frame: number; fps: number }> = ({ frame, fps }) => {
const enterY = spring({
frame,
fps,
from: -80,
to: 0,
config: { damping: 12, stiffness: 130, mass: 0.7 },
});
const enterOpacity = interpolate(frame, [0, 12], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
// Continuous pulse: scale oscillates 1 → 1.05 → 1
const pulse = 1 + Math.sin((frame / 18) * Math.PI) * 0.025;
// Lightning bolt text flicker — subtle
const flicker = frame % 24 < 2 ? 0.82 : 1;
return (
<div
style={{
opacity: enterOpacity * flicker,
transform: `translateY(${enterY}px) scale(${pulse})`,
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: 0,
}}
>
{/* Main badge chip */}
<div
style={{
background: `linear-gradient(135deg, ${RED} 0%, #b91c1c 100%)`,
borderRadius: 6,
padding: "6px 20px",
marginBottom: 10,
boxShadow: `0 0 24px ${RED}88`,
}}
>
<span
style={{
fontFamily: "system-ui, -apple-system, sans-serif",
fontWeight: 900,
fontSize: 13,
letterSpacing: 4,
color: "#fff",
textTransform: "uppercase",
}}
>
⚡ {BADGE_TEXT}
</span>
</div>
{/* FLASH SALE headline */}
<div
style={{
fontFamily: "system-ui, -apple-system, sans-serif",
fontWeight: 900,
fontSize: 96,
letterSpacing: -3,
lineHeight: 1,
color: "#ffffff",
textShadow: `0 0 60px ${RED}99, 0 2px 0 rgba(0,0,0,0.5)`,
}}
>
{SALE_LABEL}
</div>
</div>
);
};
// ── TimerDigit — a single HH / MM / SS block ──────────────────────────────────
const TimerDigit: React.FC<{
value: number;
label: string;
frame: number;
fps: number;
delay: number;
prevValue: number;
}> = ({ value, label, frame, fps, delay, prevValue }) => {
const f = Math.max(0, frame - delay);
const scale = spring({
frame: f,
fps,
from: 0,
to: 1,
config: { damping: 13, stiffness: 140, mass: 0.65 },
});
// Flash red when the digit just changed (within last 4 frames)
const digitChanged = value !== prevValue;
const flashAge = digitChanged ? 0 : 99;
const flashOpacity = interpolate(flashAge, [0, 4], [1, 0], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const digitColor = digitChanged
? `rgba(${220},${38},${38},${flashOpacity})`
: "#ffffff";
const display = padTwo(value);
return (
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: 10,
transform: `scale(${scale})`,
}}
>
{/* Card */}
<div
style={{
position: "relative",
width: 148,
height: 148,
borderRadius: 18,
background: SURFACE,
border: `1px solid ${BORDER}`,
backdropFilter: "blur(12px)",
display: "flex",
alignItems: "center",
justifyContent: "center",
boxShadow: `0 4px 32px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255,255,255,0.07)`,
overflow: "hidden",
}}
>
{/* Inner top highlight */}
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
height: 1,
background: "rgba(255,255,255,0.12)",
}}
/>
{/* Horizontal flip line */}
<div
style={{
position: "absolute",
left: 12,
right: 12,
top: "50%",
height: 1,
backgroundColor: "rgba(0,0,0,0.35)",
}}
/>
<span
style={{
fontFamily: "ui-monospace, 'Courier New', monospace",
fontWeight: 800,
fontSize: 68,
color: digitColor,
letterSpacing: -3,
lineHeight: 1,
transition: "color 0.08s",
}}
>
{display}
</span>
</div>
{/* Label */}
<span
style={{
fontFamily: "system-ui, -apple-system, sans-serif",
fontWeight: 600,
fontSize: 12,
color: "rgba(255,255,255,0.38)",
textTransform: "uppercase",
letterSpacing: 3,
}}
>
{label}
</span>
</div>
);
};
// ── TimerColon separator ───────────────────────────────────────────────────────
const TimerColon: React.FC<{ frame: number; delay: number }> = ({ frame, delay }) => {
const f = Math.max(0, frame - delay);
const opacity = interpolate(f, [0, 10], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
// Blink every second
const secondTick = Math.floor(frame / 30) % 2;
const blink = interpolate(frame % 30, [0, 4, 26, 30], [0.3, 1, 1, 0.3], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
return (
<div
style={{
display: "flex",
flexDirection: "column",
gap: 18,
alignItems: "center",
paddingBottom: 28,
opacity: opacity * blink,
}}
>
<div
style={{
width: 9,
height: 9,
borderRadius: "50%",
backgroundColor: AMBER,
boxShadow: `0 0 10px ${AMBER}99`,
}}
/>
<div
style={{
width: 9,
height: 9,
borderRadius: "50%",
backgroundColor: AMBER,
boxShadow: `0 0 10px ${AMBER}99`,
}}
/>
</div>
);
};
// ── DealText — slide-in offer line ────────────────────────────────────────────
const DealText: React.FC<{ frame: number; fps: number }> = ({ frame, fps }) => {
const f = Math.max(0, frame - 35);
const x = spring({
frame: f,
fps,
from: -90,
to: 0,
config: { damping: 16, stiffness: 110, mass: 0.8 },
});
const opacity = interpolate(f, [0, 14], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
// Sub-line delayed slightly more
const fSub = Math.max(0, frame - 48);
const xSub = spring({
frame: fSub,
fps,
from: -60,
to: 0,
config: { damping: 18, stiffness: 100 },
});
const opacitySub = interpolate(fSub, [0, 14], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
return (
<div style={{ textAlign: "center" }}>
<div
style={{
opacity,
transform: `translateX(${x}px)`,
fontFamily: "system-ui, -apple-system, sans-serif",
fontWeight: 800,
fontSize: 42,
color: "#ffffff",
letterSpacing: -1.2,
lineHeight: 1.1,
}}
>
{DEAL_TEXT}
</div>
<div
style={{
opacity: opacitySub,
transform: `translateX(${xSub}px)`,
fontFamily: "system-ui, -apple-system, sans-serif",
fontWeight: 400,
fontSize: 18,
color: "rgba(255,255,255,0.5)",
marginTop: 6,
letterSpacing: 0.2,
}}
>
{DEAL_SUB}
</div>
</div>
);
};
// ── StockBar — animated width from 100% → 35% ─────────────────────────────────
const StockBar: React.FC<{ frame: number; fps: number }> = ({ frame, fps }) => {
const f = Math.max(0, frame - 55);
const containerScale = spring({
frame: f,
fps,
from: 0.85,
to: 1,
config: { damping: 14, stiffness: 120 },
});
const containerOpacity = interpolate(f, [0, 12], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
// Stock drains from 100% to ~35% over the full clip (frames 0 → 150)
const stockPct = interpolate(frame, [0, 150], [100, 35], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
easing: Easing.inOut(Easing.quad),
});
// Remaining stock label count (12 → 7 as visual drama)
const stockDisplayCount = Math.round(interpolate(frame, [0, 150], [STOCK_TOTAL, STOCK_LEFT], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
}));
// Color shifts amber → red as stock drops
const rInterp = interpolate(stockPct, [35, 100], [220, 245], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const gInterp = interpolate(stockPct, [35, 100], [38, 158], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const barColor = `rgb(${Math.round(rInterp)},${Math.round(gInterp)},11)`;
// Shimmer on bar
const shimmerX = interpolate(frame % 45, [0, 45], [-80, 500], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
return (
<div
style={{
opacity: containerOpacity,
transform: `scale(${containerScale})`,
width: 680,
display: "flex",
flexDirection: "column",
gap: 10,
}}
>
{/* Header row */}
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<span
style={{
fontFamily: "system-ui, -apple-system, sans-serif",
fontWeight: 700,
fontSize: 14,
color: "rgba(255,255,255,0.55)",
textTransform: "uppercase",
letterSpacing: 2,
}}
>
Stock Level
</span>
<span
style={{
fontFamily: "system-ui, -apple-system, sans-serif",
fontWeight: 700,
fontSize: 15,
color: RED_LIGHT,
}}
>
⚠ Only {stockDisplayCount} left!
</span>
</div>
{/* Track */}
<div
style={{
width: "100%",
height: 14,
borderRadius: 7,
backgroundColor: "rgba(255,255,255,0.06)",
border: `1px solid rgba(255,255,255,0.08)`,
overflow: "hidden",
position: "relative",
}}
>
{/* Fill */}
<div
style={{
position: "absolute",
left: 0,
top: 0,
bottom: 0,
width: `${stockPct}%`,
borderRadius: 7,
background: `linear-gradient(90deg, ${barColor} 0%, ${AMBER_LIGHT} 100%)`,
boxShadow: `0 0 12px ${AMBER}88`,
overflow: "hidden",
}}
>
{/* Shimmer sweep */}
<div
style={{
position: "absolute",
top: 0,
left: shimmerX,
width: 60,
height: "100%",
background:
"linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent)",
transform: "skewX(-15deg)",
}}
/>
</div>
</div>
{/* Percentage label */}
<div
style={{
textAlign: "right",
fontFamily: "ui-monospace, monospace",
fontWeight: 600,
fontSize: 12,
color: "rgba(255,255,255,0.3)",
letterSpacing: 1,
}}
>
{Math.round(stockPct)}% remaining
</div>
</div>
);
};
// ── Main Composition ───────────────────────────────────────────────────────────
export const FlashSaleAd: React.FC = () => {
const frame = useCurrentFrame();
const { fps, durationInFrames } = useVideoConfig();
// Countdown state
const { h, m, s } = getCountdown(frame, fps);
// Previous frame countdown for flash-on-change
const { h: ph, m: pm, s: ps } = getCountdown(Math.max(0, frame - 1), fps);
// Global fade-out in last 20 frames
const globalOpacity = interpolate(
frame,
[durationInFrames - 20, durationInFrames],
[1, 0],
{ extrapolateLeft: "clamp", extrapolateRight: "clamp" }
);
return (
<AbsoluteFill
style={{
backgroundColor: BG,
opacity: globalOpacity,
overflow: "hidden",
}}
>
{/* Layer 0: Background glows + grid */}
<BackgroundLayer frame={frame} />
{/* Layer 1: Top — FLASH SALE badge */}
<div
style={{
position: "absolute",
top: 52,
left: 0,
right: 0,
display: "flex",
justifyContent: "center",
}}
>
<SaleBadge frame={frame} fps={fps} />
</div>
{/* Layer 2: Center — Countdown timer */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -54%)",
display: "flex",
alignItems: "center",
gap: 16,
}}
>
<TimerDigit
value={h}
prevValue={ph}
label="Hours"
frame={frame}
fps={fps}
delay={18}
/>
<TimerColon frame={frame} delay={22} />
<TimerDigit
value={m}
prevValue={pm}
label="Minutes"
frame={frame}
fps={fps}
delay={26}
/>
<TimerColon frame={frame} delay={30} />
<TimerDigit
value={s}
prevValue={ps}
label="Seconds"
frame={frame}
fps={fps}
delay={34}
/>
</div>
{/* Layer 3: Deal text block */}
<div
style={{
position: "absolute",
bottom: 180,
left: 0,
right: 0,
display: "flex",
justifyContent: "center",
}}
>
<DealText frame={frame} fps={fps} />
</div>
{/* Layer 4: Stock indicator */}
<div
style={{
position: "absolute",
bottom: 52,
left: 0,
right: 0,
display: "flex",
justifyContent: "center",
}}
>
<StockBar frame={frame} fps={fps} />
</div>
{/* Edge vignette */}
<div
style={{
position: "absolute",
inset: 0,
background:
"radial-gradient(ellipse at center, transparent 55%, rgba(0,0,0,0.55) 100%)",
pointerEvents: "none",
}}
/>
</AbsoluteFill>
);
};
// ── Remotion Root ──────────────────────────────────────────────────────────────
export const RemotionRoot: React.FC = () => (
<Composition
id="FlashSaleAd"
component={FlashSaleAd}
durationInFrames={150}
fps={30}
width={1280}
height={720}
/>
);Flash Sale Countdown Ad
A cinematic 5-second flash sale advertisement built with Remotion. The scene opens with a burst of background glows — a deep crimson radial behind the header and an amber pulse beneath the timer — that fade in as the “FLASH SALE” badge drops into view with a spring bounce. The headline pulses continuously (scale 1 → 1.05 → 1 on a sine wave), creating an unmistakable sense of urgency.
The countdown timer displays hours, minutes, and seconds computed live from frame / fps, starting at 01:30:00. Each digit pair lives in its own glass-morphism card; when a digit transitions it fires a brief red color flash so the eye is drawn instantly to the change. Below the timer, the deal copy (“Up to 70% OFF Electronics”) slides in from the left on a spring at frame 35, accompanied by a “LIMITED TIME OFFER” badge. The stock indicator bar animates width from 100% → 35% over the full clip duration, rendered in amber with a live “Only 12 left!” label — all fading to black in the final 20 frames via globalOpacity.
All constants (SALE_LABEL, DEAL_TEXT, STOCK_LEFT, START_HOURS, START_MINUTES, START_SECONDS, RED, AMBER) are declared at the top of the file for easy customization without touching component logic.
Composition specs
| Property | Value |
|---|---|
| Resolution | 1280 × 720 |
| FPS | 30 |
| Duration | 5 s (150 frames) |
Timeline
| Time | Action |
|---|---|
| 0 – 0.5 s (frames 0–15) | Background glows bloom; “FLASH SALE” badge springs down from above |
| 0.5 – 1.2 s (frames 15–36) | Countdown timer cards spring in staggered left-to-right |
| 1.2 – 2.0 s (frames 36–60) | Deal text + discount badge slide in from left; stock bar starts draining |
| 2.0 – 4.3 s (frames 60–130) | All elements held; timer counts down live; header pulses; stock bar animates |
| 4.3 – 5.0 s (frames 130–150) | Global fade-out to black |