Motion · Tương tác
Luồng phản hồi AI
Bảng phản hồi AI đưa ra một câu tóm tắt dễ đọc trước, rồi các dòng bằng chứng kèm icon trạng thái lần lượt đổ vào, gom lại thành trạng thái hoàn tất.
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
Cảnh tạo kết quả cho AI assistant/agent/search/copilot; nhấn mạnh "kết luận đến trước, bằng chứng theo sau, hoàn tất nhiệm vụ".
Ý đồ
Biến "AI đang làm việc" thành một chuỗi nhân quả dễ đọc, không phải màn cuộn log: người xem hiểu tóm tắt câu trả lời trước, sau đó thấy bằng chứng/tác vụ con lần lượt được bổ sung, cuối cùng trạng thái hoàn tất xác nhận vòng việc này đã xong.
Thời lượng & năng lượng
- Thời lượng: Khoảng 4–5s (120–150f, gồm ≥15f giữ hình ở trạng thái hoàn tất).
- Năng lượng: Trung bình cao (thông tin liên tục tăng thêm, nhưng khả năng đọc ưu tiên hơn phô diễn tốc độ).
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)
意图
把“AI 正在工作”拍成可读的因果链,而不是日志刷屏:观众先看懂答案摘要, 再看到证据/子任务逐条补齐,最后由完成态确认这轮工作结束。
动效核心
- 用目标产品真实响应面板截图做 backplate;动态区截图前隐藏,按 layout 坐标重叠回真实槽位(Q1/Q9)
- 面板进入后摘要句先以短 wipe/淡入落定,落定后至少 12f 才起首条证据
- 6–8 条证据行自下方 14–24px + 轻微 blur 逐行汇入;间隔逐渐收紧但保留可数节拍
- 行体先停,状态图标晚 2–4f 由 pending 转 running/done,形成主次拖拽层级
(模式已沉为 assets/lib/helpers/motion.ts lagged)
- 末条完成后只做一次面板级完成脉冲,随后全画面静止 ≥15f;不逐行 glint
参数表
| 参数 | 典型值 | 调节手感 | |------|--------|----------| | 摘要 | cue 18,10–14f reveal;摘要落定后 hold ≥12f,首行 cue ≥42 | 摘要与首行粘连会失去“结论先到”的叙事;正常速度必须能先读到一句完整结论 | | 行节拍 | cue[i]=42+[0,11,21,30,38,45,51,56][i],每行 10–14f | 间隔 11→5f 逐渐收紧,表达工作加速;不要密到所有行同时糊成一团(R2) | | 行入场 | y 18px→0,blur 6px→0,opacity 0→1,bezier(0.2,0.75,0.25,1) | 位移须过正常速度肉眼阈值;大幅横飞会像通知列表,不像证据归档 | | 状态图标 | 比行体 cue 晚 3f;pending 环 → running 缺口环 → done 实心勾,6–8f | 图标是状态回执,不抢行文本;同帧切换全部图标会读作假 loading | | 完成收束 | 末图标 done 后 6f,面板边框/摘要底色 opacity 0.25→0.55→0.25 共 10f | 只做面板级一次,不逐行发光(Q4);完成脉冲后留 ≥15f 真静止(R1) | | 相机 | 信息密集默认正视,zoom 1.04→1.0 微退;暗场斜拍仅作情绪变体 | Linear 参考片的斜拍不是通用命令;文字读不清时立即回正视(Q6) |
声音
摘要落定配一发 transition-soft;前 3–4 行用短 pop 轻声递减 0.32→0.20,后半加速密集段合并成一条低音量 whoosh-fast,避免逐行机枪感; 最终完成态用 click-camera 的轻量版本(约 vol 0.3)。声音在画面锁定后钉帧。
已知坑
- 逐字符流式打字会把视线困在字符层;宣传片时长里优先按“语义块”揭示摘要,
真正展示输入操作时另用 type-and-filter
- 行体和状态图标若同帧落定,会像静态列表整体切换;保留 2–4f 拖拽层级
- 行终点必须是截图里真实列表槽位,不在面板上方悬浮;动态内容也必须用虚构数据
- 此卡借鉴 Linear Agent 发布片 t≈13–17s(进化轮 #3 breakdown #6),非用户判例;
参数未经本库实战项目验证,首次使用必须按 P1 在正常速度下看片,不以逐帧 diff 代替体感
参考实现
demos/interaction/ai-stream-response/ (StreamResponse.tsx)
Mã nguồn tham chiếu (Remotion)
- demos/interaction/ai-stream-response/StreamResponse.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/interaction/ai-stream-response/StreamResponse.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/ai-stream-response.jsongit clone https://github.com/Vincentwei1021/video-shotcraft
# component: demos/interaction/ai-stream-response/StreamResponse.tsx
npx remotion render <CompositionId> out.mp4claude mcp add --transport http gimgs https://gimgs.net/mcp
# then: search_motion_templates / get_motion_template("ai-stream-response")Prompt
Use the video-shotcraft shot recipe "Luồng phản hồi AI" (Remotion composition, Apache-2.0) published on gimgs.net. Recipe JSON (prompt = timing/easing/parameter table/pitfalls, files = TSX source): https://gimgs.net/motion/ai-stream-response.json Raw TSX: https://gimgs.net/motion/ai-stream-response/template.html Repository: https://github.com/Vincentwei1021/video-shotcraft (component: demos/interaction/ai-stream-response/StreamResponse.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/ai-stream-response/StreamResponse.tsx =====
import React from 'react';
import {
AbsoluteFill,
Easing,
Img,
interpolate,
useCurrentFrame,
} from 'remotion';
import backplate from './agent-stream.jpg';
const ROW_CUES = [42, 53, 63, 72, 80, 87, 93];
const ROWS = [
['Indexed the workspace', '128 files'],
['Mapped the authentication flow', '12 modules'],
['Checked recent error traces', 'No blockers'],
['Matched API contracts to handlers', '24 routes'],
['Verified permission boundaries', '6 roles'],
['Cross-checked release notes', '3 changes'],
['Prepared an implementation plan', 'Ready'],
] as const;
const clamp = {extrapolateLeft: 'clamp', extrapolateRight: 'clamp'} as const;
const ease = Easing.bezier(0.2, 0.75, 0.25, 1);
const CheckIcon: React.FC<{progress: number}> = ({progress}) => {
const ringOpacity = interpolate(progress, [0, 0.45, 0.75], [0.36, 1, 0], clamp);
const checkOpacity = interpolate(progress, [0.62, 1], [0, 1], clamp);
const checkScale = interpolate(progress, [0.62, 1], [0.72, 1], {...clamp, easing: ease});
return (
<div style={{position: 'relative', width: 27, height: 27, flex: '0 0 auto'}}>
<div
style={{
position: 'absolute', inset: 2, borderRadius: 99,
border: '2px solid rgba(183, 190, 199, .6)',
borderTopColor: '#d4ff77', opacity: ringOpacity,
transform: `rotate(${progress * 100}deg)`,
}}
/>
<div
style={{
position: 'absolute', inset: 1, borderRadius: 99,
background: '#b8f36a', opacity: checkOpacity,
transform: `scale(${checkScale})`,
display: 'grid', placeItems: 'center',
boxShadow: '0 0 16px rgba(184,243,106,.18)',
}}
>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M3.4 8.3 6.6 11l6-6" stroke="#15200d" strokeWidth="2.1" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</div>
</div>
);
};
const EvidenceRow: React.FC<{cue: number; title: string; meta: string; index: number}> = ({cue, title, meta, index}) => {
const frame = useCurrentFrame();
const body = interpolate(frame, [cue, cue + 12], [0, 1], {...clamp, easing: ease});
const status = interpolate(frame, [cue + 3, cue + 11], [0, 1], {...clamp, easing: ease});
return (
<div
style={{
position: 'absolute', left: 0, right: 0, top: index * 68,
height: 58, borderRadius: 13,
border: '1px solid rgba(255,255,255,.065)',
background: 'rgba(255,255,255,.025)',
display: 'flex', alignItems: 'center', padding: '0 20px', gap: 15,
opacity: body,
transform: `translateY(${18 * (1 - body)}px)`,
filter: `blur(${6 * (1 - body)}px)`,
}}
>
<CheckIcon progress={status}/>
<div style={{fontSize: 21, color: '#e8eaed', letterSpacing: '-0.015em', flex: 1}}>{title}</div>
<div style={{fontSize: 16, color: status > 0.78 ? '#a9c77f' : '#777f89', fontVariantNumeric: 'tabular-nums'}}>{meta}</div>
</div>
);
};
export const StreamResponse: React.FC = () => {
const frame = useCurrentFrame();
const panelIn = interpolate(frame, [0, 16], [0, 1], {...clamp, easing: ease});
const summary = interpolate(frame, [18, 30], [0, 1], {...clamp, easing: ease});
const camera = interpolate(frame, [0, 100], [1.04, 1], {...clamp, easing: ease});
const pulse = interpolate(frame, [110, 115, 120], [0.25, 0.55, 0.25], clamp);
const complete = interpolate(frame, [110, 120], [0, 1], {...clamp, easing: ease});
return (
<AbsoluteFill style={{background: '#07090a', fontFamily: 'Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif', overflow: 'hidden'}}>
<AbsoluteFill style={{transform: `scale(${camera})`}}>
<Img src={backplate} style={{width: '100%', height: '100%', objectFit: 'cover', filter: 'brightness(.58) saturate(.76) blur(1px)', opacity: .7}}/>
<AbsoluteFill style={{background: 'radial-gradient(circle at 50% 44%, rgba(6,9,10,.08) 10%, rgba(4,6,7,.72) 92%)'}}/>
</AbsoluteFill>
<div
style={{
position: 'absolute', left: 344, top: 72, width: 1232, height: 936,
borderRadius: 29, overflow: 'hidden',
background: 'linear-gradient(145deg, rgba(25,29,32,.985), rgba(12,15,17,.99))',
border: `1px solid rgba(184,243,106,${pulse})`,
boxShadow: '0 46px 100px rgba(0,0,0,.58), inset 0 1px 0 rgba(255,255,255,.06)',
opacity: panelIn,
transform: `translateY(${12 * (1 - panelIn)}px) scale(${.985 + .015 * panelIn})`,
}}
>
<div style={{height: 76, display: 'flex', alignItems: 'center', padding: '0 34px', borderBottom: '1px solid rgba(255,255,255,.065)'}}>
<div style={{width: 29, height: 29, borderRadius: 9, background: '#e9edf0', color: '#15191b', display: 'grid', placeItems: 'center', fontWeight: 800, fontSize: 17}}>A</div>
<div style={{marginLeft: 13, fontSize: 22, fontWeight: 590, color: '#f1f3f4'}}>Ask Atlas</div>
<div style={{marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 9, color: '#7d858e', fontSize: 15}}>
<span style={{width: 7, height: 7, background: '#b8f36a', borderRadius: 99}}/> Workspace agent
</div>
</div>
<div style={{padding: '28px 38px 34px'}}>
<div style={{height: 48, borderRadius: 12, background: 'rgba(255,255,255,.048)', display: 'flex', alignItems: 'center', padding: '0 18px', color: '#b9bec4', fontSize: 18}}>
Review this codebase and identify the safest implementation path
</div>
<div style={{height: 119, marginTop: 21, borderBottom: '1px solid rgba(255,255,255,.07)', overflow: 'hidden', position: 'relative'}}>
<div style={{position: 'absolute', inset: 0, opacity: summary, transform: `translateY(${10 * (1 - summary)}px)`, clipPath: `inset(0 ${100 * (1 - summary)}% 0 0)`}}>
<div style={{color: '#838b94', fontSize: 15, textTransform: 'uppercase', letterSpacing: '.12em', marginBottom: 10}}>Result summary</div>
<div style={{color: '#f3f4f5', fontSize: 26, fontWeight: 510, lineHeight: 1.35, letterSpacing: '-.022em'}}>
The codebase is ready for a focused, low-risk implementation.
</div>
</div>
</div>
<div style={{position: 'relative', height: 466, marginTop: 17}}>
{ROWS.map(([title, meta], index) => <EvidenceRow key={title} cue={ROW_CUES[index]} title={title} meta={meta} index={index}/>)}
</div>
<div style={{height: 60, marginTop: 2, borderTop: '1px solid rgba(255,255,255,.07)', display: 'flex', alignItems: 'flex-end'}}>
<div style={{display: 'flex', alignItems: 'center', gap: 11, opacity: complete, transform: `translateY(${5 * (1 - complete)}px)`}}>
<div style={{width: 24, height: 24, borderRadius: 99, background: '#b8f36a', display: 'grid', placeItems: 'center'}}>
<svg width="14" height="14" viewBox="0 0 16 16" fill="none"><path d="M3.4 8.3 6.6 11l6-6" stroke="#15200d" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"/></svg>
</div>
<span style={{color: '#dce7d0', fontSize: 17, fontWeight: 560}}>Analysis complete</span>
<span style={{color: '#69717a', fontSize: 15}}>7 checks finished · ready to build</span>
</div>
</div>
</div>
</div>
</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/interaction/ai-stream-response/StreamResponse.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.