// word-relay-filmstrip v3 —— 批次 12 单点微调:右侧大词块(Computer+动词) // 的垂直中心与左列当前页面卡的垂直中点(y=540)对齐(top 462→402)。 // 其余沿用 v2:对照用户截图重做(perplexity-promo01,7 张): // ① 左列页面卡黑白相间、每张等高(940x530,间距 105); // ② 左列平时静止,只在右侧切词的窗口内滚动一格(滚动与切词同步); // ③ 尺寸/字号/位置按截图量取:卡 x=106 宽 940,词右对齐至 x≈1710, // Didot 系衬线 116px,"Computer" 固定第一行,动词第二行原位灰化淡出换词。 import React from 'react'; import { AbsoluteFill, interpolate, useCurrentFrame } 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 CARD_W = 940; const CARD_H = 530; const GAP = 105; const STEP = CARD_H + GAP; // —— 页面卡集合:黑白相间(奇偶强制交替),内容各异 —— const DarkArticle: React.FC<{ seed: number }> = ({ seed }) => { const rand = mulberry32(seed); return (
{[0, 1, 2, 3].map((i) =>
)}
{Array.from({ length: 6 }).map((_, i) => (
))}
{Array.from({ length: 4 }).map((_, i) => (
))}
); }; const LightMedal: React.FC<{ seed: number }> = ({ seed }) => { const rand = mulberry32(seed); return (
{[64, 96, 78, 110, 70, 88, 92].map((w, i) => (
))}
{[0, 1, 2].map((c) => (
))}
); }; const DarkStats: React.FC<{ seed: number }> = ({ seed }) => { const rand = mulberry32(seed); return (
{[ { n: 92, accent: false }, { n: 74, accent: false }, { n: 58, accent: false }, { n: 118, accent: true }, ].map((row, i) => (
))}
{[0, 1, 2].map((i) => (
))}
); }; const LightTable: React.FC<{ seed: number }> = ({ seed }) => { const rand = mulberry32(seed); return (
{Array.from({ length: 7 }).map((_, i) => (
))}
); }; const DarkMri: React.FC = () => (
{Array.from({ length: 5 }).map((_, i) => (
))}
{[[120, 100], [200, 150], [96, 210]].map(([x, y], i) => (
))}
{Array.from({ length: 8 }).map((_, i) => (
))}
); const LightPortfolio: React.FC = () => (
{[420, 470, 300].map((w, i) => (
))}
{[0, 1, 2, 3].map((i) => (
))}
); // 黑白相间的固定顺序(奇偶交替) const CARDS: React.FC<{ seed: number }>[] = [ ({ seed }) => , ({ seed }) => , () => , ({ seed }) => , ({ seed }) => , () => , ]; const WORDS = ['researches', 'builds', 'codes']; // 切词窗口:第一个词入场 f14–30;换词 f62–78、f108–124 const SWITCHES = [14, 62, 108]; const SW_DUR = 16; const SERIF = '"Didot", "Bodoni 72", "Playfair Display", Georgia, serif'; export const WordRelayFilmstrip: React.FC = () => { const frame = useCurrentFrame(); // —— 滚动步进:平时静止,仅在切词窗口内滚一格(ease-in-out)—— let stepF = 0; SWITCHES.forEach((s) => { const p = interpolate(frame, [s, s + SW_DUR], [0, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', }); // easeInOutCubic stepF += p < 0.5 ? 4 * p * p * p : 1 - Math.pow(-2 * p + 2, 3) / 2; }); const scroll = stepF * STEP; // 卡片布局:中心卡顶 y=275,向两侧铺开;黑白相间 const total = CARDS.length * STEP; const cards: React.ReactNode[] = []; for (let rep = -1; rep < 2; rep++) { CARDS.forEach((C, i) => { const y = 275 + i * STEP + rep * total - scroll; if (y > 1200 || y < -CARD_H - 120) return; cards.push(
, ); }); } // —— 右侧词接力:原位交叉淡(旧词灰化淡出,新词淡入,不位移)—— const wordStyle: React.CSSProperties = { fontFamily: SERIF, fontWeight: 400, fontSize: 116, lineHeight: 1.18, letterSpacing: '0.002em', textAlign: 'right', whiteSpace: 'nowrap', }; // 每个词的生命周期:入场淡入(黑)→ 常驻黑 → 下个切点前 14 帧灰化 //(截图 4/6 捕捉到的就是"旧词已灰、新词未现"的状态)→ 切点起淡出。 const smooth = (x: number) => x * x * (3 - 2 * x); const wordNodes = WORDS.map((w, i) => { const sIn = SWITCHES[i]; const sOut = i + 1 < SWITCHES.length ? SWITCHES[i + 1] : null; // 新词后半窗口淡入(避免与旧词叠影) const pIn = interpolate(frame, [sIn + 7, sIn + SW_DUR], [0, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', }); if (i === 0 ? frame < sIn : pIn <= 0) return null; const pInEff = i === 0 ? interpolate(frame, [sIn, sIn + 12], [0, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', }) : pIn; // 灰化:切点前 14 帧开始,切点时已全灰 const grey = sOut ? interpolate(frame, [sOut - 14, sOut - 2], [0, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', }) : 0; // 淡出:切点起前半窗口内出完 const pOut = sOut ? interpolate(frame, [sOut, sOut + 8], [1, 0], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', }) : 1; const op = smooth(pInEff) * smooth(pOut); if (op <= 0) return null; const mix = smooth(grey); const ch = Math.round(0x19 + (0x9d - 0x19) * mix); return (
{w}
); }); return (
{cards}
{/* 右侧词组:Computer 固定第一行,动词第二行原位换词。 v3:块总高≈137(Computer 行)+140(词行容器)=277,垂直中心须对齐 当前页面卡中点 y=540(卡顶 275 + 卡高 530/2)→ top=540-277/2≈402 */}
Computer
{wordNodes}
); };