diff --git a/services/remotion/briefs/character-journey.md b/services/remotion/briefs/character-journey.md new file mode 100644 index 0000000..605b6c3 --- /dev/null +++ b/services/remotion/briefs/character-journey.md @@ -0,0 +1,46 @@ +# Template Spec — CharacterJourney (pilot) + +Produced by the guided brief (`docs/TEMPLATE_BRIEF.md`). The first filled spec — +serves as the audit trail and the contract a future AI/user-form would emit. + +## Decisions (from the brief) +- **Type:** character explainer / story (framework targets ALL Renderforest-style + types — logo reveal, opener, slideshow, promo, showcase, personal/commercial — via + the shared scene-block engine; this is the first concrete template). +- **Story:** Idea → struggle → tool → win. +- **Design ingredients (all on):** flat 2.5D characters · 3D depth + parallax · + kinetic typography · film finish (grain/vignette/micro-motion). +- **Timing:** standard — 3s default, editable 1–6s per scene. +- **Colors:** theme + 4-color tweak (curated `THEMES`, then accent/secondary/bg/text + override). Pilot theme: `warm-editorial`. +- **Music:** per-category/vibe — a tagged library; this arc → "uplifting build". + *(Dependency: vendored CC0 music not yet sourced — audio hooks designed, stubbed.)* +- **SFX:** optional, operator-placed (whoosh/click on transitions), user-toggle. + *(Dependency: vendored CC0 SFX not yet sourced.)* +- **Aspect:** all three (16:9 / 1:1 / 9:16), re-flowed. + +## Scene list (storyboard) — `src/scenes/presets.ts` `CHARACTER_JOURNEY` +| # | Block | Dur | Beat | Copy (fa) | +|---|---|---|---|---| +| 1 | TitleCard | 4s | Hook | «از یک ایده تا واقعیت» | +| 2 | CharacterScene | 3s | Idea | «یک ایده» (cup) | +| 3 | CharacterScene | 3s | Struggle | «اما سخت بود» | +| 4 | CharacterScene | 3s | Tool | «تا اینکه…» (laptop) | +| 5 | Slideshow | 6s | Value | «چرا فلت‌رندر؟» × 3 | +| 6 | CharacterScene | 3s | Win | «و حالا…» (plant) | +| 7 | OutroCTA | 4s | CTA | «فلت‌رندر» + «رایگان شروع کن» | + +Total ≈ 26s (dynamic; tracks Σ per-scene durations). + +## Editable surface (the "rails") +- **Content:** per-scene title/caption/text/character/prop/slides (bounded fields, + smart defaults). +- **Structure:** flexible — add/duplicate/delete/reorder scenes; per-scene duration 1–6s. +- **Theme:** pick a `THEMES` entry, then tweak the 4 brand colors. +- **Locked (not user-editable):** layout, motion language, finishing pass, type scale — + the craft stays in the system. + +## Status +- Renders via FlexStory in all 3 aspects (engine = Phase 1, committed). +- Pending: theme picker in studio, music/SFX audio assets, seed into the catalog, + backend render passthrough (Phase 2). diff --git a/services/remotion/src/Root.tsx b/services/remotion/src/Root.tsx index 85e0a74..d1203c9 100644 --- a/services/remotion/src/Root.tsx +++ b/services/remotion/src/Root.tsx @@ -5,6 +5,7 @@ import { Three3DTest } from "./compositions/Three3DTest"; import { AssetSheet } from "./compositions/AssetSheet"; import { StoryScenes, STORY_SCENES_DURATION } from "./compositions/StoryScenes"; import { FlexStory, flexStorySchema, flexStoryDefaults, calcFlexStoryMetadata } from "./compositions/FlexStory"; +import { CHARACTER_JOURNEY } from "./scenes/presets"; import { IlluminatedCircles, illuminatedCirclesSchema, @@ -156,6 +157,22 @@ export const RemotionRoot: React.FC = () => { /> ))} + {/* CharacterJourney — pilot template: a curated FlexStory scene list (theme + story). */} + {ASPECTS.map((a) => ( + + ))} + {/* Branded templates — each registered in all three aspects. A template may supply a dedicated component per aspect (componentsByAspect) when its design differs structurally; otherwise the shared `component` adapts diff --git a/services/remotion/src/scenes/presets.ts b/services/remotion/src/scenes/presets.ts new file mode 100644 index 0000000..aa50084 --- /dev/null +++ b/services/remotion/src/scenes/presets.ts @@ -0,0 +1,23 @@ +import { getTheme } from "./themes"; +import type { SceneInstance } from "./types"; + +/** + * Template presets — a "template" is a curated default scene list + theme. The + * studio instantiates this; the user then edits content / durations / reorders / + * adds-removes scenes within the flexible engine. + * + * CharacterJourney — the pilot: "Idea → struggle → tool → win" (Persian). + */ +const warm = getTheme("warm-editorial").colors; + +const journeyScenes: SceneInstance[] = [ + { blockId: "TitleCard", durationSec: 4, props: { kicker: "داستان شما", title: "از یک ایده تا واقعیت", subtitle: "چطور ایده‌ات را به یک ویدیوی حرفه‌ای تبدیل می‌کنی" } }, + { blockId: "CharacterScene", durationSec: 3, props: { title: "یک ایده", caption: "همه‌چیز با یک جرقهٔ کوچک شروع شد", character: "illustrations/dicebear/openpeeps-04.svg", prop: "cup" } }, + { blockId: "CharacterScene", durationSec: 3, props: { title: "اما سخت بود", caption: "ساختن یک ویدیوی حرفه‌ای پیچیده به‌نظر می‌رسید", character: "illustrations/dicebear/openpeeps-11.svg", prop: "none" } }, + { blockId: "CharacterScene", durationSec: 3, props: { title: "تا اینکه…", caption: "با فلت‌رندر آشنا شدم", character: "illustrations/dicebear/openpeeps-21.svg", prop: "laptop" } }, + { blockId: "Slideshow", durationSec: 6, props: { title: "چرا فلت‌رندر؟", slide1: "ساخت در چند دقیقه", slide2: "هزینهٔ بسیار پایین", slide3: "کیفیت حرفه‌ای", slide4: "" } }, + { blockId: "CharacterScene", durationSec: 3, props: { title: "و حالا…", caption: "داستان خودم را می‌سازم", character: "illustrations/dicebear/openpeeps-27.svg", prop: "plant" } }, + { blockId: "OutroCTA", durationSec: 4, props: { brandText: "فلت‌رندر", tagline: "همین حالا داستان خود را بساز", cta: "رایگان شروع کن" } }, +]; + +export const CHARACTER_JOURNEY = { scenes: journeyScenes, ...warm }; diff --git a/services/remotion/src/scenes/themes.ts b/services/remotion/src/scenes/themes.ts new file mode 100644 index 0000000..af0e445 --- /dev/null +++ b/services/remotion/src/scenes/themes.ts @@ -0,0 +1,24 @@ +import type { SceneColors } from "./types"; + +/** + * Curated themes — the cohesion rail. A user picks ONE theme (a pre-balanced + * look) and may then fine-tune the four brand colors. Because every block derives + * its shades from these four roles (mixHex), one theme re-skins the whole video + * coherently — the single biggest lever for the "designed" feel. + */ +export interface Theme { + id: string; + label: string; // Persian name shown in the studio + colors: SceneColors; +} + +export const THEMES: Theme[] = [ + { id: "warm-editorial", label: "ادیتوریال گرم", colors: { accentColor: "#cf8a76", secondaryColor: "#6f9d96", backgroundColor: "#ece4d6", textColor: "#2b3a55" } }, + { id: "ocean-calm", label: "آرامش اقیانوس", colors: { accentColor: "#3b82c4", secondaryColor: "#5fb3b3", backgroundColor: "#eaf0f4", textColor: "#1f3a4d" } }, + { id: "berry-pop", label: "توت‌فرنگی", colors: { accentColor: "#d6477e", secondaryColor: "#8b5cf6", backgroundColor: "#f3ebf0", textColor: "#3b2440" } }, + { id: "forest", label: "جنگل", colors: { accentColor: "#5a9367", secondaryColor: "#c2a76a", backgroundColor: "#ecefe6", textColor: "#26352a" } }, + { id: "mono-luxe", label: "لوکس مینیمال", colors: { accentColor: "#c0a062", secondaryColor: "#6b7280", backgroundColor: "#f4f1ea", textColor: "#1c1c1e" } }, + { id: "midnight", label: "نیمه‌شب", colors: { accentColor: "#7c93ff", secondaryColor: "#22d3ee", backgroundColor: "#11141f", textColor: "#eef1f8" } }, +]; + +export const getTheme = (id: string): Theme => THEMES.find((t) => t.id === id) ?? THEMES[0];