Image placeholders live in
docs/images/— swap them for real stills and screenshots whenever you like.
A library of broadcast-ready motion graphics for The Aeroadvisor, built with Remotion — React components rendered to video. Every graphic is data-driven through a live prop editor, rendered to a transparent ProRes overlay (with audio) or a standard MP4, and ready to drop straight onto a timeline in DaVinci Resolve, Premiere, Final Cut or CapCut.
- 🎬 23 finished compositions — lower thirds, chart animations, logo reveals, overlays and more.
- 🎛️ Edit in the browser — the Remotion Studio gives every graphic live, typed controls.
- 🔷 On-brand by default — the amber
#E6B234, the Inter typeface and the logo are pre-wired. - 🪟 Real alpha overlays — one command renders a transparent ProRes 4444
.movthat keeps its audio. - 🔁 Resolution/fps-safe — durations and sizes route through helpers, so retargeting is a one-line change.
- 💻 Cross-platform — develop and render on Windows, macOS or Linux.
- Composition catalog
- Requirements
- Getting started
- Rendering
- Previewing renders
- Project layout
- Creating a new graphic
- Canvas, resolution and fps
- Transparency
- Sound
- Fonts
- Production tips
- Scripts
- Tech stack & license
Each entry below is a registered composition id — the exact string you pass to remotion render.
Open any of them in the Studio to edit its props live.
Titles, captions & lower thirds
id |
What it does |
|---|---|
LowerLeftThird |
Name/title lower third, anchored bottom-left |
LowerRightThird |
Name/title lower third, anchored bottom-right |
ChapterTitle |
Section/chapter divider card |
KineticTypography |
Word-by-word animated headline |
AnimatedCaptions |
Karaoke-style word-timed subtitles |
Branding, intros & transitions
id |
What it does |
|---|---|
LogoReveal |
Animated logo sting with light sweep + particles |
YouTubeEndScreen |
10s end card with subscribe + video slots |
SubscribeBellCTA |
Subscribe/bell call-to-action bug |
StingerTransition |
Full-frame logo wipe between scenes |
Data visualisation
id |
What it does |
|---|---|
BarChartHorizontal |
Animated horizontal bars |
BarChartVertical |
Animated vertical/column bars |
LineChart |
Straight-segment line chart |
SplineChart |
Smoothed (curved) line chart |
PieChart |
Pie / donut (holeRatio) chart |
DataTable |
Row-by-row animated comparison table |
KpiTile |
KPI stat tiles with delta indicators |
ProgressBarHorizontal |
Timed horizontal progress / countdown bar |
ProgressBarCircular |
Timed circular progress ring |
Overlays & effects
id |
What it does |
|---|---|
CinematicOverlay |
Film grain, light leak and vignette pass |
CameraViewfinder |
REC / viewfinder HUD frame |
BrowserHighlight |
Browser-chrome mock with a highlight box |
SplitScreenMask |
Split-screen / circular picture-in-picture mask |
AudioSpectrum |
Reactive audio spectrum bars |
Charts derive their own duration from the number of data points via calculateMetadata, so they
accept any number of values without a fixed-count variant.
- Node.js 18 or newer (LTS recommended; developed on Node 24).
- Git.
- ffmpeg is bundled with Remotion — you don't need to install it separately.
- On the first render, Remotion downloads a headless Chromium build automatically.
Linux only — headless Chromium needs a few system libraries. On Debian/Ubuntu:
sudo apt-get update && sudo apt-get install -y \ libnss3 libdbus-1-3 libatk1.0-0 libgbm-dev libasound2 libxrandr2 \ libxkbcommon-dev libxfixes3 libxcomposite1 libxdamage1 \ libatk-bridge2.0-0 libpango-1.0-0 libcairo2 libcups2macOS and Windows need no extra system packages.
HTTPS
git clone https://github.com/SonapSav/MotionGraphicsStudio.git
cd MotionGraphicsStudioSSH
git clone git@github.com:SonapSav/MotionGraphicsStudio.git
cd MotionGraphicsStudionpm installThe commands above are identical on Windows (PowerShell or Git Bash), macOS and Linux.
npm run studioThis opens the Remotion Studio at http://localhost:3014 — pick a composition from the sidebar, scrub the timeline, and edit its props live in the right-hand panel.
Renders are written to out/ (git-ignored). Replace <id> with any composition id from the
catalog.
MP4 — standard opaque delivery:
npx remotion render <id> out/<id>.mp4 --codec=h264 --crf=18--crf runs 0–51; lower is better quality and a bigger file. 18 is visually lossless.
Transparent MOV — ProRes 4444 with a real alpha channel and audio (the default for overlays):
npx remotion render <id> out/<id>.mov \
--codec=prores --prores-profile=4444 --pixel-format=yuva444p10le --image-format=pngPNG sequence — maximum quality, keeps alpha, largest on disk, no audio:
npx remotion render <id> out/<id> --sequence --image-format=pngTwo verified gotchas for PNG sequences:
--sequenceis required — without it Remotion expects a video filename and errors.- Pass a directory, not a filename pattern. Frames are named
element-000.png,element-001.png, … (rename afterwards if needed).
Verify a transparent file actually carries alpha:
npx remotion ffprobe out/<id>.movThe pixel format should start with yuva — the a is the alpha channel.
Why there's no global CRF:
remotion.config.tsdeliberately does not callConfig.setCrf(). CRF is h264/h265-only, and setting it project-wide makes every ProRes render fail. Keep--crfon the command line.
Transparent ProRes is a mezzanine format — not every player handles it the same way.
| Player | Platforms | Good for | Watch out |
|---|---|---|---|
| Remotion Studio | all | everything, before rendering | has a transparency checkerboard toggle — check here first |
| DaVinci Resolve | Win / macOS / Linux | the definitive alpha check | drop the clip on V2 over a solid on V1 |
| QuickTime Player | macOS | ProRes playback | shows alpha over black, not a checkerboard |
| VLC | all | motion, timing, layout | ignores the alpha channel — see below |
| Windows Media Player / Films & TV | Windows | nothing | Windows ships no ProRes decoder |
VLC does not merely show alpha on black — it ignores the alpha channel and shows raw RGB. A soft radial glow fading to transparent comes back as a hard-edged solid circle. Use VLC for motion and timing only; use Resolve or the Studio for anything involving transparency.
src/
index.ts registerRoot entry point
Root.tsx composition registry (all 23 graphics)
video-config.ts CANVAS + seconds() / px() / usePx() / titleSafePadding
animation.ts useEnterExit — shared enter/exit envelope
fonts.ts FONT_FAMILY (Inter via @remotion/google-fonts)
compositions/ one file per graphic
tools/
generate-sfx.mjs npm run sfx — synthesises public/audio/*.wav
check-audio.mjs peak / clipping report for a rendered wav
bbox.mjs opaque bounding-box of a rendered PNG (layout checks)
public/ fonts, images, audio — reachable via staticFile()
out/ renders (git-ignored)
docs/images/ README image placeholders
remotion.config.ts project-wide render defaults
1. Create the component in src/compositions/MyGraphic.tsx — a component plus a matching zod
schema and defaultProps:
import { AbsoluteFill } from "remotion";
import { z } from "zod";
import { zColor } from "@remotion/zod-types";
import { useEnterExit } from "../animation";
import { usePx, seconds } from "../video-config";
export const myGraphicSchema = z.object({
headline: z.string(),
accentColor: zColor(),
});
export type MyGraphicProps = z.infer<typeof myGraphicSchema>;
export const myGraphicDefaultProps: MyGraphicProps = {
headline: "Hello",
accentColor: "#E6B234",
};
export const MyGraphic: React.FC<MyGraphicProps> = ({ headline, accentColor }) => {
const px = usePx();
const { present } = useEnterExit({ enterDuration: seconds(0.8), exitDuration: seconds(0.7) });
return (
<AbsoluteFill style={{ opacity: present, fontSize: px(48), color: accentColor }}>
{headline}
</AbsoluteFill>
);
};2. Register it in src/Root.tsx at CANVAS size. The id is what you pass to
remotion render; the schema + defaultProps pair is what gives you live editable controls in the
Studio. Use zColor() for colours so you get a real picker.
3. Give it a full enter and exit via useEnterExit so it never pops at either end, and for a
variable-length graphic derive durationInFrames from props with calculateMetadata rather than
hardcoding it.
1920×1080 @ 24fps — the single delivery target. Register every composition at CANVAS size and
render one master per graphic. It's set in one place, src/video-config.ts:
export const CANVAS = {
width: 1920,
height: 1080,
fps: 24,
} as const;Two helpers keep that flexible:
seconds(5)→ frames at the current fps, so a 5-second graphic stays 5 seconds at any fps.px(96)→ sizes in 1080p design units (the identity function at 1080p).
The rule that keeps this working: never write a raw frame number. Route every duration, spring
length and stagger through seconds(), and every size through px(). A hardcoded
durationInFrames: 30 looks right at 30fps and silently becomes a 1.25× slower animation at 24fps.
px() reads the global CANVAS; usePx() scales against the actual composition width via
useVideoConfig(). While everything is registered at CANVAS size the two are identical — but
usePx() earns its keep the moment a component is registered at a different size (e.g. a 1080×1920
vertical cut for Shorts). Defaulting to usePx(), and positioning with percentages (left: "7%")
rather than CANVAS.width * 0.07, is free insurance.
An overlay renders with real alpha when nothing in the tree paints a full-frame background — give
the root <AbsoluteFill> no backgroundColor. If an overlay comes out black, something is filling
the frame.
Three flags must line up, and all three matter:
| Flag | Why |
|---|---|
--codec=prores |
h264 has no alpha channel at all |
--prores-profile=4444 |
only 4444 and 4444xq carry alpha |
--image-format=png |
JPEG frames discard alpha before encoding starts |
Two constraints specific to overlays:
- Keep any background plate near-opaque across its full width. A gradient fading to transparent looks great over dark footage and makes white text unreadable over bright footage — and you don't know what's behind an overlay at render time. Layer a colour tint on top of a ~95% opaque base.
backdrop-filterdoes nothing at render time. There is no footage behind the element yet, so it costs render time and produces no pixels. Fake glass with translucent gradients plus a 1px light inner border.
Choosing an alpha format: default to ProRes 4444 — one file, and it carries the audio track. Switch to a PNG sequence only for a silent graphic that needs bit-exact quality or half the file size. ProRes 4444 XQ is pointless here (byte-identical output to plain 4444). H.265 silently discards alpha — it writes a valid file with transparency composited away, no error. H.264 has no alpha under any circumstances.
public/audio/ holds five synthesised, royalty-free cues. Regenerate or retune them with
npm run sfx (tools/generate-sfx.mjs). Each starts its transient at
sample 0, so no trimBefore is needed.
| File | Character | Typical use |
|---|---|---|
whoosh.wav |
bandpassed noise, swell | entrance |
click.wav |
noise transient + 1400→380 Hz drop | primary press |
pop.wav |
brighter, shorter, 2300→700 Hz | secondary action |
chime.wav |
two bell notes C6→G6, inharmonic partials | success |
swish.wav |
fast descending noise sweep | exit |
import { Audio, Sequence, staticFile } from "remotion";
<Sequence name="SFX click" from={PRESS_AT} durationInFrames={seconds(0.3)} layout="none">
<Audio src={staticFile("audio/click.wav")} volume={0.55 * masterVolume} />
</Sequence>- Anchor
fromto the same constant that drives the visual, so retiming the animation retimes its sound — they can't drift apart. layout="none"matters: a Sequence otherwise wraps children in anAbsoluteFill, stacking invisible full-frame divs over your graphic.- PNG-sequence renders discard audio entirely; use MP4 or MOV if you need sound.
npx remotion render <id> out/mix.wav --codec=wav
node tools/check-audio.mjs out/mix.wavReports true sample peak, RMS and clipped-sample count. Aim for a few dB of headroom (around −8 dBFS peak) so the graphic sits under a music bed.
src/fonts.ts loads Inter through @remotion/google-fonts:
import { loadFont } from "@remotion/google-fonts/Inter";
const { fontFamily } = loadFont("normal", { weights: ["400", "600", "700"], subsets: ["latin"] });Swap the import path for another family (@remotion/google-fonts/Roboto, etc.). Remotion blocks
rendering until the font is ready, so frame 0 never flashes a fallback face. For a custom font, put the
.woff2 in public/fonts/ and load it with staticFile().
- Keep text in the title-safe area. Spread
titleSafePadding(a 5% inset on every edge) onto your root element — platforms crop frame edges unpredictably. - Always give an animation a full enter and exit. Anything still moving on the last frame reads
as a hard cut.
useEnterExitreturns apresentvalue (0 → 1 → 0); drive opacity and transforms from it. - Seed particles with Remotion's
random(seed), neverMath.random(). Frames render concurrently and re-evaluate per frame, soMath.random()strobes and won't reproduce between renders. - Use
drop-shadow, notbox-shadow, with an animatedclip-path— a clip-path wipe clipsbox-shadowaway;filter: drop-shadow()on the parent traces the clipped silhouette. - Springs for entrances,
interpolate+Easingfor exits. Springs overshoot naturally — great arriving, distracting leaving.
| Command | Does |
|---|---|
npm run studio |
live preview + prop editor at http://localhost:3014 |
npm run sfx |
regenerate public/audio/*.wav |
npm run build |
bundle for a Remotion server/Lambda deploy |
npm run typecheck |
tsc --noEmit |
npm run upgrade |
bump all Remotion packages together |
Remotion packages are pinned to matching versions on purpose — mixing versions across remotion /
@remotion/* causes hard-to-read failures. Use npm run upgrade rather than bumping one by hand.
Built with Remotion 4, React 19, TypeScript, Zod and Tailwind CSS v4.
The source code in this repository is released under the MIT License —
MIT © Panos Vasilopoulos. See LICENSE for the full text.
Remotion is licensed separately and is not covered by this MIT license. It is free for individuals and small teams but requires a company license in some cases. Review the Remotion license before using it commercially.