Motion · Luz & ênfase

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.

1920×108016:930 fps5.38s · 161 quadrosremotionLicença: Apache-2.0#shotcraft#remotion#effects#typography

Fonte: video-shotcraft (Vincent Wei) ↗ · Apache-2.0 · Composição Remotion (React/TSX) — vídeo de prévia, sem player no navegador

Variantes

spotlight-sweep
双层同文本:暗版 0.07 常驻,亮版 radial mask 随光斑走,锥形光摆两个来回后全亮定格
暗场大标题揭示开场
sheen-sweep
深墨卡上 45° 高光带 translateX 扫一次,overflow hidden 圆角裁剪
主角卡静止段的一次加冕
halation-bloom
文字复制底层 blur+brightness 当晕层,撞停帧起猛涨一圈回落成稳态柔光
crash-zoom 撞停帧的冲击加成

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.

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.

  1. Baixe o JSON — contém a receita do plano (timing, easing, tabela de parâmetros, armadilhas) e o código TSX completo.
  2. Clone o repositório video-shotcraft; o componente importa fixtures de demos/_fixtures e helpers de assets/lib.
  3. 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/light-play-moves.json
git 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.mp4
claude 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).

JSON · Markdown · llms.txt

9,479 chars · Apache-2.0Abrir template bruto ↗
// ===== 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>
  );
};


  1. Read the recipe
    Prompt = when to use, intention, parameter table with typical values and how each one feels, known pitfalls. Treat values as calibrated starting points.
  2. Get the code
    tool: git
    git clone https://github.com/Vincentwei1021/video-shotcraft && cd video-shotcraft && npm install
  3. Register + render
    tool: 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
  4. Or re-implement
    Port the timing/easing from the recipe to HTML + WAAPI (gimgs motion contract) or any engine; keep the same segment windows and easing.
paramtypedefaultdescription
—

Mais nesta categoria