Motion · Entrada de UI
Runway ground skim entrance
From a low ground-skim angle, UI cards rain down fast and land with zero bounce, then the whole page stands up and squares to camera.
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
Entrance for a dashboard/card-flow interface (content "falls from the sky to complete itself"); a squaring-up close after a low-angle showcase segment; a heavy "land-all-at-once" version of the ClickUp-style hover-and-drop language
Intention
Shoots the interface like a runway: in a low-angle perspective, UI cards hover at different heights, then rain down fast in reading order (row-first, left→right) — the falling feeling is crisp decisiveness. Three critical rules: massively overlapping in parallel (starting points offset by only 1.5f, each fall taking 9f ⇒ there are always 5–6 cards falling at once in the air, "almost together but with a ripple feel"; a version with two serial waiting rounds was rejected), zero bounce (they stop dead on landing; any bounce was rejected), and fast (a 9f fall; a 15f version was rejected as "needs to be a bit faster"). Once they've all landed, the page stands up from lying flat (rotateX 66°→0°) and the camera pulls back to center, taking the viewer from "watching the show" back to "looking at the interface."
Duration & energy
- Duration: Hover display ~0.4s + drop ~1.2s + stand up & square ~1.8s; full shot 4s
- Energy: High (the falling feeling is the spectacle, standing up and squaring is the resolution)
Shot recipe (original Chinese — timing, easing, parameter table, known pitfalls)
意图
把界面拍成跑道:低角度透视里 UI 卡片悬空在不同高度,随后按界面 位置顺序(行优先左→右)一阵急雨式贴落——掉落感=干脆利落。 三个命门:重叠并行(起点只错 1.5f、下落 9f ⇒ 空中恒有 5–6 张 同时在落,"几乎一起但有涟漪感";串行等待两轮被裁)、零回弹 (着地即停,弹起被裁)、快(下落 9f,15f 版被裁"快一点")。 落齐后页面从躺倒立起(rotateX 66°→0°)、镜头拉远居中,观众从 "看戏"回到"看界面"。
动效核心
- 机位:低角度强透视(rotateX ~66° 起),页面平躺如跑道
- 悬空:卡片初始悬浮 560–880px(高度错落),空中卡追光提亮
~1.35x(判例:被环境压黑带吞掉=悬空不可感)
- 贴落:起点 = 6 + i×1.5f + jitter(≤1.2f)(保序),下落 9f 重力
加速(距离∝t²),着地即停零回弹
- 立起:全员落定后 rotateX 66°→0° + 镜头拉远归中(easeRise),
终态正视整页
- 压黑带控制在 ~32%,给空中卡留可见度
参数表
| 参数 | 典型值 | 调节手感 | |------|--------|----------| | 起点差 | 1.5f/张(jitter ≤1.2f 不乱序) | 3f 版被裁"不需要一个落下了再启动第二个";0 差读作开关(那是 orbit 的同帧语法) | | 下落时长 | 9f 重力加速 | 15f 被裁慢;<6f 读不出下落方向 | | 回弹 | 零(着地即停) | 压弹 4.5% 版被裁"去掉弹起"——掉落感=干脆利落 | | 悬浮高度 | 560–880px 错落 | 同高读作阅兵;>1000px 落地时间拉长 | | 空中提亮 | ~1.35x 追光 | 不提亮悬空不可感(返工判例) | | 立起段 | rotateX 66→0 + 拉远,~56f | 只落不立起=没有收束;立起太快读作弹板 |
已知坑
- demo 在灰阶/占位素材上调校通过——参数是调校起点非实战定稿,
首次实战须以真实素材回验
- 贴落节奏语义(三卡判例族):本卡=急雨式重叠错峰;
graze-face-tour=巡礼错峰;neon-frame-orbit-drop=同帧齐落。 按镜头语义选,错用即被裁
- 与 deck-deal-flyin 分工:那张是卡片飞入列队(水平方向运动);
本卡是垂直贴落+页面立起的两段式,掉落感是本体
- 立起段原片无对应参考(用户口述新增),终态版式对齐 clickup11
正视图
参考实现
demos/ui-entrance/runway-ground-skim/ (RunwayGroundSkim.tsx) 原片出处:clickup-30.mp4 ≈46–50s
Reference implementation (Remotion)
- demos/ui-entrance/runway-ground-skim/RunwayGroundSkim.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/ui-entrance/runway-ground-skim/RunwayGroundSkim.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/runway-ground-skim.jsongit clone https://github.com/Vincentwei1021/video-shotcraft
# component: demos/ui-entrance/runway-ground-skim/RunwayGroundSkim.tsx
npx remotion render <CompositionId> out.mp4claude mcp add --transport http gimgs https://gimgs.net/mcp
# then: search_motion_templates / get_motion_template("runway-ground-skim")Prompt
Use the video-shotcraft shot recipe "Runway ground skim entrance" (Remotion composition, Apache-2.0) published on gimgs.net. Recipe JSON (prompt = timing/easing/parameter table/pitfalls, files = TSX source): https://gimgs.net/motion/runway-ground-skim.json Raw TSX: https://gimgs.net/motion/runway-ground-skim/template.html Repository: https://github.com/Vincentwei1021/video-shotcraft (component: demos/ui-entrance/runway-ground-skim/RunwayGroundSkim.tsx; imports demos/_fixtures and assets/lib) Spec: 1920x1080, 30 fps, 3.99s 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/ui-entrance/runway-ground-skim/RunwayGroundSkim.tsx =====
// runway-ground-skim v5 —— 源片 clickup-30.mp4 约 46–50s(clickup10 截图 5 张):
// 用户 v5 意见(逐字):"去掉落地后弹起的效果,然后整个下落的过程快一点"
// 落实:①删除落地压弹——着地即停,零回弹零压缩(判例:掉落感=干脆利落);
// ②下落整体提速——单卡下落 15→9 帧,全员落定 f45→f33,立起段随之前移;
// ③保留项不动:错峰 3 帧起点、界面位置顺序(行优先左→右)、重力加速
// (距离∝t²)、贴落完成后页面立起转正收尾。
import React from 'react';
import { AbsoluteFill, interpolate, useCurrentFrame, Easing } from 'remotion';
const FONT = 'Helvetica, Arial, sans-serif';
const INK = '#3c3c42';
const MID = '#8d8d94';
const FAINT = '#d2d2d5';
const easeRise = Easing.bezier(0.42, 0, 0.16, 1);
/* mulberry32 带种子(起跳节奏 ≤1.5 帧微差 < 3 帧错峰,顺序不乱) */
const mulberry32 = (seed: number) => () => {
seed |= 0; seed = (seed + 0x6d2b79f5) | 0;
let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
const CARD_W = 760;
const MiniCardFace: React.FC<{ title: string; sub: string }> = ({ title, sub }) => (
<div style={{
width: CARD_W, border: `4px solid ${FAINT}`, borderRadius: 22, padding: '30px 40px',
background: '#fcfcfb', display: 'flex', flexDirection: 'column', gap: 14, boxSizing: 'border-box',
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: 24 }}>
<div style={{ width: 40, height: 40, border: '6px solid #85858b', borderRadius: 8, flexShrink: 0 }} />
<div style={{ fontFamily: FONT, fontSize: 46, color: INK, fontWeight: 650, whiteSpace: 'nowrap' }}>{title}</div>
</div>
<div style={{ fontFamily: FONT, fontSize: 36, color: MID, paddingLeft: 64, whiteSpace: 'nowrap' }}>{sub}</div>
</div>
);
/* 界面位置顺序 = 数组顺序:第一行左→右,再第二行左→右 */
const CARDS: { title: string; sub: string; col: number; row: number }[] = [
{ title: 'Creative Refresh', sub: 'New logo exploration', col: 0, row: 0 },
{ title: 'New Bugs Per Week', sub: 'Bug tracker Dashboard', col: 1, row: 0 },
{ title: 'Tiger Team Roadmap', sub: 'Roadmap Outline', col: 2, row: 0 },
{ title: 'Design System', sub: 'Design Handbook Inspo', col: 3, row: 0 },
{ title: 'Development Sprint Dashboard', sub: 'Dev Team Sprints', col: 0, row: 1 },
{ title: 'CSS Bug Tracker', sub: 'Query Reports', col: 1, row: 1 },
{ title: 'Platform', sub: 'System Health Monitor', col: 2, row: 1 },
];
/* Recent 网格槽位(面板内容坐标) */
const GRID_X = 1180, GRID_Y = 760, COL_GAP = 850, ROW_GAP = 250;
const slotPos = (col: number, row: number) => ({ x: GRID_X + col * COL_GAP, y: GRID_Y + row * ROW_GAP });
/* 平躺地面:Home 仪表盘(卡片槽位留空,由悬浮卡片落入) */
const Ground: React.FC = () => (
<div style={{ width: 4600, height: 2600, background: '#f6f6f5', borderRadius: 60, position: 'relative', overflow: 'hidden' }}>
<div style={{ display: 'flex', height: '100%' }}>
{/* 左侧栏 */}
<div style={{ width: 860, borderRight: `4px solid ${FAINT}`, padding: '70px 60px 0', background: '#f1f1f0' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 26 }}>
<div style={{ width: 64, height: 64, borderRadius: 18, background: 'linear-gradient(135deg,#adadb3,#6b6b72)' }} />
<div style={{ fontFamily: FONT, fontSize: 56, fontWeight: 800, color: INK }}>ClickUp</div>
</div>
<div style={{ height: 46 }} />
{['Home', 'Inbox', 'Company', 'People & Teams', 'Goals', 'Docs', 'More'].map((t, i) => (
<div key={t} style={{
display: 'flex', alignItems: 'center', gap: 30, height: 108, paddingLeft: 32,
background: i === 0 ? '#e6e6f0' : 'transparent', borderRadius: 20,
}}>
<div style={{ width: 36, height: 36, border: '6px solid #90909a', borderRadius: 9 }} />
<div style={{ fontFamily: FONT, fontSize: 46, color: INK, fontWeight: i === 0 ? 650 : 400 }}>{t}</div>
</div>
))}
<div style={{ height: 60 }} />
<div style={{ fontFamily: FONT, fontSize: 38, letterSpacing: 5, color: MID, fontWeight: 600, paddingLeft: 32 }}>SPACES</div>
<div style={{ height: 16 }} />
{['EPD', 'Product roadmap', 'Design', 'Designer handbook', '3.0', 'Design system'].map((t) => (
<div key={t} style={{ display: 'flex', alignItems: 'center', gap: 30, height: 96, paddingLeft: 32 }}>
<div style={{ width: 44, height: 44, borderRadius: 12, background: '#d6d6da' }} />
<div style={{ fontFamily: FONT, fontSize: 42, color: INK }}>{t}</div>
</div>
))}
</div>
{/* 主区 */}
<div style={{ flex: 1, padding: '70px 100px 0', position: 'relative' }}>
{/* 顶部 tab 条 */}
<div style={{ display: 'flex', gap: 110, fontFamily: FONT, fontSize: 42, color: MID, marginBottom: 60 }}>
<div>Product analytics</div><div style={{ fontWeight: 700, color: INK }}>ClickUp 3.0</div>
<div>Widget brainstorm</div><div>Design system</div><div>Design</div>
</div>
<div style={{ fontFamily: FONT, fontSize: 110, fontWeight: 750, color: INK }}>Home</div>
<div style={{ height: 40 }} />
<div style={{
display: 'flex', alignItems: 'center', gap: 30, border: `4px solid ${FAINT}`,
borderRadius: 24, padding: '28px 42px', background: '#fff', width: 1400,
}}>
<div style={{ width: 38, height: 38, borderRadius: 19, border: '6px solid #9a9aa0' }} />
<div style={{ fontFamily: FONT, fontSize: 42, color: MID }}>Search by app, filetype, or keyword</div>
</div>
<div style={{ height: 66 }} />
<div style={{ display: 'flex', gap: 70, fontFamily: FONT, fontSize: 46 }}>
<div style={{ color: INK, fontWeight: 700 }}>Recent</div>
<div style={{ color: MID }}>Favorites</div>
</div>
{/* ↑ Recent 网格槽位区域留空(卡片从空中落入 GRID_X/GRID_Y 起的两行四列) */}
<div style={{ height: 560 }} />
<div style={{ display: 'flex', gap: 70, fontFamily: FONT, fontSize: 44 }}>
<div style={{ color: INK, fontWeight: 700 }}>Todo</div>
<div style={{ color: MID }}>Comments</div>
<div style={{ color: MID }}>Done</div>
<div style={{ color: MID }}>Delegated</div>
</div>
<div style={{ height: 36 }} />
<div style={{
display: 'inline-block', padding: '16px 40px', background: '#e4e4e3', borderRadius: 14,
fontFamily: FONT, fontSize: 36, letterSpacing: 4, color: '#6f6f75', fontWeight: 600,
}}>TODAY</div>
<div style={{ height: 40 }} />
{['New Bugs Per Week', 'Designer handbook', 'Mobile screens', 'Product roadmap'].map((t) => (
<div key={t} style={{
display: 'flex', alignItems: 'center', gap: 34, height: 118,
borderBottom: '3px solid #e5e5e3', width: 2600,
}}>
<div style={{ width: 32, height: 32, borderRadius: 9, background: '#c04a6e' }} />
<div style={{ fontFamily: FONT, fontSize: 46, color: INK, fontWeight: 550 }}>{t}</div>
<div style={{ marginLeft: 'auto', width: 180, height: 16, background: '#e3e3e8', borderRadius: 8 }} />
</div>
))}
</div>
</div>
</div>
);
export const RunwayGroundSkim: React.FC = () => {
const frame = useCurrentFrame();
const rand = mulberry32(20260718);
const jit = CARDS.map(() => rand() * 1.2); // ≤1.2 帧微差 < 3 帧错峰,顺序绝不乱
/* ---- 节奏(掉落提速+着地即停版,118 帧)----
* f0–6 开场:全员悬空在黑色空域
* f6–33 掉落:第 i 张起跳 f = 6 + i*3(错峰只差 3 帧),下落 9 帧
* ⇒ 9 >> 3,空中同时 3–4 张在落(重叠并行,非串行等待);
* 重力加速(距离∝t²),着地即停——零回弹零压缩
* f38–94 页面立起 + 视角转正(rotateX 66→0,镜头拉远居中)
* f94–118 终态正视整页 hold */
// v6(批次 15):用户意见"下落的时间差多调小一些,不需要一个落下了
// 再启动第二个"——起点差 3→1.5 帧(9 帧下落窗口重叠度 6 倍,
// 任意时刻空中 5–6 张同落,几乎齐落带涟漪感)
const START0 = 6, GAP = 1.5, FALLF = 9;
const lifts = CARDS.map((c, i) => {
const t = frame - (START0 + i * GAP + jit[i]);
const H = 560 + (i % 3) * 160; // 初始悬浮高度错落
if (t <= 0) return H;
const p = t / FALLF;
if (p < 1) return H * (1 - p * p); // 重力加速:下落距离 ∝ t²
return 0; // 着地即停,无弹起
});
/* 立起段进度 */
const riseP = interpolate(frame, [38, 94], [0, 1], { easing: easeRise, extrapolateLeft: 'clamp', extrapolateRight: 'clamp' });
/* 镜头:落卡段轻微推进(72→66°),立起段转正(→0°)并拉远居中 */
const landP = interpolate(frame, [0, 34], [0, 1], { easing: Easing.bezier(0.3, 0.1, 0.6, 0.9), extrapolateLeft: 'clamp', extrapolateRight: 'clamp' });
const rx = interpolate(landP, [0, 1], [72, 66]) - 66 * riseP;
const z = interpolate(landP, [0, 1], [-620, -320]) + riseP * (-1620 - -320);
const bright = interpolate(frame, [0, 32, 86], [0.32, 0.8, 1.0], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' });
/* 锚点/透视原点随立起归中:终态整页入画、中心≈画面中心 */
const anchorTop = 58 - riseP * 6; // % :58 → 52(终态整页不裁底)
const perspY = 30 + riseP * 20; // % :30 → 50
const cam = (children: React.ReactNode, extra?: React.CSSProperties) => (
<AbsoluteFill style={{ perspective: 1050, perspectiveOrigin: `50% ${perspY}%`, ...extra }}>
<div style={{
position: 'absolute', left: '50%', top: `${anchorTop}%`, width: 0, height: 0,
transformStyle: 'preserve-3d',
transform: `translateZ(${z}px) rotateX(${rx}deg)`,
}}>
{children}
</div>
</AbsoluteFill>
);
const scene = (
<div style={{ position: 'absolute', transformStyle: 'preserve-3d', transform: 'translate(-2300px, -1500px)' }}>
{/* 地面 */}
<div style={{ filter: `brightness(${bright})` }}>
<Ground />
</div>
{/* 地面上的软影(z≈0,卡片同形,随悬浮高度变化大小/偏移/浓度) */}
{CARDS.map((c, i) => {
const h = lifts[i];
if (h < 2) return null;
const s = slotPos(c.col, c.row);
return (
<div key={'sh' + i} style={{
position: 'absolute', left: s.x + 20, top: s.y + 14, width: CARD_W - 40, height: 150,
transform: `translateZ(1px) translate(${h * 0.08}px, ${h * 0.12}px) scale(${1 + h * 0.0004})`,
background: 'rgba(10,8,16,0.9)', borderRadius: 24,
filter: `blur(${10 + h * 0.03}px)`,
opacity: Math.max(0.12, 0.38 - h * 0.0003),
}} />
);
})}
{/* 悬空卡片:与地面同向平躺,translateZ 抬高,落回槽位。
空中时被"追光"打亮(比暗地面亮),落地融入地面亮度 */}
{CARDS.map((c, i) => {
const h = lifts[i];
const s = slotPos(c.col, c.row);
const airLit = h > 2 ? Math.max(1.35, bright) : bright;
return (
<div key={'card' + i} style={{
position: 'absolute', left: s.x, top: s.y,
transform: `translateZ(${h}px)`,
filter: `brightness(${airLit})`,
boxShadow: h > 2 ? `0 0 ${30 + h * 0.05}px rgba(240,235,255,${Math.min(0.3, h * 0.0004)})` : 'none',
}}>
<MiniCardFace title={c.title} sub={c.sub} />
</div>
);
})}
</div>
);
/* 立起段各氛围层收敛:黑空域压顶/地平线光/近景糊都随转正淡出 */
const airOp = 1 - riseP;
return (
<AbsoluteFill style={{ background: '#07060a' }}>
{/* 地平线微光(转正后消失) */}
<AbsoluteFill style={{
background: `radial-gradient(ellipse 60% 14% at 50% 40%, rgba(190,170,255,${(0.16 + landP * 0.1) * airOp}), transparent 75%)`,
}} />
{cam(scene)}
{/* 近景轻糊(转正后消失) */}
{airOp > 0.02 && (
<AbsoluteFill style={{
filter: 'blur(10px) brightness(0.88)', opacity: airOp,
WebkitMaskImage: 'linear-gradient(180deg, transparent 60%, rgba(0,0,0,0.7) 84%, black 98%)',
maskImage: 'linear-gradient(180deg, transparent 60%, rgba(0,0,0,0.7) 84%, black 98%)',
}}>
{cam(scene)}
</AbsoluteFill>
)}
{/* 上半空域压黑(随立起淡出) */}
<AbsoluteFill style={{
background: 'linear-gradient(180deg, rgba(4,3,8,0.9) 0%, rgba(4,3,8,0.35) 16%, transparent 32%)',
opacity: airOp, pointerEvents: 'none',
}} />
{/* 暗角(转正后减弱不消失) */}
<AbsoluteFill style={{
background: 'radial-gradient(ellipse 95% 90% at 50% 55%, transparent 50%, rgba(3,2,7,0.55) 85%, rgba(2,1,5,0.88) 100%)',
opacity: 1 - riseP * 0.55, pointerEvents: 'none',
}} />
</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/ui-entrance/runway-ground-skim/RunwayGroundSkim.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

Wall reveal: three moves
Three whole-wall batch entrances — bento cell-by-cell light-up, grid wave flip, and blueprint draw-on; all reveal in place with no displacement, complementing the fly-in, displacement-type deck-deal-flyin.

Value-stagger gradient bars
16 bars enter with delay as a time stagger, while height/hue/offset/blur each lay out as a value gradient from first to last; the second beat moves the stagger origin to center and re-lays the pulse peaking there.

SVG shape morph loop
A 140-point closed outline morphs smoothly into another and back; both shapes are resampled to equal point counts in polar coordinates with per-point radius interpolation and inOutCubic, plus a light scale breathing, slow rotation, and hue drifting 185°→305° mid-morph.

Skeleton reveal
Three-tier reveal from sketch to structure to content — hand-drawn doodles get swapped for skeleton bars, which resolve into real UI.