// icon-flip-bloom-logo —— 图标 Y 轴翻转压扁成竖线,绽放成花形 mark + wordmark 扫出 // 源:perplexity-promo 88–91.5s。笑脸 laptop 图标 anticipation 晃两下 → // 沿 Y 轴翻转压扁成竖线(拖影/模糊)→ 翻过最薄处绽放花形 mark(花瓣张开)→ // wordmark 从 mark 右侧带方向模糊逐段扫出(字符从糊到锐利)。 import React from 'react'; import { AbsoluteFill, useCurrentFrame, interpolate, Easing, spring, useVideoConfig } from 'remotion'; import { G } from '../../_fixtures/Fixtures'; const FONT = 'Helvetica, Arial, sans-serif'; const INK = '#20808d'; // 花形 mark 用一点 teal,其余灰阶 const SmileLaptop: React.FC<{ size: number }> = ({ size }) => ( ); // 5 瓣抽象花形 mark,bloom: 0(闭合竖线)→1(全开) const FlowerMark: React.FC<{ size: number; bloom: number }> = ({ size, bloom }) => { const petals = 5; return ( {Array.from({ length: petals }).map((_, i) => { // 从竖直方向(-90°)向两侧展开到均匀分布 const finalAngle = -90 + (i - (petals - 1) / 2) * (360 / petals); const angle = interpolate(bloom, [0, 1], [-90, finalAngle]); const len = interpolate(bloom, [0, 1], [20, 38]); const wid = interpolate(bloom, [0, 1], [3, 15]); return ( ); })} ); }; const WORD = 'perplexity'; export const IconFlipBloomLogo: React.FC = () => { const frame = useCurrentFrame(); const { fps } = useVideoConfig(); // ---- 时间轴 ---- // 0–10: 图标登场(淡入微弹) // 12–34: anticipation 倾斜蓄力晃两下 // 34–46: Y 轴翻转压扁成竖线(scaleX -> 0.04,带拖影) // 46–62: 翻过最薄处绽放花形(bloom 0->1 带过冲) // 64–100: mark 左移让位 + wordmark 逐字符方向模糊扫出 const FLIP_START = 34; const FLIP_MID = 46; const BLOOM_END = 62; const WORD_START = 66; // 登场 const inT = spring({ frame, fps, config: { damping: 13, stiffness: 140, mass: 0.8 } }); // anticipation:两次倾斜摆动,幅度递增(-10° / +14°),最后向反方向压一下蓄力 const wobble = interpolate(frame, [12, 18, 24, 30, FLIP_START], [0, -12, 14, -18, 0], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', easing: Easing.inOut(Easing.sin), }); // 翻转前半:scaleX 1 -> 0.04(加速入),伴随拖影 const flipIn = interpolate(frame, [FLIP_START, FLIP_MID], [0, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', easing: Easing.in(Easing.cubic), }); const iconScaleX = interpolate(flipIn, [0, 1], [1, 0.04]); // 绽放:spring 过冲 const bloomSpring = spring({ frame: frame - FLIP_MID, fps, config: { damping: 11, stiffness: 130, mass: 0.9 }, }); const bloom = frame < FLIP_MID ? 0 : bloomSpring; // mark 从竖线厚度撑开:scaleX 0.04 -> 1 const markScaleX = interpolate(bloom, [0, 1], [0.04, 1]); // mark 左移让位(wordmark 登场时) const shift = interpolate(frame, [WORD_START - 2, WORD_START + 16], [0, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', easing: Easing.out(Easing.cubic), }); const markX = interpolate(shift, [0, 1], [0, -420]); const showIcon = frame < FLIP_MID; // 拖影帧(翻转期间画 2 个残影) const ghosts = frame >= FLIP_START && frame < FLIP_MID ? [0.12, 0.24] : []; return (
{/* 图标 / mark 容器 */}
{showIcon ? ( <> {ghosts.map((g, i) => { const gs = Math.min(1, iconScaleX + g); return (
); })}
0.3 ? `blur(${flipIn * 5}px)` : 'none', opacity: inT, }} >
) : (
)}
{/* wordmark:逐字符方向模糊扫出 */}
{WORD.split('').map((ch, i) => { const cT = interpolate(frame, [WORD_START + i * 2.2, WORD_START + i * 2.2 + 10], [0, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', easing: Easing.out(Easing.cubic), }); return ( {ch} ); })}
); };