{
  "slug": "slam-entrance-moves",
  "title": "Slam entrance moves",
  "tagline": "Three high-energy slam entrances: a whip-in perspective stop, a heavy KPI drop, and a shockwave that hits neighboring cards.",
  "description": "Three high-energy slam entrances: a whip-in perspective stop, a heavy KPI drop, and a shockwave that hits neighboring cards.",
  "category": "effects",
  "tags": [
    "shotcraft",
    "remotion",
    "effects",
    "ui-entrance"
  ],
  "tier": "free",
  "license": "Apache-2.0",
  "spec": {
    "width": 1920,
    "height": 1080,
    "fps": 30,
    "duration": 4.39,
    "aspect": "16:9"
  },
  "kind": "remotion",
  "params_schema": {
    "type": "object",
    "properties": {}
  },
  "default_params": {},
  "preview": {
    "poster": "https://gimgs.net/motion/media/slam-entrance-moves/poster-0f92800b0a85.jpg",
    "video": "https://gimgs.net/motion/media/slam-entrance-moves/video-1dd18d969e0e.mp4"
  },
  "variants": [
    {
      "key": "kanada-perspective-snap",
      "label": "kanada-perspective-snap",
      "description": "perspective 300→1500px + rotate3d 58°→0 + scale 1.7→1 whip in over 18f, overshooting +5° in the last 4f before snapping flat, with the long diagonal shadow straightening out.",
      "use": "A flashy single-card appearance; a tone leaning anime/light-hearted",
      "video": "https://gimgs.net/motion/media/slam-entrance-moves/video-1dd18d969e0e.mp4",
      "poster": "https://gimgs.net/motion/media/slam-entrance-moves/poster-0f92800b0a85.jpg"
    },
    {
      "key": "score-slam",
      "label": "score-slam",
      "description": "The card drops from scale 2.5/rotate 5° over six frames with Easing.in(quad); on the landing frame a ring pulse expands, dust scatters, and the screen shakes all on the same frame.",
      "use": "A heavyweight entrance for KPI/scoreboard-style numbers",
      "video": "https://gimgs.net/motion/media/slam-entrance-moves/video-78610e195f04.mp4",
      "poster": "https://gimgs.net/motion/media/slam-entrance-moves/poster-score-slam-fadc0fb39dbf.jpg"
    },
    {
      "key": "impact-burst-kit",
      "label": "impact-burst-kit",
      "description": "B's three-part combo, plus a shockwave front precisely calculated by radius-distance to sweep across the frames of neighboring cards, pushing them outward 30px with a ±3° rotate and a damped spring back.",
      "use": "\"The whole scene shakes\" when there are already neighboring elements on the page",
      "video": "https://gimgs.net/motion/media/slam-entrance-moves/video-5319a1641494.mp4",
      "poster": "https://gimgs.net/motion/media/slam-entrance-moves/poster-impact-burst-kit-8972571bad98.jpg"
    }
  ],
  "credits": {
    "author": "video-shotcraft (Vincent Wei)",
    "url": "https://github.com/Vincentwei1021/video-shotcraft"
  },
  "page_url": "https://gimgs.net/motion/slam-entrance-moves.html",
  "json_url": "https://gimgs.net/motion/slam-entrance-moves.json",
  "template_url": "https://gimgs.net/motion/slam-entrance-moves/template.html",
  "play_url": null,
  "markdown_url": "https://gimgs.net/motion/slam-entrance-moves.md",
  "updated_at": "2026-09-24T07:40:22.485Z",
  "prompt": "## When to use\n\nA heavyweight entrance for a hero card/KPI card; impact-feedback handles the feedback after landing, this card handles the entrance itself being the impact\n\n## Intention\n\nThe 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.\"\n\n## Duration & energy\n\n- Duration: Per style: action beat 6–22f + impact aftershock ~16f + hold ≥45f\n- Energy: High\n\n## Shot recipe (original Chinese — timing, easing, parameter table, known pitfalls)\n\n## 意图\n库内入场词汇的力度上限一直是 deck-deal-flyin 的\"发牌\"——快但不重。\n本卡补\"砸\"这一档，三种口味：A 是方向感——卡片带鱼眼级夸张透视贴着\n镜头甩进来，\"啪\"地弹平，动漫金田流的潇洒；B 是重量感——KPI 卡从\n镜头前 2.5 倍大小砸落，圆环+尘点+震屏三件套同帧起爆，体育比分弹窗\n的分量；C 是传导感——B 的三件套之上，冲击波前沿扫过左右邻卡时把\n它们可见地推开再弹回，\"这一下震到了邻居\"，一件事说清力量大小。\n选型按叙事需要：告诉观众\"它来了\"用 A，\"它很重\"用 B，\"它震动了\n全场\"用 C。\n\n## 三式选型\n| 式 | 做法 | 适用 |\n|----|------|------|\n| A kanada-perspective-snap | perspective 300→1500px + rotate3d 58°→0 + scale 1.7→1 甩入 18f，末 4f 过冲 +5° 弹平，长斜影收正 | 单卡潇洒亮相；调性偏动漫/轻快 |\n| B score-slam | 卡从 scale 2.5/rotate 5° 六帧 Easing.in(quad) 砸落，落点帧圆环扩散+尘点飞散+震屏同帧 | KPI/比分类重磅数字登场 |\n| C impact-burst-kit | B 三件套 + 冲击波前沿按半径-距离精算扫过邻卡帧，邻卡外推 30px + rotate ±3° 阻尼弹回 | 页面上已有邻居时的\"全场震动\" |\n\n## 参数表\n| 参数 | 典型值 | 调节手感 |\n|------|--------|----------|\n| A 甩入 | 18f Easing.out(cubic)，translateX -700→0 与透视变形同跑；途中 blur 2px 落定即摘 | 快于 12f 读不到鱼眼畸变，慢于 24f 泄劲 |\n| A 落定 | rotateY 过冲 +5° 4f 回 0 + 6px 震屏 2f + 斜长影收正常投影 | \"啪\"在过冲回弹那一下，不在位移结束 |\n| B 砸落 | 6f Easing.in(quad)（加速砸，不是减速落） | ease-out 落地是\"放下\"，ease-in 才是\"砸\" |\n| B 三件套 | 圆环 80→860px/14f + 尘点 18–30px 飞 160–320px + 震屏 18px 指数衰减 4f | 原案(720px/10px 尘/8px 震)全档不可感，实渲已 ×1.5–2 |\n| B 解耦命门 | 环/尘的\"扩散\"用 out-cubic、\"消散\"用线性帧时间 | 同用 out-cubic 前段跑太快，刚扩开就淡没 |\n| C 波及帧 | 前沿过邻卡中心距反解（460px 距 ≈ 落点后 3f）；包络 cos(t/2)·e^(−t/8)，40f 硬钳 0 | 邻卡与主卡同帧动读作整版抖动，晚 3f 才是\"被波及\" |\n| 收尾 | 全部余波归零后真静止 ≥45f | 重拳 hold 按平常两倍给（同 beat-cut B 判例） |\n\n## 已知坑\n- demo 在灰阶/占位素材上调校通过——参数是调校起点非实战定稿，\n  首次实战须以真实素材回验\n- 三式都带震屏，震屏族（impact-feedback、montage-rhythm A/C）同一镜头\n  只能有一个震源；三式互相之间同镜头也只能选一\n- 声音强依赖：A 的\"啪\"、B/C 的音爆必须同帧钉（sound-design §4.5），\n  无声版 B/C 的三件套读作\"卡在抽搐\"\n- C 式邻卡必须先驻场稳定 ≥15f 再被波及——邻卡自己还在入场时被推开，\n  因果链读不出来\n- 与 domino-cascade（montage-rhythm C 式）的区别要守住：domino 是\n  连锁\"接力入场\"（邻居被撞后自己入场），C 式是\"被波及晃一下回原位\"\n  （邻居早已在场）——别混用在同一组元素上\n- 全片砸入 ≤2 次；三次以上每次都响等于都不响（P4）\n\n## 参考实现\ndemos/effects/slam-entrance-moves/\n（ImpactBurstKit.tsx / KanadaPerspectiveSnap.tsx / ScoreSlam.tsx）\n\n## Reference implementation (Remotion)\n\n- demos/effects/slam-entrance-moves/ImpactBurstKit.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/effects/slam-entrance-moves/ImpactBurstKit.tsx\n- demos/effects/slam-entrance-moves/KanadaPerspectiveSnap.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/effects/slam-entrance-moves/KanadaPerspectiveSnap.tsx\n- demos/effects/slam-entrance-moves/ScoreSlam.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/effects/slam-entrance-moves/ScoreSlam.tsx\n\nThe component imports shared fixtures from `demos/_fixtures` and helpers from `assets/lib` in the repo; SKILL.md there documents the render workflow.\n",
  "flow": [
    {
      "step": "Read the recipe",
      "note": "Prompt = when to use, intention, parameter table with typical values and how each one feels, known pitfalls. Treat values as calibrated starting points."
    },
    {
      "step": "Get the code",
      "tool": "git",
      "cmd": "git clone https://github.com/Vincentwei1021/video-shotcraft && cd video-shotcraft && npm install"
    },
    {
      "step": "Register + render",
      "tool": "remotion",
      "cmd": "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"
    },
    {
      "step": "Or re-implement",
      "note": "Port the timing/easing from the recipe to HTML + WAAPI (gimgs motion contract) or any engine; keep the same segment windows and easing."
    }
  ],
  "files": [
    {
      "path": "demos/effects/slam-entrance-moves/ImpactBurstKit.tsx",
      "url": "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/effects/slam-entrance-moves/ImpactBurstKit.tsx"
    },
    {
      "path": "demos/effects/slam-entrance-moves/KanadaPerspectiveSnap.tsx",
      "url": "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/effects/slam-entrance-moves/KanadaPerspectiveSnap.tsx"
    },
    {
      "path": "demos/effects/slam-entrance-moves/ScoreSlam.tsx",
      "url": "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/effects/slam-entrance-moves/ScoreSlam.tsx"
    }
  ],
  "contract": "React/Remotion composition (TSX) from the open-source video-shotcraft library (Apache-2.0). It is not a browser-playable template: clone the repo, register the component in a Remotion project and render with `npx remotion render`. The recipe (prompt) describes timing, easing and parameters so you can also re-implement the shot in any engine.",
  "render": {
    "engine": "remotion",
    "frames": 132,
    "repo": "https://github.com/Vincentwei1021/video-shotcraft",
    "files": [
      "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/effects/slam-entrance-moves/ImpactBurstKit.tsx",
      "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/effects/slam-entrance-moves/KanadaPerspectiveSnap.tsx",
      "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/effects/slam-entrance-moves/ScoreSlam.tsx"
    ],
    "note": "Clone https://github.com/Vincentwei1021/video-shotcraft (npm install). The component lives under demos/{category}/{name}/ and imports shared fixtures from demos/_fixtures and helpers from assets/lib. Copy it (with those imports) into a Remotion project, register a <Composition> with the template's width/height/fps/duration in Root.tsx, then `npx remotion render <CompositionId> out.mp4`. SKILL.md in the repo documents the full Claude Code / Codex workflow. The prompt below is the shot recipe (timing, easing, parameter table, pitfalls) — enough to re-implement the shot in HTML/WAAPI or another engine if you prefer."
  },
  "source": "// ===== demos/effects/slam-entrance-moves/ImpactBurstKit.tsx =====\n// 落点冲击套件（impact-burst-kit）——shockwave-ring + particle-burst 组合变异。\n// 主卡砸落的落点帧同时触发：冲击波环扩散 + 14 粒子放射迸发 + 震屏，\n// 且冲击波前沿扫到左右邻卡的那一帧（按半径-距离算准=落点后 3f）邻卡被\n// 向外推开再 spring 弹回——\"波及邻居\"即两条词汇焊接成立的证据。\n// 关键帧：0–14 两侧卡驻场、主卡 scale1.8/y-120 悬停 → 14–20 主卡 6f 加速砸落 →\n// 20 落点帧：环 80→900px(14f out-cubic, op .75→0) + 14 粒子飞散 160–340px(22f)\n//   + 4f 震屏 6px 衰减 + 主卡 6f 压扁回弹 → 23 环前沿过邻卡(中心距 460px)：\n//   邻卡外推 30px + rotate ±3° 阻尼振荡弹回(40f 内钳到 0) → 63–140 全静止(77f)。\nimport React from 'react';\nimport { useCurrentFrame, interpolate, Easing } from 'remotion';\nimport { G, Card, TitleBlock } from '../../_fixtures/Fixtures';\n\n// 伪随机（帧确定）\nconst h = (n: number): number => {\n  const s = Math.sin(n * 127.3) * 43758.5453;\n  return s - Math.floor(s);\n};\n\nconst CW = 400;\nconst CH = 280;\nconst GAP = 60;\nconst X_L = (1920 - (CW * 3 + GAP * 2)) / 2; // 300\nconst Y = (1080 - CH) / 2; // 400\nconst CX = 960; // 主卡中心\nconst CY = Y + CH / 2; // 540\n\nconst IMPACT = 20; // 落点帧\n// 冲击波：80→900px 14f out-cubic。前沿到达邻卡中心距 460px 的帧：\n// (460-80)/820=0.463 → 1-(1-p)^3 → p≈0.19 → t≈2.6f → 取落点后 3f = 帧 23\nconst HIT_NEIGHBOR = IMPACT + 3;\n\n// 14 个粒子：方/圆混合，角度带向上偏置，飞散 160–340px 减速缩小消失\nconst PARTICLES = Array.from({ length: 14 }).map((_, i) => ({\n  angle: -Math.PI / 2 + (h(i + 1) - 0.5) * Math.PI * 1.7, // 上半球为主\n  dist: 160 + h(i + 40) * 180,\n  size: 8 + h(i + 80) * 10,\n  square: i % 2 === 0,\n}));\n\n// 邻卡被推开的阻尼振荡包络：t=0 瞬时到 1，之后余弦衰减弹回，40f 后钳 0 保真静止\nconst pushEnv = (f: number): number => {\n  const t = f - HIT_NEIGHBOR;\n  if (t < 0 || t >= 40) return 0;\n  return Math.cos(t * 0.5) * Math.exp(-t / 8);\n};\n\nexport const ImpactBurstKit: React.FC = () => {\n  const frame = useCurrentFrame();\n\n  // ── 主卡砸落：14–20 帧 scale 1.8→1 / y -120→0，加速进场\n  const dropP = interpolate(frame, [14, IMPACT], [0, 1], {\n    extrapolateLeft: 'clamp',\n    extrapolateRight: 'clamp',\n    easing: Easing.in(Easing.cubic),\n  });\n  const mainScale = interpolate(dropP, [0, 1], [1.8, 1]);\n  const mainDy = interpolate(dropP, [0, 1], [-120, 0]);\n  // 落点后 6f 压扁回弹（squash & stretch）\n  const sq = interpolate(frame, [IMPACT, IMPACT + 3, IMPACT + 6], [0, 1, 0], {\n    extrapolateLeft: 'clamp',\n    extrapolateRight: 'clamp',\n    easing: Easing.out(Easing.quad),\n  });\n  const mainSx = mainScale * (1 + 0.07 * sq);\n  const mainSy = mainScale * (1 - 0.1 * sq);\n\n  // ── ① 冲击波环：半径 80→900，14f，opacity 0.75→0\n  const ringP = interpolate(frame, [IMPACT, IMPACT + 14], [0, 1], {\n    extrapolateLeft: 'clamp',\n    extrapolateRight: 'clamp',\n    easing: Easing.out(Easing.cubic),\n  });\n  const ringR = interpolate(ringP, [0, 1], [80, 900]);\n  const ringOp = frame >= IMPACT && frame < IMPACT + 14 ? 0.75 * (1 - ringP) : 0;\n\n  // ── ② 粒子：落点起 22f，减速飞散 + 缩小 + 淡出\n  const pT = interpolate(frame, [IMPACT, IMPACT + 22], [0, 1], {\n    extrapolateLeft: 'clamp',\n    extrapolateRight: 'clamp',\n    easing: Easing.out(Easing.cubic),\n  });\n  const particlesAlive = frame >= IMPACT && frame < IMPACT + 22;\n\n  // ── ③ 邻卡外推 30px + rotate ±3°，阻尼弹回\n  const env = pushEnv(frame);\n  const pushX = 30 * env;\n  const pushRot = 3 * env;\n\n  // ── ④ 震屏：落点起 4f，6px 衰减（h 伪随机方向，帧确定）\n  let shakeX = 0;\n  let shakeY = 0;\n  if (frame >= IMPACT && frame < IMPACT + 4) {\n    const amp = 6 * (1 - (frame - IMPACT) / 4);\n    shakeX = (h(frame * 3.7) - 0.5) * 2 * amp;\n    shakeY = (h(frame * 7.1 + 13) - 0.5) * 2 * amp;\n  }\n\n  return (\n    <div style={{ width: 1920, height: 1080, background: G.bg, position: 'relative', overflow: 'hidden' }}>\n      <div style={{ position: 'absolute', inset: 0, transform: `translate(${shakeX}px, ${shakeY}px)` }}>\n        <div style={{ position: 'absolute', left: 120, top: 96 }}>\n          <TitleBlock text=\"IMPACT BURST KIT\" size={54} />\n        </div>\n\n        {/* 左邻卡：被冲击波推开再弹回 */}\n        <div\n          style={{\n            position: 'absolute',\n            left: X_L,\n            top: Y,\n            transform: `translateX(${-pushX}px) rotate(${-pushRot}deg)`,\n          }}\n        >\n          <Card w={CW} h={CH} seed={2} />\n        </div>\n\n        {/* 右邻卡 */}\n        <div\n          style={{\n            position: 'absolute',\n            left: X_L + (CW + GAP) * 2,\n            top: Y,\n            transform: `translateX(${pushX}px) rotate(${pushRot}deg)`,\n          }}\n        >\n          <Card w={CW} h={CH} seed={4} />\n        </div>\n\n        {/* 主卡：砸落 + 落点压扁回弹 */}\n        <div\n          style={{\n            position: 'absolute',\n            left: X_L + CW + GAP,\n            top: Y + mainDy,\n            transform: `scale(${mainSx}, ${mainSy})`,\n            transformOrigin: '50% 100%',\n          }}\n        >\n          <Card w={CW} h={CH} seed={7} style={{ boxShadow: '0 6px 18px rgba(0,0,0,0.16)' }} />\n        </div>\n\n        {/* ② 粒子迸发（画在卡之上） */}\n        {particlesAlive &&\n          PARTICLES.map((p, i) => {\n            const px = CX + Math.cos(p.angle) * p.dist * pT;\n            const py = CY + Math.sin(p.angle) * p.dist * pT;\n            const s = p.size * (1 - pT);\n            if (s < 0.5) return null;\n            return (\n              <div\n                key={i}\n                style={{\n                  position: 'absolute',\n                  left: px - s / 2,\n                  top: py - s / 2,\n                  width: s,\n                  height: s,\n                  background: G.ink,\n                  borderRadius: p.square ? 2 : '50%',\n                  opacity: 1 - pT * pT,\n                }}\n              />\n            );\n          })}\n\n        {/* ① 冲击波环（最上层扫过邻卡） */}\n        {ringOp > 0 && (\n          <div\n            style={{\n              position: 'absolute',\n              left: CX - ringR,\n              top: CY - ringR,\n              width: ringR * 2,\n              height: ringR * 2,\n              border: `3px solid ${G.ink}`,\n              borderRadius: '50%',\n              opacity: ringOp,\n              boxSizing: 'border-box',\n            }}\n          />\n        )}\n      </div>\n    </div>\n  );\n};\n\n\n// ===== demos/effects/slam-entrance-moves/KanadaPerspectiveSnap.tsx =====\n// 金田透视急停（kanada-perspective-snap）——金田伊功式夸张透视入场。\n// 一张卡片以鱼眼级夸张透视姿态高速甩入画面中心：容器 perspective 300→1500px\n// （短焦→长焦，透视畸变随之收敛），卡片 rotate3d(0.5,1,0.1) 58°→0 +\n// scale 1.7→1 + translateX -700→0，近角冲出画面感。落定瞬间\"啪\"地弹平：\n// rotateY 过冲 +5° 再 4f 回 0；同时 6px 震屏 2f 衰减，拉长斜影收为正常投影。\n// 甩入期整卡叠 blur(2px) 增速感，落定即摘（保证收尾逐帧全同）。\n// 关键帧：0–18 透视甩入（out cubic）→ 14–18 rotateY 过冲至 +5° →\n// 18–22 回弹归 0 + 震屏衰减 + 阴影收正 → 22–130 全静止（≥45f）。\nimport React from 'react';\nimport { useCurrentFrame, interpolate, Easing } from 'remotion';\nimport { G, Card, TitleBlock } from '../../_fixtures/Fixtures';\n\n// 确定性伪随机（震屏抖动用）\nconst h = (n: number): number => {\n  const s = Math.sin(n * 127.3) * 43758.5453;\n  return s - Math.floor(s);\n};\n\nconst CLAMP = { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' } as const;\n\nconst CARD_W = 520;\nconst CARD_H = 340;\nconst CX = (1920 - CARD_W) / 2; // 700\nconst CY = (1080 - CARD_H) / 2; // 370\n\nexport const KanadaPerspectiveSnap: React.FC = () => {\n  const frame = useCurrentFrame();\n\n  // 0–18f 甩入主通道（out cubic：先猛后缓，急停感）\n  const p = interpolate(frame, [0, 18], [0, 1], { ...CLAMP, easing: Easing.out(Easing.cubic) });\n  const persp = interpolate(p, [0, 1], [300, 1500]); // 短焦鱼眼→长焦收平\n  const angle3d = interpolate(p, [0, 1], [58, 0]); // rotate3d(0.5,1,0.1)\n  const scale = interpolate(p, [0, 1], [1.7, 1]);\n  const tx = interpolate(p, [0, 1], [-700, 0]);\n\n  // rotateY 过冲通道：14–18f 冲到 +5°，18–22f \"啪\"地回 0\n  const rotY =\n    frame < 18\n      ? interpolate(frame, [14, 18], [0, 5], CLAMP)\n      : interpolate(frame, [18, 22], [5, 0], { ...CLAMP, easing: Easing.out(Easing.cubic) });\n\n  // 落定震屏：18f 起 6px，2f 内衰减到 0（21f 后恒为 0，保证真静止）\n  const shakeAmp = frame >= 18 ? interpolate(frame, [18, 21], [6, 0], CLAMP) : 0;\n  const shakeX = shakeAmp * (h(frame * 7 + 1) * 2 - 1);\n  const shakeY = shakeAmp * (h(frame * 13 + 2) * 2 - 1);\n\n  // 阴影：飞行期拉长斜影（大偏移大模糊）→ 落定收为正常投影（18–22f 收拢）\n  const shOff = frame < 18 ? interpolate(p, [0, 1], [1, 0.35]) : interpolate(frame, [18, 22], [0.35, 0], { ...CLAMP, easing: Easing.out(Easing.quad) });\n  const shX = interpolate(shOff, [0, 1], [0, 70]);\n  const shY = interpolate(shOff, [0, 1], [8, 52]);\n  const shBlur = interpolate(shOff, [0, 1], [14, 36]);\n  const shAlpha = interpolate(shOff, [0, 1], [0.16, 0.3]);\n\n  // 甩速 blur：透视扭曲期叠 2px，落定（18f）即摘，收尾无逐帧滤镜\n  const blur = frame < 18 ? 2 : 0;\n\n  return (\n    <div style={{ width: 1920, height: 1080, background: G.bg, position: 'relative', overflow: 'hidden' }}>\n      <div style={{ position: 'absolute', left: shakeX, top: shakeY, width: 1920, height: 1080 }}>\n        <div style={{ position: 'absolute', left: 120, top: 96 }}>\n          <TitleBlock text=\"KANADA PERSPECTIVE SNAP\" size={54} />\n        </div>\n        {/* 落点槽位（虚线框，给\"甩到哪\"一个参照） */}\n        <div\n          style={{\n            position: 'absolute',\n            left: CX - 20,\n            top: CY - 20,\n            width: CARD_W + 40,\n            height: CARD_H + 40,\n            border: `3px dashed ${G.bar}`,\n            borderRadius: 22,\n            boxSizing: 'border-box',\n          }}\n        />\n        {/* 透视容器：perspective 随落定从鱼眼收敛到长焦 */}\n        <div style={{ position: 'absolute', left: CX, top: CY, perspective: `${persp}px`, perspectiveOrigin: '30% 50%' }}>\n          <div\n            style={{\n              transform: `translateX(${tx}px) scale(${scale}) rotate3d(0.5, 1, 0.1, ${angle3d}deg) rotateY(${rotY}deg)`,\n              transformOrigin: '20% 50%',\n              filter: `drop-shadow(${shX}px ${shY}px ${shBlur}px rgba(0,0,0,${shAlpha}))${blur ? ` blur(${blur}px)` : ''}`,\n            }}\n          >\n            <Card w={CARD_W} h={CARD_H} seed={3} />\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n};\n\n\n// ===== demos/effects/slam-entrance-moves/ScoreSlam.tsx =====\n// 比分砸落入场（score-slam）——ESPN 比分弹窗 slam。\n// KPI 卡从 scale 2.5 / rotate 5° / y-80 高空加速砸落屏心，落点帧同时触发\n// 三件套：冲击波描边圆环扩散消散、8 个尘点小方块 seeded 抛物线飞散、\n// 整画面震屏指数衰减。砸落带 3% 压缩过冲回弹落定。\n// 关键帧：0–8 环境 hold → 8–14 砸落（Easing.in(quad)，scale 2.5→0.97，\n// rotate 5→0，y -80→0）→ 14 落点帧触发环/尘/震 → 14–22 过冲回弹 0.97→1 →\n// 14–28 圆环 60→720px 直径 op 0.7→0 → 14–30 尘点飞散 → 14–19 震屏 18px 衰减\n// → 30–135 全静止（≥45f，无逐帧噪声层）。\nimport React from 'react';\nimport { useCurrentFrame, interpolate, Easing } from 'remotion';\nimport { G, FakeDashboard, TitleBlock } from '../../_fixtures/Fixtures';\n\n// 库规伪随机\nconst h = (n: number) => {\n  const s = Math.sin(n * 127.3) * 43758.5453;\n  return s - Math.floor(s);\n};\n\nconst IMPACT = 14; // 落点帧\nconst CX = 960;\nconst CY = 540;\nconst CARD_W = 460;\nconst CARD_H = 260;\n\nexport const ScoreSlam: React.FC = () => {\n  const frame = useCurrentFrame();\n\n  // —— 卡片砸落：8–14 加速下砸到 0.97（压缩过冲），14–22 回弹到 1 ——\n  const slamScale =\n    frame < IMPACT\n      ? interpolate(frame, [8, IMPACT], [2.5, 0.97], {\n          extrapolateLeft: 'clamp',\n          extrapolateRight: 'clamp',\n          easing: Easing.in(Easing.quad),\n        })\n      : interpolate(frame, [IMPACT, 22], [0.97, 1], {\n          extrapolateLeft: 'clamp',\n          extrapolateRight: 'clamp',\n          easing: Easing.out(Easing.cubic),\n        });\n  const slamRot = interpolate(frame, [8, IMPACT], [5, 0], {\n    extrapolateLeft: 'clamp',\n    extrapolateRight: 'clamp',\n    easing: Easing.in(Easing.quad),\n  });\n  const slamY = interpolate(frame, [8, IMPACT], [-80, 0], {\n    extrapolateLeft: 'clamp',\n    extrapolateRight: 'clamp',\n    easing: Easing.in(Easing.quad),\n  });\n  const cardOp = interpolate(frame, [8, 11], [0, 1], {\n    extrapolateLeft: 'clamp',\n    extrapolateRight: 'clamp',\n  });\n\n  // —— 震屏：落点帧起 5f，18px 指数衰减，seeded 方向，19f 后严格归零 ——\n  let shakeX = 0;\n  let shakeY = 0;\n  if (frame >= IMPACT && frame < IMPACT + 5) {\n    const t = frame - IMPACT;\n    const amp = 18 * Math.exp(-t * 0.9);\n    shakeX = amp * (h(frame * 7 + 1) * 2 - 1);\n    shakeY = amp * (h(frame * 13 + 2) * 2 - 1);\n  }\n\n  // —— 冲击波圆环：14f 内直径 60→720，opacity 0.7→0 ——\n  const ringT = interpolate(frame, [IMPACT, IMPACT + 14], [0, 1], {\n    extrapolateLeft: 'clamp',\n    extrapolateRight: 'clamp',\n    easing: Easing.out(Easing.cubic),\n  });\n  const ringD = interpolate(ringT, [0, 1], [60, 860]);\n  // 不透明度走线性帧时间（与扩散的 out-cubic 解耦），扩到最大前都保持可见\n  const ringTLin = interpolate(frame, [IMPACT, IMPACT + 14], [0, 1], {\n    extrapolateLeft: 'clamp',\n    extrapolateRight: 'clamp',\n  });\n  const ringOp = interpolate(ringTLin, [0, 0.65, 1], [0.75, 0.55, 0]);\n  const ringOn = frame >= IMPACT && frame < IMPACT + 14;\n\n  // —— 8 个尘点：seeded 角度抛物线飞散 120–260px，16f 减速缩小消失 ——\n  const dustT = interpolate(frame, [IMPACT, IMPACT + 16], [0, 1], {\n    extrapolateLeft: 'clamp',\n    extrapolateRight: 'clamp',\n    easing: Easing.out(Easing.cubic),\n  });\n  const dustTLin = interpolate(frame, [IMPACT, IMPACT + 16], [0, 1], {\n    extrapolateLeft: 'clamp',\n    extrapolateRight: 'clamp',\n  });\n  const dustOn = frame >= IMPACT && frame < IMPACT + 16;\n\n  return (\n    <div\n      style={{\n        width: 1920,\n        height: 1080,\n        background: G.bg,\n        position: 'relative',\n        overflow: 'hidden',\n      }}\n    >\n      {/* 震屏容器：整画面一起抖 */}\n      <div\n        style={{\n          position: 'absolute',\n          inset: 0,\n          transform: `translate(${shakeX}px, ${shakeY}px)`,\n        }}\n      >\n        {/* 虚化 dashboard 环境底 */}\n        <div\n          style={{\n            position: 'absolute',\n            left: 0,\n            top: 0,\n            filter: 'blur(6px)',\n            transform: 'scale(1.05)',\n            transformOrigin: '50% 50%',\n          }}\n        >\n          <FakeDashboard variant=\"B\" />\n        </div>\n\n        <div style={{ position: 'absolute', left: 120, top: 96 }}>\n          <TitleBlock text=\"SCORE SLAM\" size={54} />\n        </div>\n\n        {/* ② 尘点飞散 */}\n        {dustOn &&\n          Array.from({ length: 8 }).map((_, i) => {\n            const ang = (i / 8) * Math.PI * 2 + (h(i + 3) - 0.5) * 0.7;\n            const dist = 160 + h(i + 11) * 160; // 160–320px\n            const size = 18 + h(i + 23) * 12; // 18–30px\n            const dx = Math.cos(ang) * dist * dustT;\n            // 抛物线：先随角度飞出，再叠加重力下坠\n            const dy = Math.sin(ang) * dist * dustT + 90 * dustT * dustT;\n            const s = size * (1 - 0.75 * dustTLin);\n            const op = interpolate(dustTLin, [0, 0.75, 1], [0.9, 0.7, 0]);\n            return (\n              <div\n                key={i}\n                style={{\n                  position: 'absolute',\n                  left: CX + dx - s / 2,\n                  top: CY + CARD_H / 2 - 20 + dy - s / 2,\n                  width: s,\n                  height: s,\n                  background: G.ink,\n                  opacity: op,\n                  borderRadius: 2,\n                }}\n              />\n            );\n          })}\n\n        {/* ① 冲击波圆环 */}\n        {ringOn && (\n          <div\n            style={{\n              position: 'absolute',\n              left: CX - ringD / 2,\n              top: CY - ringD / 2,\n              width: ringD,\n              height: ringD,\n              borderRadius: '50%',\n              border: `6px solid ${G.ink}`,\n              opacity: ringOp,\n              boxSizing: 'border-box',\n            }}\n          />\n        )}\n\n        {/* KPI 卡本体 */}\n        {frame >= 8 && (\n          <div\n            style={{\n              position: 'absolute',\n              left: CX - CARD_W / 2,\n              top: CY - CARD_H / 2,\n              width: CARD_W,\n              height: CARD_H,\n              background: G.card,\n              border: `3px solid ${G.border}`,\n              borderRadius: 18,\n              boxShadow: '0 10px 30px rgba(0,0,0,0.18)',\n              display: 'flex',\n              flexDirection: 'column',\n              alignItems: 'center',\n              justifyContent: 'center',\n              gap: 8,\n              opacity: cardOp,\n              transform: `translateY(${slamY}px) rotate(${slamRot}deg) scale(${slamScale})`,\n              transformOrigin: '50% 50%',\n            }}\n          >\n            <div\n              style={{\n                fontFamily: 'Helvetica, Arial, sans-serif',\n                fontWeight: 800,\n                fontSize: 110,\n                color: G.ink,\n                letterSpacing: -3,\n                lineHeight: 1,\n              }}\n            >\n              +247%\n            </div>\n            <div\n              style={{\n                fontFamily: 'Helvetica, Arial, sans-serif',\n                fontWeight: 600,\n                fontSize: 26,\n                color: G.mid,\n                letterSpacing: 4,\n              }}\n            >\n              QUARTERLY GROWTH\n            </div>\n          </div>\n        )}\n      </div>\n    </div>\n  );\n};\n\n\n"
}