Motion · Luz & ênfase
Glow & flyline moves
Three dark-scene glow-and-flyline styles: glow-orb-ambient ambient glow noise, flyline-arc a connecting light arc, and orb-flyline-relay combining both with same-frame resonance.
Fonte: video-shotcraft (Vincent Wei) ↗ · Apache-2.0 · Composição Remotion (React/TSX) — vídeo de prévia, sem player no navegador
Variantes
三团 500-700px radial 光斑 + blur(100px),双正弦漂移;卡缘辉光按光斑距离 [180,720]px→[1,0] 加权取 max 驱动
暗场段落铺底;卡呼吸感
手写 bezier 100 段采样,22f out-cubic 生长;光头条件挂载领跑,段 opacity 按离头距离渐隐;落点描边脉冲,可接力
数据流向/指标关联;dashboard 叙事
A+B 焊接:光斑 surge 与卡脉冲共用落点帧,涨亮 1+1.6×surge、5f 起升 15f 消散
暗场高光段;三卡接力收束
When to use
The atmosphere and data storytelling of the film's one dark segment: use A to lay down ambient noise, B to tell data-flow direction, C when the background needs to answer the foreground; a Linear-website feel.
Intention
This is the library's first card to live entirely in a dark scene. The white-background-can't-glow precedent, read in reverse, says: to play with light, go dark first. A is the space breathing — three heavily blurred glow orbs drift organically near black, and the central dark card's edges glow brighter as the nearest orb approaches, so the background is no longer dead; B is the verb of data — a glowing arc "strikes" from card A to card B, a bright leading point with a fading tail, the landing point pulsing along its outline, giving metric relationships a direction for the first time; C welds A and B together — the frame where a flyline lands makes the nearby orb surge brighter in the same beat, ambient layer and event layer resonating on the same frame rather than performing separately (the crux of the combo: separate the resonating frame and it degrades into two cards placed side by side).
Duration & energy
- Duration: A: ~5s / B: ~4.7s / C: ~5.2s
- Energy: A: Low (ambient-noise level) / B: Medium / C: Medium-high
Shot recipe (original Chinese — timing, easing, parameter table, known pitfalls)
意图
这是库内第一张整卡住在暗场的卡。白底提亮不可见判例反过来读就是: 要玩光,先去暗场。A 是空间的呼吸——近黑底三团重 blur 光斑有机漂移, 中央深卡边缘随最近光斑靠近而泛光,背景不再是死的;B 是数据的动词—— 发光弧线从 A 卡"打"到 B 卡,亮点头领跑、渐隐光尾,落点描边脉冲点亮, 指标关联第一次有了方向;C 是 A+B 的焊接——飞线落点帧邻近光斑同帧 涨亮一拍,氛围层与事件层同帧共振,互相搭腔而非各演各的(组合命门: 拆开共振帧就退化成两张卡并置)。
三式选型
| 式 | 做法 | 适用 | |----|------|------| | A glow-orb-ambient | 三团 500–700px radial 光斑 + blur(100px),双正弦漂移;卡缘辉光按光斑距离 [180,720]px→[1,0] 加权取 max 驱动 | 暗场段落铺底;卡呼吸感 | | B flyline-arc | 手写 bezier 100 段采样,22f out-cubic 生长;光头条件挂载领跑,段 opacity 按离头距离渐隐;落点描边脉冲,可接力 | 数据流向/指标关联;dashboard 叙事 | | C orb-flyline-relay | A+B 焊接:光斑 surge 与卡脉冲共用落点帧,涨亮 1+1.6×surge、5f 起升 15f 消散 | 暗场高光段;三卡接力收束 |
参数表
| 参数 | 典型值 | 调节手感 | |------|--------|----------| | A 光斑 | 峰值 opacity 0.32/0.22/0.18;漂移振幅合计 ≥240px、周期 92–134f 交叉不等 | 同周期漂移读作整体晃动不是"有机" | | A 卡缘辉光 | box-shadow 0→28px 白 0.25 | 无辉光光斑与卡互不认识 | | A 收敛 | 90–120f out-sine 收敛冻结(起始斜率≈0.94 近似连续),末 30f 真静止 | 硬停一帧读作卡死;缓动收敛才有真静止 | | B 线体 | 白线 4.5px + 9px 深色衬底;暗场版可去衬底 | 浅底无衬底白线直接消失(demo 判例) | | B 光头 | 白点 9px + halo,仅生长期条件挂载 | opacity 到 0 不算摘罩,永不真静止 | | B 脉冲 | 描边扩散 out-cubic 6f / 消散线性 12f | 白底禁提亮——用加深罩 0.16 替代 | | C 共振 | 落点帧同帧起 surge;卡 8f 亮到 1 + 描边脉冲残余 0.3 | 错 2f 以上共振读不出"搭腔" | | C 时间轴 | 动作 f98 前结束、光斑 f120 冻结,末 35f 真静止 | 氛围层最后冻,事件层先收 |
已知坑
- demo 在灰阶/占位素材上调校通过——参数是调校起点非实战定稿,
首次实战须以真实素材回验
- 暗场段与纸墨浅底主视觉切换成本高——全片暗场段 ≤1 段,进出
用 shot-transitions 藏切族垫,别生切明暗
- 光斑漂移末段必须缓动收敛到静止再冻结——匀速漂移直接 clamp
是一次可见的急刹(A 式 out-sine 收敛判例)
- 实战品牌色版:光斑用品牌色对(如蓝紫),灰阶参数只保证明度关系,
色相对比要重调
- B 接力线 ≥3 条时改用 C——纯飞线连打读作大屏模板,共振才有作者感
- 声音:B/C 每次落点一声轻打点,A 无声(sound-design §4.5)
参考实现
demos/effects/glow-flyline-moves/ (FlylineArc.tsx / GlowOrbAmbient.tsx / OrbFlylineRelay.tsx)
Reference implementation (Remotion)
- demos/effects/glow-flyline-moves/FlylineArc.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/effects/glow-flyline-moves/FlylineArc.tsx
- demos/effects/glow-flyline-moves/GlowOrbAmbient.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/effects/glow-flyline-moves/GlowOrbAmbient.tsx
- demos/effects/glow-flyline-moves/OrbFlylineRelay.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/effects/glow-flyline-moves/OrbFlylineRelay.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/glow-flyline-moves.jsongit clone https://github.com/Vincentwei1021/video-shotcraft
# component: demos/effects/glow-flyline-moves/FlylineArc.tsx, demos/effects/glow-flyline-moves/GlowOrbAmbient.tsx, demos/effects/glow-flyline-moves/OrbFlylineRelay.tsx
npx remotion render <CompositionId> out.mp4claude mcp add --transport http gimgs https://gimgs.net/mcp
# then: search_motion_templates / get_motion_template("glow-flyline-moves")Prompt
Use the video-shotcraft shot recipe "Glow & flyline 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/glow-flyline-moves.json Raw TSX: https://gimgs.net/motion/glow-flyline-moves/template.html Repository: https://github.com/Vincentwei1021/video-shotcraft (component: demos/effects/glow-flyline-moves/FlylineArc.tsx, demos/effects/glow-flyline-moves/GlowOrbAmbient.tsx, demos/effects/glow-flyline-moves/OrbFlylineRelay.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/effects/glow-flyline-moves/FlylineArc.tsx =====
// flyline-arc —— 飞线连接
// FakeDashboard A 微暗化(brightness 0.92),左上卡→右下卡一条贝塞尔弧线
// "打"过去(22f, out-cubic 生长),亮点头部领跑、亮头暗尾拖尾;到达帧目标卡
// 3px+ ink 描边脉冲 + 加深脉冲(白底禁提亮)。随后第二条线接力打到上中卡。
// 收尾真静止:全部动画在 f86 前结束,之后所有元素冻结。
import React, { useId } from 'react';
import { useCurrentFrame, interpolate, Easing } from 'remotion';
import { FakeDashboard } from '../../_fixtures/Fixtures';
type Pt = { x: number; y: number };
// 手写 cubic bezier 采样
const bez = (p0: Pt, p1: Pt, p2: Pt, p3: Pt, t: number): Pt => {
const u = 1 - t;
return {
x: u * u * u * p0.x + 3 * u * u * t * p1.x + 3 * u * t * t * p2.x + t * t * t * p3.x,
y: u * u * u * p0.y + 3 * u * u * t * p1.y + 3 * u * t * t * p2.y + t * t * t * p3.y,
};
};
const N = 100; // 采样段数
// 卡片几何(FakeDashboard variant A 实测网格)
const CARD_LT = { x: 256, y: 108, w: 524, h: 454, cx: 518, cy: 335 }; // 左上
const CARD_RB = { x: 1360, y: 590, w: 524, h: 454, cx: 1622, cy: 817 }; // 右下
const CARD_TM = { x: 808, y: 108, w: 524, h: 454, cx: 1070, cy: 335 }; // 上中
// 一条飞线:深色衬底 + 亮头暗尾白线分段 + 光头领跑(条件挂载)
const Flyline: React.FC<{
frame: number;
start: number; // 生长起始帧
haloId: string; // 光头径向渐变 ID(父组件按实例生成)
p0: Pt; p1: Pt; p2: Pt; p3: Pt;
}> = ({ frame, start, haloId, p0, p1, p2, p3 }) => {
const DUR = 22;
if (frame < start) return null;
const e = interpolate(frame, [start, start + DUR], [0, 1], {
easing: Easing.out(Easing.cubic),
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
const growing = frame < start + DUR;
// 到达后 10f 内尾部亮度线性抹匀 → 之后完全静止
const settle = interpolate(frame, [start + DUR, start + DUR + 10], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
const pts: Pt[] = [];
const nDrawn = Math.max(2, Math.ceil(e * N) + 1);
for (let i = 0; i < nDrawn; i++) {
const t = Math.min((i / N), e);
pts.push(bez(p0, p1, p2, p3, t));
}
const head = bez(p0, p1, p2, p3, e);
pts[pts.length - 1] = head;
const underlay = pts.map((p) => `${p.x.toFixed(1)},${p.y.toFixed(1)}`).join(' ');
// 亮头暗尾:每段按"离头距离"给 opacity;到达后 settle 抹匀到 1
const segs = [];
for (let i = 0; i < pts.length - 1; i++) {
const tSeg = Math.min(i / N, e) / Math.max(e, 0.001); // 0 尾 → 1 头
const grad = 0.4 + 0.6 * tSeg * tSeg;
const op = grad + (1 - grad) * settle;
segs.push(
<line
key={i}
x1={pts[i].x} y1={pts[i].y} x2={pts[i + 1].x} y2={pts[i + 1].y}
stroke="#fafafa" strokeWidth={4.5} strokeLinecap="round" strokeOpacity={op}
/>
);
}
return (
<g>
{/* 深色衬底:保证浅底可见 */}
<polyline
points={underlay} fill="none" stroke="#2f2f2f"
strokeWidth={9} strokeLinecap="round" strokeLinejoin="round" strokeOpacity={0.92}
/>
{segs}
{/* 光头:仅生长期挂载,摘罩即真静止 */}
{growing && (
<g>
<circle cx={head.x} cy={head.y} r={26} fill={`url(#${haloId})`} />
<circle cx={head.x} cy={head.y} r={9} fill="#ffffff" stroke="#2f2f2f" strokeWidth={3} />
</g>
)}
</g>
);
};
// 目标卡脉冲:3px+ ink 描边 + 加深罩(白底不许提亮) —— 条件挂载
const CardPulse: React.FC<{
frame: number;
at: number; // 触发帧
rect: { x: number; y: number; w: number; h: number };
}> = ({ frame, at, rect }) => {
if (frame < at || frame > at + 18) return null;
// 扩散(起升)用 out-cubic 6f,消散用线性 12f
const amp =
frame <= at + 6
? interpolate(frame, [at, at + 6], [0, 1], {
easing: Easing.out(Easing.cubic),
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
})
: interpolate(frame, [at + 6, at + 18], [1, 0], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
return (
<g>
<rect
x={rect.x + 2} y={rect.y + 2} width={rect.w - 4} height={rect.h - 4}
rx={14} fill={`rgba(0,0,0,${(0.16 * amp).toFixed(3)})`}
/>
<rect
x={rect.x + 2} y={rect.y + 2} width={rect.w - 4} height={rect.h - 4}
rx={14} fill="none" stroke="#2f2f2f" strokeWidth={4} strokeOpacity={amp}
/>
</g>
);
};
export const FlylineArc: React.FC = () => {
const frame = useCurrentFrame();
// 渐变 ID 按实例生成,多实例同场不串引(useId 的 «:» 在 url() 里非法,需清洗)
const haloId = `headHalo-${useId().replace(/[^a-zA-Z0-9]/g, '')}`;
// 时间轴:线1 生长 10–32f → 卡RB脉冲 32–50f;线2 生长 46–68f → 卡TM脉冲 68–86f
// f86 后全静止(140f 总长 → 静止 54f ≥ 35f)
const L1 = { p0: { x: CARD_LT.cx, y: CARD_LT.cy }, p1: { x: 818, y: 60 }, p2: { x: 1322, y: 380 }, p3: { x: CARD_RB.cx, y: CARD_RB.cy } };
const L2 = { p0: { x: CARD_RB.cx, y: CARD_RB.cy }, p1: { x: 1760, y: 560 }, p2: { x: 1360, y: 150 }, p3: { x: CARD_TM.cx, y: CARD_TM.cy } };
return (
<div style={{ width: 1920, height: 1080, position: 'relative', background: '#e0e0de' }}>
{/* 背景微暗化 0.92,常量、不动画 */}
<div style={{ filter: 'brightness(0.92)' }}>
<FakeDashboard variant="A" />
</div>
<svg
width={1920} height={1080} viewBox="0 0 1920 1080"
style={{ position: 'absolute', top: 0, left: 0, pointerEvents: 'none' }}
>
<defs>
<radialGradient id={haloId}>
<stop offset="0%" stopColor="rgba(0,0,0,0.38)" />
<stop offset="55%" stopColor="rgba(0,0,0,0.18)" />
<stop offset="100%" stopColor="rgba(0,0,0,0)" />
</radialGradient>
</defs>
<CardPulse frame={frame} at={32} rect={CARD_RB} />
<CardPulse frame={frame} at={68} rect={CARD_TM} />
<Flyline frame={frame} start={10} haloId={haloId} {...L1} />
<Flyline frame={frame} start={46} haloId={haloId} {...L2} />
</svg>
</div>
);
};
// ===== demos/effects/glow-flyline-moves/GlowOrbAmbient.tsx =====
// glow-orb-ambient|暗场光斑呼吸
// 近黑底上三团大光斑(radial-gradient 亮灰 + blur100)用 seed hash 驱动
// 多正弦叠加做有机漂移;中央深色描边卡的边缘辉光随最近光斑距离呼吸。
// 0–20f 光斑淡入,中段正常速度漂移,90–120f 缓动收敛到静止,末 30f 真静止。
import React from 'react';
import { AbsoluteFill, interpolate, useCurrentFrame, Easing } from 'remotion';
// 库内标准伪随机(帧确定)
const h = (n: number) => {
const s = Math.sin(n * 127.3) * 43758.5453;
return s - Math.floor(s);
};
const W = 1920;
const H = 1080;
const CX = W / 2;
const CY = H / 2;
type Orb = {
size: number; // 直径 500–700
peak: number; // 亮度峰值
bx: number; by: number; // 基准中心
p1: number; p2: number; // 两个正弦周期 90–140f
ax1: number; ax2: number; ay1: number; ay2: number; // 振幅(合计 ≥240px)
seed: number;
};
const ORBS: Orb[] = [
{ size: 680, peak: 0.32, bx: 600, by: 400, p1: 96, p2: 134, ax1: 170, ax2: 115, ay1: 160, ay2: 120, seed: 1 },
{ size: 580, peak: 0.22, bx: 1360, by: 560, p1: 110, p2: 92, ax1: 160, ax2: 120, ay1: 175, ay2: 105, seed: 2 },
{ size: 500, peak: 0.18, bx: 940, by: 860, p1: 128, p2: 98, ax1: 150, ax2: 125, ay1: 145, ay2: 118, seed: 3 },
];
const TAU = Math.PI * 2;
const orbPos = (o: Orb, t: number) => {
const f1 = h(o.seed * 7 + 1) * TAU;
const f2 = h(o.seed * 7 + 2) * TAU;
const f3 = h(o.seed * 7 + 3) * TAU;
const f4 = h(o.seed * 7 + 4) * TAU;
const x = o.bx + o.ax1 * Math.sin((TAU * t) / o.p1 + f1) + o.ax2 * Math.sin((TAU * t) / o.p2 + f2);
const y = o.by + o.ay1 * Math.sin((TAU * t) / o.p2 + f3) + o.ay2 * Math.sin((TAU * t) / o.p1 + f4);
return { x, y };
};
export const GlowOrbAmbient: React.FC = () => {
const f = useCurrentFrame();
// 有效时间:0–90f 匀速,90–120f 用 out-sine 减速收敛(起始斜率≈0.94,近似连续),
// f≥120 clamp 恒定 => 末 30f 所有位置/阴影完全静止。
const t =
f <= 90
? f
: 90 +
interpolate(f, [90, 120], [0, 18], {
easing: Easing.out(Easing.sin),
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
// 淡入 0–20f(在末 30f 之前早已结束)
const fadeIn = interpolate(f, [0, 20], [0, 1], {
easing: Easing.out(Easing.cubic),
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
const positions = ORBS.map((o) => orbPos(o, t));
// 卡缘呼吸:最近光斑距离 -> 辉光强度(按光斑峰值加权取最大)
let glow = 0;
ORBS.forEach((o, i) => {
const d = Math.hypot(positions[i].x - CX, positions[i].y - CY);
const p = interpolate(d, [180, 720], [1, 0], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
glow = Math.max(glow, p * (o.peak / 0.32));
});
const shadowBlur = 28 * glow;
const shadowSpread = 10 * glow;
const shadowAlpha = 0.25 * glow;
return (
<AbsoluteFill style={{ background: '#1d1d1b', overflow: 'hidden' }}>
{ORBS.map((o, i) => (
<div
key={i}
style={{
position: 'absolute',
left: positions[i].x - o.size / 2,
top: positions[i].y - o.size / 2,
width: o.size,
height: o.size,
borderRadius: '50%',
background: `radial-gradient(circle, rgba(232,232,228,${o.peak}) 0%, rgba(232,232,228,${o.peak * 0.5}) 42%, rgba(232,232,228,0) 70%)`,
filter: 'blur(100px)',
opacity: fadeIn,
}}
/>
))}
{/* 中央深色描边卡 */}
<div
style={{
position: 'absolute',
left: CX - 280,
top: CY - 165,
width: 560,
height: 330,
boxSizing: 'border-box',
background: '#262624',
border: '1.5px solid #6a6a68',
borderRadius: 16,
boxShadow: `0 0 ${shadowBlur}px ${shadowSpread}px rgba(255,255,255,${shadowAlpha})`,
padding: 32,
display: 'flex',
flexDirection: 'column',
gap: 16,
}}
>
<div style={{ height: 18, width: '55%', background: '#4a4a48', borderRadius: 9 }} />
<div style={{ height: 11, width: '82%', background: '#3a3a38', borderRadius: 6 }} />
<div style={{ height: 11, width: '68%', background: '#3a3a38', borderRadius: 6 }} />
<div style={{ marginTop: 'auto', display: 'flex', gap: 10, alignItems: 'center' }}>
<div style={{ width: 28, height: 28, borderRadius: 14, background: '#4a4a48' }} />
<div style={{ height: 11, width: 90, background: '#3a3a38', borderRadius: 6 }} />
</div>
</div>
</AbsoluteFill>
);
};
// ===== demos/effects/glow-flyline-moves/OrbFlylineRelay.tsx =====
// orb-flyline-relay|光斑飞线接力
// 组合变异 = glow-orb-ambient + flyline-arc。
// 近黑底(#1d1d1b)+ 两团 blur(100px) 灰亮光斑多正弦漂移当氛围层;
// 三张深色描边卡三角布局、初始半暗(0.55)。飞线 A→B 落点帧:卡 B 亮起
// (opacity→1 + 描边亮白脉冲) 且邻近光斑同帧涨亮一拍(组合共振证明点);
// 线二 B→C 接力,C 亮起收束。光斑 95–120f out-sine 减速收敛,
// 全部动画 f120 前结束,末 35f 真静止。
import React, { useId } from 'react';
import { AbsoluteFill, interpolate, useCurrentFrame, Easing } from 'remotion';
// 库内标准伪随机(帧确定)
const h = (n: number) => {
const s = Math.sin(n * 127.3) * 43758.5453;
return s - Math.floor(s);
};
const TAU = Math.PI * 2;
type Pt = { x: number; y: number };
// 手写 cubic bezier 采样
const bez = (p0: Pt, p1: Pt, p2: Pt, p3: Pt, t: number): Pt => {
const u = 1 - t;
return {
x: u * u * u * p0.x + 3 * u * u * t * p1.x + 3 * u * t * t * p2.x + t * t * t * p3.x,
y: u * u * u * p0.y + 3 * u * u * t * p1.y + 3 * u * t * t * p2.y + t * t * t * p3.y,
};
};
const N = 100;
// ===== 布局:三张 420×250 深色卡呈三角 =====
const CARD_W = 420;
const CARD_H = 250;
const CARDS = {
A: { x: 230, y: 170 }, // 左上(center 440, 295)
B: { x: 1270, y: 300 }, // 右中(center 1480, 425)
C: { x: 700, y: 740 }, // 下中(center 910, 865)
};
const center = (c: { x: number; y: number }) => ({ x: c.x + CARD_W / 2, y: c.y + CARD_H / 2 });
// ===== 光斑(氛围层)=====
type Orb = {
size: number; peak: number;
bx: number; by: number;
p1: number; p2: number;
ax1: number; ax2: number; ay1: number; ay2: number;
seed: number;
surgeAt: number; // 与哪个落点帧共振
};
const ORBS: Orb[] = [
// 邻近卡 B —— 线1落点(f42)同帧涨亮
{ size: 720, peak: 0.26, bx: 1500, by: 330, p1: 104, p2: 138, ax1: 130, ax2: 95, ay1: 120, ay2: 92, seed: 1, surgeAt: 42 },
// 邻近卡 C —— 线2落点(f76)同帧涨亮
{ size: 640, peak: 0.2, bx: 900, by: 830, p1: 122, p2: 94, ax1: 125, ax2: 88, ay1: 128, ay2: 90, seed: 2, surgeAt: 76 },
];
const orbPos = (o: Orb, t: number) => {
const f1 = h(o.seed * 7 + 1) * TAU;
const f2 = h(o.seed * 7 + 2) * TAU;
const f3 = h(o.seed * 7 + 3) * TAU;
const f4 = h(o.seed * 7 + 4) * TAU;
const x = o.bx + o.ax1 * Math.sin((TAU * t) / o.p1 + f1) + o.ax2 * Math.sin((TAU * t) / o.p2 + f2);
const y = o.by + o.ay1 * Math.sin((TAU * t) / o.p2 + f3) + o.ay2 * Math.sin((TAU * t) / o.p1 + f4);
return { x, y };
};
// 涨亮一拍:起升 5f out-cubic,消散 15f 线性 → 落点帧 +20f 内结束
const surge = (frame: number, at: number) => {
if (frame < at || frame > at + 20) return 0;
return frame <= at + 5
? interpolate(frame, [at, at + 5], [0, 1], {
easing: Easing.out(Easing.cubic),
extrapolateLeft: 'clamp', extrapolateRight: 'clamp',
})
: interpolate(frame, [at + 5, at + 20], [1, 0], {
extrapolateLeft: 'clamp', extrapolateRight: 'clamp',
});
};
// ===== 发光飞线:亮头领跑 + 渐隐尾,到达后整线线性消散并摘罩 =====
const Flyline: React.FC<{
frame: number;
start: number; // 生长起始帧
haloId: string; // 光头径向渐变 ID(父组件按实例生成)
p0: Pt; p1: Pt; p2: Pt; p3: Pt;
}> = ({ frame, start, haloId, p0, p1, p2, p3 }) => {
const DUR = 24; // 生长
const HOLD = 4; // 到达后停一拍
const FADE = 14; // 消散(线性,帧时间解耦)
if (frame < start || frame >= start + DUR + HOLD + FADE) return null; // 条件挂载=摘罩
const e = interpolate(frame, [start, start + DUR], [0, 1], {
easing: Easing.out(Easing.cubic),
extrapolateLeft: 'clamp', extrapolateRight: 'clamp',
});
const growing = frame < start + DUR;
const fade = interpolate(frame, [start + DUR + HOLD, start + DUR + HOLD + FADE], [1, 0], {
extrapolateLeft: 'clamp', extrapolateRight: 'clamp',
});
const pts: Pt[] = [];
const nDrawn = Math.max(2, Math.ceil(e * N) + 1);
for (let i = 0; i < nDrawn; i++) {
const t = Math.min(i / N, e);
pts.push(bez(p0, p1, p2, p3, t));
}
const head = bez(p0, p1, p2, p3, e);
pts[pts.length - 1] = head;
const poly = pts.map((p) => `${p.x.toFixed(1)},${p.y.toFixed(1)}`).join(' ');
// 亮头暗尾:段 opacity 随离头距离衰减(渐隐尾)
const segs = [];
for (let i = 0; i < pts.length - 1; i++) {
const tSeg = Math.min(i / N, e) / Math.max(e, 0.001); // 0 尾 → 1 头
const grad = 0.12 + 0.88 * tSeg * tSeg;
segs.push(
<line
key={i}
x1={pts[i].x} y1={pts[i].y} x2={pts[i + 1].x} y2={pts[i + 1].y}
stroke="#f4f4f0" strokeWidth={5} strokeLinecap="round"
strokeOpacity={grad * fade}
/>
);
}
return (
<g>
{/* 宽幅低透明白 = 辉光衬底(暗底上可见) */}
<polyline
points={poly} fill="none" stroke="#e8e8e4"
strokeWidth={14} strokeLinecap="round" strokeLinejoin="round"
strokeOpacity={0.18 * fade}
/>
{segs}
{/* 亮点头领跑:仅生长期挂载 */}
{growing && (
<g>
<circle cx={head.x} cy={head.y} r={34} fill={`url(#${haloId})`} />
<circle cx={head.x} cy={head.y} r={8} fill="#ffffff" />
</g>
)}
</g>
);
};
// ===== 深色描边卡:半暗 → 落点帧亮起 + 描边亮白脉冲 =====
const DarkCard: React.FC<{
frame: number;
litAt: number; // 亮起帧(Infinity = 一直半暗;A 用 8 表示开场自亮)
x: number; y: number; seed: number;
}> = ({ frame, litAt, x, y, seed }) => {
const lit = interpolate(frame, [litAt, litAt + 8], [0, 1], {
easing: Easing.out(Easing.cubic),
extrapolateLeft: 'clamp', extrapolateRight: 'clamp',
});
const op = 0.55 + 0.45 * lit;
// 描边亮白脉冲:起升 6f out-cubic → 消散 16f 线性,残余 0.3 常量
const pulse =
frame < litAt
? 0
: frame <= litAt + 6
? interpolate(frame, [litAt, litAt + 6], [0, 1], {
easing: Easing.out(Easing.cubic),
extrapolateLeft: 'clamp', extrapolateRight: 'clamp',
})
: interpolate(frame, [litAt + 6, litAt + 22], [1, 0.3], {
extrapolateLeft: 'clamp', extrapolateRight: 'clamp',
});
const borderCol = `rgba(${Math.round(90 + 165 * pulse)},${Math.round(90 + 165 * pulse)},${Math.round(88 + 164 * pulse)},1)`;
const glow = pulse > 0 ? `0 0 ${24 * pulse}px ${6 * pulse}px rgba(255,255,255,${(0.3 * pulse).toFixed(3)})` : 'none';
const titleW = 45 + ((seed * 37) % 40);
return (
<div
style={{
position: 'absolute', left: x, top: y, width: CARD_W, height: CARD_H,
boxSizing: 'border-box', background: '#262624',
border: `1.5px solid ${borderCol}`, borderRadius: 14,
boxShadow: glow, opacity: op,
padding: 24, display: 'flex', flexDirection: 'column', gap: 12,
}}
>
<div style={{ height: 15, width: `${titleW}%`, background: '#4a4a48', borderRadius: 8 }} />
<div style={{ height: 10, width: '80%', background: '#383836', borderRadius: 5 }} />
<div style={{ height: 10, width: '62%', background: '#383836', borderRadius: 5 }} />
<div style={{ marginTop: 'auto', display: 'flex', gap: 9, alignItems: 'center' }}>
<div style={{ width: 24, height: 24, borderRadius: 12, background: '#4a4a48' }} />
<div style={{ height: 10, width: 78, background: '#383836', borderRadius: 5 }} />
</div>
</div>
);
};
export const OrbFlylineRelay: React.FC = () => {
const frame = useCurrentFrame();
// 渐变 ID 按实例生成,多实例同场不串引(useId 的 «:» 在 url() 里非法,需清洗)
const haloId = `orbHeadHalo-${useId().replace(/[^a-zA-Z0-9]/g, '')}`;
// 光斑有效时间:0–95f 匀速漂移,95–120f out-sine 减速收敛,f≥120 恒定 → 末 35f 真静止
const t =
frame <= 95
? frame
: 95 +
interpolate(frame, [95, 120], [0, 15], {
easing: Easing.out(Easing.sin),
extrapolateLeft: 'clamp', extrapolateRight: 'clamp',
});
// 光斑淡入 0–18f
const fadeIn = interpolate(frame, [0, 18], [0, 1], {
easing: Easing.out(Easing.cubic),
extrapolateLeft: 'clamp', extrapolateRight: 'clamp',
});
// 时间轴:卡A f8 自亮(发起) → 线1 f18–42 A→B → f42 卡B亮+光斑1涨 →
// 线2 f52–76 B→C → f76 卡C亮+光斑2涨 → 全部动画 f98 前结束,光斑 f120 冻结
const cA = center(CARDS.A);
const cB = center(CARDS.B);
const cC = center(CARDS.C);
const L1 = { p0: cA, p1: { x: 820, y: 90 }, p2: { x: 1300, y: 180 }, p3: cB };
const L2 = { p0: cB, p1: { x: 1620, y: 820 }, p2: { x: 1240, y: 1000 }, p3: cC };
return (
<AbsoluteFill style={{ background: '#1d1d1b', overflow: 'hidden' }}>
{/* 氛围层:两团大 blur 光斑,落点帧同帧涨亮(组合共振) */}
{ORBS.map((o, i) => {
const pos = orbPos(o, t);
const s = surge(frame, o.surgeAt);
const a = Math.min(0.85, o.peak * (1 + 1.6 * s));
return (
<div
key={i}
style={{
position: 'absolute',
left: pos.x - o.size / 2,
top: pos.y - o.size / 2,
width: o.size,
height: o.size,
borderRadius: '50%',
background: `radial-gradient(circle, rgba(234,234,230,${a.toFixed(3)}) 0%, rgba(234,234,230,${(a * 0.5).toFixed(3)}) 42%, rgba(234,234,230,0) 70%)`,
filter: 'blur(100px)',
opacity: fadeIn,
}}
/>
);
})}
{/* 三张深色描边卡:A 开场自亮发起,B/C 随落点亮起 */}
<DarkCard frame={frame} litAt={8} x={CARDS.A.x} y={CARDS.A.y} seed={1} />
<DarkCard frame={frame} litAt={42} x={CARDS.B.x} y={CARDS.B.y} seed={2} />
<DarkCard frame={frame} litAt={76} x={CARDS.C.x} y={CARDS.C.y} seed={3} />
{/* 飞线接力层 */}
<svg
width={1920} height={1080} viewBox="0 0 1920 1080"
style={{ position: 'absolute', top: 0, left: 0, pointerEvents: 'none' }}
>
<defs>
<radialGradient id={haloId}>
<stop offset="0%" stopColor="rgba(255,255,255,0.55)" />
<stop offset="55%" stopColor="rgba(255,255,255,0.22)" />
<stop offset="100%" stopColor="rgba(255,255,255,0)" />
</radialGradient>
</defs>
<Flyline frame={frame} start={18} haloId={haloId} {...L1} />
<Flyline frame={frame} start={52} haloId={haloId} {...L2} />
</svg>
</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/glow-flyline-moves/FlylineArc.tsx, demos/effects/glow-flyline-moves/GlowOrbAmbient.tsx, demos/effects/glow-flyline-moves/OrbFlylineRelay.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

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.