Motion · Tipografia
Split-flap board title
Airport split-flap title — each character's two mechanical flap halves flip through 2 garbled frames and clatter to a stop on the target letter, cascading left to right.
Fonte: video-shotcraft (Vincent Wei) ↗ · Apache-2.0 · Composição Remotion (React/TSX) — vídeo de prévia, sem player no navegador
When to use
Opening/chapter big titles; countdown, release-date, or data-announcement copy; any beat that needs a "mechanical announcement" feel
Intention
The third of the three text moves: embossing (paper-title-card) is ink pressed into paper, a "whisper"; the typewriter (document-typewriter-reveal) is a document being written by hand, a "narration"; the split-flap is a station departure board, a "broadcast" with a mechanical-announcement tone. Choose split-flap for countdown/departure feels (version releases, deadlines), data-announcement feels (metric reveals), or when the title needs a layer of retro mechanical texture. It naturally reads as a monospaced dark-panel grid, which in the library's paper-and-ink aesthetic comes across as "a mechanical display panel set on the page" — the off-color block is itself the point of interest, so the background product footage dims and desaturates to give it room.
Duration & energy
- Duration: ~4.7s (140f: ≥20f garbled hold to establish + cascading flip + ≥15f settled hold)
- Energy: Medium (sustained mechanical motion, not an instant impact)
Shot recipe (original Chinese — timing, easing, parameter table, known pitfalls)
意图
文字三式的第三式:压印(paper-title-card)是纸上落墨的"低语",打字机 (document-typewriter-reveal)是文档被人写出来的"叙述",翻牌是车站大屏的 "播报"——带机械宣告腔。选翻牌的语境:倒计时/发车感(版本发布、截止日)、 数据播报感(指标揭晓)、或想给标题一层复古机械质感。它天然是等宽深底 格子,在库内纸墨审美里读作"纸面上摆了一块机械显示屏"——异色块本身 就是看点,背景产品画面压暗降饱和给它让位。
动效核心
- 每字符一个深底翻牌格(上下两半 overflow hidden + 中缝铰链线),字符
序列固定为 2 个乱码 → 1 个乱码 → 目标字(首态乱码,建立段可见)
- 单次翻牌 5f 两段式:前半旧字上半叶 rotateX 0→-90 掉落(Easing.in
重力感 + brightness 变暗到 0.55),后半新字下半叶 90→0 拍下(回亮)
- 每格翻 3 次;字符间 stagger 4f,左→右级联成波
- 停定咔哒:整格 6px 下沉回弹(下沉→过冲 -1.5px→归零)
- 节拍:开头 ≥20f 整排乱码静止建立,结尾全部停定后 ≥15f 静止收束
- 乱码用 seed 正弦哈希(sin(i·127.3)·43758 取小数),禁 Math.random
参数表
| 参数 | 典型值 | 调节手感 | |------|--------|----------| | 单次翻牌 | 5f,p 走 Easing.in(quad);<0.5 旧字上半叶 0→-90 掉落,≥0.5 新字下半叶 90→0 拍下 | Easing.in 是重力感来源——越掉越快才像机械叶片;匀速读作电子屏假翻 | | 翻牌次数 | 每格 3 次(2 乱码中间态 + 1 落定) | 少于 2 次读不出"翻找"过程;多于 4 次拖节奏且乱码抢戏 | | 级联 | stagger 4f 左→右;10 字符约 36f 波峰扫完 | 4f 是波浪感甜点——太密整排齐翻没有波,太疏尾部字符等太久 | | 停定咔哒 | 落定后 local 15→22f:translateY 0→6→-1.5→0,Easing.out(quad) | 6px 是可感下限(4px 无感判例);这一下就是"咔哒"的画面体,删了只剩滑动 | | 明暗 | 掉落叶 brightness 1→0.55,拍下叶 0.55→1 | 明暗差是叶片"转过铰链"的立体感来源,比 rotateX 本身更被肉眼读到 | | 翻牌格 | 118×156,perspective 420,圆角 10,中缝铰链线 4px #141412 | 铰链线压住翻转轴穿帮;perspective 太大叶片透视变形夸张 | | 配色字体 | 格底 #262624 / 字 #f4f4f2,Helvetica 800 100px;空格占 52px、格间 gap 12 | 深底浅字是翻牌屏的身份证;等宽格子布局,别用比例字体挤格 | | 节拍 | 建立 0–21f 静止 → 22f 起级联 → 全停后静止 ≥15f | 建立段让观众先认出"这是块翻牌屏",直接开翻读作故障闪烁 |
已知坑
- demo 在灰阶/占位素材上调校通过——参数是调校起点非实战定稿,
首次实战须以真实素材回验
- 声音强依赖:每格停定的机械拍打声是效果的一半,实战必须配——级联
停定天然是一串节奏点,assets/audio/sfx/ui/switch-click-quick.mp3 或 sfx/mech/gear-lock-metallic.mp3 起步(更贴机械翻牌),也可用 sfx/text/typewriter-hit-single.mp3;配法纪律见 sound-design.md (同类元素同音,拟音与动作等长,连发交替双样本防机枪感)
- 下沉回弹 6px 是可感下限:肉眼看不出就照可感性判例加大幅度,别调
曲线不调幅度
- 一支片翻牌字 ≤1 次:机械宣告腔太抢,重复即贬值(P4 手法去重精神);
与压印/打字机同片混用时留给最重的那句标题
参考实现
demos/typography/split-flap-title/ (SplitFlapFlip.tsx)
Reference implementation (Remotion)
- demos/typography/split-flap-title/SplitFlapFlip.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/typography/split-flap-title/SplitFlapFlip.tsx
The component imports shared fixtures from demos/_fixtures and helpers from assets/lib in the repo; SKILL.md there documents the render workflow.
Tudo nesta página também é legível por máquina. Aponte seu agente para o JSON ou adicione o servidor MCP do gimgs e peça para buscar templates de motion.
- Baixe o JSON — contém a receita do plano (timing, easing, tabela de parâmetros, armadilhas) e o código TSX completo.
- Clone o repositório video-shotcraft; o componente importa fixtures de demos/_fixtures e helpers de assets/lib.
- Registre o componente como <Composition> em um projeto Remotion e renderize com `npx remotion render`, ou reimplemente a receita no seu próprio engine.
curl -s https://gimgs.net/motion/split-flap-title.jsongit clone https://github.com/Vincentwei1021/video-shotcraft
# component: demos/typography/split-flap-title/SplitFlapFlip.tsx
npx remotion render <CompositionId> out.mp4claude mcp add --transport http gimgs https://gimgs.net/mcp
# then: search_motion_templates / get_motion_template("split-flap-title")Prompt
Use the video-shotcraft shot recipe "Split-flap board title" (Remotion composition, Apache-2.0) published on gimgs.net. Recipe JSON (prompt = timing/easing/parameter table/pitfalls, files = TSX source): https://gimgs.net/motion/split-flap-title.json Raw TSX: https://gimgs.net/motion/split-flap-title/template.html Repository: https://github.com/Vincentwei1021/video-shotcraft (component: demos/typography/split-flap-title/SplitFlapFlip.tsx; imports demos/_fixtures and assets/lib) Spec: 1920x1080, 30 fps, 4.72s 1. Read the JSON and follow its "prompt" section (when to use, intention, parameter table, known pitfalls). 2. Adapt it for: <describe what you want>. 3. Either clone the repo, register the component as a <Composition> in a Remotion project and run npx remotion render <CompositionId> out.mp4 — or re-implement the recipe in your own engine (HTML/WAAPI works: keep the same timing and easing).
// ===== demos/typography/split-flap-title/SplitFlapFlip.tsx =====
import React from 'react';
import { AbsoluteFill, Easing, interpolate, useCurrentFrame } from 'remotion';
import { FakeDashboard, G } from '../../_fixtures/Fixtures';
// split-flap-flip:机场翻牌字。每字符一个深底翻牌格(上下两半),
// 逐格翻过 3 个乱码中间态后咔哒停在目标字,左→右 4f 级联成波。
// 节拍:0–21 建立(整排乱码静止)→ 22 起级联翻牌 → 78 全部停定 → 静止到 140。
const TEXT = 'SHIP FASTER';
const CHARSET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789#$%&';
const START = 22; // 级联起始帧
const STAGGER = 4; // 字符间级联延迟
const FLIP = 5; // 单次翻牌时长
const NFLIP = 3; // 每字符翻 3 次(2 个乱码中间态 + 1 次落到目标字)
const CELL_W = 118;
const CELL_H = 156;
// seed 正弦哈希(禁 Math.random)
const rnd = (a: number) => {
const x = Math.sin(a * 127.3) * 43758.5453;
return x - Math.floor(x);
};
const garble = (i: number, k: number) =>
CHARSET[Math.floor(rnd(i * 7.13 + k * 3.71 + 1) * CHARSET.length)];
const FLAP_BG = '#262624';
const FLAP_INK = '#f4f4f2';
// 半格:上/下半各自 overflow hidden,内部整字定位错半格露出对应一半
const Half: React.FC<{ ch: string; part: 'top' | 'bottom' }> = ({ ch, part }) => (
<div
style={{
position: 'absolute',
left: 0,
top: part === 'top' ? 0 : CELL_H / 2,
width: CELL_W,
height: CELL_H / 2,
overflow: 'hidden',
background: FLAP_BG,
borderRadius: part === 'top' ? '10px 10px 0 0' : '0 0 10px 10px',
}}
>
<div
style={{
position: 'absolute',
left: 0,
top: part === 'top' ? 0 : -CELL_H / 2,
width: CELL_W,
height: CELL_H,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontFamily: 'Helvetica, Arial, sans-serif',
fontWeight: 800,
fontSize: 100,
color: FLAP_INK,
}}
>
{ch}
</div>
</div>
);
const FlapCell: React.FC<{ target: string; i: number; frame: number }> = ({
target,
i,
frame,
}) => {
// 该格的字符序列:2 个乱码 → 1 个乱码 → 目标字(首态也是乱码,建立段可见)
const seq = [garble(i, 0), garble(i, 1), garble(i, 2), target];
const local = frame - (START + i * STAGGER);
const done = local >= NFLIP * FLIP;
// 停定咔哒:整格下沉回弹(1px 肉眼无感,放大到 6px 才有"咔哒")
const clickY = done
? interpolate(local, [15, 17, 19, 22], [0, 6, -1.5, 0], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
easing: Easing.out(Easing.quad),
})
: 0;
let topCh = seq[0];
let bottomCh = seq[0];
let flap: React.ReactNode = null;
if (done) {
topCh = target;
bottomCh = target;
} else if (local > 0) {
const k = Math.min(NFLIP - 1, Math.floor(local / FLIP));
const from = seq[k];
const to = seq[k + 1];
const p = Easing.in(Easing.quad)((local - k * FLIP) / FLIP); // 重力感:越掉越快
topCh = to; // 上半静态:翻开后露出下一字符的上半
bottomCh = from; // 下半静态:保持旧字符直到活动叶盖下来
if (p < 0.5) {
// 前半程:旧字符上半叶 0→-90 掉下
const deg = p * 2 * 90;
flap = (
<div
style={{
position: 'absolute',
inset: 0,
transform: `rotateX(${-deg}deg)`,
transformOrigin: `center ${CELL_H / 2}px`,
backfaceVisibility: 'hidden',
filter: `brightness(${1 - p * 2 * 0.45})`,
zIndex: 2,
}}
>
<Half ch={from} part="top" />
</div>
);
} else {
// 后半程:新字符下半叶 90→0 拍下盖住旧下半
const deg = 90 - (p - 0.5) * 2 * 90;
flap = (
<div
style={{
position: 'absolute',
inset: 0,
transform: `rotateX(${deg}deg)`,
transformOrigin: `center ${CELL_H / 2}px`,
backfaceVisibility: 'hidden',
filter: `brightness(${0.55 + (p - 0.5) * 2 * 0.45})`,
zIndex: 2,
}}
>
<Half ch={to} part="bottom" />
</div>
);
}
}
return (
<div
style={{
position: 'relative',
width: CELL_W,
height: CELL_H,
transform: `translateY(${clickY}px)`,
perspective: 420,
borderRadius: 10,
boxShadow: '0 6px 18px rgba(0,0,0,0.3)',
}}
>
<Half ch={topCh} part="top" />
<Half ch={bottomCh} part="bottom" />
{flap}
{/* 中缝铰链线 */}
<div
style={{
position: 'absolute',
left: 0,
top: CELL_H / 2 - 2,
width: CELL_W,
height: 4,
background: '#141412',
zIndex: 3,
}}
/>
</div>
);
};
export const SplitFlapFlip: React.FC = () => {
const frame = useCurrentFrame();
let letterIdx = 0;
return (
<AbsoluteFill style={{ background: G.bg, overflow: 'hidden' }}>
{/* 背景假页面压暗,突出翻牌板 */}
<div style={{ position: 'absolute', inset: 0, opacity: 0.3, filter: 'saturate(0.8)' }}>
<FakeDashboard variant="A" />
</div>
<AbsoluteFill style={{ alignItems: 'center', justifyContent: 'center' }}>
<div style={{ display: 'flex', gap: 12, alignItems: 'center' }}>
{TEXT.split('').map((ch, idx) => {
if (ch === ' ') {
return <div key={idx} style={{ width: 52 }} />;
}
const i = letterIdx++;
return <FlapCell key={idx} target={ch} i={i} frame={frame} />;
})}
</div>
</AbsoluteFill>
</AbsoluteFill>
);
};
- Read the recipePrompt = when to use, intention, parameter table with typical values and how each one feels, known pitfalls. Treat values as calibrated starting points.
- Get the codetool: git
git clone https://github.com/Vincentwei1021/video-shotcraft && cd video-shotcraft && npm install - Register + rendertool: remotion
npx remotion render <CompositionId> out.mp4 # component: demos/typography/split-flap-title/SplitFlapFlip.tsx - Or re-implementPort the timing/easing from the recipe to HTML + WAAPI (gimgs motion contract) or any engine; keep the same segment windows and easing.
| param | type | default | description |
|---|---|---|---|
| — | |||
Mais nesta categoria

Word relay geometry
Three benefit words each carry their own dedicated geometry relay — a dashed circle spins and shrinks → three solid circles trim-grow in sequence → a metallic sheen sweeps then collapses to pure white; each new word outlines then fills in.

Word relay filmstrip
A left column of black-and-white page cards steps forward while a serif big word on the right relays in place (a fixed noun + a cycling verb) — the column steps only when the word changes, aligned to the current page card's midpoint.

Vertical word-roll cycle
The sentence's last word cycles on a vertical roller, 3 swaps at 0.55s each, fast then very slow with a slight overshoot; neighboring rows carry vertical blur and grayscale by distance, the centered word snapping from gray to accent color as it lands.

Two code reveals, side by side
The same syntax-highlighted code reveals two ways side by side — left: line-level stagger fading up 8px, right: character-by-character typing keeping each token's original color, with a #3a4468 block cursor on the current character.