# Remotion Promotional Videos -- Design Spec ## Overview Three standalone promotional videos for SnapOtter built with Remotion. Each video serves a different purpose and platform, with a shared design system for brand consistency. | # | Video | Purpose | Theme | Resolution | Duration | |---|-------|---------|-------|------------|----------| | 1 | X Launch Video | Twitter/X announcement | Dark | 1080x1080 | ~35s | | 2 | Product Demo | Website/sales walkthrough | Light | 1920x1080 | ~75s | | 3 | Promo Teaser | Social media awareness | Dark | 1080x1080 + 1080x1920 | ~20s | All videos use music + text captions (no voiceover). Music is placeholder-ready (Remotion audio infrastructure wired up, user swaps in a real track later). --- ## Technical Architecture ### Workspace Setup New monorepo workspace: `apps/videos/` ``` apps/videos/ package.json (@snapotter/videos) tsconfig.json remotion.config.ts tailwind.config.js (v3 format for Remotion Webpack compat) src/ index.ts (entry point) Root.tsx (Composition registry -- all 3 + aspect ratio variants) design-system/ colors.ts (dark + light palettes from SnapOtter brand) fonts.ts (Inter, Nunito, JetBrains Mono registration) animations.ts (spring presets, easing curves, timing constants) components/ AppWindow.tsx (macOS-style window chrome with traffic lights) Terminal.tsx (terminal with typing animation + syntax highlighting) AnimatedText.tsx (word-by-word / character reveal with clip-mask) ToolGrid.tsx (animated grid of tool cards by category) BeforeAfter.tsx (split comparison with scan-line wipe) LogoReveal.tsx (SnapOtter logo with particle convergence) GitHubCTA.tsx (star on GitHub end card) RotatingTaglines.tsx (cycling phrases from landing page) FeaturePill.tsx (animated feature badge) NumberPunch.tsx (large number with impact animation) GrainOverlay.tsx (animated Perlin noise film grain) ProgressBar.tsx (animated processing progress) GradientMesh.tsx (ambient amber/orange blob background) compositions/ x-launch/ XLaunchVideo.tsx scenes/ HookScene.tsx TerminalInstallScene.tsx ToolGridRevealScene.tsx AiShowcaseScene.tsx PrivacyBeatScene.tsx FeatureBurstScene.tsx GitHubCTAScene.tsx product-demo/ ProductDemo.tsx scenes/ DashboardScene.tsx SingleToolScene.tsx BatchProcessingScene.tsx PipelineBuilderScene.tsx AiToolsScene.tsx ImageEditorScene.tsx ApiDocsScene.tsx EndCardScene.tsx promo-teaser/ PromoTeaser.tsx PromoTeaserVertical.tsx (1080x1920 variant) scenes/ AmbientOpenScene.tsx NumberPunchScene.tsx TaglineCascadeScene.tsx LogoRevealScene.tsx CTAScene.tsx lib/ tools.ts (all 49 tool names + categories, mirrored from shared) audio.ts (audio placeholder config with fade-in/fade-out) public/ otter-logo.svg audio/ placeholder.mp3 (silent placeholder, same duration as longest video) scripts/ render-all.mjs (batch render all compositions to MP4) ``` ### Dependencies ```json { "dependencies": { "remotion": "^4.0.0", "@remotion/cli": "^4.0.0", "@remotion/renderer": "^4.0.0", "@remotion/bundler": "^4.0.0", "@remotion/tailwind": "^4.0.0", "@remotion/noise": "^4.0.0", "@remotion/motion-blur": "^4.0.0", "@remotion/paths": "^4.0.0", "@remotion/google-fonts": "^4.0.0", "@remotion/shapes": "^4.0.0", "react": "^18.3.0", "react-dom": "^18.3.0" } } ``` Note: Remotion v4 requires React 18, not React 19. This workspace pins React 18 independently (pnpm handles this with workspace overrides). `@remotion/tailwind` uses Webpack and requires Tailwind CSS v3 config format. ### Config **`remotion.config.ts`:** ```ts import { Config } from "@remotion/cli/config"; import { enableTailwind } from "@remotion/tailwind"; Config.setVideoImageFormat("jpeg"); Config.setOverwriteOutput(true); Config.overrideWebpackConfig((config) => enableTailwind(config)); ``` **`tailwind.config.js`** (v3 format): ```js module.exports = { content: ["./src/**/*.{ts,tsx}"], theme: { extend: { fontFamily: { heading: ["Nunito", "sans-serif"], body: ["Inter", "sans-serif"], mono: ["JetBrains Mono", "monospace"], }, colors: { accent: "#f59e0b", safe: "#22c55e", danger: "#ef4444", }, }, }, }; ``` ### Root Composition Registry ```tsx // Root.tsx import { Composition } from "remotion"; import { XLaunchVideo } from "./compositions/x-launch/XLaunchVideo"; import { ProductDemo } from "./compositions/product-demo/ProductDemo"; import { PromoTeaser } from "./compositions/promo-teaser/PromoTeaser"; import { PromoTeaserVertical } from "./compositions/promo-teaser/PromoTeaserVertical"; export const RemotionRoot = () => ( <> ); ``` ### Audio Setup All videos use a placeholder audio track. The infrastructure is wired so the user drops in a real track later. ```ts // lib/audio.ts import { Audio, interpolate, useCurrentFrame } from "remotion"; import { staticFile } from "remotion"; export const BackgroundMusic: React.FC<{ src?: string; volume?: number; fadeInFrames?: number; fadeOutFrames?: number; totalFrames: number; }> = ({ src = staticFile("audio/placeholder.mp3"), volume = 0.4, fadeInFrames = 30, fadeOutFrames = 60, totalFrames, }) => { const frame = useCurrentFrame(); const vol = interpolate( frame, [0, fadeInFrames, totalFrames - fadeOutFrames, totalFrames], [0, volume, volume, 0], { extrapolateLeft: "clamp", extrapolateRight: "clamp" } ); return