Motion · Luz & ênfase

Slam entrance moves

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

1920×108016:930 fps4.39s · 132 quadrosremotionLicença: Apache-2.0#shotcraft#remotion#effects#ui-entrance

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

Variantes

kanada-perspective-snap
perspective 300→1500px + rotate3d 58°→0 + scale 1.7→1 甩入 18f,末 4f 过冲 +5° 弹平,长斜影收正
单卡潇洒亮相;调性偏动漫/轻快
score-slam
卡从 scale 2.5/rotate 5° 六帧 Easing.in(quad) 砸落,落点帧圆环扩散+尘点飞散+震屏同帧
KPI/比分类重磅数字登场
impact-burst-kit
B 三件套 + 冲击波前沿按半径-距离精算扫过邻卡帧,邻卡外推 30px + rotate ±3° 阻尼弹回
页面上已有邻居时的"全场震动"

When to use

A heavyweight entrance for a hero card/KPI card; impact-feedback handles the feedback after landing, this card handles the entrance itself being the impact

Intention

The library's entrance vocabulary has always topped out at deck-deal-flyin's "deal" — fast, but not heavy. This card adds the "slam" register, in three flavors: A is a sense of direction — the card whips in with exaggerated fisheye-level perspective right up to the lens and snaps flat with a "thwack," the flair of anime Kanada-style animation; B is a sense of weight — a KPI card drops in at 2.5x its on-screen size, with a ring pulse, dust burst, and screen shake all detonating on the same frame, the heft of a sports-scoreboard popup; C is a sense of transmission — on top of B's three-part combo, the shockwave's leading edge sweeps across the left and right neighboring cards and visibly pushes them away before they spring back, "this hit shook the neighbors" — one card, three ways to say how powerful something is. Choose by narrative need: A says "it's here," B says "it's heavy," C says "it shook the whole room."

Duration & energy

  • Duration: Per style: action beat 6–22f + impact aftershock ~16f + hold ≥45f
  • Energy: High

Shot recipe (original Chinese — timing, easing, parameter table, known pitfalls)

意图

库内入场词汇的力度上限一直是 deck-deal-flyin 的"发牌"——快但不重。 本卡补"砸"这一档,三种口味:A 是方向感——卡片带鱼眼级夸张透视贴着 镜头甩进来,"啪"地弹平,动漫金田流的潇洒;B 是重量感——KPI 卡从 镜头前 2.5 倍大小砸落,圆环+尘点+震屏三件套同帧起爆,体育比分弹窗 的分量;C 是传导感——B 的三件套之上,冲击波前沿扫过左右邻卡时把 它们可见地推开再弹回,"这一下震到了邻居",一件事说清力量大小。 选型按叙事需要:告诉观众"它来了"用 A,"它很重"用 B,"它震动了 全场"用 C。

三式选型

| 式 | 做法 | 适用 | |----|------|------| | A kanada-perspective-snap | perspective 300→1500px + rotate3d 58°→0 + scale 1.7→1 甩入 18f,末 4f 过冲 +5° 弹平,长斜影收正 | 单卡潇洒亮相;调性偏动漫/轻快 | | B score-slam | 卡从 scale 2.5/rotate 5° 六帧 Easing.in(quad) 砸落,落点帧圆环扩散+尘点飞散+震屏同帧 | KPI/比分类重磅数字登场 | | C impact-burst-kit | B 三件套 + 冲击波前沿按半径-距离精算扫过邻卡帧,邻卡外推 30px + rotate ±3° 阻尼弹回 | 页面上已有邻居时的"全场震动" |

参数表

| 参数 | 典型值 | 调节手感 | |------|--------|----------| | A 甩入 | 18f Easing.out(cubic),translateX -700→0 与透视变形同跑;途中 blur 2px 落定即摘 | 快于 12f 读不到鱼眼畸变,慢于 24f 泄劲 | | A 落定 | rotateY 过冲 +5° 4f 回 0 + 6px 震屏 2f + 斜长影收正常投影 | "啪"在过冲回弹那一下,不在位移结束 | | B 砸落 | 6f Easing.in(quad)(加速砸,不是减速落) | ease-out 落地是"放下",ease-in 才是"砸" | | B 三件套 | 圆环 80→860px/14f + 尘点 18–30px 飞 160–320px + 震屏 18px 指数衰减 4f | 原案(720px/10px 尘/8px 震)全档不可感,实渲已 ×1.5–2 | | B 解耦命门 | 环/尘的"扩散"用 out-cubic、"消散"用线性帧时间 | 同用 out-cubic 前段跑太快,刚扩开就淡没 | | C 波及帧 | 前沿过邻卡中心距反解(460px 距 ≈ 落点后 3f);包络 cos(t/2)·e^(−t/8),40f 硬钳 0 | 邻卡与主卡同帧动读作整版抖动,晚 3f 才是"被波及" | | 收尾 | 全部余波归零后真静止 ≥45f | 重拳 hold 按平常两倍给(同 beat-cut B 判例) |

已知坑

  • demo 在灰阶/占位素材上调校通过——参数是调校起点非实战定稿,

首次实战须以真实素材回验

  • 三式都带震屏,震屏族(impact-feedback、montage-rhythm A/C)同一镜头

只能有一个震源;三式互相之间同镜头也只能选一

  • 声音强依赖:A 的"啪"、B/C 的音爆必须同帧钉(sound-design §4.5),

无声版 B/C 的三件套读作"卡在抽搐"

  • C 式邻卡必须先驻场稳定 ≥15f 再被波及——邻卡自己还在入场时被推开,

因果链读不出来

  • 与 domino-cascade(montage-rhythm C 式)的区别要守住:domino 是

连锁"接力入场"(邻居被撞后自己入场),C 式是"被波及晃一下回原位" (邻居早已在场)——别混用在同一组元素上

  • 全片砸入 ≤2 次;三次以上每次都响等于都不响(P4)

参考实现

demos/effects/slam-entrance-moves/ (ImpactBurstKit.tsx / KanadaPerspectiveSnap.tsx / ScoreSlam.tsx)

Reference implementation (Remotion)

  • demos/effects/slam-entrance-moves/ImpactBurstKit.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/effects/slam-entrance-moves/ImpactBurstKit.tsx
  • demos/effects/slam-entrance-moves/KanadaPerspectiveSnap.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/effects/slam-entrance-moves/KanadaPerspectiveSnap.tsx
  • demos/effects/slam-entrance-moves/ScoreSlam.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/effects/slam-entrance-moves/ScoreSlam.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/slam-entrance-moves.json
git clone https://github.com/Vincentwei1021/video-shotcraft
# component: demos/effects/slam-entrance-moves/ImpactBurstKit.tsx, demos/effects/slam-entrance-moves/KanadaPerspectiveSnap.tsx, demos/effects/slam-entrance-moves/ScoreSlam.tsx
npx remotion render <CompositionId> out.mp4
claude mcp add --transport http gimgs https://gimgs.net/mcp
# then: search_motion_templates / get_motion_template("slam-entrance-moves")

Prompt

Use the video-shotcraft shot recipe "Slam entrance 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/slam-entrance-moves.json
Raw TSX: https://gimgs.net/motion/slam-entrance-moves/template.html
Repository: https://github.com/Vincentwei1021/video-shotcraft  (component: demos/effects/slam-entrance-moves/ImpactBurstKit.tsx, demos/effects/slam-entrance-moves/KanadaPerspectiveSnap.tsx, demos/effects/slam-entrance-moves/ScoreSlam.tsx; imports demos/_fixtures and assets/lib)
Spec: 1920x1080, 30 fps, 4.39s

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

16,582 chars · Apache-2.0Abrir template bruto ↗
// ===== demos/effects/slam-entrance-moves/ImpactBurstKit.tsx =====
// 落点冲击套件(impact-burst-kit)——shockwave-ring + particle-burst 组合变异。
// 主卡砸落的落点帧同时触发:冲击波环扩散 + 14 粒子放射迸发 + 震屏,
// 且冲击波前沿扫到左右邻卡的那一帧(按半径-距离算准=落点后 3f)邻卡被
// 向外推开再 spring 弹回——"波及邻居"即两条词汇焊接成立的证据。
// 关键帧:0–14 两侧卡驻场、主卡 scale1.8/y-120 悬停 → 14–20 主卡 6f 加速砸落 →
// 20 落点帧:环 80→900px(14f out-cubic, op .75→0) + 14 粒子飞散 160–340px(22f)
//   + 4f 震屏 6px 衰减 + 主卡 6f 压扁回弹 → 23 环前沿过邻卡(中心距 460px):
//   邻卡外推 30px + rotate ±3° 阻尼振荡弹回(40f 内钳到 0) → 63–140 全静止(77f)。
import React from 'react';
import { useCurrentFrame, interpolate, Easing } from 'remotion';
import { G, Card, TitleBlock } from '../../_fixtures/Fixtures';

// 伪随机(帧确定)
const h = (n: number): number => {
  const s = Math.sin(n * 127.3) * 43758.5453;
  return s - Math.floor(s);
};

const CW = 400;
const CH = 280;
const GAP = 60;
const X_L = (1920 - (CW * 3 + GAP * 2)) / 2; // 300
const Y = (1080 - CH) / 2; // 400
const CX = 960; // 主卡中心
const CY = Y + CH / 2; // 540

const IMPACT = 20; // 落点帧
// 冲击波:80→900px 14f out-cubic。前沿到达邻卡中心距 460px 的帧:
// (460-80)/820=0.463 → 1-(1-p)^3 → p≈0.19 → t≈2.6f → 取落点后 3f = 帧 23
const HIT_NEIGHBOR = IMPACT + 3;

// 14 个粒子:方/圆混合,角度带向上偏置,飞散 160–340px 减速缩小消失
const PARTICLES = Array.from({ length: 14 }).map((_, i) => ({
  angle: -Math.PI / 2 + (h(i + 1) - 0.5) * Math.PI * 1.7, // 上半球为主
  dist: 160 + h(i + 40) * 180,
  size: 8 + h(i + 80) * 10,
  square: i % 2 === 0,
}));

// 邻卡被推开的阻尼振荡包络:t=0 瞬时到 1,之后余弦衰减弹回,40f 后钳 0 保真静止
const pushEnv = (f: number): number => {
  const t = f - HIT_NEIGHBOR;
  if (t < 0 || t >= 40) return 0;
  return Math.cos(t * 0.5) * Math.exp(-t / 8);
};

export const ImpactBurstKit: React.FC = () => {
  const frame = useCurrentFrame();

  // ── 主卡砸落:14–20 帧 scale 1.8→1 / y -120→0,加速进场
  const dropP = interpolate(frame, [14, IMPACT], [0, 1], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
    easing: Easing.in(Easing.cubic),
  });
  const mainScale = interpolate(dropP, [0, 1], [1.8, 1]);
  const mainDy = interpolate(dropP, [0, 1], [-120, 0]);
  // 落点后 6f 压扁回弹(squash & stretch)
  const sq = interpolate(frame, [IMPACT, IMPACT + 3, IMPACT + 6], [0, 1, 0], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
    easing: Easing.out(Easing.quad),
  });
  const mainSx = mainScale * (1 + 0.07 * sq);
  const mainSy = mainScale * (1 - 0.1 * sq);

  // ── ① 冲击波环:半径 80→900,14f,opacity 0.75→0
  const ringP = interpolate(frame, [IMPACT, IMPACT + 14], [0, 1], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
    easing: Easing.out(Easing.cubic),
  });
  const ringR = interpolate(ringP, [0, 1], [80, 900]);
  const ringOp = frame >= IMPACT && frame < IMPACT + 14 ? 0.75 * (1 - ringP) : 0;

  // ── ② 粒子:落点起 22f,减速飞散 + 缩小 + 淡出
  const pT = interpolate(frame, [IMPACT, IMPACT + 22], [0, 1], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
    easing: Easing.out(Easing.cubic),
  });
  const particlesAlive = frame >= IMPACT && frame < IMPACT + 22;

  // ── ③ 邻卡外推 30px + rotate ±3°,阻尼弹回
  const env = pushEnv(frame);
  const pushX = 30 * env;
  const pushRot = 3 * env;

  // ── ④ 震屏:落点起 4f,6px 衰减(h 伪随机方向,帧确定)
  let shakeX = 0;
  let shakeY = 0;
  if (frame >= IMPACT && frame < IMPACT + 4) {
    const amp = 6 * (1 - (frame - IMPACT) / 4);
    shakeX = (h(frame * 3.7) - 0.5) * 2 * amp;
    shakeY = (h(frame * 7.1 + 13) - 0.5) * 2 * amp;
  }

  return (
    <div style={{ width: 1920, height: 1080, background: G.bg, position: 'relative', overflow: 'hidden' }}>
      <div style={{ position: 'absolute', inset: 0, transform: `translate(${shakeX}px, ${shakeY}px)` }}>
        <div style={{ position: 'absolute', left: 120, top: 96 }}>
          <TitleBlock text="IMPACT BURST KIT" size={54} />
        </div>

        {/* 左邻卡:被冲击波推开再弹回 */}
        <div
          style={{
            position: 'absolute',
            left: X_L,
            top: Y,
            transform: `translateX(${-pushX}px) rotate(${-pushRot}deg)`,
          }}
        >
          <Card w={CW} h={CH} seed={2} />
        </div>

        {/* 右邻卡 */}
        <div
          style={{
            position: 'absolute',
            left: X_L + (CW + GAP) * 2,
            top: Y,
            transform: `translateX(${pushX}px) rotate(${pushRot}deg)`,
          }}
        >
          <Card w={CW} h={CH} seed={4} />
        </div>

        {/* 主卡:砸落 + 落点压扁回弹 */}
        <div
          style={{
            position: 'absolute',
            left: X_L + CW + GAP,
            top: Y + mainDy,
            transform: `scale(${mainSx}, ${mainSy})`,
            transformOrigin: '50% 100%',
          }}
        >
          <Card w={CW} h={CH} seed={7} style={{ boxShadow: '0 6px 18px rgba(0,0,0,0.16)' }} />
        </div>

        {/* ② 粒子迸发(画在卡之上) */}
        {particlesAlive &&
          PARTICLES.map((p, i) => {
            const px = CX + Math.cos(p.angle) * p.dist * pT;
            const py = CY + Math.sin(p.angle) * p.dist * pT;
            const s = p.size * (1 - pT);
            if (s < 0.5) return null;
            return (
              <div
                key={i}
                style={{
                  position: 'absolute',
                  left: px - s / 2,
                  top: py - s / 2,
                  width: s,
                  height: s,
                  background: G.ink,
                  borderRadius: p.square ? 2 : '50%',
                  opacity: 1 - pT * pT,
                }}
              />
            );
          })}

        {/* ① 冲击波环(最上层扫过邻卡) */}
        {ringOp > 0 && (
          <div
            style={{
              position: 'absolute',
              left: CX - ringR,
              top: CY - ringR,
              width: ringR * 2,
              height: ringR * 2,
              border: `3px solid ${G.ink}`,
              borderRadius: '50%',
              opacity: ringOp,
              boxSizing: 'border-box',
            }}
          />
        )}
      </div>
    </div>
  );
};


// ===== demos/effects/slam-entrance-moves/KanadaPerspectiveSnap.tsx =====
// 金田透视急停(kanada-perspective-snap)——金田伊功式夸张透视入场。
// 一张卡片以鱼眼级夸张透视姿态高速甩入画面中心:容器 perspective 300→1500px
// (短焦→长焦,透视畸变随之收敛),卡片 rotate3d(0.5,1,0.1) 58°→0 +
// scale 1.7→1 + translateX -700→0,近角冲出画面感。落定瞬间"啪"地弹平:
// rotateY 过冲 +5° 再 4f 回 0;同时 6px 震屏 2f 衰减,拉长斜影收为正常投影。
// 甩入期整卡叠 blur(2px) 增速感,落定即摘(保证收尾逐帧全同)。
// 关键帧:0–18 透视甩入(out cubic)→ 14–18 rotateY 过冲至 +5° →
// 18–22 回弹归 0 + 震屏衰减 + 阴影收正 → 22–130 全静止(≥45f)。
import React from 'react';
import { useCurrentFrame, interpolate, Easing } from 'remotion';
import { G, Card, TitleBlock } from '../../_fixtures/Fixtures';

// 确定性伪随机(震屏抖动用)
const h = (n: number): number => {
  const s = Math.sin(n * 127.3) * 43758.5453;
  return s - Math.floor(s);
};

const CLAMP = { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' } as const;

const CARD_W = 520;
const CARD_H = 340;
const CX = (1920 - CARD_W) / 2; // 700
const CY = (1080 - CARD_H) / 2; // 370

export const KanadaPerspectiveSnap: React.FC = () => {
  const frame = useCurrentFrame();

  // 0–18f 甩入主通道(out cubic:先猛后缓,急停感)
  const p = interpolate(frame, [0, 18], [0, 1], { ...CLAMP, easing: Easing.out(Easing.cubic) });
  const persp = interpolate(p, [0, 1], [300, 1500]); // 短焦鱼眼→长焦收平
  const angle3d = interpolate(p, [0, 1], [58, 0]); // rotate3d(0.5,1,0.1)
  const scale = interpolate(p, [0, 1], [1.7, 1]);
  const tx = interpolate(p, [0, 1], [-700, 0]);

  // rotateY 过冲通道:14–18f 冲到 +5°,18–22f "啪"地回 0
  const rotY =
    frame < 18
      ? interpolate(frame, [14, 18], [0, 5], CLAMP)
      : interpolate(frame, [18, 22], [5, 0], { ...CLAMP, easing: Easing.out(Easing.cubic) });

  // 落定震屏:18f 起 6px,2f 内衰减到 0(21f 后恒为 0,保证真静止)
  const shakeAmp = frame >= 18 ? interpolate(frame, [18, 21], [6, 0], CLAMP) : 0;
  const shakeX = shakeAmp * (h(frame * 7 + 1) * 2 - 1);
  const shakeY = shakeAmp * (h(frame * 13 + 2) * 2 - 1);

  // 阴影:飞行期拉长斜影(大偏移大模糊)→ 落定收为正常投影(18–22f 收拢)
  const shOff = frame < 18 ? interpolate(p, [0, 1], [1, 0.35]) : interpolate(frame, [18, 22], [0.35, 0], { ...CLAMP, easing: Easing.out(Easing.quad) });
  const shX = interpolate(shOff, [0, 1], [0, 70]);
  const shY = interpolate(shOff, [0, 1], [8, 52]);
  const shBlur = interpolate(shOff, [0, 1], [14, 36]);
  const shAlpha = interpolate(shOff, [0, 1], [0.16, 0.3]);

  // 甩速 blur:透视扭曲期叠 2px,落定(18f)即摘,收尾无逐帧滤镜
  const blur = frame < 18 ? 2 : 0;

  return (
    <div style={{ width: 1920, height: 1080, background: G.bg, position: 'relative', overflow: 'hidden' }}>
      <div style={{ position: 'absolute', left: shakeX, top: shakeY, width: 1920, height: 1080 }}>
        <div style={{ position: 'absolute', left: 120, top: 96 }}>
          <TitleBlock text="KANADA PERSPECTIVE SNAP" size={54} />
        </div>
        {/* 落点槽位(虚线框,给"甩到哪"一个参照) */}
        <div
          style={{
            position: 'absolute',
            left: CX - 20,
            top: CY - 20,
            width: CARD_W + 40,
            height: CARD_H + 40,
            border: `3px dashed ${G.bar}`,
            borderRadius: 22,
            boxSizing: 'border-box',
          }}
        />
        {/* 透视容器:perspective 随落定从鱼眼收敛到长焦 */}
        <div style={{ position: 'absolute', left: CX, top: CY, perspective: `${persp}px`, perspectiveOrigin: '30% 50%' }}>
          <div
            style={{
              transform: `translateX(${tx}px) scale(${scale}) rotate3d(0.5, 1, 0.1, ${angle3d}deg) rotateY(${rotY}deg)`,
              transformOrigin: '20% 50%',
              filter: `drop-shadow(${shX}px ${shY}px ${shBlur}px rgba(0,0,0,${shAlpha}))${blur ? ` blur(${blur}px)` : ''}`,
            }}
          >
            <Card w={CARD_W} h={CARD_H} seed={3} />
          </div>
        </div>
      </div>
    </div>
  );
};


// ===== demos/effects/slam-entrance-moves/ScoreSlam.tsx =====
// 比分砸落入场(score-slam)——ESPN 比分弹窗 slam。
// KPI 卡从 scale 2.5 / rotate 5° / y-80 高空加速砸落屏心,落点帧同时触发
// 三件套:冲击波描边圆环扩散消散、8 个尘点小方块 seeded 抛物线飞散、
// 整画面震屏指数衰减。砸落带 3% 压缩过冲回弹落定。
// 关键帧:0–8 环境 hold → 8–14 砸落(Easing.in(quad),scale 2.5→0.97,
// rotate 5→0,y -80→0)→ 14 落点帧触发环/尘/震 → 14–22 过冲回弹 0.97→1 →
// 14–28 圆环 60→720px 直径 op 0.7→0 → 14–30 尘点飞散 → 14–19 震屏 18px 衰减
// → 30–135 全静止(≥45f,无逐帧噪声层)。
import React from 'react';
import { useCurrentFrame, interpolate, Easing } from 'remotion';
import { G, FakeDashboard, TitleBlock } from '../../_fixtures/Fixtures';

// 库规伪随机
const h = (n: number) => {
  const s = Math.sin(n * 127.3) * 43758.5453;
  return s - Math.floor(s);
};

const IMPACT = 14; // 落点帧
const CX = 960;
const CY = 540;
const CARD_W = 460;
const CARD_H = 260;

export const ScoreSlam: React.FC = () => {
  const frame = useCurrentFrame();

  // —— 卡片砸落:8–14 加速下砸到 0.97(压缩过冲),14–22 回弹到 1 ——
  const slamScale =
    frame < IMPACT
      ? interpolate(frame, [8, IMPACT], [2.5, 0.97], {
          extrapolateLeft: 'clamp',
          extrapolateRight: 'clamp',
          easing: Easing.in(Easing.quad),
        })
      : interpolate(frame, [IMPACT, 22], [0.97, 1], {
          extrapolateLeft: 'clamp',
          extrapolateRight: 'clamp',
          easing: Easing.out(Easing.cubic),
        });
  const slamRot = interpolate(frame, [8, IMPACT], [5, 0], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
    easing: Easing.in(Easing.quad),
  });
  const slamY = interpolate(frame, [8, IMPACT], [-80, 0], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
    easing: Easing.in(Easing.quad),
  });
  const cardOp = interpolate(frame, [8, 11], [0, 1], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
  });

  // —— 震屏:落点帧起 5f,18px 指数衰减,seeded 方向,19f 后严格归零 ——
  let shakeX = 0;
  let shakeY = 0;
  if (frame >= IMPACT && frame < IMPACT + 5) {
    const t = frame - IMPACT;
    const amp = 18 * Math.exp(-t * 0.9);
    shakeX = amp * (h(frame * 7 + 1) * 2 - 1);
    shakeY = amp * (h(frame * 13 + 2) * 2 - 1);
  }

  // —— 冲击波圆环:14f 内直径 60→720,opacity 0.7→0 ——
  const ringT = interpolate(frame, [IMPACT, IMPACT + 14], [0, 1], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
    easing: Easing.out(Easing.cubic),
  });
  const ringD = interpolate(ringT, [0, 1], [60, 860]);
  // 不透明度走线性帧时间(与扩散的 out-cubic 解耦),扩到最大前都保持可见
  const ringTLin = interpolate(frame, [IMPACT, IMPACT + 14], [0, 1], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
  });
  const ringOp = interpolate(ringTLin, [0, 0.65, 1], [0.75, 0.55, 0]);
  const ringOn = frame >= IMPACT && frame < IMPACT + 14;

  // —— 8 个尘点:seeded 角度抛物线飞散 120–260px,16f 减速缩小消失 ——
  const dustT = interpolate(frame, [IMPACT, IMPACT + 16], [0, 1], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
    easing: Easing.out(Easing.cubic),
  });
  const dustTLin = interpolate(frame, [IMPACT, IMPACT + 16], [0, 1], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
  });
  const dustOn = frame >= IMPACT && frame < IMPACT + 16;

  return (
    <div
      style={{
        width: 1920,
        height: 1080,
        background: G.bg,
        position: 'relative',
        overflow: 'hidden',
      }}
    >
      {/* 震屏容器:整画面一起抖 */}
      <div
        style={{
          position: 'absolute',
          inset: 0,
          transform: `translate(${shakeX}px, ${shakeY}px)`,
        }}
      >
        {/* 虚化 dashboard 环境底 */}
        <div
          style={{
            position: 'absolute',
            left: 0,
            top: 0,
            filter: 'blur(6px)',
            transform: 'scale(1.05)',
            transformOrigin: '50% 50%',
          }}
        >
          <FakeDashboard variant="B" />
        </div>

        <div style={{ position: 'absolute', left: 120, top: 96 }}>
          <TitleBlock text="SCORE SLAM" size={54} />
        </div>

        {/* ② 尘点飞散 */}
        {dustOn &&
          Array.from({ length: 8 }).map((_, i) => {
            const ang = (i / 8) * Math.PI * 2 + (h(i + 3) - 0.5) * 0.7;
            const dist = 160 + h(i + 11) * 160; // 160–320px
            const size = 18 + h(i + 23) * 12; // 18–30px
            const dx = Math.cos(ang) * dist * dustT;
            // 抛物线:先随角度飞出,再叠加重力下坠
            const dy = Math.sin(ang) * dist * dustT + 90 * dustT * dustT;
            const s = size * (1 - 0.75 * dustTLin);
            const op = interpolate(dustTLin, [0, 0.75, 1], [0.9, 0.7, 0]);
            return (
              <div
                key={i}
                style={{
                  position: 'absolute',
                  left: CX + dx - s / 2,
                  top: CY + CARD_H / 2 - 20 + dy - s / 2,
                  width: s,
                  height: s,
                  background: G.ink,
                  opacity: op,
                  borderRadius: 2,
                }}
              />
            );
          })}

        {/* ① 冲击波圆环 */}
        {ringOn && (
          <div
            style={{
              position: 'absolute',
              left: CX - ringD / 2,
              top: CY - ringD / 2,
              width: ringD,
              height: ringD,
              borderRadius: '50%',
              border: `6px solid ${G.ink}`,
              opacity: ringOp,
              boxSizing: 'border-box',
            }}
          />
        )}

        {/* KPI 卡本体 */}
        {frame >= 8 && (
          <div
            style={{
              position: 'absolute',
              left: CX - CARD_W / 2,
              top: CY - CARD_H / 2,
              width: CARD_W,
              height: CARD_H,
              background: G.card,
              border: `3px solid ${G.border}`,
              borderRadius: 18,
              boxShadow: '0 10px 30px rgba(0,0,0,0.18)',
              display: 'flex',
              flexDirection: 'column',
              alignItems: 'center',
              justifyContent: 'center',
              gap: 8,
              opacity: cardOp,
              transform: `translateY(${slamY}px) rotate(${slamRot}deg) scale(${slamScale})`,
              transformOrigin: '50% 50%',
            }}
          >
            <div
              style={{
                fontFamily: 'Helvetica, Arial, sans-serif',
                fontWeight: 800,
                fontSize: 110,
                color: G.ink,
                letterSpacing: -3,
                lineHeight: 1,
              }}
            >
              +247%
            </div>
            <div
              style={{
                fontFamily: 'Helvetica, Arial, sans-serif',
                fontWeight: 600,
                fontSize: 26,
                color: G.mid,
                letterSpacing: 4,
              }}
            >
              QUARTERLY GROWTH
            </div>
          </div>
        )}
      </div>
    </div>
  );
};


  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/slam-entrance-moves/ImpactBurstKit.tsx, demos/effects/slam-entrance-moves/KanadaPerspectiveSnap.tsx, demos/effects/slam-entrance-moves/ScoreSlam.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