From aea3b4f8006c113d8e72e1ba83b30d1ffb02be8d Mon Sep 17 00:00:00 2001 From: Soroush Asadi Date: Sun, 21 Jun 2026 18:50:25 +0330 Subject: [PATCH] feat: add FlatRender Remotion template-creation skill suite 10 skills for building high-quality video templates with Remotion/Three.js: design-styles, character-design, aspect-ratios, template-composition, sound-effects, music-picker, template-catalog, svg-colors, persian-fonts, and flatrender-template-seo (category/tags/SEO/related, code-verified). Co-Authored-By: Claude Opus 4.8 --- flatrender-template-seo/SKILL.md | 171 +++++++++++++++++++++++++ persian-fonts/SKILL.md | 55 ++++++++ remotion-aspect-ratios/SKILL.md | 91 +++++++++++++ remotion-character-design/SKILL.md | 51 ++++++++ remotion-design-styles/SKILL.md | 49 +++++++ remotion-music-picker/SKILL.md | 59 +++++++++ remotion-sound-effects/SKILL.md | 56 ++++++++ remotion-svg-colors/SKILL.md | 45 +++++++ remotion-template-catalog/SKILL.md | 54 ++++++++ remotion-template-composition/SKILL.md | 60 +++++++++ 10 files changed, 691 insertions(+) create mode 100644 flatrender-template-seo/SKILL.md create mode 100644 persian-fonts/SKILL.md create mode 100644 remotion-aspect-ratios/SKILL.md create mode 100644 remotion-character-design/SKILL.md create mode 100644 remotion-design-styles/SKILL.md create mode 100644 remotion-music-picker/SKILL.md create mode 100644 remotion-sound-effects/SKILL.md create mode 100644 remotion-svg-colors/SKILL.md create mode 100644 remotion-template-catalog/SKILL.md create mode 100644 remotion-template-composition/SKILL.md diff --git a/flatrender-template-seo/SKILL.md b/flatrender-template-seo/SKILL.md new file mode 100644 index 0000000..286a113 --- /dev/null +++ b/flatrender-template-seo/SKILL.md @@ -0,0 +1,171 @@ +--- +name: flatrender-template-seo +description: >- + Classifies and merchandises a FlatRender video template when an admin or developer creates or + publishes it. Decides the single primary category, the faceted tags, the SEO title/description/ + keywords/slug, the Persian-first presentation copy, and the related-templates set — every choice + driven by the template's actual design, editable content, and usability. Use whenever creating, + editing, publishing, or seeding a template (project_container) and you must fill category_ids, + tag_ids, keywords, slug, description, news_text, or write its catalog/detail copy. Persian (fa) is + the source of truth; English (en) mirrors it 1:1. +--- + +# FlatRender Template SEO & Taxonomy + +Decide a template's category, tags, SEO, presentation copy, and related set — grounded in what the +template *actually is*, not what you wish it ranked for. **fa is canonical; en mirrors it.** + +## When to use +- An admin creates/edits a template in `TemplatesAdmin.tsx`, or you set fields on `content.project_containers`. +- You run/extend `scripts/seed_remotion_templates.py` (which currently leaves SEO + taxonomy EMPTY — see below). +- You publish (`is_published = true`) and need the template to be findable and well-presented. + +Cross-reference: `remotion-template-catalog` (template TYPE → pattern), `remotion-template-composition` +(what's editable, used to write "what you can customize"), `persian-fonts` (RTL copy & Persian numerals). + +## The real data model (use these exact names — do not invent fields) + +Template = `content.project_containers`. Admin sets via `TemplatesAdmin.tsx` → `POST/PUT /v1/templates` +(`Create/UpdateContainerRequest`). Settable, SEO/taxonomy-relevant fields: + +| Purpose | Admin label | form key | DTO | DB column | +|---|---|---|---|---| +| Name (→ H1) | نام | `name` | `Name` | `name` | +| Slug | اسلاگ | `slug` | `Slug` | `slug` (CITEXT UNIQUE) | +| Description / presentation copy | توضیحات | `description` | `Description` | `description` | +| Keywords (opaque free text) | کلمات کلیدی (سئو) | `keywords` | `Keywords` | `keywords` (TEXT) | +| Announcement (NOT seo) | متن خبر | `news_text` | `NewsText` | `news_text` | +| Categories (M2M) | دسته‌بندی‌ها | `category_ids[]` | `CategoryIds` | `container_categories` | +| Tags (M2M) | برچسب‌ها | `tag_ids[]` | `TagIds` | `container_tags` | +| Primary mode | حالت اصلی | `primary_mode` | `PrimaryMode` | `primary_mode` enum | + +**Hard constraints from the platform (work within these):** +- **No `meta_title` / `meta_description` on a template.** The only SEO string on a container is `keywords`. + Per-page `meta_title`/`meta_description`/`meta_keywords`/`bot_follow` exist ONLY on `content.categories` + (and on `blogs`). Workaround: put the page's discoverable meta intent into `keywords`; pack the human + title/description into `name` + `description`; lift the rest of the meta from the **assigned category's** + `meta_*` fields (so choose the category deliberately — its SEO is inherited). +- **The public detail page emits title-only metadata** (`templates/[id]/page.tsx` → `{ title: "${name} — FlatRender" }`), + and the public mapper (`admin-api.ts` `containerToAdminProject`) DROPS `keywords`, `coverImageUrl`→OG, and + hardcodes `categoryName: undefined`. So today your `keywords`/category never reach the public ``. + Still fill them: they power the backend list filters and are the fix-point when the frontend SEO gap is closed. + When you can, also fix the mapper to surface `keywords` + cover OG; otherwise flag it. +- **No related-templates table/column/endpoint exists.** "Related" is computed by re-querying + `GET /v1/templates?categoryId={guid}` or `?tagSlug={slug}` and excluding the current template. + There is no precomputed set — pick the category/tags such that this query returns good neighbors. +- **Tags are not shown anywhere in the templates UI** today, and the list page ignores the real DB + categories: the sidebar is ~12 hardcoded buckets (11 real categories + `"all"`), and because the + public mapper hardcodes `categoryName: undefined`, every template defaults to `"social"`. Tags/categories + still matter for the backend filters and future UI — set them correctly regardless. +- `demo_script_tag` exists in DB + DTO but is NOT in the admin form — you cannot set it there. +- Categories are a tree (`parent_id`); `GET /v1/categories` only eager-loads one child level. Keep the + primary set flat (8–12). Tags have `applies_to_mode`; categories do not. + +## Step-by-step decision process + +Run this for every template, in order. Each step feeds the next. + +**0. Read the design + content + usability.** What does it output (format)? What's editable (text/colors/ +logo/images/music — from `remotion-template-composition`)? Who hires it and when? 2D or 3D (real Remotion +build: SVG vs `@remotion/three`)? Which aspects exist (16:9 / 1:1 / 9:16 child projects)? + +**1. Category — pick exactly ONE primary.** Category = the *output format*, never the occasion/industry. +Test: which single shelf would a user expect this on? If the answer is a format (logo reveal, story, slideshow) +it's the category; if it's when/why/who (Nowruz, real estate, teens) that's a TAG. Use the flat set: +اینترو و لوگو / Intro & Logo · استوری و ریلز / Story & Reels · پست شبکه اجتماعی / Social Post · +تبلیغ و پروموشن / Promo & Ad · اسلایدشو / Slideshow · معرفی محصول / Product Showcase · +دعوت‌نامه و مناسبت / Invitation & Event · تیتراژ و زیرنویس / Titles & Lower-thirds · +ارائه و اینفوگرافیک / Presentation & Infographic · یوتیوب و اینترو کانال / YouTube & Channel. +No "Other"; no category with <5 templates. → set `category_ids` (the primary first; its `sort` = index 0). + +**2. Tags — 6–12, from a controlled fa↔en vocabulary**, each TRUE of this design, across facets: +use-case · occasion · industry · style/aesthetic · aspect ratio · color/mood · 2D-3D · audience. +Empty facets are fine — don't invent tags. Aspect ratio is a TAG (one template, three outputs), never a +separate catalog entry. Tag 3D only if it really renders 3D. Reuse existing `content.tags` (match by +`slug`/`latin_name`); create a new tag only as a deliberate dictionary edit. → set `tag_ids`. + +**3. SEO title / description / keywords / slug.** +- **Title** (lives in `name`): `{Template Name} | {Category} {use/occasion}`. Keep the keyword in the + first ~30 chars (Persian runs long); brand is appended by the layout, don't double it. +- **Description** (lives in `description`, also reused as presentation copy): benefit + what + how-to-edit + + soft CTA. fa 120–155 / en 120–158 chars. One natural keyword, no stuffing. +- **Keywords** (lives in `keywords`): how Iranians actually search — «قالب آماده», «ساخت/دانلود استوری», + «تیزر تبلیغاتی», «اینترو لوگو» — include loanword + Persian spelling both ways (استوری/Story, اینترو/Intro). + 1 primary + 2–3 secondaries. Note: `keywords` is an opaque free-text column — the platform does NOT + tokenize, split, index, or search on it (no comma parsing anywhere in form or backend). Commas are + purely an authoring convention for human readability; write it however reads cleanest. +- **Slug** (`slug`): the slugifier KEEPS Persian (no transliteration). Prefer a curated **latin/transliterated** + keyword slug for shareable URLs (Persian slugs become `%D9%82...` when copied). Lowercase, hyphenated, short, + keyword-bearing. Never change a published slug. + +**4. Presentation copy** (write into `description`; structure it so it doubles as SEO + usability): +H1 = human Persian `name` → one-line benefit hook → "این قالب برای چیست؟" (use-case+occasion+audience) → +"چه چیزهایی قابل تغییر است؟" (bullet the REAL editable fields — don't claim editable music if there's none) → +"چطور بسازم؟" (انتخاب → ویرایش متن/رنگ → دانلود) → spec strip (aspects, duration, 2D/3D). Benefit before +feature; second person; every claim true of THIS template. + +**5. Related set** (caller-computed; pick category/tags so this works). Query +`GET /v1/templates?categoryId=...` then `?tagSlug=...`; rank occasion > category+use-case > style > industry; +show 6–8; **exclude this template's own aspect siblings** (dedupe by template id); cap same-style clones; +label pack-mates «از همین مجموعه». + +## fa / en parity & RTL rules +- Fill fa first, then en as a faithful mirror. Two self-consistent pages — never a Persian page with an + English title. Same template id; localized labels resolve per current locale. +- Persian uses Vazirmatn / RTL (see `persian-fonts`); use Persian digits in user-facing copy; don't fight the + `[dir="rtl"]` block. Tag/category *slugs* stay latin for stability; *display names* are localized. +- hreflang fa↔en + x-default is the right target even though the detail page doesn't emit it yet — flag the gap. + +## Worked examples + +**A. Nowruz 3D greeting (تبریک نوروز سه‌بعدی)** +- Category: `دعوت‌نامه و مناسبت / Invitation & Event`. +- Tags: occasion=نوروز/Nowruz · use-case=تبریک/greeting · style=لاکچری/luxury · 2D-3D=سه‌بعدی/3D · + aspect=۹:۱۶,۱:۱,۱۶:۹ · color=طلایی/gold · audience=کسب‌وکار کوچک/small business. +- name (fa): «قالب تبریک نوروز سه‌بعدی | مناسبت و تبریک» · (en): "Nowruz 3D Greeting Template | Invitation & Event" +- description (fa): «کارت تبریک نوروزی سه‌بعدی و لاکچری؛ نام برند، متن تبریک و رنگ طلایی را در چند دقیقه شخصی‌سازی کن و ویدیوی آماده انتشار بساز.» +- keywords: `قالب تبریک نوروز, تبریک عید, نوروز سه بعدی, دانلود کارت تبریک, Nowruz greeting` +- slug: `nowruz-3d-greeting` · Related: other نوروز items first, then Invitation & Event + greeting. + +**B. Instagram sale promo (پروموشن فروش ویژه اینستاگرام)** +- Category: `استوری و ریلز / Story & Reels` (output is a 9:16 vertical — format wins over "sale"). +- Tags: use-case=فروش ویژه/flash sale · industry=فروشگاه آنلاین/e-shop · style=نئون/neon · aspect=عمودی ۹:۱۶/vertical · + color=انرژیک/energetic · audience=فروشگاه/store · 2D. +- name (fa): «قالب استوری فروش ویژه | استوری و ریلز» · (en): "Flash-Sale Story Template | Story & Reels" +- description (fa): «قالب آماده استوری برای اعلام تخفیف و فروش ویژه؛ متن، قیمت، رنگ و لوگوی فروشگاهت را جایگزین کن و استوری حرفه‌ای ۹:۱۶ بساز.» +- keywords: `قالب استوری فروش, تخفیف اینستاگرام, ساخت استوری تبلیغاتی, دانلود قالب استوری, flash sale reels` +- slug: `instagram-flash-sale-story` · Related: same use-case (flash sale) across categories, then Story & Reels. + +**C. Corporate logo reveal (لوگو موشن شرکتی)** +- Category: `اینترو و لوگو / Intro & Logo`. +- Tags: use-case=معرفی برند/brand intro · style=مینیمال/minimal · audience=شرکتی/corporate · + aspect=۱۶:۹,۱:۱,۹:۱۶ · color=آبی/blue · 2D (or 3D only if it truly is). +- name (fa): «قالب لوگو موشن شرکتی | اینترو و لوگو» · (en): "Corporate Logo Reveal Template | Intro & Logo" +- description (fa): «اینترو حرفه‌ای برای نمایش لوگوی شرکت؛ لوگو، نام برند و رنگ سازمانی را وارد کن و یک اینترو تمیز و مینیمال بساز.» +- keywords: `لوگو موشن, اینترو لوگو, ساخت اینترو, لوگو موشن شرکتی, logo motion intro` +- slug: `corporate-logo-reveal` · Related: other Intro & Logo + brand-intro use-case. + +## Where each value goes +- **Admin UI** (`TemplatesAdmin.tsx`): name→`name`, slug→`slug`, presentation copy→`description`, + SEO keywords→`keywords`, categories→`category_ids` (multi-select chips), tags→`tag_ids`, mode→`primary_mode`. + Saves via `/api/admin/resource/templates` → `/v1/templates`. +- **Categories/Tags** are managed on their own admin pages (`admin-resources.tsx` → `/v1/categories`, `/v1/tags`); + category-level `meta_title`/`meta_description`/`meta_keywords`/`bot_follow` live there — set them once per category. +- **Seeder** (`scripts/seed_remotion_templates.py`): currently SETS only `name/slug/description/image/demo*/ + is_published/primary_mode/sort` and inserts ZERO `container_categories` / `container_tags` and NO `keywords`. + To seed SEO+taxonomy: add `keywords` to the `project_containers` INSERT, and add INSERTs into + `content.container_categories` (with `sort` = index) and `content.container_tags`. Otherwise each seeded + template ships with no keywords, no category, no tags — invisible to backend category/tag filters until an + admin opens it and assigns them. + +## Final checklist +- [ ] Exactly ONE primary category, format-based, from the flat set → `category_ids` (primary at index 0). +- [ ] 6–12 tags, controlled fa↔en vocab, each true of the design, across facets; aspect is a tag → `tag_ids`. +- [ ] `name` carries the SEO title (keyword in first ~30 chars; no doubled brand). +- [ ] `description` = benefit→for-what→customize(real fields)→how-to→specs; doubles as the meta description. +- [ ] `keywords` = 1 primary + 2–3 secondary, loanword + Persian spelling (free text — commas are just an authoring convention, not parsed). +- [ ] `slug` = curated latin keyword slug, stable, never changed after publish. +- [ ] fa filled first, en a 1:1 mirror; Persian digits + RTL in copy. +- [ ] Related works via category/tag query; own aspect siblings excluded. +- [ ] Gaps flagged: no template `meta_*`, public mapper drops `keywords`/cover OG/category, no related table, + no hreflang on detail page — note these where relevant rather than pretending they're set. diff --git a/persian-fonts/SKILL.md b/persian-fonts/SKILL.md new file mode 100644 index 0000000..98511c0 --- /dev/null +++ b/persian-fonts/SKILL.md @@ -0,0 +1,55 @@ +--- +name: persian-fonts +description: Persian (Farsi) and Latin font selection for FlatRender — which font to use where, with a Persian-first priority. Use when choosing typefaces for a template or UI, pairing display vs body fonts, handling RTL/Persian numerals, or adding a new font to the Remotion project. Persian fonts are the priority. +--- + +# Persian-first typography + +This is an Iran-facing product: **Persian (Farsi) is the default and the priority**. Latin is secondary. Get the Persian type right first. + +## What the project already uses +- **Vazirmatn** — the default Persian face everywhere. Remotion bundles `services/remotion/public/fonts/vazirmatn-{400,600,700,800,900}.woff2` and exposes `FONT` from `src/lib/fonts.ts` (use `fontFamily: FONT` + `direction: "rtl"` in every composition). +- Web app: `globals.css` has a `[dir="rtl"]` block that FORCES Vazirmatn on all elements — don't fight it with utility classes; work with it. +- Latin pairing in the web app: **Plus Jakarta Sans** + **Inter**. + +## Persian font palette (pick by role) +| Font | Character | Use for | +|---|---|---| +| **Vazirmatn** | clean, neutral, many weights | DEFAULT — body, UI, most template text | +| **Estedad** | modern, geometric, friendly | headings, modern brand templates | +| **Yekan Bakh** | contemporary sans, professional | corporate/business templates | +| **Shabnam / Sahel** | soft, readable | body text, calm/elegant designs | +| **IRANSans / IRANYekan** | familiar Iranian UI standard | UI-style promos, app mockups | +| **Morabba** | bold display, rounded | big punchy headlines, posters | +| **Gandom** | strong display | impactful titles, sale/sport | +| **Lalezar** | playful, heavy, fun | kids, party, birthday, casual | +| **Shekari / Vahid** | decorative/calligraphic | festive, traditional (Nowruz, wedding, Yalda) | +| **Nastaliq (e.g. IranNastaliq)** | classical calligraphy | very formal/traditional, invitations — use sparingly, hard to read small | + +Pairing rule: one display face for the headline + Vazirmatn (or Shabnam) for everything else. Don't mix two display faces. + +## Match font to template mood +- Corporate / SaaS → Estedad / Yekan Bakh + Vazirmatn. +- Festive (birthday, party) → Lalezar / Morabba + Vazirmatn. +- Traditional / occasion (Nowruz, wedding, Yalda, Eid) → a decorative/Nastaliq display for the greeting + Vazirmatn for details. +- Sale / bold promo → Gandom / Morabba (heavy) + Vazirmatn. +- Minimal / elegant → Shabnam / Sahel, lighter weights. + +## Persian typesetting rules +- **RTL:** always `direction: "rtl"`; align right or center. Mixed Persian+Latin/numbers needs care (bidi) — test the actual string. +- **Numerals:** decide Persian (۱۲۳) vs Latin (123) and be consistent. For Persian digits, format with `toLocaleString('fa-IR')` or use a font that supports Persian numerals. Years/prices in templates are usually Persian digits (۱۴۰۶, ۲۹۹٬۰۰۰). +- **Weights:** Persian script needs a bit more weight to feel solid — headings 700-900, body 400-600. Avoid ultra-thin for small text. +- **Line-height:** Persian needs slightly more (`lineHeight` 1.4-1.6 for body) — descenders/diacritics need room. +- **ZWNJ (نیم‌فاصله, `‌`):** preserve it in words like «می‌شود», «نیم‌فاصله» — don't strip it. +- **No fake bold/italic:** use real weights; Persian has no italic — don't slant it. + +## Adding a new font to a Remotion template +1. Get the woff2 (license-checked — Vazirmatn/Estedad/Shabnam are SIL OFL, free for commercial; verify others). Place in `services/remotion/public/fonts/`. +2. Register it (a `@font-face` injected via `