Compare commits

...
Author SHA1 Message Date
Claude a370d270c0 Add PRD and phased roadmap for Firefly-style cloud platform
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UUceKW1u6RsURcq5xZwh34
2026-10-20 07:39:40 +00:00
keithranaandClaude Sonnet 5.5 0b42fdeae1 Add remotion-video skill (#1)
* Add remotion-video skill

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019yhnsd9RDz7jDSUUX7oEcH

* Add Phase 2 templates to remotion-video skill

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019yhnsd9RDz7jDSUUX7oEcH

* Fix captions template background; document browser flag after test render

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019yhnsd9RDz7jDSUUX7oEcH

* Add audio/voiceover template (Phase 3), tested with real render

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019yhnsd9RDz7jDSUUX7oEcH

* Document font loading and its network requirement; captions-over-video verified

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019yhnsd9RDz7jDSUUX7oEcH

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-15 10:42:46 +11:00
7 changed files with 379 additions and 0 deletions

No files matched your search

+102
View File
@@ -0,0 +1,102 @@
---
name: remotion-video
description: Create videos from React code with Remotion. Use when the user asks to make, animate, preview, or render a video, motion graphic, intro, caption, or social clip using code.
---
# Remotion Video
Remotion makes videos out of React components. Each frame is a web page. Render them in order and you get an MP4.
## Core ideas
- **Composition**: one video. Has `width`, `height`, `fps`, `durationInFrames`.
- **Frame**: `useCurrentFrame()` returns the current frame number. All animation is a function of the frame.
- **`interpolate`**: maps a frame range to a value range (fade, slide).
- **`spring`**: natural bouncy motion.
- **`Sequence`**: shows children only during a frame range.
- **`AbsoluteFill`**: full-screen layer; stack them for backgrounds.
- **`Audio` / `OffthreadVideo` / `Img`**: media. Put files in `public/` and load with `staticFile("name.mp3")`.
## Workflow
1. **Plan first.** Ask or decide: length (seconds), size (1920x1080, or 1080x1920 for vertical), fps (30), and the scenes. Keep scenes to a short list.
2. **Set up** (skip if the project exists):
```
npx create-video@latest
cd <project> && npm install
```
3. **Write** compositions in `src/`, register them in `src/Root.tsx`.
4. **Preview**: `npm run dev` (opens Remotion Studio).
5. **Render**: `npx remotion render <CompositionId> out/video.mp4`
Still frame: `npx remotion still <CompositionId> out/frame.png --frame=30`
6. **Verify**: render one still first, check it looks right, then render the full video.
## Starter example
`src/Root.tsx`
```tsx
import { Composition } from "remotion";
import { Hello } from "./Hello";
export const RemotionRoot = () => (
<Composition
id="Hello"
component={Hello}
durationInFrames={150}
fps={30}
width={1920}
height={1080}
defaultProps={{ title: "Hello, world" }}
/>
);
```
`src/Hello.tsx`
```tsx
import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig } from "remotion";
export const Hello = ({ title }: { title: string }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const opacity = interpolate(frame, [0, 30], [0, 1], { extrapolateRight: "clamp" });
const scale = spring({ frame, fps, config: { damping: 12 } });
return (
<AbsoluteFill style={{ backgroundColor: "#0b1020", justifyContent: "center", alignItems: "center" }}>
<h1 style={{ color: "white", fontSize: 120, opacity, transform: `scale(${scale})` }}>{title}</h1>
</AbsoluteFill>
);
};
```
## Rules of thumb
- Never animate with CSS transitions, `setTimeout`, or CSS animations. They don't render per frame. Drive everything from `useCurrentFrame()`.
- Always pass `extrapolateRight: "clamp"` to `interpolate` unless you want values to keep growing.
- Seconds to frames: `seconds * fps`.
- Use `<Sequence from={..} durationInFrames={..}>` to order scenes; `<Series>` to chain them.
- Load fonts with `@remotion/google-fonts` so they are ready before rendering.
- Keep props in `defaultProps` so the user can edit text in the Studio.
- Rendering needs Chromium and ffmpeg (Remotion installs/handles these); first render can be slow.
## Fonts
Google Fonts: `npm i @remotion/google-fonts`, then
```tsx
import { loadFont } from "@remotion/google-fonts/Inter";
const { fontFamily } = loadFont(); // use as style={{ fontFamily }}
```
It downloads the font while rendering, so the machine needs internet access to fonts.googleapis.com. If the render fails with "Failed to load font", the network is blocking it: use a local font file in `public/` with `@remotion/fonts` (`loadFont({ family, url: staticFile("x.woff2") })`) or a system font. (Not verified in the sandbox where this skill was tested, which had no access to Google.)
## Common problems
- **Can't download the browser (no internet / locked-down machine)**: point Remotion at an installed Chromium:
`npx remotion render <Id> out/video.mp4 --browser-executable=/path/to/chromium --chrome-mode=chrome-for-testing`
- **Blank/white frames**: an asset path is wrong; use `staticFile()`.
- **Flicker**: an image or font not loaded; use `<Img>` and `@remotion/google-fonts`.
- **Slow render**: lower resolution/fps while testing, or use `--concurrency`.
## Templates
Copy-paste starting points. Read the one that fits:
- `templates/captions.md` - words highlighted in time with speech
- `templates/charts.md` - animated bar chart
- `templates/intro-outro.md` - title card + chaining scenes with `Series`
- `templates/audio-voiceover.md` - background music, voiceover, auto video length
- `templates/vertical.md` - 1080x1920 clips for Reels/TikTok/Shorts
## Docs
https://www.remotion.dev/docs
@@ -0,0 +1,62 @@
# Template: Audio, music and voiceover
Put sound files in `public/` (mp3 or wav). Load them with `staticFile()`.
`src/Narrated.tsx`
```tsx
import { AbsoluteFill, Audio, Sequence, interpolate, staticFile, useVideoConfig } from "remotion";
import { getAudioDurationInSeconds } from "@remotion/media-utils";
import type { CalculateMetadataFunction } from "remotion";
type Props = { title: string; voice: string; music: string };
// Make the video exactly as long as the voiceover (+1 second of padding).
export const calcNarratedMetadata: CalculateMetadataFunction<Props> = async ({ props }) => {
const seconds = await getAudioDurationInSeconds(staticFile(props.voice));
return { durationInFrames: Math.ceil((seconds + 1) * 30) };
};
export const Narrated = ({ title, voice, music }: Props) => {
const { fps, durationInFrames } = useVideoConfig();
return (
<AbsoluteFill style={{ background: "#0b1020", justifyContent: "center", alignItems: "center" }}>
<h1 style={{ color: "white", fontSize: 110 }}>{title}</h1>
{/* Background music: quiet, fades out over the last second. */}
<Audio
src={staticFile(music)}
volume={(f) => interpolate(f, [durationInFrames - fps, durationInFrames], [0.15, 0], { extrapolateLeft: "clamp", extrapolateRight: "clamp" })}
/>
{/* Voiceover starts half a second in, full volume. */}
<Sequence from={Math.round(fps * 0.5)}>
<Audio src={staticFile(voice)} volume={1} />
</Sequence>
</AbsoluteFill>
);
};
```
Register it (note `calculateMetadata`):
```tsx
<Composition
id="Narrated"
component={Narrated}
calculateMetadata={calcNarratedMetadata}
durationInFrames={300} // placeholder, replaced by calculateMetadata
fps={30} width={1920} height={1080}
defaultProps={{ title: "My story", voice: "voice.mp3", music: "music.mp3" }}
/>
```
Install: `npm i @remotion/media-utils`
Getting a voiceover
- Record yourself, or generate speech with a text-to-speech service, and save as `public/voice.mp3`. Never commit API keys; keep them in environment variables.
- Pair with `templates/captions.md`: feed it word timings from a transcription of the same file.
Rules
- Music volume 0.1-0.2 so the voice stays clear.
- `<Sequence from={...}>` delays when a sound starts.
- Audio is only in the final render (mp4). Stills (`remotion still`) have no sound.
- Check the sound made it in: `ffprobe out/video.mp4` should list an `Audio:` stream.
@@ -0,0 +1,38 @@
# Template: Captions
Words appear in time with speech. Give each word a start time in seconds.
`src/Captions.tsx`
```tsx
import { AbsoluteFill, useCurrentFrame, useVideoConfig } from "remotion";
type Word = { text: string; start: number; end: number }; // seconds
export const Captions = ({ words, background = "#111" }: { words: Word[]; background?: string }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const t = frame / fps;
// Show a window of up to 5 words around the current one.
const current = words.findIndex((w) => t >= w.start && t < w.end);
const i = current === -1 ? 0 : current;
const visible = words.slice(Math.floor(i / 5) * 5, Math.floor(i / 5) * 5 + 5);
return (
<AbsoluteFill style={{ backgroundColor: background, justifyContent: "flex-end", alignItems: "center", paddingBottom: 200 }}>
<div style={{ fontSize: 72, fontWeight: 800, color: "white", textShadow: "0 4px 12px black", textAlign: "center", maxWidth: "80%" }}>
{visible.map((w, k) => (
<span key={k} style={{ color: t >= w.start && t < w.end ? "#ffd400" : "white", marginRight: 16 }}>
{w.text}
</span>
))}
</div>
</AbsoluteFill>
);
};
```
Tips
- Get word times from a transcription tool (e.g. `@remotion/captions` or Whisper) and save them as JSON in `public/`.
- `background` is the backdrop when there is no video. Pass `"transparent"` when layering over a video.
- Layer it over the video: `<AbsoluteFill><OffthreadVideo src={staticFile("clip.mp4")} /><Captions words={words} background="transparent" /></AbsoluteFill>`.
@@ -0,0 +1,37 @@
# Template: Animated bar chart
Bars grow one after another.
`src/BarChart.tsx`
```tsx
import { AbsoluteFill, spring, useCurrentFrame, useVideoConfig } from "remotion";
type Bar = { label: string; value: number };
export const BarChart = ({ data, color = "#4f8cff" }: { data: Bar[]; color?: string }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const max = Math.max(...data.map((d) => d.value));
return (
<AbsoluteFill style={{ backgroundColor: "#0b1020", padding: 120, justifyContent: "flex-end" }}>
<div style={{ display: "flex", alignItems: "flex-end", gap: 40, height: 700 }}>
{data.map((d, i) => {
const grow = spring({ frame: frame - i * 6, fps, config: { damping: 15 } });
return (
<div key={d.label} style={{ flex: 1, textAlign: "center", color: "white", fontSize: 36 }}>
<div style={{ height: (d.value / max) * 600 * grow, background: color, borderRadius: 8 }} />
<div style={{ marginTop: 16 }}>{d.label}</div>
</div>
);
})}
</div>
</AbsoluteFill>
);
};
```
Tips
- Delay each bar with `frame - i * 6` (6 frames apart).
- For lines, animate `strokeDashoffset` on an SVG path using `interpolate`.
- Keep numbers readable: show the value as text, rounded.
@@ -0,0 +1,34 @@
# Template: Title intro / outro card
A short card (2-3 seconds) with a title and subtitle. Use at the start or end.
`src/TitleCard.tsx`
```tsx
import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig } from "remotion";
export const TitleCard = ({ title, subtitle }: { title: string; subtitle?: string }) => {
const frame = useCurrentFrame();
const { fps, durationInFrames } = useVideoConfig();
const rise = spring({ frame, fps, config: { damping: 14 } });
const fadeOut = interpolate(frame, [durationInFrames - 15, durationInFrames], [1, 0], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
return (
<AbsoluteFill style={{ background: "linear-gradient(135deg,#1b2a6b,#0b1020)", justifyContent: "center", alignItems: "center", opacity: fadeOut }}>
<h1 style={{ color: "white", fontSize: 130, margin: 0, transform: `translateY(${(1 - rise) * 60}px)`, opacity: rise }}>{title}</h1>
{subtitle && <p style={{ color: "#9fb3ff", fontSize: 48, opacity: rise }}>{subtitle}</p>}
</AbsoluteFill>
);
};
```
Chain with the main video using `<Series>`:
```tsx
<Series>
<Series.Sequence durationInFrames={90}><TitleCard title="My Video" /></Series.Sequence>
<Series.Sequence durationInFrames={600}><MainScene /></Series.Sequence>
<Series.Sequence durationInFrames={90}><TitleCard title="Thanks for watching" /></Series.Sequence>
</Series>
```
@@ -0,0 +1,23 @@
# Template: Vertical clips (Reels / TikTok / Shorts)
Same code, different size. Register a vertical composition:
```tsx
<Composition
id="Vertical"
component={MyScene}
width={1080}
height={1920}
fps={30}
durationInFrames={30 * 30} // 30 seconds
/>
```
Render: `npx remotion render Vertical out/vertical.mp4`
Rules
- Keep text inside the **safe zone**: about 150px from the top, 350px from the bottom (apps cover those parts with buttons and captions), and 60px from the sides.
- Use big text (60px+). Phone screens are small.
- Keep clips under 60 seconds (Shorts limit); 15-30 seconds does best.
- Use `useVideoConfig()` for width/height instead of hard-coding, so the same scene works wide and tall.
- Put captions in the lower-middle, above the bottom safe zone (see captions.md).
+83
View File
@@ -0,0 +1,83 @@
# PRD: "Firefly-style" Cloud Control Platform (working name: CloudLens)
Status: DRAFT 1. Written from general knowledge of Firefly.ai (their website was
blocked from my work environment). Please compare against firefly.ai before we build.
## 1. What is this, in plain words?
Companies rent computers, storage and databases from Amazon (AWS), Google (GCP) and
Microsoft (Azure). Over time nobody knows exactly what they have, what it costs, or
who changed what. Firefly is like a **smart inventory + safety camera for the cloud**:
1. It **looks** at your cloud account and lists everything you own.
2. It spots things that were made **by hand** (nobody wrote them down as code).
3. It **writes the code** (Terraform) so those things are tracked.
4. It **watches for changes** ("drift") and warns you.
5. It checks **rules** (e.g. "no public storage buckets") and **cost**.
## 2. Core features (the full dream list)
| # | Feature | 4th-grade explanation |
|---|---------|-----------------------|
| 1 | Cloud connection | Give the app a read-only key so it can look at your cloud. |
| 2 | Asset inventory | One big searchable list of everything you own. |
| 3 | Unmanaged-asset finder | Highlights things made by hand, not by code. |
| 4 | Code generator (IaC) | Turns a hand-made thing into Terraform code. |
| 5 | Drift detection | "This changed and nobody told us!" alerts. |
| 6 | Policy / rules engine | Checks against safety and cost rules. |
| 7 | Cost view | Shows what each thing costs per month. |
| 8 | Alerts | Email / Slack messages when something is wrong. |
| 9 | Backup & restore | Saves a copy of your setup so you can rebuild it. |
| 10 | AI helper | Ask "which servers are public?" in normal words. |
| 11 | Teams, logins, audit log | Who can see what, and who did what. |
## 3. Phased roadmap (small steps, test each one)
### Phase 0 - Quick win TODAY (about 1 session)
Goal: something you can open and click.
- A web dashboard with a **sample** cloud inventory (fake data, clearly labeled).
- Search, filter by cloud / type / region, and a red badge for "unmanaged".
- Summary cards: total assets, unmanaged count, monthly cost.
- No logins, no real cloud yet. Zero risk.
- **Tests:** page loads with no errors -> search/filter buttons work.
### Phase 1 - Real data (read-only)
- Connect ONE real AWS account with a read-only key.
- Replace sample data with real resources (EC2, S3, RDS first).
- **You must:** create a read-only AWS user and give me its keys (I will walk you through it).
- **Tests:** real API returns data -> keys never shown in the browser or saved in code.
### Phase 2 - Drift + rules
- Save a snapshot every day; show "what changed".
- First 10 rules (public bucket, no encryption, untagged, etc.).
- Tests: change something on purpose -> app notices.
### Phase 3 - Code generation
- Click an unmanaged item -> get Terraform code. Start with S3 + EC2 only.
- Tests: generated code passes `terraform validate`.
### Phase 4 - Alerts, cost, teams
- Email/Slack alerts, real cost numbers, logins and roles, audit log.
- Security review: passwords, permissions, secret storage, rate limits.
### Phase 5 - Multi-cloud + AI
- Add GCP and Azure. Add the "ask in plain English" helper (Claude API).
- Backup & restore of cloud setups.
## 4. Suggested tech (so you know, no need to learn it)
- Frontend: Next.js + React (what Firefly-style dashboards use).
- Backend: Node/TypeScript API, PostgreSQL database.
- Cloud reading: official AWS / GCP / Azure SDKs.
- Secrets: a vault / environment variables, never in code.
## 5. Security rules (non-negotiable)
- Read-only cloud keys first. Write access only in a later phase, with approval.
- Keys stored encrypted on the server only.
- Every phase ends with a security check before the next starts.
## 6. Out of scope (for now)
Copying Firefly's logo, name, text or design. We build our **own** product with the
same *kind* of features. Their exact code/designs are theirs.
## 7. Open questions for you
1. Who is it for: just you, your company, or customers you will sell to?
2. Which cloud do you use most: AWS, Google, or Azure?
3. Do you have a cloud account we can use for testing? (If not, Phase 0 stays on fake data.)