Loading...
Loading...
This skill should be used when the user asks to "make a YouTube intro", "create a channel intro", "build a logo sting/bumper", "design an outro / end screen / end card", "add subscribe and next-video cards", "make an intro animation", "build a reusable intro template", or "sync the logo reveal to an audio sting". Covers the 3-5s branded intro, the last-20s end-screen outro inside YouTube's element safe zones, timing conventions, audio-sting sync, and a swap-the-brand template.
npx skill4agent add iart-ai/youtube-video-skills youtube-intro-outro| Beat | Window | Job |
|---|---|---|
| Windup | 0-0.8s | whoosh / build; logo not yet readable |
| Impact | ~0.8s | logo snaps to full on the audio hit + a white flash |
| Settle | 0.8-1.2s | overshoot relaxes to rest; tagline fades in |
| Hold | 1.2-3.5s | brand legible, then cut to content |
import {useCurrentFrame, useVideoConfig, spring, interpolate, Img, Audio, staticFile} from "remotion";
export const LogoSting = ({brand}) => {
const frame = useCurrentFrame();
const {fps} = useVideoConfig();
const IMPACT = Math.round(0.8 * fps); // logo lands here, on the sting hit
// overshoot scale: springs up past 1, settles to 1 (the "snap")
const scale = spring({frame: frame - IMPACT, fps, config: {damping: 9, mass: 0.5}, from: 1.18, to: 1});
const logoOpacity = frame < IMPACT ? 0 : 1;
const flash = interpolate(frame - IMPACT, [0, 6], [0.9, 0], {extrapolateLeft: "clamp", extrapolateRight: "clamp"});
return (
<div style={{flex: 1, background: brand.bg, display: "flex", alignItems: "center", justifyContent: "center"}}>
<Audio src={staticFile(brand.sting)} /> {/* impact of this file must sit at 0.8s */}
<Img src={staticFile(brand.logo)} style={{width: 420, opacity: logoOpacity, transform: `scale(${scale})`}} />
<div style={{position: "absolute", inset: 0, background: "#fff", opacity: flash}} />
</div>
);
};springflashIMPACTreferences/intro-outro.md| Element | Native size | Where to leave room (1920×1080) |
|---|---|---|
| Subscribe / channel circle | 294×294 | right side, vertically centered-ish, off the edge |
| Next-video card (thumbnail) | 615×345 | upper-right or center-right |
| Playlist / link card | 294×294 | pair beside the video card |
| Your branding (logo, name) | — | left third only, top-aligned |
import {useCurrentFrame, interpolate, Img, staticFile} from "remotion";
// 1920×1080 outro plate. Left third = brand; right two-thirds = YouTube element zones (drawn as guides).
export const EndScreen = ({brand}) => {
const frame = useCurrentFrame();
const fade = interpolate(frame, [0, 18], [0, 1], {extrapolateRight: "clamp"});
const zone = {position: "absolute", border: "2px dashed rgba(255,255,255,.25)", borderRadius: 12};
return (
<div style={{flex: 1, background: brand.bg, opacity: fade}}>
{/* LEFT THIRD: your brand — safe from YouTube's overlays */}
<div style={{position: "absolute", left: 80, top: 90, width: 560}}>
<Img src={staticFile(brand.logo)} style={{width: 220}} />
<div style={{color: brand.fg, font: "700 56px Inter", marginTop: 24}}>{brand.name}</div>
<div style={{color: brand.accent, font: "500 30px Inter", marginTop: 8}}>Subscribe for more →</div>
</div>
{/* RIGHT TWO-THIRDS: leave these empty; YouTube draws the live elements here */}
<div style={{...zone, width: 615, height: 345, right: 90, top: 110}} /> {/* next-video card */}
<div style={{...zone, width: 294, height: 294, right: 250, bottom: 150, borderRadius: 999}} /> {/* subscribe circle */}
</div>
);
};brand--props// brand.json — the only thing that changes per channel
{ "name": "PixelForge", "logo": "logo.png", "sting": "sting.mp3",
"bg": "#0B0B14", "fg": "#FFFFFF", "accent": "#7C5CFF" }# render intro + outro for every brand file in /brands
for f in brands/*.json; do
name=$(basename "$f" .json)
npx remotion render Intro "out/${name}-intro.mp4" --props="$f"
npx remotion render Outro "out/${name}-outro.mp4" --props="$f"
donebrandPackaged helper (): tile your stills withscripts/, then assert the encode withscripts/contact-sheet.sh sheet.png f-hook.png f-mid.png f-end.png. Seescripts/probe-mp4.sh out.mp4 [WxH] [fps].scripts/README.md
<Composition>schemadefaultPropsDate.now()Math.random()IMPACT = Math.round(0.8 * fps)<Audio src={staticFile()}>out/*-intro.mp4out/*-outro.mp4brand# 1. Frame-exact stills WITH SHIPPED brand props (not just defaultProps).
# Intro: windup / impact / settle. Outro: fade-in / held end-screen plate.
npx remotion still Intro out/i-windup.png --frame=12 --props=./brand.json
npx remotion still Intro out/i-impact.png --frame=24 --props=./brand.json # = round(0.8*fps)
npx remotion still Intro out/i-settle.png --frame=40 --props=./brand.json
npx remotion still Outro out/o-end.png --frame=N --props=./brand.json
# 2. Inspect each PNG — fidelity (logo readable + captions/tagline correct; on the impact
# frame the logo is full-scale and the flash fires) AND artifacts (end-screen elements
# inside YouTube safe zones — branding in the LEFT third only, right two-thirds quiet,
# nothing within ~10% of any edge or in the bottom UI band; no off-canvas text/overflow).
# 3. Only once the stills check out, encode both plates:
npx remotion render Intro out/intro.mp4 --props=./brand.json
npx remotion render Outro out/outro.mp4 --props=./brand.jsonnpx remotion render Intro out/demo.gif --codec=gifnpx remotion stillDate.now()Math.random()brandreferences/intro-outro.md