# FlatRender Design & Motion R&D — Trends + Professional Craft > Single-source R&D brief for the FlatRender Remotion engine (`services/remotion`). Stack: **Remotion 4 + `@remotion/three`** (R3F v9, `gl="angle"`), Persian-first (Vazirmatn, RTL), three mandatory aspects (16:9 / 1:1 / 9:16), color-customizable templates driven by `colorSchema` props. Operating context: **Iran** — geo-blocked CDNs, sanctioned SaaS dashboards, reachable Nexus mirror (`mirror.soroushasadi.com`). Render is headless Chrome in Docker, so **every value must be a pure function of `useCurrentFrame()`** and **every asset must be vendored** into `public/`. > > Existing grounding files: `src/lib/anim.ts`, `aspect.ts`, `branding.ts`, `fonts.ts`, `three-kit.tsx`, `kit.tsx`; templates in `src/templates.tsx`; `public/` currently holds only `fonts/`. --- ## The two meta-truths to keep over everything 1. **Imperfect-by-design beats glossy.** As feeds fill with AI-perfect imagery, deliberate imperfection — grain, texture, hand-rendered type, natural-feeling motion — now signals "a real human made this." Even fully-rendered templates win by *adding back* texture and human-feeling motion. Apply this lens to every trend below. 2. **A masterpiece is ~8 finishing layers, not one big thing.** Sound design, micro-easing, a design system, depth/lighting, color grade, pacing, a clear hero moment, and subtle texture. Amateurs stop at "the text animates in." We must finish all eight. --- ## 1) Design trends to adopt (each with a concrete how-to in our stack) Every trend below survives all three aspects only if you anchor to safe-zone **percentages / `layout.vmin()`**, never absolute pixels. Read `../remotion-aspect-ratios/SKILL.md` before positioning anything. ### Typography (type is a first-class motion element, not a label) | Trend | When to use | How in our stack | |---|---|---| | **Bold / oversized hero type** (fills 60–90% of frame, clipped by edges) | Logo reveals, promo hooks; strongest on 9:16 | `fitText` from `@remotion/layout-utils` to auto-scale a word to frame width; animate `scale`/`translateY` with `spring()`; parent `overflow:hidden` to clip. | | **Variable-font animation** (`wght`/`wdth`/`slnt` over time) | Premium beat-synced intros | `style={{ fontVariationSettings: \`'wght' ${interpolate(frame,[0,30],[100,900])}\` }}`. **Vazirmatn ships a variable build** — animate its weight axis for Persian hero type. Load via `@font-face` (vendored in `public/fonts/`, never Google CDN at render). | | **Kinetic typography** (word-by-word / line-by-line) | Quotes, captions, fast hooks | Split into spans; per-word `delay = i * staggerFrames`; drive each with `spring({frame: frame - delay, fps})`; combine `translateY` + `opacity` + slight `rotate`. `` per line for timeline clarity. | | **Anti-AI / hand-rendered / scribbled** | Grunge/street/youth/music/events | Pre-make rough-edged SVG/PNG lettering; "draw" it on with a `clipPath`/mask wipe; add `filter:url(#displace)` (SVG `feTurbulence` + `feDisplacementMap`) with a per-frame jitter for photocopy wobble. | | **Chrome / Y2K metallic type** | Hype, music, fashion, "premium" reveals | CSS: layered `linear-gradient` text fill via `background-clip:text` cycling silver→steel→highlight by shifting `background-position` per frame. For real reflections, use Three.js (see liquid-chrome below). | ### 3D / Blender-look (`@remotion/three`) - **Real-time 3D logo reveals** — the default "premium intro." Render ``, drive camera/object rotation from `useCurrentFrame()` (**never `useFrame`**). Extrude logo via `TextGeometry`/extruded SVG shape, `meshStandardMaterial`, an HDRI `Environment`, `spring()`-driven entrance. Use our `StudioEnv/StudioLights/StudioFloor/StudioEffects` from `three-kit.tsx`. - **"Plushcore" / soft-3D / claymation** — friendly counter to hard chrome. Pre-render GLTF in Blender (subsurface/soft shaders), import via `useGLTF`; bobbing idle = `Math.sin(frame/fps)` on position. 2D fake-3D = big soft inner-shadows + highlight gradients in CSS. - **Mixed 2D/3D** — one of the strongest 2026 looks. Layer a `` behind/within absolutely-positioned 2D Remotion layers (SVG strokes, flat shapes); composite with blend modes. ### Surface & color treatments - **Grain / texture / noise** — near-universal in 2026; add to almost everything, especially flat/gradient backgrounds. Cheap: tiling noise PNG overlay at low opacity with `mix-blend-mode: overlay`/`soft-light`. **Animated grain must move or it looks frozen** — offset `background-position` per frame, or SVG `feTurbulence` with a per-frame `seed`/`baseFrequency` jitter. - **Mesh gradients** — soft multi-point blends (not linear), the sophisticated 2026 background. Pre-bake a mesh PNG and slowly drift/scale it, or animate live with a fragment shader in Three.js driven by `frame`. **Always add grain on top.** - **Glassmorphism (evolved)** — used *selectively* (cards, lower-thirds), multi-layer depth, dynamic blur. `backdrop-filter: blur(16px) saturate(140%)`, semi-transparent `rgba` fill, 1px light top-border, soft shadow; animate blur radius + a moving specular highlight per frame; stack 2–3 panels at different parallax depths. - **Retro / Y2K** — chrome + iridescent mesh gradient + occasional pixelate (`image-rendering:pixelated` on a downscaled layer) + sparkle SVG bursts. Music/fashion/Gen-Z/party greetings. - **Anti-design / tactile brutalism** — hard `1px solid #000` borders, no radius, pure-saturated bg, system/monospace fonts, deliberate overlap. Motion is blunt — **hard cuts and `step`/`linear` snaps, not smooth springs.** - **Mixed-media / collage** — PNG cutouts with rough edges as layers; "paper-drop" overshoot springs on slight rotation/scale; tape/staple SVGs, paper-grain overlay, occasional 1-frame jump-cut jitter. - **Isometric** — `transform: rotateX(60deg) rotateZ(-45deg)` on stacked layers with `transform-style:preserve-3d`; stagger layer entrances; move elements along iso axes (matched X/Y deltas). Great for SaaS/feature explainers. - **Kinetic / liquid / morph** — morphing blobs (animate SVG `path d` with `flubber`; gooey via `feGaussianBlur` + `feColorMatrix`). **Liquid chrome (top-5 2026 animation trend):** Three.js `meshStandardMaterial` `metalness:1, roughness:~0.05` + HDRI `Environment` on a morphing/`MeshDistort`-style geometry — color shifts as the camera moves. - **AI-aesthetic vs anti-AI** — AI stills/clips as ``/`` backgrounds with Ken-Burns for surreal promos; lean into hand-type + grain + collage for trust/authenticity brands. ### Color direction 2026 - **Dopamine / electric accents** (electric blue, neon coral, acid yellow, vivid teal, cobalt) — as high-energy *accents* to guide the eye, never whole palettes. - **Tech pastels** (lavender haze, powder blue, digital pink) — calm/mature, great for SaaS/UI promos. - **Warm earth neutrals** (mocha/espresso/caramel/tan) — "quiet luxury," premium and human. - **Strategy:** bold dopamine accent + neutral/pastel base + grain on top. **Avoid all-flat saturated fields — they read as AI/template.** All color comes from `colorSchema` props so the studio can recolor; pass a user's hex through a grade so a garish value doesn't break the look. --- ## 2) Animating anything (craft + the pro workflow) ### The one rule everything hangs on A frame in Remotion is **pure**: `frame → pixels`. Motion is *evaluated* at an arbitrary frame, exactly the After Effects mental model (a keyframe graph sampled at time `t`). **Never** use `useFrame`, `Math.random()`, `Date.now()`, `setState`, or `useEffect`-driven motion — the renderer samples frames out of order and in parallel. Use `rand(seed)` from `anim.ts` for deterministic "randomness." If a value can't be derived from `frame`, it doesn't belong in the render. ### The 12 principles → Remotion (the four you reach for every shot in bold) | Principle | Remotion expression | |---|---| | Squash & stretch | `scaleX`/`scaleY` inversely around an impact frame (conserve volume: `sx = 1/sy`) | | **Anticipation** | Dip the value below its start before the main move | | Staging | Stagger reveals; dim/blur everything but the hero | | Straight-ahead vs pose-to-pose | `interpolate` between frames (keyed) vs per-frame formula (sim, e.g. `Confetti3D`) | | **Follow-through & overlapping** | Same motion, **delayed per child** + a *looser* spring so parts settle later | | **Slow in & slow out** | `Easing.bezier` / `spring()` — the single biggest quality lever | | Arcs | Drive `y` with a `sin`/parabola while `x` moves linearly | | Secondary action | A small `sin` bob/shimmer alongside the primary reveal | | Timing | Frame count + spring `mass`/`damping` = weight & mood | | **Exaggeration / overshoot** | Overshoot > 1.0, then settle to 1.0 | | Solid drawing | `StudioLights` + reflective material + shadows (3D) | | Appeal | Choreography + `StudioEffects` (bloom/DOF/vignette) + good type | ### `spring()` vs `interpolate()` - **`interpolate`** — *you* author the curve. Use when a value must hit an exact mark on an exact frame (storyboard reveals, crossfades, value remaps). **Always set `extrapolateLeft/Right: "clamp"`** — forgetting this is the #1 cause of elements drifting off-screen or opacity going negative. - **`spring`** — *physics* authors the curve. Use for organic entrances, pops, bounces. - **Combine:** spring drives the *feel*, interpolate *remaps* its 0→1 output to real px/units: `const y = interpolate(spring(...), [0,1], [vmin(80), 0])`. **Spring config cheat-sheet:** | Feel | mass | damping | stiffness | Use for | |---|--:|--:|--:|---| | Snappy, no overshoot | 0.5 | 200 | 200 | Clean UI/logo reveals | | Natural pop (slight overshoot) | 0.6 | 12 | 180 | **Default** cards/badges/icons | | Bouncy / playful | 1 | 8 | 120 | Kids, birthday, mascots | | Heavy / weighty | 2.5 | 26 | 90 | Big titles, 3D objects landing | | Loose wobble (follow-through) | 1 | 6 | 80 | Secondary / trailing parts | Lower damping = more overshoot; higher mass = heavier/slower; higher stiffness = faster snap. ### Easing cheat-sheet (linear is the sound of an amateur) - **Entrances → ease-out** (`Easing.out(Easing.cubic)`), your default. Hero titles → `Easing.out(Easing.quint)` or `Easing.bezier(0.16,1,0.3,1)`. - **Exits → ease-in** (`Easing.in(Easing.cubic)`), and **always sharper than the entrance** — things leave faster than they arrive. - **A→B on-screen moves / camera → ease-in-out.** - **Snappy "ta-da" → back/overshoot** `Easing.bezier(0.34,1.56,0.64,1)`. - **Wind-up → anticipate** `Easing.bezier(0.36,0,0.66,-0.56)`. - **Linear ONLY for mechanical continuous motion** — rotation, scroll, conveyor, marquee. ### Timing & frame budgets (30fps default — but use `sec(s)=Math.round(s*fps)`, never hardcode 30) Micro pop 8–14f · standard reveal 18–28f · hero entrance 28–40f · scene transition 12–20f · hold = a comfortable read of the text. **Cut frames before you add them — amateurs over-animate.** Robotic = linear spacing; floaty/late = timing too long. ### The four quality multipliers (concrete patterns) - **Anticipation:** `interpolate(frame,[0,6,30],[0,-0.12,1])` — small negative dip before launch. - **Overshoot+settle:** the back bezier, or a low-damping spring. Ensure the curve *reaches and holds* the target (clamp) or it micro-drifts forever. - **Follow-through:** drive children from the same trigger frame, **delay each** (`frame - start - i*stagger`) with a *looser* spring so they settle after the parent. Biggest "feels professional" upgrade for grouped elements. - **Staggering / choreography:** default to a **cascade**; tune per aspect via a `pick(wide,square,tall)` helper (wider reads faster → tighter stagger; tall reads slower → looser). Patterns: cascade (lists), center-out (logos/hero rows), random-but-deterministic via `rand(i)` (particles), beat-synced (snap `start` to music beats). **One thing enters the eye at a time** — staging. ### 3D motion Drive every transform off `useCurrentFrame()` (deterministic under ANGLE). Rotation/orbit = `linear` (mechanical); entrances/landings = `spring` with high mass for weight. Let `StudioEffects` (bloom + DOF + vignette) do the cinematic polish in one component. ### The pro workflow (5 passes, IN ORDER — polishing before timing is locked wastes the most time) 1. **Reference** — decide the feel before code; pull an AE template/Dribbble loop; pick style (`../remotion-design-styles/SKILL.md`), type (`../persian-fonts/SKILL.md`), composition (`../remotion-template-composition/SKILL.md`), per-aspect rules (`../remotion-aspect-ratios/SKILL.md`); write the beat list ("logo in → tagline → 3 features cascade → CTA → out"). 2. **Blocking** — every element on screen at final position with crude `interpolate` fades, no easing. Fix off-screen/cropping in all three aspects now. 3. **Timing** — lock frame counts, stagger, beats, holds, transitions. Watch at full speed repeatedly. Mood lives here. 4. **Polish** — swap linear for easing/springs; add anticipation+overshoot+settle, follow-through, secondary motion, arcs, squash/stretch; `StudioEffects` for 3D; wire SFX (`../remotion-sound-effects/SKILL.md`) and music sync (`../remotion-music-picker/SKILL.md`) to the locked frames. 5. **Review** — scrub frame-by-frame + full speed. Nothing pops without an ease; nothing leaves slower than it arrived; the eye always knows where to look; reads in all three aspects; Persian RTL not clipped; colors from `colorSchema`; re-render twice → identical (deterministic). Then run `../flatrender-template-seo/SKILL.md`. ### Top amateur mistakes → fixes (review gate) Linear motion → ease/spring · no anticipation/overshoot → dip-then-launch / back bezier · everything on one frame → stagger · forgot `clamp` → clamp both ends · hardcoded 30fps → `useVideoConfig().fps` · `useFrame`/`random`/`Date.now` → `useCurrentFrame` + `rand` · pixel-hardcoded sizes → `vmin`/`unit` + branch on `isWide/isSquare/isTall` · over-animating → one idea per beat · no hold → real hold sized to reading · exit = entrance speed → exits sharper · dead holds → `sin` bob/breathe/shimmer · flat 3D lighting → `StudioLights` + floor shadows + `StudioEffects` · color hardcoded → read from props. --- ## 3) Asset pipeline (collecting + designing footage, Iran-aware) ### The Iron Rule The Iran environment punishes runtime dependencies. **All assets are vendored** — download once (over VPN if needed), commit into `services/remotion/public/`, reference with `staticFile()`. **Never** `