// 比分砸落入场(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 (