chore(skills+remotion): add flat-artist skill bundle; register 3D templates
- .claude/skills/flat-artist: the bundled FlatRender template-creation suite (orchestrator + 16 sub-skills + design/motion R&D), mirrors the Gitea AISkills repo. - services/remotion Root.tsx/templates.tsx: register the 3D templates + Three3DTest. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
---
|
||||
name: remotion-aspect-ratios
|
||||
description: How to design ONE Remotion template that genuinely fits all three FlatRender aspects — 16:9, 1:1, 9:16 — without text cropping, off-screen elements, or a layout that is really just the 16:9 version letterboxed. Use whenever building or reviewing a template's layout. Read this BEFORE positioning any text or element.
|
||||
---
|
||||
|
||||
# Designing for 16:9 / 1:1 / 9:16 (do this right)
|
||||
|
||||
Every template registers in all three aspects (`ASPECTS` in `src/lib/aspect.ts`). A common mistake (made in early FlatRender templates) is to design for 16:9 and just let the same coordinates render in 9:16 — which **crops text, pushes elements off-screen, and looks broken**. A template must be *re-laid-out* per aspect, not scaled.
|
||||
|
||||
## Two strategies — responsive component OR per-aspect components
|
||||
There are TWO legitimate ways to support the three aspects; pick per template:
|
||||
|
||||
1. **One responsive component** (default) — a single composition that adapts via `useLayout()` (`isWide/isSquare/isTall`, `pick()`). Use when the design is fundamentally the same and only positions/sizes change. Less code, stays in sync.
|
||||
|
||||
2. **A dedicated component per aspect** — when the design must differ STRUCTURALLY (different layout, different scene, different element set), not just reposition. e.g. a cinematic wide hero vs a stacked vertical story vs a centered square badge can be genuinely different scenes.
|
||||
|
||||
The registry supports both. In `services/remotion/src/templates.tsx` a `TemplateDef` has `component` (shared default) plus an optional `componentsByAspect` map keyed by aspect id:
|
||||
```tsx
|
||||
{
|
||||
id: "MyTemplate",
|
||||
component: MyTemplateWide, // fallback for any aspect not overridden
|
||||
componentsByAspect: {
|
||||
"1x1": MyTemplateSquare, // dedicated square design
|
||||
"9x16": MyTemplateTall, // dedicated vertical design
|
||||
},
|
||||
schema, durationSec, defaultProps, // SHARED across aspects — keep the editable
|
||||
} // fields, props and duration identical
|
||||
```
|
||||
`Root.tsx` picks `componentsByAspect[aspectId] ?? component`. **Keep `schema`, `defaultProps`, and `durationSec` shared** so the studio shows the same editable fields and the same composition ids (`${id}-${aspect}`) regardless — only the visual layout differs. Reuse shared sub-components (background, characters, text overlay) across the per-aspect files so they don't drift.
|
||||
|
||||
Guideline: start with one responsive component; split into per-aspect components only when responsive branching gets gnarly or the designs truly diverge. Don't duplicate three files when `pick()` would do.
|
||||
|
||||
## Why the naive approach breaks
|
||||
`useLayout().vmin(n)` sizes off the SHORT side (1080 in all three aspects), so a `vmin(92)` font is the same pixel size everywhere. But the WIDTH differs hugely: **1920px (16:9) vs 1080px (9:16)**. A headline that fits 1920 wide overflows/crops at 1080 wide. Likewise positioning at `width*0.34` puts an element in a totally different place relative to its own size when width changes.
|
||||
|
||||
## The rules
|
||||
|
||||
1. **Design 9:16 (tall) first.** It's the tightest. If it fits there, widening to 1:1 and 16:9 is easy. Building 16:9-first guarantees the tall version breaks.
|
||||
|
||||
2. **Branch layout on `L.isWide / L.isSquare / L.isTall`** — don't just scale. Things that sit side-by-side in 16:9 should STACK vertically in 9:16:
|
||||
```tsx
|
||||
const L = useLayout();
|
||||
// hero element position differs per aspect
|
||||
const heroX = L.isTall ? L.width * 0.5 : L.width * 0.34; // centered in tall, left in wide
|
||||
// layout direction
|
||||
flexDirection: L.isTall ? "column" : "row"
|
||||
```
|
||||
Add a tiny helper to `aspect.ts` and use it everywhere:
|
||||
```ts
|
||||
pick: <T,>(wide: T, square: T, tall: T): T =>
|
||||
kind === "wide" ? wide : kind === "tall" ? tall : square,
|
||||
```
|
||||
Then: `fontSize: L.pick(L.vmin(92), L.vmin(84), L.vmin(72))`.
|
||||
|
||||
3. **Cap font size to the WIDTH, not just the short side.** Headlines must wrap, never crop. Always set `maxWidth` and let text wrap:
|
||||
```tsx
|
||||
maxWidth: L.width * 0.86, // safe text column
|
||||
// and scale type DOWN in tall:
|
||||
fontSize: L.pick(L.vmin(90), L.vmin(80), L.vmin(64)),
|
||||
wordBreak: "normal", lineHeight: 1.15,
|
||||
```
|
||||
Test with the LONGEST realistic Persian string for that field, not the short default.
|
||||
|
||||
4. **Respect SAFE ZONES.** Keep all meaningful content inside the central safe area; give tall more vertical margin:
|
||||
- 16:9: ~5% horizontal / 8% vertical padding.
|
||||
- 9:16: ~8% horizontal, and keep the hero in the middle 60% vertically (top/bottom of phones get UI chrome).
|
||||
Anchor text blocks to a zone (top third / bottom third), put the hero visual in the center.
|
||||
|
||||
5. **Reposition the hero per aspect.** A character/object that's at `x=34%` and text on the right in 16:9 should become hero-centered with text above/below in 1:1 and 9:16. Use `pick()` for x/y and for `justifyContent`/`alignItems`.
|
||||
|
||||
6. **Scale element COUNT/spread, not just size.** A row of 5 floating shapes that spans 1920 looks sparse/clipped at 1080 — reduce spread radius or count in tall (`L.pick(...)`).
|
||||
|
||||
7. **3D:** adjust `camera.fov` / `position.z` per aspect so the subject fills the frame (a tall frame needs the camera pulled back or a narrower fov). Keep the 2D text overlay using the same `pick()` rules.
|
||||
|
||||
## Mandatory verification (the step that was skipped before)
|
||||
Render a still in **all three aspects** at a frame where text is visible, with a LONG test string, and LOOK at each:
|
||||
```
|
||||
npx remotion still src/index.ts "<Comp>-16x9" out/_a.png --frame=NN
|
||||
npx remotion still src/index.ts "<Comp>-1x1" out/_b.png --frame=NN
|
||||
npx remotion still src/index.ts "<Comp>-9x16" out/_c.png --frame=NN
|
||||
```
|
||||
Reject if: text is clipped at any edge, an element is off-frame, the hero is tiny/huge, or the tall version is obviously "the wide one squished".
|
||||
|
||||
## Checklist
|
||||
- [ ] Designed tall-first; used `pick()`/`isTall` to branch layout (not just scale).
|
||||
- [ ] Headlines wrap with `maxWidth`; tested with long Persian text — no cropping.
|
||||
- [ ] Hero repositioned/centered per aspect; content in safe zones.
|
||||
- [ ] Spread/count adjusted for narrow frames; 3D fov/camera tuned per aspect.
|
||||
- [ ] Eyeballed stills in ALL THREE aspects.
|
||||
|
||||
Related: `../remotion-template-composition/SKILL.md`, `../remotion-design-styles/SKILL.md`.
|
||||
Reference in New Issue
Block a user