Motion · Chữ & title card
Từ khóa quét gradient nạp năng lượng
Trong khẩu hiệu nền đen, ánh sáng gradient quét nhanh trái sang phải "nạp năng lượng" cho một từ khóa — sáng nhất ở làn sóng đầu, tắt dần phía sau, rồi giữ trạng thái phát sáng thở đều kèm tia sé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
Nạp năng lượng cho một động từ/từ bán hàng đơn lẻ trong khẩu hiệu (Supercharged/faster/AI…); khoảnh khắc chữ nghĩa của đoạn cao trào năng lượng; khung khẩu hiệu của phim thương hiệu nền đen.
Ý đồ
Trong cả câu chữ trắng, chỉ từ khóa được "cấp điện": ánh sáng gradient quét nhanh từ trái sang phải qua các ký tự, như một lần bơm năng lượng — sáng nhất ngay khoảnh khắc quét qua (vệt làn sóng đầu), rồi tắt dần về trạng thái ổn định; nạp đầy xong, tia sét mảnh nhảy giữa các ký tự và cả từ giữ độ sáng thở đều. Ba điểm chốt: nhanh (quét nạp 15–20f, chậm hơn sẽ bị đọc thành thanh tiến trình), làn sóng đầu sáng nhất (ký tự vừa sáng lên có hiệu ứng mạnh nhất, thiếu độ dốc này chỉ là một gradient tĩnh bị cắt), và quầng sáng tiết chế (bloom nhiều lớp kiểu After Effects nhưng đừng để nhòe thành một khối, sét phải mảnh và thưa mới là điểm nhấn).
Thời lượng & năng lượng
- Thời lượng: Quét nạp ~15–20f (phải nhanh) + sét thở ổn định 1–2s; cả đoạn 2.5–3.5s
- Năng lượng: Cao (đỉnh điểm một từ; trước và sau đều nên nhường chỗ)
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)
意图
整句白字里只有关键词被"通电":渐变彩光从左到右快速扫过字符, 像能量注入——扫过的瞬间最亮(波前尾迹),随后衰减到稳态; 填满后字符之间跳起勾连的细闪电,词保持泛光呼吸。三个命门: 快(扫充 15–20f,慢了读作进度条)、波前最亮(刚点亮的 字符效果最强,没有这个梯度就是静态渐变裁切)、辉光克制 (AE 式多层泛光但别糊成一团,闪电细而稀疏才是点缀)。
动效核心
- 渐变填充:关键词做双层文字(白/渐变彩),彩层用从左到右推进的
mask 揭开;斜切 mask 边缘略糊(~8% 词宽)
- 波前尾迹增亮:跟随 mask 前沿一条 ~34% 词宽的增亮带,波前处
辉光最强、向后衰减;填充结束后 ~10f 淡出到稳态
- 辉光:4 层泛光(环境大柔光+中晕+近核+亮核),稳态 opacity
0.55–0.72 档(实测:0.75–0.95 档被裁"泛光感有点强")
- 闪电:填满后字符间勾连细闪电,紫红系三层描边(外晕 6/中 2.4/
亮核 1.4),mulberry32 种子随机跳位闪烁,稀疏零星(不是持续电弧)
- 禁:填充过程不带跟随移动的光点/星光(实测判例:跟随白色光点读作廉价装饰,删)
参数表
| 参数 | 典型值 | 调节手感 | |------|--------|----------| | 扫充时长 | 15–20f | 原片"速度很快";>28f 读作进度条 | | 波前增亮带 | ~34% 词宽,波前最亮向后衰减 | 无梯度=静态渐变;带太宽=整词一起亮没有"注入"感 | | 稳态泛光 | 4 层 opacity 0.55/0.55/0.62/0.72 | 0.75+ 档被裁太强;辉光层数<3 读作纯色 | | 闪电线宽 | 外 6/中 2.4/核 1.4 @1080p | 加倍即被裁"粗";颜色偏紫红(magenta 216,60,190 系) | | 闪电频率 | 稀疏零星(同屏 ≤2 道,间歇 >8f) | 频率高读作电弧故障;实测 demo 仍略密于原片 |
已知坑
- demo 在灰阶/占位素材上调校通过——参数是调校起点非实战定稿,
首次实战须以真实素材回验
- 判例三条(用户逐字意见沉淀):①填充过程禁跟随光点;②闪电
必须紫红+细;③泛光宁弱勿强,波前梯度补足冲击力
- 与 type-rhythm-sync/cel-flash-stomp 分工:那些是节拍/砸字;
本卡是单词"充能"的能量语义,词必须是句中卖点词
- 关键词外的正文保持纯白静止——全句都动就没有"只有它通电"的戏
参考实现
demos/typography/gradient-word-sweep/ (GradientWordSweep.tsx) 原片出处:clickup-30.mp4
Mã nguồn tham chiếu (Remotion)
- demos/typography/gradient-word-sweep/GradientWordSweep.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/typography/gradient-word-sweep/GradientWordSweep.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/gradient-word-sweep.jsongit clone https://github.com/Vincentwei1021/video-shotcraft
# component: demos/typography/gradient-word-sweep/GradientWordSweep.tsx
npx remotion render <CompositionId> out.mp4claude mcp add --transport http gimgs https://gimgs.net/mcp
# then: search_motion_templates / get_motion_template("gradient-word-sweep")Prompt
Use the video-shotcraft shot recipe "Từ khóa quét gradient nạp năng lượ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/gradient-word-sweep.json Raw TSX: https://gimgs.net/motion/gradient-word-sweep/template.html Repository: https://github.com/Vincentwei1021/video-shotcraft (component: demos/typography/gradient-word-sweep/GradientWordSweep.tsx; imports demos/_fixtures and assets/lib) Spec: 1920x1080, 30 fps, 3.71s 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/gradient-word-sweep/GradientWordSweep.tsx =====
// gradient-word-sweep v3 —— 批次 12 按用户意见微调(v2 结构保留):
// 1) 闪电偏紫红色 + 线宽调细(约减半);
// 2) 整体泛光强度略降(各辉光层 opacity 下调);
// 3) 波前尾迹梯度:刚被点亮的字符辉光最强,随扫过距离衰减到稳态
// (trailing-window 增亮层,填充结束后淡出到稳态呼吸)。
import React from 'react';
import { AbsoluteFill, useCurrentFrame, interpolate, Easing } from 'remotion';
const mulberry32 = (a: number) => () => {
let t = (a += 0x6d2b79f5);
t = Math.imul(t ^ (t >>> 15), t | 1);
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
const FONT = '"Avenir Next", Futura, "Helvetica Neue", sans-serif';
// 截图 5:S 偏蓝青 → 中段紫 → 粉 → 尾部琥珀
const GRAD = 'linear-gradient(92deg, #59c2ff 0%, #9d6bff 32%, #ff6ed4 62%, #ffc46b 100%)';
const FILL_START = 12;
const FILL_END = 30; // 18 帧 ≈ 0.6s,快扫
const LIGHT_START = FILL_END + 3;
// ---------- 种子化噪声与闪电 ----------
const rand = mulberry32(20260718);
const FLICKER: number[] = Array.from({ length: 160 }, () => rand());
type Bolt = { d: string; long: boolean };
const makeLongBolt = (r: () => number): Bolt => {
// 词上方勾连长弧:横跨若干字符,锯齿折线
const x0 = 30 + r() * 220;
const x1 = x0 + 160 + r() * 320;
const yBase = 38 + r() * 42;
const n = 7 + Math.floor(r() * 4);
let d = `M ${x0.toFixed(1)} ${(yBase + 26 + r() * 20).toFixed(1)}`;
for (let i = 1; i <= n; i++) {
const x = x0 + ((x1 - x0) * i) / n + (r() - 0.5) * 22;
const arch = Math.sin((i / n) * Math.PI) * -22; // 中段拱起
const y = yBase + arch + (r() - 0.5) * 30 + (i === n ? 30 + r() * 18 : 0);
d += ` L ${x.toFixed(1)} ${y.toFixed(1)}`;
}
return { d, long: true };
};
const makeShortBolt = (r: () => number): Bolt => {
// 字符之间的短勾连:竖向小锯齿
const x = 60 + r() * 540;
const y0 = 72 + r() * 24;
const y1 = y0 + 55 + r() * 45;
const n = 4 + Math.floor(r() * 3);
let d = `M ${x.toFixed(1)} ${y0.toFixed(1)}`;
for (let i = 1; i <= n; i++) {
const y = y0 + ((y1 - y0) * i) / n;
const xx = x + (r() - 0.5) * 30;
d += ` L ${xx.toFixed(1)} ${y.toFixed(1)}`;
}
return { d, long: false };
};
const BOLTS: Bolt[] = Array.from({ length: 16 }, (_, i) =>
i % 3 === 0 ? makeShortBolt(rand) : makeLongBolt(rand),
);
// 闪烁事件:帧窗内某条闪电点亮,随机跳位
type Flash = { at: number; life: number; bolt: number };
const FLASHES: Flash[] = Array.from({ length: 36 }, () => ({
at: LIGHT_START + Math.floor(rand() * 70),
life: 2 + Math.floor(rand() * 4),
bolt: Math.floor(rand() * BOLTS.length),
}));
export const GradientWordSweep: React.FC = () => {
const frame = useCurrentFrame();
const enter = interpolate(frame, [0, 12], [0, 1], {
extrapolateRight: 'clamp',
easing: Easing.out(Easing.cubic),
});
// 快速填充进度
const p = interpolate(frame, [FILL_START, FILL_END], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
easing: Easing.out(Easing.quad),
});
const pPct = p * 100;
const filling = frame >= FILL_START && frame <= FILL_END + 4;
const headFade = interpolate(frame, [FILL_END, FILL_END + 6], [1, 0], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
// 波前尾迹:刚点亮字符最亮,向后衰减;填充结束后整体淡出到稳态
const trailFade = interpolate(frame, [FILL_END, FILL_END + 10], [1, 0], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
const TRAIL = 34; // 尾迹长度(% 宽度)
const trailMask =
`linear-gradient(90deg, transparent 0%, transparent ${Math.max(0, pPct - TRAIL)}%, ` +
`rgba(0,0,0,0.9) ${Math.max(0, pPct - 3)}%, rgba(0,0,0,0.9) ${Math.min(100, pPct + 1)}%, ` +
`transparent ${Math.min(100, pPct + 6)}%)`;
const noise = FLICKER[Math.min(frame, FLICKER.length - 1)];
// 当前帧活跃闪电
const active = FLASHES.filter((f) => frame >= f.at && frame < f.at + f.life);
const boltBoost = active.length > 0 ? 0.35 : 0;
// 辉光呼吸:填满后带噪声微闪 + 闪电时增亮
const glowLvl =
interpolate(frame, [FILL_START, FILL_END], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
}) *
(0.82 + 0.18 * noise) +
boltBoost;
// 软边遮罩:辉光层的填充边缘不生硬
const softMask = (soft: number): string =>
p >= 1
? 'none'
: `linear-gradient(90deg, #000 0%, #000 ${Math.max(0, pPct - soft)}%, transparent ${Math.min(100, pPct + soft * 0.6)}%)`;
const maskStyle = (soft: number): React.CSSProperties =>
p >= 1
? {}
: {
WebkitMaskImage: softMask(soft),
maskImage: softMask(soft),
};
const lineStyle: React.CSSProperties = {
fontFamily: FONT,
fontWeight: 700,
fontStyle: 'italic',
fontSize: 92,
letterSpacing: -1,
lineHeight: 1.28,
color: '#ffffff',
whiteSpace: 'nowrap',
};
const gradText: React.CSSProperties = {
position: 'absolute',
inset: 0,
backgroundImage: GRAD,
WebkitBackgroundClip: 'text',
backgroundClip: 'text',
color: 'transparent',
};
return (
<AbsoluteFill style={{ background: '#050505', justifyContent: 'center', alignItems: 'center' }}>
{/* 词后方环境泛光(大半径,随充能增强) */}
<div
style={{
position: 'absolute',
left: 500,
top: 340,
width: 820,
height: 320,
borderRadius: '50%',
background:
'radial-gradient(closest-side, rgba(180,110,255,0.6), rgba(255,110,212,0.25) 55%, transparent 78%)',
filter: 'blur(38px)',
opacity: 0.55 * glowLvl,
}}
/>
<div
style={{
textAlign: 'center',
opacity: enter,
transform: `translateY(${(1 - enter) * 36}px)`,
}}
>
<div style={lineStyle}>
<span style={{ position: 'relative', display: 'inline-block' }}>
<span>Supercharged</span>
{/* AE 式辉光:大半径柔光层(最糊) */}
<span
aria-hidden
style={{
...gradText,
...maskStyle(14),
filter: 'blur(46px) saturate(1.6)',
opacity: 0.55 * glowLvl,
transform: 'scale(1.05)',
}}
>
Supercharged
</span>
{/* 中晕层 */}
<span
aria-hidden
style={{
...gradText,
...maskStyle(10),
filter: 'blur(18px) saturate(1.4) brightness(1.15)',
opacity: 0.62 * glowLvl,
}}
>
Supercharged
</span>
{/* 近核柔光 */}
<span
aria-hidden
style={{
...gradText,
...maskStyle(7),
filter: 'blur(6px) brightness(1.25)',
opacity: 0.72 * Math.min(1, glowLvl + 0.1),
}}
>
Supercharged
</span>
{/* 清晰渐变本体 */}
<span
aria-hidden
style={{
...gradText,
clipPath: `inset(-25% ${100 - pPct}% -25% 0)`,
}}
>
Supercharged
</span>
{/* 波前尾迹增亮:刚点亮字符辉光最强,向后衰减到稳态 */}
{trailFade > 0.01 && (
<span
aria-hidden
style={{
...gradText,
WebkitMaskImage: trailMask,
maskImage: trailMask,
filter: 'blur(9px) saturate(1.7) brightness(1.7)',
opacity: 0.95 * trailFade,
}}
>
Supercharged
</span>
)}
{/* 填充头字符过曝亮核(字符形状,非独立光点;仅填充期间) */}
{filling && p < 1 && (
<span
aria-hidden
style={{
position: 'absolute',
inset: 0,
color: '#fff',
clipPath: `inset(-25% ${Math.max(0, 100 - pPct)}% -25% ${Math.max(0, pPct - 10)}%)`,
filter: 'blur(3px)',
opacity: 0.9 * headFade,
textShadow: '0 0 22px rgba(255,255,255,0.9), 0 0 55px rgba(216,150,255,0.8)',
}}
>
Supercharged
</span>
)}
{/* 勾连闪电:填充完成后,字符之间/词上方随机闪烁跳位 */}
<svg
aria-hidden
viewBox="0 0 700 240"
style={{
position: 'absolute',
left: -25,
top: -62,
width: 700,
height: 240,
overflow: 'visible',
pointerEvents: 'none',
}}
>
{active.map((f, i) => {
const b = BOLTS[f.bolt];
const decay = 1 - (frame - f.at) / f.life;
return (
<g key={`${f.at}-${i}`} opacity={Math.min(1, 1.1 * decay)}>
<path
d={b.d}
fill="none"
stroke="rgba(216,60,190,0.65)"
strokeWidth={b.long ? 6 : 4.5}
strokeLinejoin="miter"
style={{ filter: 'blur(6px)' }}
/>
<path
d={b.d}
fill="none"
stroke="rgba(235,110,215,0.9)"
strokeWidth={b.long ? 2.4 : 1.9}
strokeLinejoin="miter"
style={{ filter: 'blur(1.5px)' }}
/>
<path
d={b.d}
fill="none"
stroke="#ffd8f2"
strokeWidth={b.long ? 1.4 : 1.1}
strokeLinejoin="miter"
/>
</g>
);
})}
</svg>
</span>{' '}
<span>performance</span>
</div>
<div style={lineStyle}>with rock-solid reliability</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/typography/gradient-word-sweep/GradientWordSweep.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

Ba từ tiếp sức bằng hình học
Ba từ lợi ích mỗi từ mang riêng một bộ hình học tiếp sức — vòng tròn nét đứt tự xoay thu nhỏ → ba vòng tròn nét liền mọc lên lần lượt → ánh kim sheen quét qua rồi thu về trắng thuần; mỗi từ mới viền nét rồi tô đầy.

Tiếp sức từ với cuộn phim trang
Cột trái các thẻ trang đen trắng cuộn theo bước, bên phải từ lớn chữ serif tiếp sức tại chỗ (danh từ cố định + động từ luân phiên) — chỉ khi đổi từ mới cuộn một bước, khớp chính xác điểm giữa thẻ trang hiện tại.

Cuộn từ dọc theo nhịp
Từ cuối câu đổi trên cuộn dọc, 3 lần đổi từ mỗi lần 0.55s, nhanh trước chậm sau kèm hơi overshoot; các dòng lân cận nhòe dọc và xám dần theo khoảng cách, từ ở giữa vừa đậu liền nhuộm từ xám sang màu nhấn.

Hai cách tiết lộ code song song
Cùng một khối code tô cú pháp được tiết lộ hai cách song song — trái: stagger cấp dòng mờ vào nổi lên 8px, phải: gõ từng ký tự nhưng giữ nguyên màu token gốc, con trỏ vuông #3a4468 đệm dưới ký tự hiện tại.