Files
flatrender/services/remotion/src/templates.tsx
T
soroush.asadi a3152ee84f feat(remotion): premium CharacterStory template (13 flexible scenes) + fix detail-page SSR
- CharacterStory: refined flat-illustration character (gradient-shaded sweater,
  modern hair, calm minimal face), muted editorial palette (coral/teal/sand/navy),
  abstract environment (soft depth blobs, ground "stage", sparse particles,
  vignette + grain), scene-number kicker. Verified in 16:9/1:1/9:16 and all poses.
- seed: 13 editable scene cards (c1..c13, keys s{N}_title/s{N}_text) via new
  MULTISCENE path; per-aspect previews; muted defaults.
- assets: 3 thumbnails + 4 preview MP4s vendored into public/template-media.
- fix: load BrandedVideoPlayer (plyr-react) client-only via next/dynamic
  (ssr:false) — plyr touches `document` at import, which was 500-ing every
  template detail page during SSR.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 16:58:48 +03:30

265 lines
12 KiB
TypeScript

/**
* Registry of FlatRender branded templates. Each entry is rendered into the
* three supported aspects (16:9 / 1:1 / 9:16) by Root.tsx, producing composition
* ids like "LogoMotion-16x9". Every template uses Persian text presets + the
* shared colour props so the studio can offer one consistent edit experience.
*/
import React from "react";
import type { AnyZodObject } from "zod";
import { BRAND } from "./lib/branding";
import { LogoMotion, logoMotionSchema } from "./compositions/LogoMotion";
import { Opener, openerSchema } from "./compositions/Opener";
import { InstaPromo, instaPromoSchema } from "./compositions/InstaPromo";
import { YouTubeIntro, youTubeIntroSchema } from "./compositions/YouTubeIntro";
import { Slideshow, slideshowSchema } from "./compositions/Slideshow";
import { HappyBirthday, happyBirthdaySchema } from "./compositions/HappyBirthday";
import { SalePromo, salePromoSchema } from "./compositions/SalePromo";
import { QuoteCard, quoteCardSchema } from "./compositions/QuoteCard";
import { EventInvite, eventInviteSchema } from "./compositions/EventInvite";
import { Countdown, countdownSchema } from "./compositions/Countdown";
import { GlitterReveal, glitterRevealSchema } from "./compositions/GlitterReveal";
import { NowruzGreeting, nowruzGreetingSchema } from "./compositions/NowruzGreeting";
import { Hero3D, hero3DSchema } from "./compositions/Hero3D";
import { Nowruz3D, nowruz3DSchema } from "./compositions/Nowruz3D";
import { Birthday3D, birthday3DSchema } from "./compositions/Birthday3D";
import { Promo3D, promo3DSchema } from "./compositions/Promo3D";
import { AppShowcase3D, appShowcase3DSchema } from "./compositions/AppShowcase3D";
import { CharacterStory, characterStorySchema, characterStoryDefaults } from "./compositions/CharacterStory";
export interface TemplateDef {
/** Base id; the registered composition ids are `${id}-${aspect}`. */
id: string;
/** Persian display name (used when seeding the site catalog). */
name: string;
/** Short Persian description for the catalog. */
description: string;
/** Default/shared component — used for any aspect without an override below. */
component: React.FC<any>; // eslint-disable-line @typescript-eslint/no-explicit-any
/**
* Optional per-aspect component overrides, keyed by aspect id
* ("16x9" | "1x1" | "9x16"). Provide one when a design must differ
* STRUCTURALLY between aspects (a different layout/scene, not just
* repositioning — repositioning should be done responsively inside the shared
* `component` via useLayout). Aspects you omit fall back to `component`.
*/
componentsByAspect?: Partial<Record<string, React.FC<any>>>; // eslint-disable-line @typescript-eslint/no-explicit-any
schema: AnyZodObject;
durationSec: number;
defaultProps: Record<string, unknown>;
}
const c = (accent: string, secondary: string, bg: string) => ({
accentColor: accent,
secondaryColor: secondary,
backgroundColor: bg,
textColor: BRAND.white,
});
export const TEMPLATES: TemplateDef[] = [
{
id: "LogoMotion",
name: "موشن لوگو",
description: "نمایش حرفه‌ای لوگو و نام برند با درخشش و حرکت",
component: LogoMotion,
schema: logoMotionSchema,
durationSec: 5,
defaultProps: { brandText: "فلت‌رندر", tagline: "موشن، ساده و حرفه‌ای", ...c(BRAND.blue, BRAND.purple, "#04060f") },
},
{
id: "Opener",
name: "تیتراژ آغازین",
description: "شروع سینمایی برای ویدیو با عنوان و زیرعنوان",
component: Opener,
schema: openerSchema,
durationSec: 5,
defaultProps: { kicker: "تقدیم می‌کند", title: "یک شروع تازه", subtitle: "داستان شما از همین‌جا آغاز می‌شود", ...c(BRAND.cyan, "#6366f1", "#0a0a12") },
},
{
id: "InstaPromo",
name: "تبلیغ پیج اینستاگرام",
description: "معرفی و تبلیغ صفحهٔ اینستاگرام با دعوت به فالو",
component: InstaPromo,
schema: instaPromoSchema,
durationSec: 5,
defaultProps: { handle: "@flatrender", headline: "پیج ما را دنبال کنید", subtext: "هر روز محتوای تازه و الهام‌بخش", cta: "فالو کنید", ...c(BRAND.pink, BRAND.amber, "#140a12") },
},
{
id: "YouTubeIntro",
name: "اینترو کانال یوتیوب",
description: "اینترو حرفه‌ای کانال یوتیوب با دکمهٔ سابسکرایب",
component: YouTubeIntro,
schema: youTubeIntroSchema,
durationSec: 5,
defaultProps: { channelName: "کانال فلت‌رندر", subtitle: "آموزش، ترفند و انگیزه", cta: "سابسکرایب کنید", ...c("#ff4d4d", BRAND.purple, "#0c0810") },
},
{
id: "Slideshow",
name: "اسلایدشو",
description: "نمایش پشت‌سرهم چند پیام یا ویژگی به‌صورت اسلاید",
component: Slideshow,
schema: slideshowSchema,
durationSec: 9,
defaultProps: { title: "چرا فلت‌رندر؟", slide1: "ساخت ویدیو در چند دقیقه", slide2: "بدون نیاز به دانش فنی", slide3: "خروجی با کیفیت حرفه‌ای", ...c(BRAND.green, "#3b82f6", "#060b0a") },
},
{
id: "HappyBirthday",
name: "تولدت مبارک",
description: "کارت تبریک تولد با کاغذرنگی و نام شخص",
component: HappyBirthday,
schema: happyBirthdaySchema,
durationSec: 6,
defaultProps: { greeting: "تولدت مبارک", name: "سارا", message: "بهترین‌ها را برایت آرزومندیم 🎉", ...c(BRAND.pink, "#fde047", "#140a18") },
},
{
id: "SalePromo",
name: "فروش ویژه",
description: "بنر تبلیغاتی فروش و تخفیف با دعوت به خرید",
component: SalePromo,
schema: salePromoSchema,
durationSec: 5,
defaultProps: { badge: "۵۰٪ تخفیف", headline: "فروش ویژهٔ پایان فصل", subtext: "فقط تا پایان همین هفته", cta: "همین حالا خرید کنید", ...c(BRAND.amber, BRAND.pink, "#120a08") },
},
{
id: "QuoteCard",
name: "کارت نقل‌قول",
description: "نمایش جملهٔ انگیزشی یا نقل‌قول با نام گوینده",
component: QuoteCard,
schema: quoteCardSchema,
durationSec: 6,
defaultProps: { quote: "موفقیت، مجموع تلاش‌های کوچکِ هر روز است.", author: "فلت‌رندر", ...c(BRAND.cyan, "#6366f1", "#0a0a12") },
},
{
id: "EventInvite",
name: "دعوت‌نامهٔ رویداد",
description: "دعوت‌نامهٔ شیک برای رویداد با تاریخ و مکان",
component: EventInvite,
schema: eventInviteSchema,
durationSec: 6,
defaultProps: { kicker: "دعوت‌نامه", eventTitle: "همایش سالانهٔ نوآوری", date: "۱۵ مهر ۱۴۰۳", location: "تهران، سالن همایش‌ها", cta: "ثبت‌نام کنید", ...c(BRAND.purple, BRAND.blue, "#0a0814") },
},
{
id: "Countdown",
name: "شمارش معکوس",
description: "شمارش معکوس هیجان‌انگیز برای شروع یک رویداد",
component: Countdown,
schema: countdownSchema,
durationSec: 8,
defaultProps: { title: "شروع رویداد تا", startNumber: 5, goText: "شروع!", subtitle: "آماده‌اید؟", ...c(BRAND.blue, BRAND.cyan, "#04060f") },
},
{
id: "GlitterReveal",
name: "نمایش لوگو با غبار درخشان",
description: "نمایش جادویی لوگو با ذرات درخشان؛ لوگو و متن قابل ویرایش",
component: GlitterReveal,
schema: glitterRevealSchema,
durationSec: 6,
defaultProps: { brandText: "فلت‌رندر", tagline: "موشن، ساده و حرفه‌ای", logoUrl: "", ...c(BRAND.blue, BRAND.purple, "#05040e") },
},
{
id: "NowruzGreeting",
name: "تبریک نوروز",
description: "صحنهٔ بهاری نوروز با شخصیت‌های متحرک؛ حاجی‌فیروز، ماهی قرمز و سبزه",
component: NowruzGreeting,
schema: nowruzGreetingSchema,
durationSec: 7.5,
defaultProps: {
greeting: "نوروز مبارک",
subtitle: "سال نو پیروز و شادمان",
message: "۱۴۰۶",
accentColor: "#f5b942",
secondaryColor: "#e23b3b",
backgroundColor: "#1fb6b0",
textColor: "#fdf6e3",
},
},
{
id: "Hero3D",
name: "نمایش سه‌بعدی برند",
description: "نمایش حرفه‌ای و سه‌بعدی لوگو و برند با نورپردازی و جلوه‌های واقعی",
component: Hero3D,
schema: hero3DSchema,
durationSec: 6,
defaultProps: { brandText: "فلت‌رندر", tagline: "موشن، ساده و حرفه‌ای", ...c(BRAND.blue, BRAND.purple, "#04060f") },
},
{
id: "Nowruz3D",
name: "تبریک نوروز سه‌بعدی",
description: "صحنهٔ سه‌بعدی نوروز با حاجی‌فیروز، سفرهٔ هفت‌سین و نورپردازی سینمایی",
component: Nowruz3D,
schema: nowruz3DSchema,
durationSec: 7,
defaultProps: {
greeting: "نوروز مبارک",
subtitle: "سال نو پیروز و شادمان",
message: "۱۴۰۶",
accentColor: "#f5c542",
secondaryColor: "#e23b3b",
backgroundColor: "#1a1228",
textColor: "#fdf6e3",
},
},
{
id: "Birthday3D",
name: "تولد سه‌بعدی",
description: "صحنهٔ سه‌بعدی تولد با کیک و شمع‌های روشن، بادکنک و کاغذرنگی",
component: Birthday3D,
schema: birthday3DSchema,
durationSec: 6,
defaultProps: {
greeting: "تولدت مبارک",
name: "سارا",
message: "بهترین‌ها را برایت آرزومندیم 🎉",
accentColor: "#fb7185",
secondaryColor: "#a855f7",
backgroundColor: "#1a1226",
textColor: "#fdf6e3",
},
},
{
id: "Promo3D",
name: "فروش ویژه سه‌بعدی",
description: "تبلیغ سه‌بعدی فروش و تخفیف با جعبه‌های هدیه و نورپردازی سینمایی",
component: Promo3D,
schema: promo3DSchema,
durationSec: 6,
defaultProps: {
badge: "۵۰٪ تخفیف",
headline: "فروش ویژهٔ پایان فصل",
subtext: "فقط تا پایان همین هفته",
cta: "همین حالا خرید کنید",
accentColor: "#f59e0b",
secondaryColor: "#fb7185",
backgroundColor: "#140e1f",
textColor: "#ffffff",
},
},
{
id: "AppShowcase3D",
name: "معرفی اپلیکیشن سه‌بعدی",
description: "نمایش سه‌بعدی و حرفه‌ای اپلیکیشن روی گوشی پرچم‌دار با نورپردازی استودیویی",
component: AppShowcase3D,
schema: appShowcase3DSchema,
durationSec: 6,
defaultProps: {
appName: "اپلیکیشن شما",
tagline: "تجربه‌ای روان، سریع و زیبا",
cta: "همین حالا دانلود کنید",
screenUrl: "",
accentColor: "#3b82f6",
secondaryColor: "#8b5cf6",
backgroundColor: "#f4f5f7",
textColor: "#0f172a",
},
},
{
id: "CharacterStory",
name: "داستان شخصیتی (۱۳ صحنه)",
description: "قالب داستان‌گویی منعطف با شخصیت متحرک؛ تا ۱۳ صحنهٔ قابل‌ویرایش (صحنه‌های خالی نمایش داده نمی‌شوند)",
component: CharacterStory,
schema: characterStorySchema,
durationSec: 39,
defaultProps: characterStoryDefaults,
},
];