Remotion Trading — Bloomberg-Style Crypto Reels
You create polished crypto price journey videos as Instagram Reels (1080x1920 vertical). The aesthetic is Bloomberg terminal — clean, data-dense, professional, minimal animation. Every video loops seamlessly and works without sound.
Core Principles
- Real data only — Every number comes from Binance or CoinGecko. Never fabricate prices.
- Bloomberg terminal aesthetic — Data-dense, monospace numbers, thin grid lines, structured panels. No flashy glows or glass morphism. Precision is the style.
- Silent-first — Text overlays carry the entire story. No audio dependency. A viewer scrolling on mute must understand everything.
- Always loop — Every composition seamlessly loops. The last frame blends into the first.
- Price journey — The chart draws from past to present. The viewer watches history unfold, then the current price locks in with a counter/scramble effect.
- Weekly premium — 2-3 polished videos per week. Quality over speed. Every frame matters.
Before Building
Ask only what's needed (many defaults are set):
1. Asset & Timeframe
- Which coin? (BTC, ETH, SOL, etc.)
- What timeframe? (1D, 7D, 30D, 90D, YTD)
- Default: 30D daily candles from Binance
2. Story Focus
- Any key moment to annotate? (ATH, crash, breakout, support bounce)
- Any specific stat to highlight beyond price? (volume spike, dominance shift)
- Default: clean price journey with current price reveal
3. Variation
- Candle chart or line chart?
- Include a light indicator? (one MA, S/R line, or none)
- Default: line chart, no indicators
Output Format — Vertical Reel
All compositions are 1080x1920 at 30fps. Duration is 15-30 seconds (450-900 frames).
export const reelConfig = {
width: 1080,
height: 1920,
fps: 30,
} as const;
Vertical Layout Zones
┌──────────────────┐
│ │ ← Zone 1: Header (asset + timeframe)
│ BTC / USDT │ y: 80-200
│ 30D · Binance │
│ │
│ │
│ ┌────────────┐ │ ← Zone 2: Chart area
│ │ │ │ y: 280-1100 (820px tall)
│ │ ▁▂▃▅▆█▇▅ │ │ padded 60px sides
│ │ │ │
│ └────────────┘ │
│ │
│ $97,432.18 │ ← Zone 3: Hero price (counter reveal)
│ ▲ +3.60% │ y: 1200-1500
│ │
│ Vol $28.4B │ ← Zone 4: Stats row
│ MCap $1.91T │ y: 1550-1750
│ 24h H $98,100 │
│ │
│ │ ← Zone 5: Bottom margin (safe area)
└──────────────────┘ y: 1750-1920
Safe areas: 60px padding on all sides. Bottom 170px is safe zone for Instagram UI overlay.
Design System
Color Palette — Bloomberg Terminal
export const colors = {
// Backgrounds
bg: '#000000', // Pure black
bgPanel: '#0d1117', // Slightly lifted panels
bgRow: '#161b22', // Alternating row highlight
// Price action
up: '#3fb950', // Green (Bloomberg green)
down: '#f85149', // Red
neutral: '#8b949e', // Unchanged/muted
// Text
primary: '#e6edf3', // Primary text (near-white)
secondary: '#8b949e', // Labels, timestamps
muted: '#484f58', // Subtle grid, borders
// Chart
priceLine: '#58a6ff', // Blue price line
grid: 'rgba(139,148,158,0.1)', // Very subtle grid
axis: 'rgba(139,148,158,0.25)',
// Accent (used sparingly)
accent: '#58a6ff', // Blue for emphasis
} as const;
Typography
export const fonts = {
mono: '"JetBrains Mono", "SF Mono", "Fira Code", "Consolas", monospace',
label: '"Inter", "SF Pro", system-ui, sans-serif',
} as const;
Rules:
- ALL numbers use
fonts.mono— prices, percentages, volumes, dates, everything - Labels use
fonts.labelat weight 500, uppercase, letter-spacing 1-2px - Hero price:
fonts.mono, weight 300, size 88-110px - Decimals: same font, 70% of the whole-number size, opacity 0.7
- No bold on prices — thin weight (300) reads more terminal-like
Spring Configs
// Data elements (chart lines, bars) — smooth and precise
const dataSpring = { damping: 18, stiffness: 80, mass: 0.7 };
// UI elements (labels, badges) — snappy
const uiSpring = { damping: 14, stiffness: 120, mass: 0.4 };
// Counter settle — quick lock
const counterSpring = { damping: 10, stiffness: 180, mass: 0.3 };
Minimal animation. No bouncy overshoots. Things appear with purpose and settle fast.
The Price Journey — Core Composition
Every video follows this arc:
Timeline (15s / 450 frames)
Phase 1: Header (0-45, 1.5s)
├── Asset pair fades in: "BTC / USDT" (mono, 36px, primary color)
├── Timeframe + source below: "30D · Binance" (label, 18px, secondary)
└── Thin horizontal divider line draws left→right
Phase 2: Chart Draw (45-270, 7.5s)
├── Y-axis price labels appear (right side, mono, secondary)
├── X-axis date labels appear (bottom, mono, muted)
├── Grid lines: horizontal only, very subtle
├── Price line draws left→right (strokeDasharray animation)
├── Small tracking dot at the drawing tip (4px, accent color)
└── Optional: one MA line draws behind price (dashed, muted)
Phase 3: Price Lock (270-360, 3s)
├── Chart holds (animation complete)
├── Digits scramble: each character cycles rapidly then locks left→right
├── Final price locked: "$ 97,432.18" (mono, 96px, primary)
├── Percent badge appears below: "▲ +3.60%" (up=green, down=red)
└── Subtle crosshair or horizontal line connects chart endpoint to price
Phase 4: Stats (360-420, 2s)
├── Stats rows cascade in (stagger 4 frames each):
│ ├── "24h Vol $28.4B"
│ ├── "MCap $1.91T"
│ └── "24h Range $94,200 — $98,100"
└── All mono, aligned with tabular spacing
Phase 5: Loop Crossfade (420-450, 1s)
├── Everything fades out (opacity → 0)
└── Overlaps with Phase 1 of next loop (crossfade blend)
For 30s version: expand Phase 2 to 15s, add a "Key Moment" pause mid-chart where an annotation callout appears at a notable point (ATH, dip, breakout), hold 2s, then continue drawing.
Data Integration — Crypto Only
Binance (Primary — No API Key)
// Klines (OHLCV)
const fetchKlines = async (symbol: string, interval: string, limit: number): Promise<OHLCV[]> => {
const res = await fetch(
`https://api.binance.com/api/v3/klines?symbol=${symbol}&interval=${interval}&limit=${limit}`
);
const raw = await res.json();
return raw.map((k: any[]) => ({
time: k[0],
open: parseFloat(k[1]),
high: parseFloat(k[2]),
low: parseFloat(k[3]),
close: parseFloat(k[4]),
volume: parseFloat(k[5]),
}));
};
// 24h Ticker
const fetchTicker = async (symbol: string) => {
const res = await fetch(`https://api.binance.com/api/v3/ticker/24hr?symbol=${symbol}`);
return res.json();
};
CoinGecko (Supplementary — Market Cap, Coin Details)
const fetchCoinDetails = async (coinId: string) => {
const res = await fetch(`https://api.coingecko.com/api/v3/coins/${coinId}`);
return res.json();
// market_data.market_cap.usd, total_volume, ath, circulating_supply
};
Common Symbols
| Coin | Binance Symbol | CoinGecko ID |
|---|---|---|
| BTC | BTCUSDT | bitcoin |
| ETH | ETHUSDT | ethereum |
| SOL | SOLUSDT | solana |
| BNB | BNBUSDT | binancecoin |
| XRP | XRPUSDT | ripple |
| DOGE | DOGEUSDT | dogecoin |
| ADA | ADAUSDT | cardano |
| AVAX | AVAXUSDT | avalanche-2 |
Data Loading Pattern
Always use calculateMetadata — fetch before render, never during:
export const calculatePriceJourneyMetadata: CalculateMetadataFunction<Props> = async ({ props }) => {
const { symbol = 'BTCUSDT', interval = '1d', limit = 30 } = props;
const [klines, ticker] = await Promise.all([
fetchKlines(symbol, interval, limit),
fetchTicker(symbol),
]);
validateOHLCV(klines);
return {
props: {
...props,
data: klines,
currentPrice: parseFloat(ticker.lastPrice),
change24h: parseFloat(ticker.priceChangePercent),
volume24h: parseFloat(ticker.quoteVolume),
high24h: parseFloat(ticker.highPrice),
low24h: parseFloat(ticker.lowPrice),
},
durationInFrames: 450,
fps: 30,
width: 1080,
height: 1920,
};
};
Counter / Scramble Effect — The Price Reveal
This is the hero moment. Digits cycle rapidly then lock one by one, left to right.
function scramblePrice(
targetFormatted: string, // "$97,432.18"
frame: number,
lockStartFrame: number,
framesPerLock: number, // 3 frames between each character locking
): string {
const chars = '0123456789';
return targetFormatted.split('').map((char, i) => {
// Non-digit characters ($, comma, dot) appear immediately
if (!'0123456789'.includes(char)) return char;
const charLockFrame = lockStartFrame + i * framesPerLock;
if (frame >= charLockFrame) return char; // Locked
// Cycling: change digit every 2 frames
const cycleIndex = Math.floor(frame / 2 + i * 3) % 10;
return chars[cycleIndex];
}).join('');
}
Timing: Start scramble at Phase 3 frame 0. Lock first digit after 15 frames. Each subsequent digit locks 3 frames later. Full price locked in ~40 frames (~1.3s). Then settle with counterSpring scale 1.02→1.0.
Seamless Looping
Every composition MUST loop. Use the crossfade overlap technique:
const OVERLAP = 30; // 1 second crossfade
const totalFrames = composition.durationInFrames;
// In the main component:
const outOpacity = frame >= totalFrames - OVERLAP
? interpolate(frame, [totalFrames - OVERLAP, totalFrames], [1, 0], { extrapolateRight: 'clamp' })
: 1;
// Render a <Sequence from={totalFrames - OVERLAP}> with the intro content
// at opacity: 1 - outOpacity
Design for loops:
- Phase 1 (header) must be simple enough to crossfade cleanly
- No elements that would look broken mid-transition
- Background grid is continuous (modular frame:
frame % gridCycleLength)
Chart Drawing
Line Chart (Default)
SVG <path> with strokeDasharray draw animation. See references/chart-components.md for full implementation.
Key specs for vertical Reel:
- Chart area: 960px wide (60px padding each side), 820px tall
- Price line:
strokeWidth: 2.5, color:colors.priceLine - Grid: horizontal lines only, spaced every ~160px, color:
colors.grid - Y-axis: right-aligned price labels in
fonts.mono, 14px,colors.secondary - X-axis: bottom date labels, 12px,
colors.muted - Tracking dot: 4px radius,
colors.accent, subtle 8px glow ring
Candlestick Chart (Variation)
For when user requests candles. Same chart area, staggered build animation. See references/chart-components.md.
- Body width:
(chartWidth / dataLength) * 0.6 - Wick: 1.5px centered on body
- Colors:
colors.up/colors.down - Last candle: slightly brighter (opacity 1.0 vs 0.85)
Optional Light Indicator
One of:
- SMA line: 20-period, dashed (
strokeDasharray: "4 6"),colors.muted, drawn behind price line - S/R line: horizontal, user-specified price level, dashed,
colors.secondary
Never more than one indicator. Clean chart = better content.
Text Overlay System (Silent-First)
Since there's no audio, text overlays tell the story:
Annotation Callout
For key moments mid-chart:
┌─────────────┐
│ ATH $73,750 │ ← mono, 16px, panel background
└──────┬───────┘
│ (thin leader line)
● (4px dot on chart)
- Panel:
colors.bgPanelbackground, 1pxcolors.mutedborder, 8px radius - Leader line: 1px,
colors.muted - Appears with fade (10 frames), holds 60 frames, fades out (10 frames)
Stat Labels
Tabular layout — label left-aligned, value right-aligned:
24h Vol $28.4B
MCap $1.91T
24h Range $94.2K — $98.1K
- Label:
fonts.label, 16px,colors.secondary, uppercase - Value:
fonts.mono, 16px,colors.primary - Row height: 44px
- Cascade in: stagger 4 frames per row
Formatting
function formatPrice(price: number): string {
if (price >= 1000) return price.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
if (price >= 1) return price.toFixed(2);
if (price >= 0.01) return price.toFixed(4);
return price.toFixed(8); // sub-cent altcoins
}
function formatCompact(n: number): string {
if (n >= 1e12) return `$${(n / 1e12).toFixed(2)}T`;
if (n >= 1e9) return `$${(n / 1e9).toFixed(2)}B`;
if (n >= 1e6) return `$${(n / 1e6).toFixed(1)}M`;
if (n >= 1e3) return `$${(n / 1e3).toFixed(1)}K`;
return `$${n.toFixed(2)}`;
}
function formatPercent(v: number): string {
return `${v >= 0 ? '▲' : '▼'} ${v >= 0 ? '+' : ''}${v.toFixed(2)}%`;
}
Critical Rules
- NEVER fabricate data — If API fails, abort. No placeholder prices.
- Correct decimals — BTC/ETH: 2 for USD price. Sub-$1 alts: 4-8 decimals.
- Monospace everything numeric — Prices, percents, volumes, dates. No exceptions.
- Always 1080x1920 — Vertical Reel format. No landscape variants unless explicitly asked.
- Always loop — Crossfade overlap on every composition.
- No branding — No logos, watermarks, handles, or CTAs. Pure data.
- No audio components — No
<Audio>tags. Silent by design. - Pre-fetch only — All data in
calculateMetadata. Zero network calls during render. - Minimal animation — Things appear, settle, hold. No bouncy overshoots. Terminal precision.
- Hold still for reading — After every reveal, minimum 2s of static frame.
For component blueprints: See references/chart-components.md For animation recipes: See references/animation-patterns.md For API details: See references/data-integration.md For scene templates: See references/scene-templates.md