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>
This commit is contained in:
@@ -16,6 +16,29 @@ def q(s): return "'" + str(s).replace("'", "''") + "'"
|
||||
|
||||
ASPECTS = [("16x9", 1920, 1080, "16:9"), ("1x1", 1080, 1080, "1:1"), ("9x16", 1080, 1920, "9:16")]
|
||||
CTITLES = {"accentColor": "رنگ اصلی", "secondaryColor": "رنگ دوم", "backgroundColor": "پسزمینه", "textColor": "رنگ متن"}
|
||||
SCENE_SECONDS = 3 # CharacterStory: per-scene duration
|
||||
|
||||
# CharacterStory: 13 explainer beats → 26 content fields (title + caption per scene).
|
||||
# Keys s{N}_title / s{N}_text match the globally-unique Remotion props.
|
||||
CS_BEATS = [
|
||||
("داستان شما", "روایت خود را در سیزده صحنه بسازید"),
|
||||
("معرفی", "شخصیت یا برند خود را معرفی کنید"),
|
||||
("شروع ماجرا", "همهچیز از یک ایده آغاز شد"),
|
||||
("یک چالش", "اما یک مشکل سر راه پیدا شد"),
|
||||
("جستوجو", "به دنبال یک راهحل گشتیم"),
|
||||
("قدم اول", "اولین قدم را برداشتیم"),
|
||||
("یک مانع", "همهچیز آسان نبود"),
|
||||
("نقطهٔ عطف", "و سپس همهچیز تغییر کرد"),
|
||||
("ایده", "راهحل را پیدا کردیم"),
|
||||
("اقدام", "دست به کار شدیم"),
|
||||
("اوج داستان", "بزرگترین لحظه فرا رسید"),
|
||||
("نتیجه", "و به هدف رسیدیم"),
|
||||
("پایان", "همین حالا داستان خود را بسازید"),
|
||||
]
|
||||
CS_TEXTS = []
|
||||
for _i, (_t, _c) in enumerate(CS_BEATS, 1):
|
||||
CS_TEXTS.append((f"s{_i}_title", f"صحنهٔ {_i} — عنوان", _t))
|
||||
CS_TEXTS.append((f"s{_i}_text", f"صحنهٔ {_i} — متن", _c))
|
||||
|
||||
# id, slug, name(fa), desc(fa), dur, [(textKey,title,value)], (accent,secondary,bg)
|
||||
T = [
|
||||
@@ -53,6 +76,8 @@ T = [
|
||||
[("badge","نشان تخفیف","۵۰٪ تخفیف"),("headline","عنوان","فروش ویژهٔ پایان فصل"),("subtext","توضیح","فقط تا پایان همین هفته"),("cta","دکمه","همین حالا خرید کنید")],("#f59e0b","#fb7185","#140e1f")),
|
||||
("AppShowcase3D","fr-app-showcase","معرفی اپلیکیشن سهبعدی","نمایش سهبعدی و حرفهای اپلیکیشن روی گوشی پرچمدار با نورپردازی استودیویی",6,
|
||||
[("appName","نام اپلیکیشن","اپلیکیشن شما"),("tagline","شعار","تجربهای روان، سریع و زیبا"),("cta","دکمه","همین حالا دانلود کنید")],("#3b82f6","#8b5cf6","#f4f5f7")),
|
||||
("CharacterStory","fr-character-story","داستان شخصیتی (۱۳ صحنه)","روایت داستان شما در سیزده صحنهٔ متحرک با شخصیت؛ تصویرسازی مدرن و مینیمال، صحنهها کاملاً قابل ویرایش و انعطافپذیر",39,
|
||||
CS_TEXTS,("#cf8a76","#6f9d96","#ece4d6")),
|
||||
]
|
||||
|
||||
# Optional Media (image) content elements per template — these surface in the
|
||||
@@ -65,11 +90,16 @@ MEDIA = {
|
||||
# Per-template text colour (default white for dark backgrounds; dark for light studios).
|
||||
TEXTCOLORS = {
|
||||
"AppShowcase3D": "#0f172a",
|
||||
"CharacterStory": "#2b3a55",
|
||||
}
|
||||
|
||||
# Templates that ship a distinct preview video PER aspect (so the detail page shows
|
||||
# the matching render, not the 16:9 cropped). Others reuse the single 16:9 preview.
|
||||
PERASPECT_VIDEO = {"AppShowcase3D"}
|
||||
PERASPECT_VIDEO = {"AppShowcase3D", "CharacterStory"}
|
||||
|
||||
# Templates whose content is split across MANY scenes (key c1..cN), one editable
|
||||
# scene card per beat. value = scene count; texts are assigned 2-per-scene in order.
|
||||
MULTISCENE = {"CharacterStory": len(CS_BEATS)}
|
||||
|
||||
def swatch_svg(colors):
|
||||
rects = "".join(f'<rect x="{i*50}" y="0" width="50" height="40" fill="{c}"/>' for i, c in enumerate(colors))
|
||||
@@ -104,17 +134,30 @@ for idx, (tid, slug, name, desc, dur, texts, (accent, sec, bg)) in enumerate(T):
|
||||
"project_duration_sec,free_fps,choose_mode,resolution,render_engine,render_remotion_comp,is_published,sort) VALUES ("
|
||||
f"{q(pid)},{q(cid)},{q(aspstr)},{q(thumb)},{q(pvideo)},{w},{h},{q(aspstr)},"
|
||||
f"{dur},30,'FLEXIBLE','FullHD','Remotion',{q(tid+'-'+asp)},TRUE,0);")
|
||||
out.append(
|
||||
"INSERT INTO content.scenes (id,project_id,key,title,scene_color_svg,default_duration_sec,sort) VALUES ("
|
||||
f"{q(sid)},{q(pid)},'c1','صحنه ۱',{q(swatch_svg([accent,sec,bg,txt]))},{dur},0);")
|
||||
for pos, (k, title, val) in enumerate(texts):
|
||||
nscenes = MULTISCENE.get(tid, 1)
|
||||
if nscenes > 1:
|
||||
# one editable scene card per beat; 2 text fields (title+caption) each.
|
||||
for sc in range(1, nscenes + 1):
|
||||
skid = uid(f"s-{tid}-{asp}-{sc}")
|
||||
out.append(
|
||||
"INSERT INTO content.scenes (id,project_id,key,title,scene_color_svg,default_duration_sec,sort) VALUES ("
|
||||
f"{q(skid)},{q(pid)},{q('c'+str(sc))},{q('صحنه '+str(sc))},{q(swatch_svg([accent,sec,bg,txt]))},{SCENE_SECONDS},{sc-1});")
|
||||
for pos, (k, title, val) in enumerate(texts[(sc - 1) * 2: sc * 2]):
|
||||
out.append(
|
||||
"INSERT INTO content.scene_content_elements (id,scene_id,key,title,type,default_value,position_in_container,direction_layer_value) VALUES ("
|
||||
f"{q(uid(f'ce-{tid}-{asp}-{k}'))},{q(skid)},{q(k)},{q(title)},'Text',{q(val)},{pos},1);")
|
||||
else:
|
||||
out.append(
|
||||
"INSERT INTO content.scene_content_elements (id,scene_id,key,title,type,default_value,position_in_container,direction_layer_value) VALUES ("
|
||||
f"{q(uid(f'ce-{tid}-{asp}-{k}'))},{q(sid)},{q(k)},{q(title)},'Text',{q(val)},{pos},1);")
|
||||
for mpos, (k, title) in enumerate(MEDIA.get(tid, [])):
|
||||
out.append(
|
||||
"INSERT INTO content.scene_content_elements (id,scene_id,key,title,type,default_value,position_in_container,direction_layer_value) VALUES ("
|
||||
f"{q(uid(f'ce-{tid}-{asp}-{k}'))},{q(sid)},{q(k)},{q(title)},'Media','',{len(texts)+mpos},0);")
|
||||
"INSERT INTO content.scenes (id,project_id,key,title,scene_color_svg,default_duration_sec,sort) VALUES ("
|
||||
f"{q(sid)},{q(pid)},'c1','صحنه ۱',{q(swatch_svg([accent,sec,bg,txt]))},{dur},0);")
|
||||
for pos, (k, title, val) in enumerate(texts):
|
||||
out.append(
|
||||
"INSERT INTO content.scene_content_elements (id,scene_id,key,title,type,default_value,position_in_container,direction_layer_value) VALUES ("
|
||||
f"{q(uid(f'ce-{tid}-{asp}-{k}'))},{q(sid)},{q(k)},{q(title)},'Text',{q(val)},{pos},1);")
|
||||
for mpos, (k, title) in enumerate(MEDIA.get(tid, [])):
|
||||
out.append(
|
||||
"INSERT INTO content.scene_content_elements (id,scene_id,key,title,type,default_value,position_in_container,direction_layer_value) VALUES ("
|
||||
f"{q(uid(f'ce-{tid}-{asp}-{k}'))},{q(sid)},{q(k)},{q(title)},'Media','',{len(texts)+mpos},0);")
|
||||
for si, (k, hexv) in enumerate(colors):
|
||||
out.append(
|
||||
"INSERT INTO content.shared_colors (id,project_id,element_key,title,icon,attr_value,default_color,sort) VALUES ("
|
||||
|
||||
Reference in New Issue
Block a user