From content-vault
Builds a 30-60s motion-graphics launch video using Remotion 4, ElevenLabs narration and sound effects, with beat-anchored timing and brand-aligned design.
How this skill is triggered — by the user, by Claude, or both
Slash command
/content-vault:launch-videoThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Builds a **30-60s motion-graphics launch video** for a <YOUR_BRAND>
Builds a 30-60s motion-graphics launch video for a <YOUR_BRAND> product or feature, end-to-end:
script.timings.json. Edit copy → regen audio
→ all visuals auto-shift. No manual frame nudging.If your repo has a reference Remotion project, point this skill at it.
Otherwise the patterns described below stand alone. Reference
implementation path in the source repo: <YOUR_REFERENCE_VIDEO_PATH>.
Trigger on:
/launch-videoSkip for:
/graphics-designer/video-use
instead. That skill cuts on word boundaries from a Scribe transcript,
removes filler words, color grades, and burns subtitles. This skill is
for synthetic motion graphics built from scratch in Remotion. The
two compose: a /video-use edit can spawn Remotion overlay slots for
branded animation inserts, but the cut comes from /video-use.motion/<video-name>/
├── package.json # remotion 4 + react 19 + tsx + dotenv + mp3-duration
├── remotion.config.ts # publicDir → ../../brand/motion (single asset source)
├── tsconfig.json
├── SCRIPT.md # human-readable narration script (per beat)
├── .env # ELEVENLABS_API_KEY (gitignored!)
├── .env.example
├── generate-audio.ts # ElevenLabs narration runner, hash-cached per beat
├── generate-sfx.ts # ElevenLabs sound-generation runner, hash-cached
├── generate-music.ts # ElevenLabs music runner + leading-silence trim
├── script.timings.json # narration durations (generated)
├── sfx.timings.json # SFX durations (generated)
├── music.timings.json # music duration (generated)
└── src/
├── Root.tsx # registerRoot
├── Video.tsx # composition registry + LaunchVideoComposition wrapper
├── theme.ts # fps + duration + colors (mirrors <YOUR_BRAND_TOKENS_PATH>)
├── script.ts # narration beats (id, phase, text)
├── timings.ts # loader for script.timings.json + beatStartFrame()
├── sfx.ts # SFX clip definitions (anchored to frames)
├── music.ts # music prompt + volume
├── Narration.tsx # narration-only Sequence track
├── SoundDesign.tsx # narration + SFX + music combined
└── scenes/
├── HeroBackground.tsx # looped /video/hero-bg.mp4
├── OpeningScene.tsx # the orchestration scene (anchors all phases)
├── GlassCursor.tsx # multiplayer-style "you" cursor
├── CompanyTable.tsx # populating data table with column reveals
├── AgentDashboard.tsx # Google-Calendar week view
├── AgentRunDetail.tsx # tool-call trace view
├── MemoryPanel.tsx # stats + sparkline dashboard
├── Confetti.tsx # particle burst
└── ClosingCard.tsx # closing chat callback (the "Inviting" pattern)
Assets live in brand/motion/:
video/hero-bg.mp4 + poster (your brand's hero background loop · e.g.
an ambient video at brand/motion/video/hero-bg.mp4)logos/{models,tools,companies,sequencers}/ — third-party brand marksaudio/{launch-narration,sfx,music}/ — generated mp3s (committed for
reproducible renders)components/HeroChatMockup.tsx — framework-agnostic chat shellGet from the user:
Each beat = one narration sentence that lands during one visual phase. Typically 6-8 beats. Sum of beat audio lengths = total runtime. Beat lengths target 3-7s each.
For each beat capture: id, phase (one-line visual description), text
(spoken line, 10-20 words).
Save the script as a sibling of package.json for human review.
Format:
## Beat N — Phase name
**Frames** ~start–end (seconds) · **target ~Nwords**
> "Spoken line here."
**Visual sync:** what happens on screen.
src/script.tsMirrors SCRIPT.md as data. The narration generator and the composition
both import SCRIPT. No Remotion imports here so generators can run
in plain Node:
export interface Beat { id: string; phase: string; text: string; }
export const SCRIPT: Beat[] = [...];
export const AUDIO_SUBDIR = "audio/launch-narration";
export function audioPath(id: string) { return `${AUDIO_SUBDIR}/${id}.mp3`; }
cd motion/<video-name>
cp .env.example .env # paste ELEVENLABS_API_KEY
npm install
npm run generate-audio
This creates brand/motion/audio/launch-narration/{beat-id}.mp3 and
writes durations to script.timings.json. Hashes by beat text so
unchanged beats are skipped on subsequent runs (cheap iteration).
In OpeningScene.tsx:
const HERO_BEAT = beatStart("hero", 0); // → 0
const DATABASE_BEAT = beatStart("database", 0); // → 171 (after hero finishes)
// ... etc
BEAT_X + offset:
const BADGE_IN = HERO_BEAT + 6;
const TYPE_START = HERO_BEAT + 38;
const TOOLS_START = HERO_BEAT + 115; // synced to "GTM stack" lyric
const CURSOR_PRESS = HERO_BEAT + 165;
Define click/transition moments in src/sfx.ts. Anchor each clip to a
specific fromFrame using the same beatStartFrame() helper. Example
clips that earn their place:
npm run generate-sfx
Define one prompt + duration in src/music.ts. generate-music.ts
trims leading silence automatically (ElevenLabs music often opens with
a quiet bar that reads as "no music" against narration).
npm run generate-music
npm run render:landscape
Rendering takes ~30-90s. Always re-encode the output to yuv420p TV
range for QuickTime compatibility — the bg video is yuvj420p (JPEG
range) and the propagation breaks playback in some players:
ffmpeg -y -i out/launch-landscape.mp4 \
-c:v libx264 -profile:v high -level:v 4.0 \
-pix_fmt yuv420p -color_range tv -crf 18 -c:a copy \
out/launch-landscape-fixed.mp4
mv out/launch-landscape-fixed.mp4 out/launch-landscape.mp4
Use ffmpeg -ss N -i ... -frames:v 1 /tmp/check.jpg to spot-check
specific timestamps without watching the whole render.
leverage, synergy, robust, world-class,
seamless, intuitive, powerful, innovative.Light, neutral. One accent only, used once per scene where it earns meaning (live dot, highlight, success state).
| Token | Hex | Use |
|---|---|---|
--bg | #fafafa | page bg |
--surface | #ffffff | cards, dashboards |
--ink | #0a0a0a | primary text + numerals |
--muted | #71717a | secondary text |
--line | #e4e4e7 | borders |
--accent | #10b981 | the one accent |
'Helvetica Neue', Helvetica, Arial, sans-serifui-monospace, 'SFMono-Regular', Menlo-0.05em)0.16-0.18em,
color #71717a or #a1a1aa<span style={{ color: "#0a0a0a", fontWeight: 500 }}>bold</span> vs
surrounding #71717a text.Pick one per scene; don't mix:
backdrop-filter: blur(28px),
rgba(255,255,255,0.45).bg #ffffff, 1px solid #e4e4e7, subtle drop shadow.The bg-fade transition between modes is one of the most cinematic moments;
budget ~20-30 frames for it and use Easing.bezier(0.4, 0, 0.2, 1)
(material standard) for smoothness.
with_timestamps from ElevenLabs for word-level sync,
or estimate from beat duration / word count if approximate is fine.minHeight on the row prevents layout from jumping as content fills.
Looks janky if rows grow during the cascade.opacity gate animated via interpolate + ease curves.generate-audio.ts defaults to Bill
(pqHfZKP75CvOlQylNhV4) on eleven_multilingual_v2. Bill is the
advertisement use-case voice in ElevenLabs' library: wise, mature,
crisp. Reads as a real ad voiceover, not "AI guy on social media".
multilingual_v2 is the most stable + lifelike model for production
narration. Do not use eleven_turbo_v2_5 for launch videos —
it's the fast/cheap model and the difference is audible against a
hero bg video. Turbo is fine for previews / SFX timing checks, not
the final render.stability 0.40,
similarity_boost 0.75, style 0.30, use_speaker_boost true.
Per ElevenLabs best-practices: stability under 0.30 is unstable,
over 0.60 is monotone; similarity 0.75 is the docs sweet spot;
style 0.20–0.40 adds expressiveness without drift. Don't push style
above 0.4 unless you want noticeable inflection swings..env (VOICE_ID=... MODEL_ID=...)
or generate-audio.ts and re-run npm run generate-audio.cjVigY5qzO86Huf0OWal — smooth, trustworthy, modern SaaS feelHIGUfNOdjuWQwwapnTRW — pro clone, deep raspy, cinematicJBFqnCBsd6RMkjVDRZzb — British storyteller, narrative_storyjD4PjnscE4XmlzgsuqY0 — pro clone, young storytellernPczCjzI2devNBz1zQrb — the legacy default; labeled social_media, reads more "AI" than Billvoices_read permission. Some keys
don't have it; if you can't GET /v1/voices, fall back to known IDs.silenceremove ffmpeg pass strips it so playback hits at
frame 0. Keep that filter in place.0.10 reads as silent under narration; 0.15-0.18
is the sweet spot — clearly there but doesn't fight the voice.<Audio> without <Sequence>: it'll play continuously
from frame 0. Use <Series.Sequence> or <Sequence from={...}> to
place audio at specific positions.BEAT_X + offset. Re-derives automatically when
you regen audio.script.ts should not import
from remotion. Import staticFile lazily where needed.ffmpeg -ss N to verify..env (gitignored).script.ts as the single source of truth and
reference it from visuals where appropriate.The scene library is written to be reusable. For a new launch video, copy what fits and edit data + copy:
| Component | Use for |
|---|---|
HeroBackground | The looped hero background video |
GlassCursor | Multiplayer-style "you" cursor with label pill |
CompanyTable | Any populating tabular content with cascading columns |
AgentDashboard | Google-Calendar week-view with event cards |
AgentRunDetail | Step-by-step tool-call trace with reasoning headers |
MemoryPanel | Stats + dashboard preview pattern |
Confetti | Celebration burst (use sparingly, once per video max) |
ClosingCard | The "Inviting" chat callback closing pattern |
HeroChatMockup (in brand/motion) | Standalone chat shell for stills/static use |
For new components, follow the same pattern:
cardInFrame, firstRowFrame, rowStagger)staticFile)spring, interpolate, or interpolate with
Easing.bezier for smooth curves# 1. Scaffold the project (or copy a reference Remotion project)
cp -R motion/launch-video motion/<new-video-name>
cd motion/<new-video-name>
# 2. Reset state
rm -rf node_modules out
rm script.timings.json sfx.timings.json music.timings.json
echo "{}" > script.timings.json
echo "{}" > sfx.timings.json
echo "{}" > music.timings.json
rm -rf ../../brand/motion/audio/launch-narration/* \
../../brand/motion/audio/sfx/* \
../../brand/motion/audio/music/*
# 3. Install + add API key
npm install
cp .env.example .env # paste ELEVENLABS_API_KEY
# 4. Edit script.ts with the new beats
# 5. Edit OpeningScene.tsx to wire the new beats to scenes
# 6. Edit sfx.ts + music.ts prompts
# 7. Generate audio
npm run generate-all
# 8. Render + spot-check
npm run render:landscape
# repeat: tweak → render → check frames with ffmpeg
out/;
publishing to YouTube / LinkedIn / etc. is on the user.Once the MP4 renders cleanly:
mcp__claude_ai_Google_Drive__create_file
into your videos folder (<YOUR_DRIVE_VIDEOS_FOLDER>). Title pattern
<feature> · launch · YYYY-MM-DD.mp4.<YOUR_NOTION_CONTENT_DB_ID>). Create the row with Title,
Format=Video, Channel (LinkedIn / YouTube / X per where it's going),
Pillar=Promotional, Status=Scripting (if awaiting review) or
Scheduled, and paste the Drive viewUrl into Drive Assets.<YOUR_REFERENCE_VIDEO_PATH><YOUR_BRAND_DOC><YOUR_BRAND_TOKENS_PATH>Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub timscheuerai/content-vault