Files
flatrender/services/remotion/src/Root.tsx
T
soroush.asadi 7ed2ccc414 feat(remotion): Instagram channel-promo template + taste system + design-quality kit
The reference-round workflow, run end to end for a real template:

Taste system (how we learn the user's taste, persisted):
- references/TASTE_PROFILE.md (living design contract) + references/README.md (the
  daily loop) + a "reference round" stage in docs/TEMPLATE_BRIEF.md (provide refs or
  I suggest+mock directions).

Design-quality before/after:
- HeroDemo — the fix recipe vs the faint default: layered-depth background, a proper
  big video type scale, and a bold composed focal object. (Backgrounds were naked,
  text too small, scenes had no objects.)
- YaldaSofreh3D + IGPromoDirections + IGProfileMock — reference-match proofs
  (low-poly 3D, 3 IG-promo style directions, the realistic IG-light page).

Instagram channel-promo template (the deliverable — a flexible 5-scene FlexStory):
- igkit + 5 blocks: IGIntro, IGProfile (realistic IG-light profile, scales to all
  aspects), IGFeed (post grid), IGStats (animated count-up), IGFollowCTA (Follow taps
  to "Following").
- FlexStory gains a `finish` toggle so the IG-light scenes render clean (no brand
  grade). INSTAGRAM_PROMO preset + 3 aspect comps in Root.

Verified: a still of every scene at 9:16 renders clean; full preview MP4 rendering.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 09:16:31 +03:30

280 lines
8.4 KiB
TypeScript

import { Composition } from "remotion";
import { ASPECTS } from "./lib/aspect";
import { TEMPLATES } from "./templates";
import { Three3DTest } from "./compositions/Three3DTest";
import { YaldaSofreh3D } from "./compositions/YaldaSofreh3D";
import { HeroDemo } from "./compositions/HeroDemo";
import { IGPromoDirections, igPromoSchema } from "./compositions/IGPromoDirections";
import { IGProfileMock } from "./compositions/IGProfileMock";
import { AssetSheet } from "./compositions/AssetSheet";
import { StoryScenes, STORY_SCENES_DURATION } from "./compositions/StoryScenes";
import { FlexStory, flexStorySchema, flexStoryDefaults, calcFlexStoryMetadata } from "./compositions/FlexStory";
import { LogoMotion3D, logoMotion3DSchema, logoMotion3DDefaults } from "./compositions/LogoMotion3D";
import { CHARACTER_JOURNEY, INSTAGRAM_PROMO } from "./scenes/presets";
import {
IlluminatedCircles,
illuminatedCirclesSchema,
} from "./compositions/IlluminatedCircles";
import {
KineticQuote,
kineticQuoteSchema,
} from "./compositions/KineticQuote";
import {
GradientPromo,
gradientPromoSchema,
} from "./compositions/GradientPromo";
import {
VerticalStory,
verticalStorySchema,
} from "./compositions/VerticalStory";
const FPS = 30;
export const RemotionRoot: React.FC = () => {
return (
<>
{/* Logo intro — 16:9 */}
<Composition
id="IlluminatedCircles"
component={IlluminatedCircles}
durationInFrames={FPS * 6}
fps={FPS}
width={1920}
height={1080}
schema={illuminatedCirclesSchema}
defaultProps={{
logoText: "FLATRENDER",
tagline: "MOTION MADE SIMPLE",
accentColor: "#3ba7ff",
secondaryColor: "#a855f7",
backgroundColor: "#04060f",
}}
/>
{/* Kinetic typography quote — 1:1 social */}
<Composition
id="KineticQuote"
component={KineticQuote}
durationInFrames={FPS * 7}
fps={FPS}
width={1080}
height={1080}
schema={kineticQuoteSchema}
defaultProps={{
quote: "Great motion design is felt long before it is noticed.",
author: "FlatRender Studio",
accentColor: "#22d3ee",
secondaryColor: "#6366f1",
backgroundColor: "#0a0a12",
}}
/>
{/* Marketing / sale promo — 16:9 */}
<Composition
id="GradientPromo"
component={GradientPromo}
durationInFrames={FPS * 6}
fps={FPS}
width={1920}
height={1080}
schema={gradientPromoSchema}
defaultProps={{
eyebrow: "Limited time offer",
headline: "Make videos that move people.",
subheadline:
"Customizable code-based templates, rendered in the cloud in minutes.",
ctaText: "Start free →",
badgeText: "50% OFF",
accentColor: "#fb7185",
secondaryColor: "#f59e0b",
backgroundColor: "#0c0a14",
}}
/>
{/* Vertical social story — 9:16 */}
<Composition
id="VerticalStory"
component={VerticalStory}
durationInFrames={FPS * 6}
fps={FPS}
width={1080}
height={1920}
schema={verticalStorySchema}
defaultProps={{
kicker: "New drop",
line1: "Your story.",
line2: "Your style.",
line3: "One tap.",
ctaText: "Swipe up",
accentColor: "#34d399",
secondaryColor: "#3b82f6",
backgroundColor: "#060b0a",
}}
/>
{/* Tech/3D logo motion — quality-preview composition */}
{ASPECTS.map((a) => (
<Composition
key={`LogoMotion3D-${a.id}`}
id={`LogoMotion3D-${a.id}`}
component={LogoMotion3D}
durationInFrames={5 * FPS}
fps={FPS}
width={a.width}
height={a.height}
schema={logoMotion3DSchema}
defaultProps={logoMotion3DDefaults}
/>
))}
{/* 3D feasibility test */}
<Composition
id="Three3DTest"
component={Three3DTest}
durationInFrames={120}
fps={30}
width={1280}
height={720}
/>
{/* Design-quality before/after demo (rich bg + big type + bold object) */}
{ASPECTS.map((a) => (
<Composition
key={`HeroDemo-${a.id}`}
id={`HeroDemo-${a.id}`}
component={HeroDemo}
durationInFrames={5 * FPS}
fps={FPS}
width={a.width}
height={a.height}
/>
))}
{/* Instagram profile mock — realistic light-theme page (gate still) */}
<Composition
id="IGProfileMock"
component={IGProfileMock}
durationInFrames={150}
fps={FPS}
width={1080}
height={1920}
/>
{/* Instagram promo — reference round (3 style directions to pick from) */}
<Composition
id="IGPromoDir"
component={IGPromoDirections}
durationInFrames={150}
fps={FPS}
width={1080}
height={1920}
schema={igPromoSchema}
defaultProps={{ variant: "A" as const }}
/>
{/* Low-poly Yalda sofreh — reference-match challenge */}
<Composition
id="YaldaSofreh3D"
component={YaldaSofreh3D}
durationInFrames={150}
fps={30}
width={1080}
height={1080}
/>
{/* Dev preview: vendored CC0 character library (not a customer template) */}
<Composition
id="AssetSheet"
component={AssetSheet}
durationInFrames={60}
fps={30}
width={1920}
height={1080}
/>
{/* 2.5D story scenes proof (Three.js room + flat CC0 characters) — dev preview */}
{ASPECTS.map((a) => (
<Composition
key={`StoryScenes-${a.id}`}
id={`StoryScenes-${a.id}`}
component={StoryScenes}
durationInFrames={STORY_SCENES_DURATION * FPS}
fps={FPS}
width={a.width}
height={a.height}
/>
))}
{/* FlexStory — the scene engine: an ordered list of editable scene blocks,
duration computed dynamically from the per-scene durations. */}
{ASPECTS.map((a) => (
<Composition
key={`FlexStory-${a.id}`}
id={`FlexStory-${a.id}`}
component={FlexStory}
durationInFrames={26 * FPS}
fps={FPS}
width={a.width}
height={a.height}
schema={flexStorySchema}
defaultProps={flexStoryDefaults}
calculateMetadata={calcFlexStoryMetadata}
/>
))}
{/* CharacterJourney — pilot template: a curated FlexStory scene list (theme + story). */}
{ASPECTS.map((a) => (
<Composition
key={`CharacterJourney-${a.id}`}
id={`CharacterJourney-${a.id}`}
component={FlexStory}
durationInFrames={26 * FPS}
fps={FPS}
width={a.width}
height={a.height}
schema={flexStorySchema}
defaultProps={CHARACTER_JOURNEY}
calculateMetadata={calcFlexStoryMetadata}
/>
))}
{/* InstagramPromo — "follow our channel" template (realistic IG-light page). */}
{ASPECTS.map((a) => (
<Composition
key={`InstagramPromo-${a.id}`}
id={`InstagramPromo-${a.id}`}
component={FlexStory}
durationInFrames={18 * FPS}
fps={FPS}
width={a.width}
height={a.height}
schema={flexStorySchema}
defaultProps={INSTAGRAM_PROMO}
calculateMetadata={calcFlexStoryMetadata}
/>
))}
{/* 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
responsively. */}
{TEMPLATES.flatMap((tpl) =>
ASPECTS.map((a) => (
<Composition
key={`${tpl.id}-${a.id}`}
id={`${tpl.id}-${a.id}`}
component={tpl.componentsByAspect?.[a.id] ?? tpl.component}
durationInFrames={Math.round(FPS * tpl.durationSec)}
fps={FPS}
width={a.width}
height={a.height}
schema={tpl.schema}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
defaultProps={tpl.defaultProps as any}
/>
))
)}
</>
);
};