'use client';
import React from 'react';
import { AbsoluteFill, Easing, Sequence } from 'remotion';
import { TransitionSeries, linearTiming } from '@remotion/transitions';
import {
TrackingIn,
trackingInSchema,
} from '@onda/registry/components/tracking-in/TrackingIn';
import {
WordStagger,
wordStaggerSchema,
} from '@onda/registry/components/word-stagger/WordStagger';
import {
TitleCard,
titleCardSchema,
} from '@onda/registry/components/title-card/TitleCard';
import {
Highlight,
highlightSchema,
} from '@onda/registry/components/highlight/Highlight';
import {
ShimmerSweep,
shimmerSweepSchema,
} from '@onda/registry/components/shimmer-sweep/ShimmerSweep';
import {
StatCard,
statCardSchema,
} from '@onda/registry/components/stat-card/StatCard';
import {
Terminal,
terminalSchema,
} from '@onda/registry/components/terminal/Terminal';
import {
EndCard,
endCardSchema,
} from '@onda/registry/components/end-card/EndCard';
import {
GradientShift,
gradientShiftSchema,
} from '@onda/registry/components/gradient-shift/GradientShift';
import {
DynamicGrid,
dynamicGridSchema,
} from '@onda/registry/components/dynamic-grid/DynamicGrid';
import {
GrainOverlay,
grainOverlaySchema,
} from '@onda/registry/components/grain-overlay/GrainOverlay';
import {
Vignette,
vignetteSchema,
} from '@onda/registry/components/vignette/Vignette';
import { glassWipe } from '@onda/registry/transitions/glass-wipe/glassWipe';
import { depthPush } from '@onda/registry/transitions/depth-push/depthPush';
import { morph } from '@onda/registry/transitions/morph/morph';
import { zoom } from '@onda/registry/transitions/zoom/zoom';
import { dipToColor } from '@onda/registry/transitions/dip-to-color/dipToColor';
import { blur } from '@onda/registry/transitions/blur/blur';
import { crossFade } from '@onda/registry/transitions/cross-fade/crossFade';
// House timing pinned per cut. 18-frame base for calm cuts; 24-frame for
// the dipToColor accent so the accent register reads as deliberate, not
// a flicker. Same easing curve everywhere — that's the fingerprint.
const HOUSE_EASE = Easing.bezier(0.16, 1, 0.3, 1);
const baseCut = linearTiming({ durationInFrames: 18, easing: HOUSE_EASE });
const accentCut = linearTiming({ durationInFrames: 24, easing: HOUSE_EASE });
// The "why Onda matters" trailer. Five acts in 30 seconds:
// 1. Premise — programmatic video has been possible, but generic.
// 2. Answer — Onda exists.
// 3. Proof — the signature: calm by default, bold when it earns it.
// 4. Moat — source you own, Zod-typed, agent-driveable.
// 5. CTA — onda.video.
// Eight component types, seven transition flavors, one signature feel.
export const ExplainerComposition: React.FC = () => {
return (
<AbsoluteFill>
{/* Atmosphere stack — drifts the whole 30s underneath every cut.
GradientShift gives slow color depth; DynamicGrid sits at low
opacity so the canvas never feels flat. */}
<GradientShift
{...gradientShiftSchema.parse({
from: '#08080A',
to: '#1A0E12',
angle: 135,
speed: 0.2,
})}
/>
<Sequence from={0} durationInFrames={900}>
<DynamicGrid
{...dynamicGridSchema.parse({
variant: 'dots',
opacity: 0.14,
speed: 0.18,
glow: false,
})}
/>
</Sequence>
<TransitionSeries>
{/* ── Act 1 — Premise ──────────────────────────────────────── */}
{/* 1a (0–3s) — TrackingIn states the domain. Cinematic open. */}
<TransitionSeries.Sequence durationInFrames={108}>
<TrackingIn
{...trackingInSchema.parse({
text: 'video. in code.',
fontSize: 140,
placement: 'center',
})}
/>
</TransitionSeries.Sequence>
<TransitionSeries.Transition presentation={glassWipe({ direction: 'left' })} timing={baseCut} />
{/* 1b (3–5s) — The question. Held still so it reads as a thought. */}
<TransitionSeries.Sequence durationInFrames={72}>
<WordStagger
{...wordStaggerSchema.parse({
text: 'what if it felt premium?',
size: 'subheading',
stagger: 4,
justify: 'center',
placement: 'center',
color: '#8E8E98',
})}
/>
</TransitionSeries.Sequence>
<TransitionSeries.Transition presentation={depthPush({ direction: 'left' })} timing={baseCut} />
{/* ── Act 2 — Answer ───────────────────────────────────────── */}
{/* 2 (5–10s) — Brand reveal. TitleCard composes BlurReveal +
WordStagger + Underline — the signature in one beat. */}
<TransitionSeries.Sequence durationInFrames={132}>
<TitleCard
{...titleCardSchema.parse({
title: 'Onda',
subtitle: 'motion graphics with a feel',
titleSize: 'hero',
subtitleSize: 'subheading',
placement: 'center',
accent: true,
})}
/>
</TransitionSeries.Sequence>
<TransitionSeries.Transition presentation={morph()} timing={baseCut} />
{/* ── Act 3 — Proof: the signature ─────────────────────────── */}
{/* 3a (10–13s) — Calm: Highlight earns the accent on "calm". */}
<TransitionSeries.Sequence durationInFrames={96}>
<AbsoluteFill style={{ alignItems: 'center', justifyContent: 'center', flexDirection: 'column', gap: 32 }}>
<Highlight
{...highlightSchema.parse({
text: 'calm by default',
size: 'hero',
})}
/>
<WordStagger
{...wordStaggerSchema.parse({
text: 'no overshoot · spring-driven · 18-frame house',
size: 'subheading',
stagger: 3,
justify: 'center',
color: '#8E8E98',
delay: 24,
})}
/>
</AbsoluteFill>
</TransitionSeries.Sequence>
<TransitionSeries.Transition presentation={zoom({ direction: 'in', scaleAmount: 0.12 })} timing={baseCut} />
{/* 3b (13–17s) — Bold: ShimmerSweep proves range without re-using
the earned-color register. Two different motion fingerprints
making the same point — "calm is default, not the ceiling."
Items in this flex column omit `placement` so the wrapper
centers them as a stack instead of each one parking at canvas
center on its own layer (which would overlap). */}
<TransitionSeries.Sequence durationInFrames={108}>
<AbsoluteFill style={{ alignItems: 'center', justifyContent: 'center', flexDirection: 'column', gap: 32 }}>
<ShimmerSweep
{...shimmerSweepSchema.parse({
text: 'bold when it earns it',
fontSize: 130,
align: 'center',
})}
/>
<WordStagger
{...wordStaggerSchema.parse({
text: 'full catalog · calm → kinetic · craft is the gate',
size: 'subheading',
stagger: 3,
justify: 'center',
color: '#8E8E98',
delay: 36,
})}
/>
</AbsoluteFill>
</TransitionSeries.Sequence>
<TransitionSeries.Transition presentation={dipToColor({ color: '#D96B82' })} timing={accentCut} />
{/* 3c (17–21s) — The number. dipToColor through accent rose lands
us on a hard data beat — one earned punctuation moment. */}
<TransitionSeries.Sequence durationInFrames={120}>
<AbsoluteFill style={{ alignItems: 'center', justifyContent: 'center' }}>
<StatCard
{...statCardSchema.parse({
value: 88,
label: 'components and transitions · one signature feel',
accent: true,
numberFontSize: 280,
})}
/>
</AbsoluteFill>
</TransitionSeries.Sequence>
<TransitionSeries.Transition presentation={glassWipe({ direction: 'right' })} timing={baseCut} />
{/* ── Act 4 — The moat ─────────────────────────────────────── */}
{/* 4a (21–25s) — Terminal types the install command. "Source you
own" stops being a tagline and becomes a thing you watched
happen. */}
<TransitionSeries.Sequence durationInFrames={120}>
<AbsoluteFill style={{ alignItems: 'center', justifyContent: 'center' }}>
<Terminal
{...terminalSchema.parse({
command: 'npx ondajs add stat-card',
output: [
'✓ added stat-card',
'✓ wrote 4 files to your project',
' your code, your styles, your motion',
],
typeSpeed: 36,
outputDelay: 12,
})}
/>
</AbsoluteFill>
</TransitionSeries.Sequence>
<TransitionSeries.Transition presentation={blur({ blurAmount: 12 })} timing={baseCut} />
{/* 4b (25–27.5s) — The agent moat in two lines. Held tight so the
close lands clean. */}
<TransitionSeries.Sequence durationInFrames={75}>
<AbsoluteFill style={{ alignItems: 'center', justifyContent: 'center', flexDirection: 'column', gap: 24 }}>
<WordStagger
{...wordStaggerSchema.parse({
text: 'Zod-typed. agent-driveable.',
size: 'hero',
stagger: 4,
justify: 'center',
})}
/>
<WordStagger
{...wordStaggerSchema.parse({
text: 'drive the same catalog from any LLM',
size: 'subheading',
stagger: 3,
justify: 'center',
color: '#8E8E98',
delay: 18,
})}
/>
</AbsoluteFill>
</TransitionSeries.Sequence>
<TransitionSeries.Transition presentation={crossFade()} timing={baseCut} />
{/* ── Act 5 — Close ────────────────────────────────────────── */}
{/* 5 (27.5–30s) — Onda.video. */}
<TransitionSeries.Sequence durationInFrames={75}>
<EndCard
{...endCardSchema.parse({
cta: 'Made with Onda',
handles: ['onda.video'],
accent: true,
placement: 'center',
})}
/>
</TransitionSeries.Sequence>
</TransitionSeries>
{/* Texture + frame — the two passes that make the whole 30s read
as a single piece of motion, not a stitched reel. */}
<GrainOverlay
{...grainOverlaySchema.parse({
opacity: 0.05,
baseFrequency: 0.9,
numOctaves: 1,
})}
/>
<Vignette {...vignetteSchema.parse({ intensity: 0.7 })} />
</AbsoluteFill>
);
};
1920×1080 · 30s · 30fps
Explainer · 30s
The 'why Onda matters' trailer — five acts in 30 seconds. A premise ("video, in code") asks the question, a brand reveal answers it, two proof beats show the signature (calm by default, bold when it earns it), a terminal demos the install, and an end-card closes. Eight component types stitched with seven transition flavors — glass-wipe, depth-push, morph, zoom, dip-to-color through the accent rose, blur, cross-fade — over one continuous atmosphere of gradient drift, dot grid, grain, and vignette.
The whole composition
Below is the entire source for this showcase — every component, every transition, every prop. Copy it into your Remotion project as a starting point, then swap copy, swap colors, swap timing.
Install the pieces
This showcase uses the following Onda categories — every item is one CLI install:
typographyscene blocksdatainterfacegraphicsatmospheretransitions
Browse the full component catalog for the individual slugs.
Other showcases
- Podcast intro · 10s →
- Music release card · 12s →
- Synthwave promo · 10s →
- Meditation breath cue · 20s →
- Live-stream overlay · loop →
- Social ad · vertical · 15s →
- Q4 performance · 12s →
- KPI snapshot · 8s →
- Tutorial intro · 12s →
- Course complete · 10s →
- Launch countdown · 10s →
- Save the date · 8s →
- Dev demo · 11s →
- Changelog · 9s →
- Product launch · 11s →
- Live metrics · 11s →
- Changelog loop · 6s →
- Deploy reveal · 8s →
- Annotated click · 7s →
- Browser walkthrough · 9s →
- Bento drift · 9s →
- Device assemble · 6s →
- Launch trailer · 9s →
- Integration orbit · 8s →
- Board flow · 8s →
- Dashboard fill · 8s →
- Prompt to dashboard · 8s →
- Pricing focus · 7s →
- Code to preview · 8s →
- Kinetic type · 10s →