// ===== demos/outro/ui-to-brand-morph/IconFlipBloomLogo.tsx ===== // 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} ); })}
); }; // ===== demos/outro/ui-to-brand-morph/InputMorphsIntoLogo.tsx ===== // input-morphs-into-logo —— slack-promo 40–41s // 消息输入框(一行文字 + 发送键)点击发送:文字飞走,输入框收缩变形成 // 圆角胶囊;上方依次落下 圆、胶囊、小圆,四粒元素集结排成抽象 logo // 单瓣(泪滴 + 胶囊的抽象组合,非真 Slack logo),落定呼吸。 import React from 'react'; import { AbsoluteFill, useCurrentFrame, useVideoConfig, interpolate, spring, Easing, } from 'remotion'; const BG = '#3d1f47'; // 深梅紫 const CX = 960; const CY = 560; // 落定后的抽象单瓣布局(横向胶囊在中,上方泪滴圆、右侧胶囊、下方小圆) // 单瓣 = 主胶囊(输入框变) + 大圆 + 竖胶囊 + 小圆 集结成花瓣角 const FINAL = { mainPill: { x: CX - 150, y: CY + 10, w: 300, h: 108, r: 54 }, // 横胶囊 bigDot: { x: CX - 204, y: CY + 10, d: 108 }, // 左端圆(与胶囊左帽相切,构成泪滴感) vPill: { x: CX + 96, y: CY - 152, w: 108, h: 260, r: 54 }, // 右上竖胶囊 smallDot: { x: CX + 150, y: CY - 226, d: 76 }, // 竖胶囊顶上的小圆 }; export const InputMorphsIntoLogo: React.FC = () => { const f = useCurrentFrame(); const { fps } = useVideoConfig(); // —— 时间轴 —— const CLICK = 22; // 光标按下发送 const FLY = 26; // 文字飞走 const MORPH = 34; // 输入框开始收缩变形 const DROPS = [56, 70, 84]; // 大圆 / 竖胶囊 / 小圆 依次落下 const SETTLE = 108; // 全部落定,开始呼吸 // 输入框初始几何 const box0 = { x: CX - 430, y: CY - 60, w: 860, h: 120, r: 26 }; // 光标移入 + 点击 const cursorX = interpolate(f, [0, CLICK], [1500, box0.x + box0.w - 60], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', easing: Easing.out(Easing.cubic), }); const cursorY = interpolate(f, [0, CLICK], [900, box0.y + 60], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', easing: Easing.out(Easing.cubic), }); const press = f >= CLICK && f <= CLICK + 4 ? 0.82 : 1; const cursorGone = interpolate(f, [FLY + 4, FLY + 12], [1, 0], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', }); // 发送键按下反馈 const btnFlash = f >= CLICK && f <= CLICK + 6 ? 1 : 0; // 文字飞走:整行向右上飞出 + 加速 const flyT = interpolate(f, [FLY, FLY + 12], [0, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', easing: Easing.in(Easing.cubic), }); // 输入框 → 主胶囊 morph(几何插值,带一点弹性落定) const m = spring({ frame: f - MORPH, fps, config: { damping: 13, stiffness: 90, mass: 0.9 } }); const bx = interpolate(m, [0, 1], [box0.x, FINAL.mainPill.x]); const by = interpolate(m, [0, 1], [box0.y, FINAL.mainPill.y - FINAL.mainPill.h / 2 + 60 - 60]); const bw = interpolate(m, [0, 1], [box0.w, FINAL.mainPill.w]); const bh = interpolate(m, [0, 1], [box0.h, FINAL.mainPill.h]); const br = interpolate(m, [0, 1], [box0.r, FINAL.mainPill.r]); const bColor = m; // 边框白框 → 实心暖白 // 三粒元素依次从画外上方落下(spring 落定,带轻微过冲) const dropSpring = (i: number) => spring({ frame: f - DROPS[i], fps, config: { damping: 12, stiffness: 110, mass: 0.85 } }); // 落定呼吸:整瓣轻微 scale 脉动 const breathe = f >= SETTLE ? 1 + 0.03 * Math.sin((f - SETTLE) * 0.18) : 1; const dropY = (finalY: number, s: number) => interpolate(s, [0, 1], [-260, finalY]); const s0 = dropSpring(0); const s1 = dropSpring(1); const s2 = dropSpring(2); const WHITE = '#fdf6ee'; return ( {/* 主体:输入框 → 主胶囊 */}
0.6 ? '0 0 60px rgba(253,246,238,0.25)' : 'none', }} > {/* 一行文字(发送后飞走) */}
Ready, set, go! {/* 光标闪烁 */} |
{/* 发送键 */}
{/* 纸飞机三角 */}
{/* 大圆(第一粒,落在主胶囊左端形成泪滴组合) */} {f >= DROPS[0] && (
)} {/* 竖胶囊(第二粒) */} {f >= DROPS[1] && (
)} {/* 小圆(第三粒,压在竖胶囊顶端旁) */} {f >= DROPS[2] && (
)} {/* 光标 */}
); };