// ===== demos/ui-entrance/skeleton-reveal/SkeletonReveal.tsx ===== // skeleton-reveal —— slack-promo 4–15s 合并 // 三级显影:全屏手绘粗笔触涂鸦占位(圆头 blob 线稿,煮沸抖动)→ 一拍内被 // 灰条骨架 UI 窗口替换 → 骨架消息列表滚入,镜头推近时灰条逐行"显影"成 // 头像+文字内容(最后一行末词晚半拍到)。 import React from 'react'; import { AbsoluteFill, useCurrentFrame, useVideoConfig, interpolate, spring, Easing, } from 'remotion'; const mulberry32 = (a: number) => () => { let t = (a += 0x6d2b79f5); t = Math.imul(t ^ (t >>> 15), t | 1); t ^= t + Math.imul(t ^ (t >>> 7), t | 61); return ((t ^ (t >>> 14)) >>> 0) / 4294967296; }; const INK = '#2f2f2f'; const PAPER = '#f2f0ea'; // ——— 手绘涂鸦工具:把折线/圆加种子抖动,画粗圆头 stroke ——— const wobbleLine = ( x1: number, y1: number, x2: number, y2: number, seed: number, amp = 7, segs = 8, ) => { const rnd = mulberry32(seed); const pts: string[] = []; for (let i = 0; i <= segs; i++) { const t = i / segs; const jx = (rnd() - 0.5) * amp * 2; const jy = (rnd() - 0.5) * amp * 2; pts.push(`${i === 0 ? 'M' : 'L'} ${x1 + (x2 - x1) * t + jx} ${y1 + (y2 - y1) * t + jy}`); } return pts.join(' '); }; const wobbleBlobRect = ( x: number, y: number, w: number, h: number, seed: number, amp = 10, r = 70, ) => { // 圆头 blob 矩形:四条边中段抖动,四角大圆弧 const rnd = mulberry32(seed); const j = () => (rnd() - 0.5) * amp * 2; return [ `M ${x + r + j()} ${y + j()}`, `L ${x + w / 2 + j()} ${y + j()}`, `L ${x + w - r + j()} ${y + j()}`, `Q ${x + w + j()} ${y + j()} ${x + w + j()} ${y + r + j()}`, `L ${x + w + j()} ${y + h / 2 + j()}`, `L ${x + w + j()} ${y + h - r + j()}`, `Q ${x + w + j()} ${y + h + j()} ${x + w - r + j()} ${y + h + j()}`, `L ${x + w / 2 + j()} ${y + h + j()}`, `L ${x + r + j()} ${y + h + j()}`, `Q ${x + j()} ${y + h + j()} ${x + j()} ${y + h - r + j()}`, `L ${x + j()} ${y + h / 2 + j()}`, `L ${x + j()} ${y + r + j()}`, `Q ${x + j()} ${y + j()} ${x + r + j()} ${y + j()}`, ].join(' '); }; const wobbleCircle = (cx: number, cy: number, r: number, seed: number, amp = 6) => { const rnd = mulberry32(seed); const n = 14; const pts: string[] = []; for (let i = 0; i <= n; i++) { const a = (i / n) * Math.PI * 2; const rr = r + (rnd() - 0.5) * amp * 2; pts.push(`${i === 0 ? 'M' : 'L'} ${cx + Math.cos(a) * rr} ${cy + Math.sin(a) * rr}`); } return pts.join(' ') + ' Z'; }; const Doodle: React.FC<{ boil: number }> = ({ boil }) => { const S = boil * 977; // 每次煮沸换一套抖动种子 const stroke = { fill: 'none' as const, stroke: INK, strokeLinecap: 'round' as const, strokeLinejoin: 'round' as const, }; return ( ); }; // ——— 骨架/内容消息行 ——— const NAMES = ['Ana', 'Ben', 'Kai', 'Mia']; const AVA = ['#5a5a58', '#7a7a78', '#4a4a48', '#8f8f8d']; const MSGS = [ 'Morning! Kicking off the rebrand today', 'Logo drafts are ready for review', 'Nice — shipping the deck this afternoon', 'Love it. Can we make it pink?', ]; const Row: React.FC<{ i: number; dev: number; wordAt: (w: number, n: number) => number }> = ({ i, dev, wordAt, }) => { const words = MSGS[i].split(' '); return (