// bottom-push-stack-wipe —— slack-promo 22–27s // 换章手法:新场景连底色整屏从底边向上推入,把旧场景顶出画外, // 连推三章(三种饱和底色,每章中央钉一张灰阶窗口卡随底色走)。 // 推入用重 ease-out(快进慢停)。 import React from 'react'; import { AbsoluteFill, useCurrentFrame, interpolate, Easing } from 'remotion'; import { Card, G } from '../../_fixtures/Fixtures'; const H = 1080; // 章节定义:底色 + 卡片种子。第 0 章是起始灰阶场景。 const CHAPTERS = [ { color: G.bg, label: 0 }, { color: '#2bac76', label: 1 }, // 绿 { color: '#36c5f0', label: 2 }, // 蓝 { color: '#e01e5a', label: 3 }, // 粉红 ]; // 每章推入的起始帧;约 32 帧完成一次推入,随后 hold const PUSH_STARTS = [18, 55, 92]; const PUSH_DUR = 30; const heavyEaseOut = Easing.bezier(0.12, 0.9, 0.2, 1); // 快进慢停 const ChapterScene: React.FC<{ chapter: number }> = ({ chapter }) => { const c = CHAPTERS[chapter]; return ( {/* 底色上的淡装饰条,让"底色也在动"更可读 */} {chapter > 0 && ( <> > )} {/* 中央钉住的灰阶窗口卡 */} {['#e0605a', '#e8b93e', '#67bb5a'].map((dot, i) => ( ))} ); }; export const BottomPushStackWipe: React.FC = () => { const frame = useCurrentFrame(); // 每章的推入进度 const progress = PUSH_STARTS.map((s) => interpolate(frame, [s, s + PUSH_DUR], [0, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', easing: heavyEaseOut, }) ); return ( {CHAPTERS.map((_, i) => { // 第 i 章的位移 = 自己被推入的进度 + 被后续章顶出的进度 const pushedIn = i === 0 ? 1 : progress[i - 1]; // 自己进入 const pushedOut = i < CHAPTERS.length - 1 ? progress[i] : 0; // 被下一章顶出 const y = (1 - pushedIn) * H - pushedOut * H; if (y <= -H || y >= H) return null; return ( {/* 推入时上缘接缝阴影,强化"顶出"的物理感 */} {i > 0 && ( )} ); })} ); };