feat: content strategy, image posts and a posting plan for the films

Adds twelve image posts made the same way as the films: six posters
(one picture of each film with its idea in two lines) and six cards
that answer a question clients ask before the first call. The answers
come from the questions and principles on the website.

The posting plan is data (six weeks, film, card and poster per week),
and the export writes the library in that order, by language, week and
day, with the text to post. All video posts now open with the hook of
their film. The reasoning is in plans/content-strategy.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-05 10:00:28 +02:00
co-authored by Claude Opus 5.5
parent 2dab728129
commit 5cbcb2eac4
26 changed files with 964 additions and 219 deletions
+24 -17
View File
@@ -1,27 +1,18 @@
import type { FC } from "react";
import { Composition } from "remotion";
import { FORMATS, FPS, type FormatName } from "./brand/tokens";
import { appRescue } from "./compositions/AppRescue";
import { automationDocuments } from "./compositions/AutomationDocuments";
import { automationEnquiry } from "./compositions/AutomationEnquiry";
import { automationOrder } from "./compositions/AutomationOrder";
import { productDeveloper } from "./compositions/ProductDeveloper";
import { websiteDone } from "./compositions/WebsiteDone";
import { IMAGES, IMAGE_IDS } from "./content/images";
import { LANGS } from "./content/videos";
import { ServiceVideo, videoDuration, type VideoEntry } from "./slides/ServiceVideo";
/** Register new videos here. Each one is offered as `<id>-<language>-<format>`. */
const VIDEOS: readonly VideoEntry[] = [
automationOrder,
automationDocuments,
automationEnquiry,
websiteDone,
appRescue,
productDeveloper,
];
import { ImagePost } from "./images/ImagePost";
import { VIDEOS } from "./registry";
import { SHOT_FRAMES } from "./films/shot";
import { ServiceVideo, videoDuration } from "./slides/ServiceVideo";
const FORMAT_NAMES = Object.keys(FORMATS) as FormatName[];
/** Images are posted in the feed: 4:5, and square for places that crop. */
const IMAGE_FORMATS = ["feed", "square"] as const;
/** Videos are `<VideoId>-<language>-<format>`, images `<ImageId>-<language>-<feed|square>`. */
export const Root: FC = () => (
<>
{VIDEOS.flatMap((video) =>
@@ -39,5 +30,21 @@ export const Root: FC = () => (
)),
),
)}
{IMAGE_IDS.flatMap((id) =>
LANGS.flatMap((lang) =>
IMAGE_FORMATS.map((format) => (
// A poster is a film at one of its frames, so it needs the film's length; a card is a single frame.
<Composition
key={`${id}-${lang}-${format}`}
id={`${id}-${lang}-${format}`}
component={() => <ImagePost id={id} lang={lang} />}
durationInFrames={IMAGES[id].poster ? SHOT_FRAMES : 1}
fps={FPS}
width={FORMATS[format].width}
height={FORMATS[format].height}
/>
)),
),
)}
</>
);