Files
last30days-skill/docs/plans/2026-04-22-007-marketing-v3-1-launch-video-plan.md
T
Matt Van Horn b31ec05c74 marketing: v3.1 launch video — 30s Remotion render
Six-scene 30-second MP4 announcing v3.1 (competitors mode) for X.

Lives at marketing/v3.1-launch/. Renders to out/last30days-v3.1-launch.mp4
(gitignored). 1920×1080 @ 30fps, H.264, ~3MB final.

Six scenes:
1. Hook (0-3s): badge + "What if one search ran 3 at once?"
2. Old way (3-8s): single terminal /last30days OpenAI
3. Fan-out (8-14s): --competitors splits into 3 parallel panes
4. Comparison (14-21s): 3 panes collapse into Head-to-Head table
5. How (21-26s): "You pick the topic. Agent picks peers. Engine fans out."
6. CTA (26-30s): install command + repo URL

Reusable components: TerminalWindow, TypedLine, BadgeBar,
ComparisonTable. Scene timing in src/lib/timing.ts as single source of
truth — one edit to retime everything.

Marketing version 3.1 ≠ engine code version (still 3.0.14). 3.1 is the
launch label that bundles 3.0.11-3.0.14 into one shippable narrative.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 21:48:02 -07:00

19 KiB
Raw Blame History

title, type, status, date
title type status date
marketing: v3.1 launch video — 30s Remotion piece for X feat active 2026-04-22

marketing: v3.1 launch video — 30s Remotion piece for X

Overview

Build a 30-second Remotion-rendered MP4 marketing video announcing v3.1: Competitors mode for /last30days. Posts to X. Frames the new vs-mode-with-auto-discovery as the headline — three full passes, three save files, one comparison — without burying it in CLI minutiae.

Marketing release tag is v3.1 (rebrands the 3.0.11-3.0.14 bundle into one shippable narrative for the launch tweet). Engine version stays 3.0.14 — 3.1 is the marketing version, not a code version bump.

Script (60 frames per second × 30 seconds = 900 frames; this version assumes 30fps × 30s = 900 frames at 30fps)

Total runtime: 30.0 seconds @ 30fps = 900 frames. Six scenes, on-screen text only (silent autoplay-friendly).

Scene 1 — Hook (0:00 0:03 | frames 0-90)

Visual: Black background. The /last30days badge animates in (the literal 🌐 last30days v3.1 · synced 2026-04-22 line) with the spring scale-in used in slick devtool intros.

Caption (overlay, large):

What if one search
ran 3 at once?

Scene 2 — Set the world (0:03 0:08 | frames 90-240)

Visual: Single mac terminal window center-stage. Type-on animation:

$ /last30days OpenAI

Below it, a simple result card stub appears (Reddit upvote count + X likes), then static.

Caption (small bottom-left):

The old way: one topic.

Scene 3 — The reveal (0:08 0:14 | frames 240-420)

Visual: The terminal types one more flag:

$ /last30days OpenAI --competitors

Hard cut → the single terminal splits into 3 panes side by side. Each pane shows a different topic header animating in, in this order:

  • Left: OpenAI
  • Middle: vs Anthropic
  • Right: vs xAI

Pane content scrolls fake "search progress" lines (Reddit, X, YouTube indicators) in parallel, like a live fan-out.

Caption (top center):

Now it discovers competitors
and runs all 3.

Scene 4 — Result reveal (0:14 0:21 | frames 420-630)

Visual: The 3 panes collapse into a single comparison surface — the ## Head-to-Head table from the actual engine output, with rows fading in one by one (What it is, Streams, Best for, Trajectory). Each entity column lights up as its row populates.

Caption (bottom):

3 full passes. 3 save files.
1 comparison.

Scene 5 — How it's special (0:21 0:26 | frames 630-780)

Visual: Cut to a clean text card, large mono font:

You pick the topic.
The agent picks the peers.
The engine fans out.

Each line fades in 1.5s apart.

Scene 6 — CTA (0:26 0:30 | frames 780-900)

Visual: Black background. Centered:

  • Top: 🌐 last30days v3.1
  • Middle: /last30days {topic} --competitors
  • Bottom: github.com/mvanhorn/last30days-skill

Subtle pulse on the install line.

End frame holds for ~0.5s.

Problem Frame

The 3.0.11-3.0.14 release bundle ships a transformative feature (per-entity vs-mode fanout + --competitors shortcut + per-entity save files), but the value lands flat in a tweet thread or screenshot. A 30s video does what static text cannot: shows the fan-out happening in real time and the 3 → 1 collapse into a comparison. Higher tweet engagement, easier to RT/QT.

Requirements Trace

  • R1. Final artifact: a single MP4 file, 1920×1080, 30fps, ~30 seconds (±0.5s), under 30MB, suitable for direct X upload.
  • R2. Six-scene script as defined above, with text/visuals/timing matching to within 5 frames.
  • R3. Branded look: matches the 🌐 last30days v3.1 badge style (terminal aesthetic, mono font, dark background).
  • R4. Silent — no voiceover, no music in v1. Captions baked in. Designed for autoplay-muted feeds.
  • R5. Reproducible: another contributor (or a future-me) can re-render with one command. Project lives in-repo so the source is versioned.

Scope Boundaries

  • No voiceover. Text-on-screen only. (Voice can be a v1.1 if engagement is high.)
  • No background music in the rendered MP4. (Music can be added in post via QuickTime/iMovie if desired before posting.)
  • No localization. English captions only.
  • No A/B test variants. One video.
  • No 9:16 vertical version. 16:9 only. (Vertical can be a separate render after launch validates the format.)

Deferred to Separate Tasks

  • Voiceover variant: defer to follow-up if v1 lands well.
  • 9:16 mobile cut: defer; same source compositions can re-render at 1080×1920 in a follow-up.
  • Animated GIF for embedding in README.md: defer; can be ffmpeg-extracted from the MP4.

Context & Research

Relevant Code and Patterns

  • SKILL.md — the comparison render scaffold (## Head-to-Head table) is the visual reference for Scene 4's table look.
  • scripts/lib/render.py _render_comparison_scaffold — emits the 9-axis table whose visual style we're recreating in a more polished form.
  • CHANGELOG.md 3.0.11-3.0.14 entries — the prose source for the script's beats.
  • .claude-plugin/plugin.json version 3.0.14 — current code version (marketing version is 3.1 for the launch).

External References

  • Remotion 4.x docs (https://www.remotion.dev/docs/) — current API for compositions, sequences, springs, and render CLI.
  • X video specs 2026: max 2 min 20 s, ≤512MB, MP4 with H.264 + AAC, recommended 1920×1080 for landscape autoplay.

Institutional Learnings

  • No prior marketing/ dir or video plans in docs/plans/. This is a greenfield asset directory.

Key Technical Decisions

  • Remotion, not ffmpeg-only. Remotion's React-based compositions handle the typed-on terminal effect, spring-animated badges, and scene transitions far more cleanly than raw ffmpeg filtergraphs. Render output is still MP4 via Remotion's bundled ffmpeg.
  • In-repo asset directory at marketing/v3.1-launch/. Lives with the product so future versions can fork the project. Adds marketing/ to .gitignore exceptions only for source files; rendered MP4 stays out of git (uploaded separately).
  • 16:9 1920×1080 @ 30fps. Best fit for X landscape autoplay on desktop and mobile feed. 30fps is plenty for typed-text + UI animation; 60fps doubles render time without obvious quality gain.
  • Silent + captions. X autoplay defaults to muted. Sound-off is the realistic viewing condition. Captions baked into the visual.
  • Six scenes, one composition. Single Remotion composition with sequenced child compositions per scene. Easier to re-time than scene-files. Frame-numbered timing in the script enables precise edits.
  • Mono font (JetBrains Mono or Geist Mono). Matches the terminal aesthetic of the actual /last30days output. Available via Google Fonts or @remotion/google-fonts.
  • Marketing version 3.1 ≠ engine version 3.0.14. 3.1 is the launch label. Engine stays 3.0.14. Avoids confusion in CHANGELOG.

Open Questions

Resolved During Planning

  • Aspect ratio? 16:9 1080p. Best X autoplay format; vertical can be a follow-up.
  • Voiceover or silent? Silent + on-screen captions. Autoplay-muted is the realistic condition.
  • In-repo or separate repo? In-repo at marketing/v3.1-launch/. Rendered MP4 not committed; source compositions are.
  • Length? Exactly 30s (900 frames @ 30fps). No flex.
  • Marketing version label? v3.1. Engine code version stays 3.0.14.

Deferred to Implementation

  • Exact animation easing curves per scene — pick during build via Remotion preview iteration.
  • Whether the comparison-table mock in Scene 4 uses canned text or pulls from the actual saved *-raw.md files. Probably canned for visual control.
  • Whether Scene 3's "search progress" lines are typed individually or use a marquee scroll. Pick during preview.
  • Exact accent color palette beyond "terminal dark." Iterate against preview.

Output Structure

marketing/
  v3.1-launch/
    package.json                  # Remotion dependency manifest
    tsconfig.json                 # TypeScript config
    remotion.config.ts            # Remotion render config (codec, fps, resolution)
    src/
      index.ts                    # Remotion entry — registers compositions
      Root.tsx                    # Root composition definition
      LaunchVideo.tsx             # Main 30s composition that sequences scenes
      scenes/
        Scene1Hook.tsx
        Scene2OldWay.tsx
        Scene3FanOut.tsx
        Scene4Comparison.tsx
        Scene5HowItWorks.tsx
        Scene6CTA.tsx
      components/
        TerminalWindow.tsx        # Reusable mac-style terminal frame
        TypedLine.tsx             # Type-on animation primitive
        ComparisonTable.tsx       # The Head-to-Head table mock
        BadgeBar.tsx              # The 🌐 last30days v3.1 badge
      lib/
        timing.ts                 # Frame ranges per scene (single source of truth)
        colors.ts                 # Brand palette
    public/                       # Static assets (logo, fonts if local)
    out/                          # Rendered MP4 lives here (gitignored)
    README.md                     # How to preview/render

High-Level Technical Design

Directional guidance for review — not implementation specification.

LaunchVideo (durationInFrames = 900)
├── <Sequence from={0} durationInFrames={90}>     <Scene1Hook />
├── <Sequence from={90} durationInFrames={150}>   <Scene2OldWay />
├── <Sequence from={240} durationInFrames={180}>  <Scene3FanOut />
├── <Sequence from={420} durationInFrames={210}>  <Scene4Comparison />
├── <Sequence from={630} durationInFrames={150}>  <Scene5HowItWorks />
└── <Sequence from={780} durationInFrames={120}>  <Scene6CTA />

Per-scene components use useCurrentFrame() + interpolate() + spring() for timing. TerminalWindow is the dominant motif across scenes 2-4.

Implementation Units

  • Unit 1: Remotion project scaffold

Goal: Spin up a working Remotion project at marketing/v3.1-launch/ that previews a blank composition and renders to MP4.

Requirements: R1, R5

Files:

  • Create: marketing/v3.1-launch/package.json
  • Create: marketing/v3.1-launch/tsconfig.json
  • Create: marketing/v3.1-launch/remotion.config.ts
  • Create: marketing/v3.1-launch/src/index.ts
  • Create: marketing/v3.1-launch/src/Root.tsx
  • Create: marketing/v3.1-launch/README.md
  • Modify: .gitignore (add marketing/v3.1-launch/out/, marketing/v3.1-launch/node_modules/)

Approach:

  • Use npx create-video@latest --blank (Remotion 4.x scaffolding) targeting marketing/v3.1-launch/. Strip the demo composition.
  • Configure: 1920×1080, 30fps, H.264, AAC (audio codec needed even for silent — empty track).
  • README documents npm install, npm run preview (Remotion Studio), npm run render (one-command MP4).

Test scenarios:

  • Test expectation: none for scaffold, but verification = npm run preview opens Remotion Studio with a blank 30s composition; npm run render produces a black MP4 at the right resolution.

Verification:

  • Studio loads at localhost:3000 with the empty LaunchVideo composition listed.

  • A render produces out/launch-video.mp4 at 1920×1080, 30s, valid MP4.

  • Unit 2: Reusable components (Terminal, TypedLine, BadgeBar)

Goal: Build the three primitive components scenes 2-6 will compose. Each is independently previewable.

Requirements: R3, R5

Dependencies: Unit 1

Files:

  • Create: marketing/v3.1-launch/src/components/TerminalWindow.tsx
  • Create: marketing/v3.1-launch/src/components/TypedLine.tsx
  • Create: marketing/v3.1-launch/src/components/BadgeBar.tsx
  • Create: marketing/v3.1-launch/src/lib/colors.ts
  • Create: marketing/v3.1-launch/src/lib/timing.ts

Approach:

  • TerminalWindow: mac-style traffic-light header, dark gradient background, mono content area. Accepts children.
  • TypedLine: takes a string and a startFrame, renders character-by-character at ~30 chars/sec. Reuses Remotion's interpolate(useCurrentFrame() - startFrame, [0, lengthFrames], [0, text.length]) clamped.
  • BadgeBar: renders the literal 🌐 last30days v3.1 · synced 2026-04-22 line in mono with the same gradient color treatment as the engine's compact emit.
  • colors.ts: 5-7 brand colors (terminal-bg, terminal-fg, accent-cyan, accent-magenta, muted, success-green, warning-amber).
  • timing.ts: exports the scene frame ranges as named constants. Single source of truth for any retiming.

Test scenarios:

  • Test expectation: none — visual components verified in Remotion Studio.

Verification:

  • Each component renders standalone in Studio when wrapped in a temporary preview composition.

  • TypedLine animates character-by-character without flicker.

  • Unit 3: Scenes 1-3 (Hook, Old way, Fan-out reveal)

Goal: Build the first half of the video (frames 0-420). The narrative arc up through the visual fan-out.

Requirements: R2

Dependencies: Unit 2

Files:

  • Create: marketing/v3.1-launch/src/scenes/Scene1Hook.tsx
  • Create: marketing/v3.1-launch/src/scenes/Scene2OldWay.tsx
  • Create: marketing/v3.1-launch/src/scenes/Scene3FanOut.tsx
  • Modify: marketing/v3.1-launch/src/Root.tsx (register sequences)

Approach:

  • Scene 1: spring-in BadgeBar, large overlay caption, 3-second hold.
  • Scene 2: TerminalWindow with TypedLine ($ /last30days OpenAI), then a single result-card mock fading in.
  • Scene 3: typing animation appends --competitors, hard cut, three TerminalWindow components arranged in a row with staggered fade-in. Each pane shows a different entity header + scrolling progress lines.

Test scenarios:

  • Test expectation: none — verified visually in Studio.

Verification:

  • Scrub the 0-14s range in Studio; visuals match the script timing within 5 frames.

  • The fan-out moment (frame 240) lands cleanly; no jank in the transition from 1 → 3 panes.

  • Unit 4: Scenes 4-6 (Comparison reveal, How it works, CTA)

Goal: Build the back half of the video (frames 420-900). Resolution + payoff + call to action.

Requirements: R2

Dependencies: Unit 2

Files:

  • Create: marketing/v3.1-launch/src/scenes/Scene4Comparison.tsx
  • Create: marketing/v3.1-launch/src/scenes/Scene5HowItWorks.tsx
  • Create: marketing/v3.1-launch/src/scenes/Scene6CTA.tsx
  • Create: marketing/v3.1-launch/src/components/ComparisonTable.tsx

Approach:

  • Scene 4: ComparisonTable component renders 3-column markdown-style table; rows fade in one by one (stagger 15-20 frames). Uses canned data — OpenAI / Anthropic / xAI with believable cell content drawn from real 3.0.13 outputs.
  • Scene 5: three-line text card; lines fade in 45 frames apart.
  • Scene 6: three centered text blocks; install line gets a 1Hz subtle opacity pulse for emphasis.

Test scenarios:

  • Test expectation: none — verified visually.

Verification:

  • Scrub 14-30s; table reveal feels paced (not too slow, not strobed); CTA holds long enough to read (~3-4s).

  • ComparisonTable cells are legible at 1920×1080 (mono font ≥ 28px).

  • Unit 5: Final composition wiring + render

Goal: Wire the six scenes into the master LaunchVideo composition, render to MP4, verify against X upload constraints.

Requirements: R1, R2, R5

Dependencies: Units 3, 4

Files:

  • Modify: marketing/v3.1-launch/src/LaunchVideo.tsx (sequence all 6 scenes)
  • Modify: marketing/v3.1-launch/README.md (add render command + verification checklist)

Approach:

  • LaunchVideo is a single Composition that imports Scene1HookScene6CTA and wraps each in <Sequence from=… durationInFrames=…> matching lib/timing.ts.
  • Run npx remotion render LaunchVideo out/last30days-v3.1-launch.mp4 --codec=h264 --crf=18.
  • Verify output: 30.0s ±0.1s, 1920×1080, file size <30MB, opens in QuickTime, plays without dropped frames.

Test scenarios:

  • Test expectation: none — verification is the rendered MP4 itself.

Verification:

  • ffprobe out/last30days-v3.1-launch.mp4 reports 1920×1080, 30fps, ~30.0s, h264, faststart-friendly.

  • Manual play-through end-to-end in QuickTime feels coherent and on-pace.

  • File <30MB so X upload is instant.

  • Unit 6: Polish pass + ship

Goal: Watch the full render, fix obvious jank, do a second render, and stage for X posting.

Requirements: R1, R2, R3

Dependencies: Unit 5

Files:

  • Possibly modify: any scene file based on watch-through findings.

Approach:

  • Watch the rendered MP4 at full size. Note: timing felt off, transitions too fast, captions overflow, color clash, anything visibly broken.
  • Iterate: edit scene component → re-preview in Studio → re-render full MP4.
  • Cap at 2 polish passes; ship the better of the two renders.
  • Final MP4 sits at marketing/v3.1-launch/out/last30days-v3.1-launch.mp4 ready for X upload.

Test scenarios:

  • Test expectation: none — pure subjective polish.

Verification:

  • User watches the final render and approves.
  • No glaring visual bugs (overflowing text, frozen frames, color clashes).

System-Wide Impact

  • Interaction graph: None — this is a standalone marketing artifact. Doesn't touch the Python engine, doesn't change any user-facing behavior.
  • State lifecycle risks: None.
  • API surface parity: N/A.
  • Unchanged invariants: The shipped 3.0.14 engine is untouched.

Risks & Dependencies

Risk Mitigation
Remotion install pulls 200MB+ of node_modules. marketing/v3.1-launch/node_modules/ in .gitignore; checked-in source stays small.
Render time blows past patience (>5 min for 30s @ 1080p30). Bun-based render or --concurrency flag. Default Remotion is fast enough on M-series Macs. If slow, lower preview to 720p, render final at 1080p.
Captions overflow the 1920px width on certain fonts. Use a known mono font with a measured per-character width; cap caption lines at 36 chars.
The "fan-out" visual in Scene 3 looks confusing instead of magical. Polish pass (Unit 6) is the safety net; if still bad, fall back to a simpler "1 → 3 panes wipe" instead of typed split.
File size >30MB hits X upload friction. Use --crf=18 (high quality, reasonable size); fall back to --crf=23 if over. 30s @ 1080p30 H.264 is normally 5-15MB.

Documentation / Operational Notes

  • README at marketing/v3.1-launch/README.md documents preview / render commands.
  • After render, the MP4 is uploaded directly to X. Tweet copy is the user's call (this plan stops at the rendered file).

Sources & References

  • Related code: scripts/lib/render.py (_render_comparison_scaffold is the visual model for Scene 4); SKILL.md Competitor mode section (the narrative source).
  • Related PRs: #308, #311, #312 (the 3.0.11 → 3.0.14 release bundle this video markets as "v3.1").
  • External docs: https://www.remotion.dev/docs/ (Remotion 4.x API).
  • X video specs: https://help.x.com/en/using-x/twitter-videos.