Motion · Light & emphasis
Light play moves
Three light styles: spotlight-sweep sweeping a spotlight across text, sheen a single sheen sweep, and halation-bloom a crash-stop glow bloom.
Source: video-shotcraft (Vincent Wei) ↗ · Apache-2.0 · Remotion composition (React/TSX) — preview video, no in-browser player
Variants
Two layers of the same text: a dark version stays at 0.07 opacity, a bright version follows a radial mask tied to the light spot; the cone of light swings back and forth twice before freezing at full brightness.
A dark-scene big-title reveal opener
A 45° highlight band translateX-sweeps once across a deep-ink card, clipped by overflow:hidden rounded corners.
A single coronation during a hero card's still moment
A duplicated copy of the text underneath, blurred and brightened as a glow layer; on the crash-stop frame it surges into a ring then settles into steady soft light.
An impact boost on a crash-zoom stop frame
When to use
Treats light as a fourth kind of brushstroke (sweep/wipe/bloom): a dark-scene title reveal (A), crowning a hero card (B), impact on a crash-stop frame (D).
Intention
The library previously only had one "light" technique; these three styles build light into a system, three brushstrokes with three jobs: A is sweeping — a title, almost invisible in the dark, gets swept bright by an oscillating cone of light; B is wiping — a 45° highlight band sweeps once across a deep-ink hero card, clipped by its rounded corners, a silent coronation; D is blooming — on a white-text crash-stop frame, a ring of soft glow surges up and bursts, then settles into a resting breathing glow. All of this lives on dark backgrounds or dark hero subjects — glow is invisible on white backgrounds is the bedrock of this whole card.
Duration & energy
- Duration: A: ~5.3s / B: ~4.7s / D: ~4.8s
- Energy: A: Medium / B: Low / D: High
Shot recipe (original Chinese — timing, easing, parameter table, known pitfalls)
意图
库内"光"一直只有 spotlight-hero-card 一个点射手法;这三式把光做成 体系,三种笔触三种职责:A 是扫——暗场里几乎不可见的标题被锥形 光摆动扫亮,光到哪哪亮,与 spotlight-hero-card 的区别是那个打卡片、 这个扫文字;B 是擦——深墨主角卡上一道 45° 高光带缓扫一次,被 圆角裁住,无声的加冕;D 是晕——白字撞停帧一圈柔晕猛涨炸开再 回落成驻留呼吸光,与 B 的区别是 B 移动扫过、D 驻留在高光边缘。 全部深底或深主角——白底上提亮不可见(判例)是这整张卡的地基。
三式选型
| 式 | 做法 | 适用 | |----|------|------| | A spotlight-sweep | 双层同文本:暗版 0.07 常驻,亮版 radial mask 随光斑走,锥形光摆两个来回后全亮定格 | 暗场大标题揭示开场 | | B sheen-sweep | 深墨卡上 45° 高光带 translateX 扫一次,overflow hidden 圆角裁剪 | 主角卡静止段的一次加冕 | | D halation-bloom | 文字复制底层 blur+brightness 当晕层,撞停帧起猛涨一圈回落成稳态柔光 | crash-zoom 撞停帧的冲击加成 |
参数表
| 参数 | 典型值 | 调节手感 | |------|--------|----------| | A 反差 | 暗版 0.07 vs 全亮 1.0,白 #f5f5f3 对 #2a2a28 暗场 | 反差是本式全部;暗版 >0.15 就没有"点亮"了 | | A 光斑 | circle 380px(0.95 硬芯+柔边),x = 960+560·sin(2πf/55),两来回至 f110 | f110–125 全亮 out-cubic、光效线性消散(扩散/消散解耦判例) | | B 高光带 | 宽 1.6×卡宽,115° 渐变峰值 rgba(255,255,255,0.32),40–68f inOut cubic 扫一次 | 0.32 在深墨底上足够响;双向 clamp 保证只扫一次 | | D 晕层 | blur 22px + brightness 1.8;scale 1→1.3 用 6f out-cubic 猛涨,opacity 20f 线性回落 0.35 再 15f 缓收 0.22 稳态 | 扩散 out-cubic/消散线性解耦(判例);同快涨快消读作闪光灯 | | 摘罩 | 三式光效层全部条件挂载,扫完/消散完即卸载 | opacity 0 残留 = 永不真静止(feTurbulence 摘罩判例同族) | | 收尾 | A 35f / B 72f / D 89f 真静止 | — |
已知坑
- demo 在灰阶/占位素材上调校通过——参数是调校起点非实战定稿,
首次实战须以真实素材回验
- 全片光效合计 ≤2 次——光是最快掉价的手法,每个卡都闪一下
等于廉价模板
- B 式是高标准重试通过:旧否决(2026-07-13 判例)针对当时实现不好看,
非路径不可行;四约束 = 单点+主角+圆角裁剪+只扫一次,违反任何 一条回到否决区
- 深底是地基:三式全部依赖深底/深主角,白底上做光效直接跳过本卡
- D 与 crash-zoom-punch 撞停帧天然联动(撞停即起晕);A 与
spotlight-hero-card 同片二选一,都是"聚光"语汇
参考实现
demos/effects/light-play-moves/ (HalationBloom.tsx / SheenSweepRetry.tsx / SpotlightSweepReveal.tsx)
Reference implementation (Remotion)
- demos/effects/light-play-moves/HalationBloom.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/effects/light-play-moves/HalationBloom.tsx
- demos/effects/light-play-moves/SheenSweepRetry.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/effects/light-play-moves/SheenSweepRetry.tsx
- demos/effects/light-play-moves/SpotlightSweepReveal.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/effects/light-play-moves/SpotlightSweepReveal.tsx
The component imports shared fixtures from demos/_fixtures and helpers from assets/lib in the repo; SKILL.md there documents the render workflow.
Everything on this page is also machine-readable. Point your agent at the JSON, or add the gimgs MCP server and ask it to search motion templates.
- Fetch the JSON — it holds the shot recipe (timing, easing, parameter table, pitfalls) and the full TSX source.
- Clone the video-shotcraft repo; the component imports shared fixtures from demos/_fixtures and helpers from assets/lib.
- Register the component as a <Composition> in a Remotion project and render with `npx remotion render`, or re-implement the recipe in your own engine.
curl -s https://gimgs.net/motion/light-play-moves.jsongit clone https://github.com/Vincentwei1021/video-shotcraft
# component: demos/effects/light-play-moves/HalationBloom.tsx, demos/effects/light-play-moves/SheenSweepRetry.tsx, demos/effects/light-play-moves/SpotlightSweepReveal.tsx
npx remotion render <CompositionId> out.mp4claude mcp add --transport http gimgs https://gimgs.net/mcp
# then: search_motion_templates / get_motion_template("light-play-moves")Prompt
Use the video-shotcraft shot recipe "Light play moves" (Remotion composition, Apache-2.0) published on gimgs.net. Recipe JSON (prompt = timing/easing/parameter table/pitfalls, files = TSX source): https://gimgs.net/motion/light-play-moves.json Raw TSX: https://gimgs.net/motion/light-play-moves/template.html Repository: https://github.com/Vincentwei1021/video-shotcraft (component: demos/effects/light-play-moves/HalationBloom.tsx, demos/effects/light-play-moves/SheenSweepRetry.tsx, demos/effects/light-play-moves/SpotlightSweepReveal.tsx; imports demos/_fixtures and assets/lib) Spec: 1920x1080, 30 fps, 5.38s 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/effects/light-play-moves/HalationBloom.tsx =====
// halation-bloom —— 高光晕染
// 深灰底大白字 "10x" crash-zoom 急停入场(2.4→1,7f in-quad + 2f 回弹)。
// 撞停帧起:底层复制文字(blur+提亮的白色晕层)猛涨一圈(scale 1→1.3,6f out-cubic 扩散),
// 再 20f 线性回落到 0.35 驻留柔晕(扩散/消散解耦判例),随后 15f 缓收到 0.22 稳态。
// 深底白字:白底上提亮不可见判例。收尾真静止 ≥40f。
import React from 'react';
import { useCurrentFrame, interpolate, Easing } from 'remotion';
const BG = '#2a2a28';
const WHITE = '#f7f7f5';
const MID = '#8f8f8d';
// —— 时间轴(30fps)——
const ZOOM_START = 8; // 入场起始
const IMPACT = 15; // 撞停帧(7f 急速缩入)
const REBOUND_END = 17; // 2f 回弹
const POP_END = IMPACT + 6; // 晕层猛涨 6f → f21
const FALL_END = POP_END + 20; // 20f 线性回落 → f41
const SETTLE_END = FALL_END + 15; // 15f 缓收 → f56,此后全静止
// 总时长 145f → 静止 89f ≥ 40f
const TextBlock: React.FC<{ color: string }> = ({ color }) => (
<div
style={{
fontFamily: 'Helvetica, Arial, sans-serif',
fontSize: 260,
fontWeight: 800,
color,
letterSpacing: '-0.02em',
lineHeight: 1,
whiteSpace: 'nowrap',
}}
>
10x
</div>
);
export const HalationBloom: React.FC = () => {
const frame = useCurrentFrame();
// —— crash-zoom 入场:scale 2.4 → 0.94(7f in-quad 加速撞停)→ 1(2f 回弹)——
const zoomIn = interpolate(frame, [ZOOM_START, IMPACT], [2.4, 0.94], {
easing: Easing.in(Easing.quad),
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
const rebound = interpolate(frame, [IMPACT, REBOUND_END], [0.94, 1], {
easing: Easing.out(Easing.quad),
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
const textScale = frame < IMPACT ? zoomIn : rebound;
const textOpacity = interpolate(frame, [ZOOM_START, ZOOM_START + 3], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
// —— 晕层:撞停帧起条件挂载 ——
// 扩散(scale):out-cubic,6f 猛涨 1 → 1.3
const bloomScale = interpolate(frame, [IMPACT, POP_END], [1, 1.3], {
easing: Easing.out(Easing.cubic),
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
// 消散(opacity):与扩散解耦——猛涨段保持全亮,随后 20f 线性回落到 0.35
const bloomFall = interpolate(frame, [POP_END, FALL_END], [1, 0.35], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
// 驻留晕 15f 缓收到 0.22 稳态
const bloomSettle = interpolate(frame, [FALL_END, SETTLE_END], [0.35, 0.22], {
easing: Easing.out(Easing.quad),
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
const bloomOpacity = frame < FALL_END ? bloomFall : bloomSettle;
return (
<div
style={{
width: 1920,
height: 1080,
background: BG,
position: 'relative',
overflow: 'hidden',
}}
>
{/* 小标签,全程静态 */}
<div
style={{
position: 'absolute',
top: 110,
width: '100%',
textAlign: 'center',
fontFamily: 'Helvetica, Arial, sans-serif',
fontSize: 30,
fontWeight: 700,
letterSpacing: '0.35em',
color: MID,
}}
>
HALATION BLOOM
</div>
{/* 晕层:文字复制底层,blur + 提亮,撞停帧起条件挂载 */}
{frame >= IMPACT && (
<div
style={{
position: 'absolute',
inset: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
transform: `scale(${bloomScale})`,
opacity: bloomOpacity,
filter: 'blur(22px) brightness(1.8)',
}}
>
<TextBlock color={WHITE} />
</div>
)}
{/* 本体文字:crash-zoom 急停 */}
<div
style={{
position: 'absolute',
inset: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
transform: `scale(${textScale})`,
opacity: textOpacity,
}}
>
<TextBlock color={WHITE} />
</div>
</div>
);
};
// ===== demos/effects/light-play-moves/SheenSweepRetry.tsx =====
// sheen-sweep-retry —— 单点扫光(高标准重试)
// 深墨大卡居中,一道 45° 高光带在 40–68f 从左外扫到右外,仅此一次。
// 约束:单点(只扫主角卡)、圆角裁剪(overflow hidden)、扫前扫后完全静止。
import React from 'react';
import { useCurrentFrame, interpolate, Easing } from 'remotion';
import { G } from '../../_fixtures/Fixtures';
const CARD_W = 760;
const CARD_H = 420;
const SHEEN_W = CARD_W * 1.6; // 1216
export const SheenSweepRetry: React.FC = () => {
const frame = useCurrentFrame();
// 扫光:40–68f,从卡左外(-SHEEN_W)扫到卡右外(CARD_W),inOut(cubic),只一次
const sweepActive = frame >= 40 && frame <= 68;
const x = interpolate(frame, [40, 68], [-SHEEN_W, CARD_W], {
easing: Easing.inOut(Easing.cubic),
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
return (
<div
style={{
width: 1920,
height: 1080,
background: G.bg,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontFamily: 'Helvetica, Arial, sans-serif',
}}
>
<div
style={{
width: CARD_W,
height: CARD_H,
background: G.side,
borderRadius: 24,
overflow: 'hidden', // 圆角裁剪:高光带被卡的圆角裁住
position: 'relative',
boxShadow: '0 12px 40px rgba(0,0,0,0.22)',
boxSizing: 'border-box',
padding: '64px 72px',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
gap: 28,
}}
>
<div
style={{
fontSize: 130,
fontWeight: 800,
color: '#f5f5f3',
letterSpacing: 2,
lineHeight: 1,
}}
>
PRO
</div>
<div style={{ height: 16, width: 380, background: G.sideBar, borderRadius: 8 }} />
<div style={{ height: 16, width: 260, background: G.sideBar, borderRadius: 8 }} />
{/* 高光带:条件挂载,扫完即摘罩,收尾真静止 */}
{sweepActive && (
<div
style={{
position: 'absolute',
top: 0,
left: 0,
width: SHEEN_W,
height: CARD_H,
transform: `translateX(${x}px)`,
background:
'linear-gradient(115deg, transparent 42%, rgba(255,255,255,0.32) 50%, transparent 58%)',
pointerEvents: 'none',
}}
/>
)}
</div>
</div>
);
};
// ===== demos/effects/light-play-moves/SpotlightSweepReveal.tsx =====
import React from 'react';
import { AbsoluteFill, useCurrentFrame, interpolate, Easing } from 'remotion';
// spotlight-sweep-reveal: 聚光灯摆动扫字
// 暗场 #2a2a28,标题两行常驻 opacity 0.07;亮版同文本用 radial-gradient mask,
// 光斑 x 按 sin 摆动扫两个来回(周期 ~55f),f110 后全字提亮定格,
// 光斑/光锥线性消散并在 f>=125 条件卸载,125–160 真静止 35f。
const BG = '#2a2a28';
const INKW = '#f5f5f3';
const CX = 960;
const CY = 540;
const AMP = 560; // 摆动幅度
const PERIOD = 55; // 单个来回帧数
const SWEEP_END = 110; // 两个来回结束
const REVEAL_END = 125; // 提亮完成,之后真静止
const textStyle: React.CSSProperties = {
fontFamily: 'Helvetica Neue, Helvetica, Arial, sans-serif',
fontSize: 150,
fontWeight: 800,
lineHeight: 1.15,
letterSpacing: '0.04em',
color: INKW,
textAlign: 'center',
whiteSpace: 'pre',
};
const TitleText: React.FC = () => (
<AbsoluteFill style={{ justifyContent: 'center', alignItems: 'center' }}>
<div style={textStyle}>{'SPOTLIGHT\nON'}</div>
</AbsoluteFill>
);
export const SpotlightSweepReveal: React.FC = () => {
const f = useCurrentFrame();
// 光斑 x:sin 摆动,两个来回后停在中心
const sweepF = Math.min(f, SWEEP_END);
const x = CX + AMP * Math.sin((2 * Math.PI * sweepF) / PERIOD);
// 全字提亮(扩散感 out-cubic)
const brighten = interpolate(f, [SWEEP_END, REVEAL_END], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
easing: Easing.out(Easing.cubic),
});
// 光斑/光锥消散(线性)
const fadeOut = interpolate(f, [SWEEP_END, REVEAL_END], [1, 0], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
const sweepAlive = f < REVEAL_END; // 条件卸载,保证末段真静止
const maskGrad = `radial-gradient(circle 380px at ${x}px ${CY}px, rgba(0,0,0,1) 0%, rgba(0,0,0,0.95) 45%, rgba(0,0,0,0) 100%)`;
return (
<AbsoluteFill style={{ backgroundColor: BG }}>
{/* 暗版文字常驻 */}
<AbsoluteFill style={{ opacity: 0.07 }}>
<TitleText />
</AbsoluteFill>
{sweepAlive && (
<>
{/* 光锥:从顶部收拢到光斑 */}
<AbsoluteFill
style={{
opacity: fadeOut,
clipPath: `polygon(${CX - 70}px -40px, ${CX + 70}px -40px, ${x + 420}px ${CY + 230}px, ${x - 420}px ${CY + 230}px)`,
background:
'linear-gradient(to bottom, rgba(245,245,243,0.16), rgba(245,245,243,0.03) 85%, rgba(245,245,243,0) 100%)',
}}
/>
{/* 落在暗墙上的柔光斑 */}
<AbsoluteFill
style={{
opacity: fadeOut,
background: `radial-gradient(circle 460px at ${x}px ${CY}px, rgba(245,245,243,0.22) 0%, rgba(245,245,243,0.08) 55%, rgba(245,245,243,0) 100%)`,
}}
/>
{/* 亮版文字,按帧移动的 radial mask */}
<AbsoluteFill
style={{
opacity: fadeOut === 1 ? 1 : Math.max(fadeOut, 0),
WebkitMaskImage: maskGrad,
maskImage: maskGrad,
}}
>
<TitleText />
</AbsoluteFill>
</>
)}
{/* 全亮定格层 */}
<AbsoluteFill style={{ opacity: brighten }}>
<TitleText />
</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/effects/light-play-moves/HalationBloom.tsx, demos/effects/light-play-moves/SheenSweepRetry.tsx, demos/effects/light-play-moves/SpotlightSweepReveal.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 |
|---|---|---|---|
| — | |||
More in this category

Dark-stage spotlight reveals
Three dark-stage spotlight reveals — A wake-sleep sweep (light in = bright, light out = dark), B edge-hugging glow pan (violet light seeps along UI edges + steady rightward sweep), C corner reveal (radial light expands steadily from a corner to light the whole screen); light itself narrates in the dark.

Slam entrance moves
Three high-energy slam entrances: a whip-in perspective stop, a heavy KPI drop, and a shockwave that hits neighboring cards.

Scanline assemble fly-in
A scan line sweeps down an empty grid, flying each region's component in from off-screen — finishing as the scan ends.

Scanline annotate focus
A bright scan line sweeps down the page, framing each element it passes and labeling it with monospace annotations.