| name | viral-video-ads |
| description | Create scroll-stopping video ads using Remotion + AI. Hook formulas, scene structure,
kinetic typography, platform specs for IG Reels, TikTok, YouTube Shorts.
Use when: user asks about "video ads," "viral video," "Remotion," "TikTok ads,"
"Instagram Reels," "video marketing," "motion graphics," "animated ads,"
"scroll-stopping video," or "video creative."
|
| metadata | {"author":"Cybrflux","version":"1.0.0","tags":["video","ads","remotion","motion-graphics","tiktok","reels","creative"]} |
Viral Video Ads — Create Scroll-Stopping Creative with Remotion + AI
Most video ads are ignored in 0.5 seconds. The best ones? They stop the scroll, hold attention, and drive action.
This skill teaches you to build professional video ads programmatically using Remotion (React for video), combined with AI for rapid iteration and scale. No After Effects. No motion design team. Just code, creativity, and conversion.
Cybrflux uses this stack to produce 50+ video variants per campaign, test aggressively, and scale winners.
When to Use
- Paid Social Campaigns — TikTok, Instagram Reels, YouTube Shorts, Facebook
- Product Launches — Announcements that need to pop
- App Install Campaigns — Mobile-first video creative
- SaaS Explainers — Complex products made simple
- E-commerce Promos — Product showcases that sell
- Brand Awareness — Memorable content at scale
Trigger phrases: "video ads," "viral video," "Remotion," "TikTok ads," "Instagram Reels," "video marketing," "motion graphics," "animated ads," "scroll-stopping video," "video creative," "short-form video," "video content"
The Anatomy of a Viral Video Ad
┌─────────────────────────────────────────────────────────────────────────┐
│ 0-3 SECONDS: THE HOOK │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ PATTERN INTERRUPT │ │
│ │ - Visual surprise (fast cut, unusual image) │ │
│ │ - Auditory hook (sound effect, voice, music drop) │ │
│ │ - Curiosity gap ("I stopped doing X and Y happened") │ │
│ └─────────────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────────────┤
│ 3-15 SECONDS: THE PROBLEM/AGITATION │
│ - Relatable pain point (show, don't tell) │
│ - Quick cuts, kinetic text │
│ - Build tension or frustration │
├─────────────────────────────────────────────────────────────────────────┤
│ 15-30 SECONDS: THE SOLUTION │
│ - Product/app enters frame │
│ - Show the transformation │
│ - Before/after or use-case demonstration │
├─────────────────────────────────────────────────────────────────────────┤
│ 30-45 SECONDS: PROOF/SOCIAL │
│ - Results, testimonials, reviews │
│ - "Join X people who..." │
│ - Quick credibility indicators │
├─────────────────────────────────────────────────────────────────────────┤
│ 45-60 SECONDS: THE CTA │
│ - Clear next step │
│ - Urgency or incentive │
│ - On-screen button animation │
└─────────────────────────────────────────────────────────────────────────┘
Hook Formulas That Work
The 10 Scroll-Stopping Hooks
| Hook Type | Formula | Example | Best For |
|---|
| Pattern Interrupt | "Stop [common action]" | "Stop scrolling if you hate your 9-5" | Broad audiences |
| Curiosity Gap | "I [did X] and [unexpected Y]" | "I quit caffeine for 30 days—here's what happened" | Health/wellness |
| Direct Challenge | "If you [condition], watch this" | "If you make under $100K, watch this" | Finance/career |
| Contrarian | "Unpopular opinion: [statement]" | "Unpopular opinion: college is a scam" | Education/career |
| Question | "Why does [X] always [Y]?" | "Why do successful people wake up at 5am?" | Self-improvement |
| Number/Stat | "[X]% of [group] don't know [fact]" | "87% of startups fail for this reason" | B2B/SaaS |
| Transformation | "From [bad] to [good] in [time]" | "From broke to $50K/month in 6 months" | Business/finance |
| Exclusivity | "The [group] don't want you to know [secret]" | "Billionaires don't want you to know this" | Finance/investing |
| Urgency | "Do this before [deadline/event]" | "Do this before the recession hits" | Finance/real estate |
| Controversy | "[Common belief] is wrong—here's why" | "Working hard is wrong—here's why" | Self-improvement |
Hook Templates by Platform
export const HOOK_TEMPLATES = {
tiktok: {
styles: [
"POV: {situation}",
"Tell me you {trait} without telling me you {trait}",
"Things I wish I knew before {event}",
"This is your sign to {action}",
"The {adjective} truth about {topic}"
]
},
instagram: {
styles: [
"How I {achievement} in {timeframe}",
"{Number} ways to {benefit}",
"The {adjective} guide to {topic}",
"Why I {unusual_action} (and you should too)",
"{Before_state} → {after_state}"
]
},
youtube: {
styles: [
"I tried {method} for {timeframe}—here's what happened",
"The real reason {thing} happens",
"{Number} mistakes killing your {thing}",
"How to {benefit} (step-by-step)",
"What {expert_group} know about {topic} that you don't"
]
}
};
Remotion Fundamentals
Project Setup
npx create-video@latest my-video-ads
cd my-video-ads
npm install @remotion/player @remotion/transitions @remotion/animation-utils
npm install -D @remotion/cli @remotion/tailwind
npm install remotion-ai-helper
Core Concepts
import { Composition } from 'remotion';
import { AdTemplate } from './templates/AdTemplate';
export const MyVideo = () => {
return (
<>
{/* 15-second Instagram Reel */}
<Composition
id="IG-Reel-15s"
component={AdTemplate}
durationInFrames={15 * 30} // 15 seconds at 30fps
fps={30}
width={1080}
height={1920} // 9:16 vertical
defaultProps={{
hook: "Stop scrolling if you hate your 9-5",
productName: "FreedomOS",
scenes: [
{ type: 'hook', duration: 3 },
{ type: 'problem', duration: 4 },
{ type: 'solution', duration: 5 },
{ type: 'cta', duration: 3 }
]
}}
/>
{/* 30-second YouTube Pre-roll */}
<Composition
id="YT-PreRoll-30s"
component={AdTemplate}
durationInFrames={30 * 30}
fps={30}
width={1920}
height={1080} // 16:9 horizontal
defaultProps={{
hook: "This app saved me 10 hours a week",
// ...
}}
/>
</>
);
};
Scene Structure Pattern
import { useVideoConfig, useCurrentFrame } from 'remotion';
import { HookScene } from './scenes/HookScene';
import { ProblemScene } from './scenes/ProblemScene';
import { SolutionScene } from './scenes/SolutionScene';
import { CTAScene } from './scenes/CTAScene';
export const AdTemplate: React.FC<AdProps> = (props) => {
const { durationInFrames } = useVideoConfig();
const scenes = calculateSceneTimings(props.scenes, durationInFrames);
return (
<div className="relative w-full h-full bg-black overflow-hidden">
{/* Hook: 0-3s */}
<Sequence from={scenes[0].start} durationInFrames={scenes[0].duration}>
<HookScene text={props.hook} />
</Sequence>
{/* Problem: 3-7s */}
<Sequence from={scenes[1].start} durationInFrames={scenes[1].duration}>
<ProblemScene painPoints={props.painPoints} />
</Sequence>
{/* Solution: 7-15s */}
<Sequence from={scenes[2].start} durationInFrames={scenes[2].duration}>
<SolutionScene
productName={props.productName}
screenshot={props.productScreenshot}
/>
</Sequence>
{/* CTA: 15s-end */}
<Sequence from={scenes[3].start} durationInFrames={scenes[3].duration}>
<CTAScene
text={props.cta}
url={props.ctaUrl}
urgency={props.urgency}
/>
</Sequence>
</div>
);
};
Kinetic Typography
Text Animation Components
import { useCurrentFrame, useVideoConfig, interpolate, spring } from 'remotion';
export const WordReveal: React.FC<{
text: string;
startFrame: number;
wordDuration?: number;
}> = ({ text, startFrame, wordDuration = 5 }) => {
const frame = useCurrentFrame();
const words = text.split(' ');
return (
<div className="flex flex-wrap gap-2">
{words.map((word, i) => {
const wordStart = startFrame + i * wordDuration;
const progress = interpolate(
frame,
[wordStart, wordStart + wordDuration],
[0, 1],
{ extrapolateLeft: 'clamp', extrapolateRight: 'clamp' }
);
return (
<span
key={i}
style={{
opacity: progress,
transform: `translateY(${(1 - progress) * 20}px)`,
}}
className="text-white font-bold text-6xl"
>
{word}
</span>
);
})}
</div>
);
};
export const BounceText: React.FC<{
text: string;
startFrame: number;
}> = ({ text, startFrame }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const scale = spring({
frame: frame - startFrame,
fps,
config: {
damping: 10,
stiffness: 100,
mass: 0.5,
},
});
return (
<div
style={{ transform: `scale(${scale})` }}
className="text-white font-black text-7xl text-center"
>
{text}
</div>
);
};
export const TypewriterText: React.FC<{
text: string;
startFrame: number;
charDuration?: number;
}> = ({ text, startFrame, charDuration = 2 }) => {
const frame = useCurrentFrame();
const charsToShow = Math.floor((frame - startFrame) / charDuration);
return (
<div className="text-white font-bold text-5xl">
{text.slice(0, charsToShow)}
<span className="animate-pulse">|</span>
</div>
);
};
export const GlitchText: React.FC<{
text: string;
intensity?: number;
}> = ({ text, intensity = 1 }) => {
const frame = useCurrentFrame();
const glitchOffset = Math.sin(frame * 0.5) * intensity * 5;
return (
<div className="relative">
<span
className="text-cyan-400 font-black text-6xl absolute"
style={{ transform: `translateX(${glitchOffset}px)` }}
>
{text}
</span>
<span
className="text-pink-500 font-black text-6xl absolute"
style={{ transform: `translateX(${-glitchOffset}px)` }}
>
{text}
</span>
<span className="text-white font-black text-6xl relative">
{text}
</span>
</div>
);
};
Text Layout Best Practices
export const TEXT_LAYOUTS = {
mobile: {
fontSize: 'clamp(32px, 8vw, 72px)',
lineHeight: 1.2,
maxWidth: '90%',
textAlign: 'center',
safeZone: {
top: 150,
bottom: 250,
left: 50,
right: 50,
}
},
desktop: {
fontSize: 'clamp(48px, 6vw, 96px)',
lineHeight: 1.1,
maxWidth: '80%',
textAlign: 'left',
}
};
export const ensureContrast = (backgroundColor: string) => {
const isDark = isColorDark(backgroundColor);
return {
color: isDark ? '#ffffff' : '#000000',
textShadow: isDark ? '0 2px 4px rgba(0,0,0,0.5)' : 'none',
};
};
Data Visualization Animation
Animated Charts
import { useCurrentFrame, interpolate, Easing } from 'remotion';
export const AnimatedBar: React.FC<{
value: number;
maxValue: number;
color: string;
label: string;
delay?: number;
}> = ({ value, maxValue, color, label, delay = 0 }) => {
const frame = useCurrentFrame();
const height = interpolate(
frame,
[delay, delay + 30],
[0, (value / maxValue) * 100],
{
easing: Easing.elastic(1.2),
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
}
);
return (
<div className="flex flex-col items-center">
<div className="relative w-16 bg-gray-800 rounded-t-lg overflow-hidden">
<div
className="absolute bottom-0 w-full rounded-t-lg transition-all"
style={{
height: `${height}%`,
backgroundColor: color,
}}
/>
</div>
<span className="text-white text-sm mt-2">{label}</span>
<span className="text-white font-bold">{value}%</span>
</div>
);
};
export const AnimatedCounter: React.FC<{
value: number;
prefix?: string;
suffix?: string;
duration?: number;
}> = ({ value, prefix = '', suffix = '', duration = 60 }) => {
const frame = useCurrentFrame();
const currentValue = interpolate(
frame,
[0, duration],
[0, value],
{ extrapolateRight: 'clamp' }
);
return (
<span className="text-white font-black text-8xl">
{prefix}{Math.round(currentValue).toLocaleString()}{suffix}
</span>
);
};
export const BeforeAfterSlider: React.FC<{
beforeImage: string;
afterImage: string;
beforeLabel: string;
afterLabel: string;
}> = ({ beforeImage, afterImage, beforeLabel, afterLabel }) => {
const frame = useCurrentFrame();
const sliderPosition = interpolate(
frame,
[30, 90],
[0, 100],
{ extrapolateRight: 'clamp' }
);
return (
<div className="relative w-full h-full">
{/* Before image (full width) */}
<Img src={beforeImage} className="absolute inset-0 w-full h-full object-cover" />
<span className="absolute top-4 left-4 bg-red-500 text-white px-3 py-1 rounded">
{beforeLabel}
</span>
{/* After image (clipped) */}
<div
className="absolute inset-0 overflow-hidden"
style={{ clipPath: `inset(0 ${100 - sliderPosition}% 0 0)` }}
>
<Img src={afterImage} className="w-full h-full object-cover" />
<span className="absolute top-4 left-4 bg-green-500 text-white px-3 py-1 rounded">
{afterLabel}
</span>
</div>
{/* Slider handle */}
<div
className="absolute top-0 bottom-0 w-1 bg-white"
style={{ left: `${sliderPosition}%` }}
>
<div className="absolute top-1/2 -translate-y-1/2 -translate-x-1/2 w-8 h-8 bg-white rounded-full flex items-center justify-center">
↔
</div>
</div>
</div>
);
};
Product Showcase Patterns
App Screen Recording Integration
import { Video } from 'remotion';
export const AppDemo: React.FC<{
screenRecording: string;
highlightAreas?: Array<{
x: number;
y: number;
width: number;
height: number;
startFrame: number;
duration: number;
label: string;
}>;
}> = ({ screenRecording, highlightAreas = [] }) => {
return (
<div className="relative w-full h-full bg-black">
{/* Phone frame overlay */}
<div className="absolute inset-0 flex items-center justify-center">
<div className="relative">
<img src="/phone-frame.png" className="relative z-10" />
<div className="absolute inset-4 overflow-hidden rounded-[40px]">
<Video
src={screenRecording}
className="w-full h-full object-cover"
/>
</div>
</div>
</div>
{/* Highlight overlays */}
{highlightAreas.map((area, i) => (
<HighlightBox key={i} {...area} />
))}
</div>
);
};
const HighlightBox: React.FC<HighlightArea> = (props) => {
const frame = useCurrentFrame();
const isVisible = frame >= props.startFrame &&
frame < props.startFrame + props.duration;
if (!isVisible) return null;
return (
<div
className="absolute border-2 border-yellow-400 rounded-lg animate-pulse"
style={{
left: props.x,
top: props.y,
width: props.width,
height: props.height,
}}
>
<span className="absolute -top-8 left-0 bg-yellow-400 text-black text-sm px-2 py-1 rounded">
{props.label}
</span>
</div>
);
};
E-commerce Product Showcase
export const ProductShowcase: React.FC<{
product: {
images: string[];
name: string;
price: string;
originalPrice?: string;
discount?: string;
features: string[];
};
}> = ({ product }) => {
return (
<div className="w-full h-full bg-gradient-to-br from-purple-900 to-blue-900">
{/* Product image carousel */}
<Carousel
images={product.images}
interval={60}
transition="slide"
/>
{/* Product info overlay */}
<div className="absolute bottom-0 left-0 right-0 p-8 bg-gradient-to-t from-black/80 to-transparent">
<h2 className="text-white text-4xl font-bold mb-2">{product.name}</h2>
<div className="flex items-center gap-4 mb-4">
<span className="text-3xl font-black text-green-400">{product.price}</span>
{product.originalPrice && (
<span className="text-xl text-gray-400 line-through">{product.originalPrice}</span>
)}
{product.discount && (
<span className="bg-red-500 text-white px-3 py-1 rounded-full text-sm font-bold">
SAVE {product.discount}
</span>
)}
</div>
{/* Feature pills */}
<div className="flex flex-wrap gap-2">
{product.features.map((feature, i) => (
<span
key={i}
className="bg-white/20 text-white px-3 py-1 rounded-full text-sm"
>
✓ {feature}
</span>
))}
</div>
</div>
</div>
);
};
Platform Specifications
Format Cheat Sheet
| Platform | Aspect Ratio | Resolution | Duration | Max Size | Audio |
|---|
| TikTok | 9:16 | 1080x1920 | 15-60s | 287MB | Required |
| Instagram Reels | 9:16 | 1080x1920 | 15-90s | 4GB | Recommended |
| YouTube Shorts | 9:16 | 1080x1920 | <60s | 2GB | Required |
| YouTube (Pre-roll) | 16:9 | 1920x1080 | 15-30s | - | Required |
| Facebook Feed | 4:5 | 1080x1350 | <240s | 4GB | Recommended |
| LinkedIn | 1:1 or 9:16 | 1080x1080 | <30min | 5GB | Recommended |
Platform-Specific Best Practices
export const PLATFORM_CONFIG = {
tiktok: {
aspectRatio: '9:16',
dimensions: { width: 1080, height: 1920 },
maxDuration: 60,
fps: 30,
safeZones: {
top: 120,
bottom: 150,
right: 100,
},
features: {
captions: true,
trendingAudio: true,
textToSpeech: true,
}
},
instagramReels: {
aspectRatio: '9:16',
dimensions: { width: 1080, height: 1920 },
maxDuration: 90,
fps: 30,
safeZones: {
top: 100,
bottom: 200,
left: 80,
},
features: {
captions: true,
hashtags: '3-5 in caption, not video',
coverImage: true,
}
},
youtubeShorts: {
aspectRatio: '9:16',
dimensions: { width: 1080, height: 1920 },
maxDuration: 60,
fps: 30,
safeZones: {
top: 80,
bottom: 120,
},
features: {
title: 'Include #Shorts in title',
description: 'Detailed with keywords',
endScreen: 'Subscribe animation',
}
}
};
Rendering at Scale
Batch Generation
import { renderMedia, RenderMediaOnProgress } from '@remotion/renderer';
interface Variant {
id: string;
hook: string;
cta: string;
colorScheme: string;
productImage: string;
}
async function generateVariants(variants: Variant[]) {
const compositions = await getCompositions();
const adTemplate = compositions.find(c => c.id === 'AdTemplate');
for (const variant of variants) {
console.log(`Rendering variant: ${variant.id}`);
await renderMedia({
composition: adTemplate,
serveUrl: 'http://localhost:3000',
codec: 'h264',
outputLocation: `outputs/${variant.id}.mp4`,
inputProps: variant,
onProgress: ({ progress }) => {
console.log(`${variant.id}: ${Math.round(progress * 100)}%`);
},
});
}
}
const variants = await loadVariantsFromCSV('variants.csv');
await generateVariants(variants);
Cloud Rendering
import { deploySite, renderMediaOnLambda } from '@remotion/lambda';
export async function createRenderJob(variant: Variant) {
const { serveUrl } = await deploySite({
entryPoint: './src/index.tsx',
siteName: 'video-ads',
region: 'us-east-1',
});
const { renderId } = await renderMediaOnLambda({
region: 'us-east-1',
functionName: 'remotion-render',
serveUrl,
composition: 'AdTemplate',
inputProps: variant,
codec: 'h264',
imageFormat: 'jpeg',
maxRetries: 1,
});
return renderId;
}
A/B Testing Creative
Test Matrix
| Variable | Options to Test | Impact |
|---|
| Hook | 3-5 different opens | High |
| Duration | 15s vs 30s vs 60s | Medium |
| CTA | "Download" vs "Get Started" vs "Try Free" | High |
| Color Scheme | Brand colors vs high contrast | Medium |
| Music | Upbeat vs calm vs trending | Medium |
| Voiceover | Male vs female vs none | Low-Medium |
Test Setup
interface CreativeTest {
id: string;
variants: Array<{
id: string;
props: AdProps;
trafficAllocation: number;
}>;
successMetric: 'ctr' | 'cvr' | 'viewRate' | 'cpm';
minSampleSize: number;
confidenceLevel: number;
}
const TESTS: CreativeTest[] = [
{
id: 'hook-test-1',
variants: [
{ id: 'hook-pattern', props: { hook: 'Stop scrolling...' }, trafficAllocation: 0.33 },
{ id: 'hook-curiosity', props: { hook: 'I quit my job and...' }, trafficAllocation: 0.33 },
{ id: 'hook-direct', props: { hook: 'This app saves 10 hours/week' }, trafficAllocation: 0.34 },
],
successMetric: 'ctr',
minSampleSize: 10000,
confidenceLevel: 0.95,
}
];
Cross-References
- revenue-website — Convert video traffic with optimized landing pages
- ad-creative — Copywriting for video scripts and CTAs
- analytics-tracking — Measure video performance and ROI
- ai-voice-agent — Add voiceover to your video ads
Quick Start Template
npx create-video@latest viral-ads
cd viral-ads
npm run dev
npx remotion render src/index.tsx MyAd ./output.mp4
15-Second Reel Template
export const FirstReel: React.FC = () => {
return (
<div className="w-[1080px] h-[1920px] bg-black relative overflow-hidden">
{/* 0-3s: Hook */}
<Sequence from={0} durationInFrames={90}>
<div className="flex items-center justify-center h-full">
<BounceText text="This changed everything" startFrame={0} />
</div>
</Sequence>
{/* 3-7s: Problem */}
<Sequence from={90} durationInFrames={120}>
<div className="p-12">
<WordReveal
text="I was working 60 hours a week for $4K/month"
startFrame={90}
/>
</div>
</Sequence>
{/* 7-12s: Solution */}
<Sequence from={210} durationInFrames={150}>
<div className="flex flex-col items-center justify-center h-full">
<AnimatedCounter value={50000} prefix="$" suffix="/month" />
<p className="text-white text-2xl mt-4">Now I work 20 hours/week</p>
</div>
</Sequence>
{/* 12-15s: CTA */}
<Sequence from={360} durationInFrames={90}>
<div className="flex flex-col items-center justify-center h-full bg-blue-600">
<p className="text-white text-5xl font-bold mb-4">Link in bio</p>
<p className="text-white text-2xl">Free training →</p>
</div>
</Sequence>
</div>
);
};
Time to first render: 30 minutes.