feat: video films for social media, with the skill to make more
Adds apps/video, a standalone Remotion package (not part of the pnpm workspace, so it never reaches the web build or the Docker image). It renders six looping films, one per service, in German and English, as 4:5 for LinkedIn and 16:9 for YouTube and ads, together with the text to post and an overview page. Each film is one continuous shot: close on something abstract, the camera pulls back to show what it really is, the form changes, and the picture folds back into its opening. The slide deck code from the earlier attempts is still in the package and no longer used. Also adds the Google Ads concept, brings the relaunch concept in line with the site as built, and a project skill that describes how to make further films. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
---
|
||||
name: video-film
|
||||
description: Make or change a social media film in apps/video (Remotion) in the approved mintel.me style - one continuous shot, minimal, with a camera pull-back as the reveal, in German and English. Use when asked for a new video, a new topic, changes to an existing film, new formats, or to re-render the video library.
|
||||
---
|
||||
|
||||
# Making a film for mintel.me
|
||||
|
||||
The videos in `apps/video` are short looping films for LinkedIn and ads. The owner approved this
|
||||
style after rejecting several others. Read `apps/video/CLAUDE.md` first; this skill is the procedure.
|
||||
|
||||
## What the owner rejected, so do not go back to it
|
||||
|
||||
- Slides with a panel, a progress bar, labels and a frame around everything.
|
||||
- Much text, small text, text sizes that change from line to line.
|
||||
- Hectic motion: hops of a third of a second, six statements in 15 seconds.
|
||||
- Busy drawings: icons, labels, counters, stacks, several effects at once.
|
||||
- A tiny logo in a black box.
|
||||
- German that reads translated.
|
||||
|
||||
## The style
|
||||
|
||||
One continuous shot on a dark stage. Only squares, dots, lines and bars. One statement at a time,
|
||||
two short lines, large and centred under the drawing. Calm: nothing moves faster than it has to, every
|
||||
statement stays for about three seconds.
|
||||
|
||||
Every film tells the same kind of story in six beats, on the shared timeline `T` in
|
||||
`src/films/shot.ts` (690 frames at 30 fps, 23 seconds):
|
||||
|
||||
| Beat | Caption id | What happens |
|
||||
| :--- | :--------- | :----------- |
|
||||
| Hook | `hook` | Close on something small and abstract that looks like something else. "Looks like ping-pong." |
|
||||
| Reveal | `reveal` | The camera pulls back and shows what it really is. "It's your office." This is the aha moment. |
|
||||
| Problem | `problem` | The whole picture keeps going, and it is clearly too much. |
|
||||
| Turn | `turn` | Everything slows to a halt, then the form changes: arcs become a line, a pile leaves by itself. |
|
||||
| Calm | `calm` | The result at rest, with one quiet movement. |
|
||||
| Call to action | `cta` | A question that picks up the hook ("Done with ping-pong?") and the website's button. |
|
||||
|
||||
Then the picture folds back into its opening, so the last frame equals the first and the film loops.
|
||||
Frame 0 is the preview image: it must already look good and carry the hook line.
|
||||
|
||||
## Procedure
|
||||
|
||||
1. **Bring the worktree up to date with `main`** and read the service in
|
||||
`apps/web/src/content/de.ts` and `en.ts`. Claims, prices and terms come from there and nowhere else.
|
||||
2. **Find the picture before writing code.** Answer in one line each:
|
||||
- What does the viewer see close up, and what harmless thing does it look like?
|
||||
- What does the camera reveal when it pulls back?
|
||||
- Which single change of form resolves it?
|
||||
If the reveal does not make you think "oh", look for another picture. Existing ones:
|
||||
ping-pong / office, skyscraper / pile of invoices, star / unanswered enquiries,
|
||||
blinking cursor / empty website, Jenga / fragile app, loading bar / backlog.
|
||||
3. **Write the six statements** in `src/content/videos.ts`, German and English, plus the post text.
|
||||
Two lines each, as short as possible: one font size is used for the whole film, set by the longest
|
||||
line. The hook is "Sieht aus wie ..." / "Looks like ...". A little sarcasm is welcome. German uses
|
||||
"Sie" and has to sound like a native wrote it; write it first, do not translate it from English.
|
||||
The post text opens with the hook. Copy rules of the root `CLAUDE.md` apply.
|
||||
4. **Write the film** as `src/films/<Name>Film.tsx`. Copy the structure of `AppFilm.tsx` (simplest)
|
||||
or `OrderFilm.tsx` (several moving things):
|
||||
- `cameraAt(frame, zoomIn, zoomOut, focus)` gives `transform`, `zoom`, `hair` and `others`.
|
||||
Everything that is not part of the opening picture gets `opacity={others}`.
|
||||
- Draw in stage coordinates, (0, 0) in the centre, 1000 x 700. Use `hair` for stroke widths and
|
||||
divide glow sizes by `zoom`, so lines look the same at every zoom.
|
||||
- All motion is a pure function of the frame: `progress`, `inOut`, `out`, `mix` from
|
||||
`src/illustrations/motion.ts`. No timers, no randomness, no CSS animations.
|
||||
- For the loop: after `T.fold` the opening picture has to be rebuilt exactly. Anything periodic
|
||||
needs a period that divides 690 (23, 30, 46). Compare frame 0 and frame 689.
|
||||
- Logic worth testing goes into `src/lib` with a spec, written first (see `hop.ts`).
|
||||
5. **Register it**: `src/compositions/<VideoId>.tsx` exporting a `VideoEntry` with `Film`, and the
|
||||
list in `src/Root.tsx`. A new id also goes into `VIDEO_IDS` in `videos.ts`.
|
||||
6. **Check**: `pnpm typecheck` and `pnpm test` in `apps/video`.
|
||||
7. **Render and look**: `node scripts/export.ts --only <VideoId>`, then pull frames from the MP4
|
||||
(start, middle of the pull-back, problem, turn, calm, call to action, last frame) in `feed` and
|
||||
`wide`, and read them. Check: nothing overlaps the text, the drawing fades at the stage edges
|
||||
during the camera move, frame 689 equals frame 0, German lines fit.
|
||||
8. **Report** what was rendered, what you looked at, and what you did not. You cannot play video:
|
||||
say so, and leave the judgement of pace and feel to the owner.
|
||||
|
||||
## Traps that have cost time here
|
||||
|
||||
- **A render can silently not happen.** Redirect logs with `>|`, not `>` (the shell refuses to
|
||||
overwrite an existing file and the command never starts). After every export, compare the file times
|
||||
with the clock before sending anything. Run one export at a time.
|
||||
- **Glows**: `drop-shadow` on SVG shapes is fine. Blur filters under a CSS mask, blend modes and dashed
|
||||
strokes came out with hard edges; build soft light from gradients that fade to transparent.
|
||||
- **The stage is not clipped by itself.** While the camera is close, the drawing is larger than the
|
||||
stage; `FilmStage` fades it at the edges. Do not rely on things being "off screen".
|
||||
- **Do not name a helper script `copy.py`, `json.py` or similar**: it shadows the Python module.
|
||||
- **Text from an old worktree is stale.** The website changed language and wording during this work.
|
||||
|
||||
## Formats and output
|
||||
|
||||
Compositions are `<VideoId>-<de|en>-<feed|wide|square|story>`. `node scripts/export.ts` renders
|
||||
`feed` (4:5, LinkedIn) and `wide` (16:9, YouTube and ads) in both languages into
|
||||
`out/library/<VideoId>/<lang>/`, with `post.txt` and an overview page `out/library/index.html`.
|
||||
`out/` is not committed.
|
||||
Reference in New Issue
Block a user