Motion · Tương tác
Command palette hạ xuống
Command palette giáng thế — toàn màn hình tối và mờ lại, panel ⌘K bật lò xo rơi xuống có vọt quá, các dòng ứng viên hiện ra so le, danh sách thu hẹp theo thời gian thực khi gõ chữ.
Nguồn: video-shotcraft (Vincent Wei) ↗ · Apache-2.0 · Composition Remotion (React/TSX) — có video preview, không có player trong trình duyệt
Khi nào dùng
Tường thuật "cả sản phẩm nằm trong một ô nhập liệu" cho sản phẩm hướng hiệu suất; màn ra mắt biểu tượng cho tính năng command palette/tìm kiếm/phím tắt.
Ý đồ
Nghi thức biểu tượng trong phim ra mắt Raycast/Linear: một tiếng động nhẹ, cả thế giới UI tối lại nhường chỗ, panel ⌘K từ giữa phía trên bật rơi xuống, danh sách ứng viên hiện ra so le; gõ hai chữ cái, danh sách thu hẹp theo thời gian thực — "mọi thứ bạn cần đều nằm trong ô nhập liệu này". Tương tác mô phỏng theo đúng tốc độ gõ của người thật (R3), cảm giác "ép lại" khi thu hẹp đến từ chiều cao dòng sụp xuống chứ không phải mờ dần.
Thời lượng & năng lượng
- Thời lượng: 4–5s.
- Năng lượng: Trung bình (cảm giác nghi thức, khung bật rơi và lúc thu hẹp là hai điểm va chạm nhỏ).
Công thức cảnh quay (bản gốc tiếng Trung — timing, easing, bảng tham số, lỗi hay gặp)
意图
Raycast/Linear 发布片的标志性仪式:一声轻响,整个 UI 世界压暗 让路,⌘K 面板从中心上方弹落,候选列表错峰浮现;敲两个字母, 列表实时收窄——"你要的一切都在这个输入框里"。模拟交互按真人 操作速度走(R3),收窄的"挤压感"来自行高塌缩而非淡出。
动作阶段
| 阶段 | 帧段参考 | 内容 | |------|------|------| | 1 静置 | f0–12 | dashboard 初始状态 | | 2 让路 | f12–22 | 背景压暗至 45% + blur 10px | | 3 降临 | f18–33 | 面板 -20px 落入,过冲 +8px 回正(与压暗跨骑) | | 4 候选浮现 | f32–56 | 5 行每 4f 错峰上浮 | | 5 输入收窄 | ~f62–90 | 两次按键字符出现,行高塌缩收窄 5→2(塌缩完条件卸载) | | 6 选中定格 | 末 ≥40f | 首条高亮(底色+左描边),光标转常亮,真静止 |
参数表
| 参数 | 典型值 | 调节手感 | |------|--------|----------| | 压暗 | 亮度 45% + blur 10px,10f | 压不够面板不突出;全黑失去上下文 | | 弹落 | 起点上方 20px,overshoot 一拍回正 | 无过冲读作淡入,仪式感没了 | | 候选错峰 | 4f/行 | 同帧出读作贴图 | | 收窄 | height→0 + overflow hidden 塌缩 | 淡出收窄没有"挤压"感;塌缩完必须卸载 | | 光标 | 闪烁周期 16f;定格后强制常亮 | 闪到结尾就永不真静止(demo 判例) |
已知坑
- demo 在灰阶/占位素材上调校通过——参数是调校起点非实战定稿,
首次实战须以真实素材回验
- 输入节奏按真人打字速度(R3 判例),两键间隔 ≥12f
- 组合款见 theme-switch-moves B 式(面板收缩成点引发主题涟漪)
- 实战候选行放真实功能名——这是功能清单的天然展位(P4 映射)
- 声音:唤出一声轻响(非游戏音),按键 2 声键帽拟音,
高亮一声轻 pop(S1/S4 同源)
参考实现
demos/interaction/command-palette-summon/ (CommandPaletteSummon.tsx)
Mã nguồn tham chiếu (Remotion)
- demos/interaction/command-palette-summon/CommandPaletteSummon.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/interaction/command-palette-summon/CommandPaletteSummon.tsx
Component import fixture chung từ demos/_fixtures và helper từ assets/lib của repo; xem SKILL.md trong repo để biết quy trình render.
Mọi thứ trên trang này đều có bản máy đọc. Đưa agent link JSON, hoặc thêm MCP server gimgs rồi bảo nó tìm motion template.
- Tải JSON — trong đó có công thức cảnh quay (timing, easing, bảng tham số, lỗi hay gặp) và toàn bộ source TSX.
- Clone repo video-shotcraft; component import fixture chung từ demos/_fixtures và helper từ assets/lib.
- Đăng ký component thành <Composition> trong project Remotion rồi render bằng `npx remotion render`, hoặc dựng lại công thức bằng engine của bạn.
curl -s https://gimgs.net/motion/command-palette-summon.jsongit clone https://github.com/Vincentwei1021/video-shotcraft
# component: demos/interaction/command-palette-summon/CommandPaletteSummon.tsx
npx remotion render <CompositionId> out.mp4claude mcp add --transport http gimgs https://gimgs.net/mcp
# then: search_motion_templates / get_motion_template("command-palette-summon")Prompt
Use the video-shotcraft shot recipe "Command palette hạ xuống" (Remotion composition, Apache-2.0) published on gimgs.net. Recipe JSON (prompt = timing/easing/parameter table/pitfalls, files = TSX source): https://gimgs.net/motion/command-palette-summon.json Raw TSX: https://gimgs.net/motion/command-palette-summon/template.html Repository: https://github.com/Vincentwei1021/video-shotcraft (component: demos/interaction/command-palette-summon/CommandPaletteSummon.tsx; imports demos/_fixtures and assets/lib) Spec: 1920x1080, 30 fps, 5.06s 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/interaction/command-palette-summon/CommandPaletteSummon.tsx =====
// command-palette-summon —— 命令面板降临
// FakeDashboard 静置 → 整屏压暗+blur → ⌘K 面板从中心上方 20px 带 overshoot 弹落
// → 5 条候选行错峰浮现 → 模拟输入 2 字母(灰块当字符)候选 5→3→2 收窄
// → 高亮首条。f=110 后全静止(40f)。光标 f<104 闪烁、之后常亮。
import React from 'react';
import { useCurrentFrame, interpolate, Easing } from 'remotion';
import { G, FakeDashboard } from '../../_fixtures/Fixtures';
const CL = { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' } as const;
// 时间轴
const DIM0 = 12; // 压暗开始(前 12f 初始静置)
const DIM1 = 22;
const PANEL_IN = 18; // 面板开始弹落
const ROWS_START = 32; // 候选行开始错峰浮现
const KEY1 = 62; // 第一个字母
const KEY2 = 78; // 第二个字母
const HL = 94; // 高亮首条
const BLINK_END = 104; // 光标停止闪烁(常亮)
const PANEL_W = 780;
const PANEL_X = (1920 - PANEL_W) / 2;
const PANEL_Y = 290;
const ROW_H = 72;
const ROW_GAP = 8;
const EXIT_DUR = 10;
// exitAt: 0=留到最后,1=第一次按键后退出,2=第二次按键后退出
const ROWS = [
{ titleW: 52, exitAt: 0 },
{ titleW: 38, exitAt: 0 },
{ titleW: 61, exitAt: 2 },
{ titleW: 45, exitAt: 1 },
{ titleW: 56, exitAt: 1 },
];
const PaletteRow: React.FC<{ i: number; frame: number }> = ({ i, frame }) => {
const { titleW, exitAt } = ROWS[i];
const inStart = ROWS_START + i * 4;
const exitStart = exitAt === 1 ? KEY1 + 3 : exitAt === 2 ? KEY2 + 3 : null;
if (exitStart !== null && frame >= exitStart + EXIT_DUR) return null; // 条件卸载,非 opacity 0
const inOp = interpolate(frame, [inStart, inStart + 8], [0, 1], CL);
const inY = interpolate(frame, [inStart, inStart + 8], [12, 0], {
easing: Easing.out(Easing.cubic),
...CL,
});
const exitT =
exitStart === null
? 1
: interpolate(frame, [exitStart, exitStart + EXIT_DUR], [1, 0], {
easing: Easing.inOut(Easing.cubic),
...CL,
});
// 高亮首条
const hl = i === 0 ? interpolate(frame, [HL, HL + 10], [0, 1], CL) : 0;
return (
<div
style={{
height: (ROW_H + ROW_GAP) * exitT,
opacity: inOp * exitT,
overflow: 'hidden',
}}
>
<div
style={{
height: ROW_H,
borderRadius: 12,
background: hl > 0 ? `rgba(228,228,226,${hl})` : 'transparent',
boxShadow: hl > 0 ? `inset 4px 0 0 rgba(47,47,47,${hl})` : 'none',
display: 'flex',
alignItems: 'center',
gap: 20,
padding: '0 22px',
boxSizing: 'border-box',
transform: `translateY(${inY}px)`,
}}
>
<div style={{ width: 36, height: 36, borderRadius: 9, background: G.mid }} />
<div style={{ height: 14, width: `${titleW}%`, background: G.bar, borderRadius: 7 }} />
<div style={{ marginLeft: 'auto', width: 58, height: 24, borderRadius: 6, background: G.line }} />
</div>
</div>
);
};
export const CommandPaletteSummon: React.FC = () => {
const frame = useCurrentFrame();
// 背景压暗 + blur
const dim = interpolate(frame, [DIM0, DIM1], [0, 0.45], CL);
const blur = interpolate(frame, [DIM0, DIM1], [0, 10], CL);
// 面板弹落:上方 20px → 过冲 +8px → 落回 0
const panelY =
frame < PANEL_IN + 9
? interpolate(frame, [PANEL_IN, PANEL_IN + 9], [-20, 8], {
easing: Easing.out(Easing.cubic),
...CL,
})
: interpolate(frame, [PANEL_IN + 9, PANEL_IN + 15], [8, 0], {
easing: Easing.inOut(Easing.cubic),
...CL,
});
const panelOp = interpolate(frame, [PANEL_IN, PANEL_IN + 7], [0, 1], CL);
// 模拟输入:已敲字符数
const typed = (frame >= KEY1 ? 1 : 0) + (frame >= KEY2 ? 1 : 0);
// 光标闪烁(周期 16f),BLINK_END 后常亮保证收尾静止
const cursorOn = frame >= BLINK_END ? true : (frame - PANEL_IN) % 16 < 8;
return (
<div style={{ width: 1920, height: 1080, background: G.bg, position: 'relative', overflow: 'hidden' }}>
<div style={{ filter: frame < DIM0 ? undefined : `blur(${blur}px)` }}>
<FakeDashboard variant="A" />
</div>
<div style={{ position: 'absolute', inset: 0, background: `rgba(20,20,20,${dim})` }} />
{frame >= PANEL_IN && (
<div
style={{
position: 'absolute',
left: PANEL_X,
top: PANEL_Y,
width: PANEL_W,
transform: `translateY(${panelY}px)`,
opacity: panelOp,
background: G.card,
borderRadius: 18,
border: `2px solid ${G.border}`,
boxShadow: '0 28px 90px rgba(0,0,0,0.4)',
padding: 20,
boxSizing: 'border-box',
}}
>
{/* 输入框 */}
<div
style={{
height: 76,
borderBottom: `2px solid ${G.line}`,
display: 'flex',
alignItems: 'center',
gap: 14,
padding: '0 10px 14px 10px',
boxSizing: 'border-box',
}}
>
<div style={{ width: 32, height: 32, borderRadius: 8, background: G.bar }} />
{/* 已敲入的字符灰块 */}
{Array.from({ length: typed }).map((_, c) => (
<div key={c} style={{ width: 28, height: 38, borderRadius: 6, background: '#4a4a48' }} />
))}
{/* 光标 */}
{cursorOn && <div style={{ width: 4, height: 42, background: G.ink, borderRadius: 2 }} />}
{/* 占位提示条:敲第一个字母时卸载 */}
{typed === 0 && (
<div style={{ width: 260, height: 14, borderRadius: 7, background: G.line, opacity: 0.8 }} />
)}
</div>
{/* 候选行 */}
<div style={{ paddingTop: 14 }}>
{ROWS.map((_, i) => (
<PaletteRow key={i} i={i} frame={frame} />
))}
</div>
</div>
)}
</div>
);
};
- 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/interaction/command-palette-summon/CommandPaletteSummon.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 |
|---|---|---|---|
| — | |||
Cùng danh mục

Sóng âm giọng nói trực tiếp
Sóng âm trực tiếp trên viên nang ghi âm — 64 cột dọc mảnh nhấp nhô theo "lời nói", giữa lúc nói vươn cao, ngừng thì co lại thành dòng chấm, sóng cuộn từ phải sang trái; trọn màn nói→ngừng→nói→gửi.

Gõ, lọc, đi vào chi tiết
Gõ tìm kiếm trên UI thật, lưới tự thu về một thẻ duy nhất, rồi cú click xuyên vào trang chi tiết.

Đổi theme: quét dọc và gợn sóng
Hai kiểu đổi theme — theme-sweep quét chéo (biên quét qua đâu đổi da đến đó) và palette-ripple kiểu kết hợp (panel ⌘K co lại thành điểm, gợn sóng lan ra từ điểm đó đổi da).
Thumb điều khiển phân đoạn làm nhân vật chính
Cú dịch chuyển thumb trong control phân đoạn thành nhân vật chính cận cảnh — con trỏ bấm vào, thumb trắng trượt qua, icon mới bật lên.