// ===== demos/effects/fui-hud-moves/LineUnfoldPanel.tsx ===== // line-unfold-panel —— 一线展面(Jarvis/FUI 母题) // 暗底。入场两拍:3px 细线从中点向两侧极快抽出(5f)→ 定宽后纵向 // 撑开成 Card 面板(9f,out 缓动)→ 内容延迟淡入。 // 静置展示后反向退场:压扁成线(7f)→ 线缩成点 → 熄灭,像老 CRT 关机。 // f0–12 空场静置;入场 f12–34;持面板至 f78;退场 f78–98;末静止 ≥42f(140f)。 import React from 'react'; import { useCurrentFrame, interpolate, Easing } from 'remotion'; import { G, Card } from '../../_fixtures/Fixtures'; const PANEL_W = 760; const PANEL_H = 460; const CX = 960; const CY = 540; // —— 入场时间表 —— const T0 = 12; // 点亮起点 const LINE_END = T0 + 5; // 线抽出完成 f17 const UNFOLD_END = LINE_END + 9; // 面板撑开完成 f26 const CONTENT_END = UNFOLD_END + 8; // 内容淡入完成 f34 // —— 退场时间表 —— const OUT0 = 78; // 开始压扁 const COLLAPSE_END = OUT0 + 7; // 压成线 f85 const SHRINK_END = COLLAPSE_END + 6; // 线缩成点 f91 const OFF = SHRINK_END + 4; // 点熄灭 f95 const clamp = { extrapolateLeft: 'clamp' as const, extrapolateRight: 'clamp' as const }; export const LineUnfoldPanel: React.FC = () => { const frame = useCurrentFrame(); // 入场:scaleX(线抽出)快进快停,入场后保持 1 const inSX = interpolate(frame, [T0, LINE_END], [0.004, 1], { easing: Easing.out(Easing.poly(4)), ...clamp, }); // 入场:scaleY(纵向撑开),线阶段压在 3px const inSY = interpolate(frame, [LINE_END, UNFOLD_END], [3 / PANEL_H, 1], { easing: Easing.out(Easing.cubic), ...clamp, }); // 内容淡入(面板撑开过半才开始) const contentOp = interpolate(frame, [UNFOLD_END - 3, CONTENT_END], [0, 1], { easing: Easing.out(Easing.quad), ...clamp, }); // 退场:先压 Y 回线,再缩 X 回点 const outSY = interpolate(frame, [OUT0, COLLAPSE_END], [1, 3 / PANEL_H], { easing: Easing.in(Easing.cubic), ...clamp, }); const outSX = interpolate(frame, [COLLAPSE_END, SHRINK_END], [1, 0.004], { easing: Easing.in(Easing.poly(4)), ...clamp, }); // 内容在压扁前先撤 const contentOutOp = interpolate(frame, [OUT0 - 4, OUT0 + 2], [1, 0], clamp); const sx = frame < OUT0 ? inSX : outSX; const sy = frame < OUT0 ? inSY : outSY; // 末点熄灭:opacity 快落。f >= OFF 后整个元素条件卸载 → 真静止 const dotOp = interpolate(frame, [SHRINK_END, OFF], [1, 0], { easing: Easing.in(Easing.quad), ...clamp, }); const alive = frame >= T0 && frame < OFF; // 面板阶段(sy 足够大)显示卡片内容;线/点阶段显示发光条 const isPanel = sy > 0.15; return (
LINE UNFOLD PANEL
{alive && (
{isPanel ? ( <> {/* 内容层单独控 opacity:盖一层暗板模拟"内容未亮" */}
) : ( // 线/点阶段:白色发光条填满整个盒(被 scale 压成线)
)}
)}
); }; // ===== demos/effects/fui-hud-moves/ReticleLockOn.tsx ===== // reticle-lock-on —— 准星咬合(钢铁侠 HUD / 安德的游戏) // FakeDashboard 静置。四个 L 形角标组成的取景框从画外飞入(大框), // 超调回弹后收缩贴紧目标卡片四角"咔"地咬合定格;咬合帧卡片微亮 + // 旁弹一行小标签。画面不冻结,是运动中的捕获。 // f0–14 面板静置;f14–24 飞入;f24–46 收缩(含过冲回弹);f46 咬合; // 标签 f46–56 弹出;f56 后真静止 ≥84f(140f 总长)。 import React from 'react'; import { useCurrentFrame, interpolate, Easing } from 'remotion'; import { G, FakeDashboard } from '../../_fixtures/Fixtures'; // 目标:variant A 网格第 2 张卡(第一行中间)。 // 布局推导:侧栏 220 + padding 36,网格 3 列 gap 28, // 内容区宽 1700-72=1628 → 卡宽 (1628-56)/3 ≈ 524,卡高 (1080-72-72-28)/2 ≈ 454... // 实测近似:目标框取第一行中间卡的外接矩形(含少量呼吸边距)。 const CARD_X = 220 + 36 + 524 + 28; // ≈ 808 const CARD_Y = 72 + 36; // 108 const CARD_W = 524; const CARD_H = 425; const PAD = 14; // 咬合后角标与卡的呼吸距 const TCX = CARD_X + CARD_W / 2; const TCY = CARD_Y + CARD_H / 2; const FLY_IN = 14; // 飞入开始 const FLY_END = 24; // 飞入结束(大框就位) const LOCK_END = 46; // 咬合帧 const LABEL_END = 56; const ARM = 56; // L 臂长 const THICK = 10; // L 粗 const clamp = { extrapolateLeft: 'clamp' as const, extrapolateRight: 'clamp' as const }; export const ReticleLockOn: React.FC = () => { const frame = useCurrentFrame(); // 飞入:整框从右下画外冲进来,同时是个放大 2.2× 的大框 const flyT = interpolate(frame, [FLY_IN, FLY_END], [0, 1], { easing: Easing.out(Easing.cubic), ...clamp, }); const flyDX = (1 - flyT) * 1100; const flyDY = (1 - flyT) * 620; // 收缩咬合:半宽/半高从 2.2× 收到贴紧,带超调(收过头 6% 再弹回) const shrink = interpolate( frame, [FLY_END, LOCK_END - 8, LOCK_END - 3, LOCK_END], [2.2, 1.06, 0.94, 1], { easing: Easing.inOut(Easing.cubic), ...clamp }, ); const hw = (CARD_W / 2 + PAD) * shrink; const hh = (CARD_H / 2 + PAD) * shrink; // 咬合帧:卡片微亮(一层白色 overlay 快闪后停在 0.28) const glow = interpolate(frame, [LOCK_END, LOCK_END + 3, LOCK_END + 10], [0, 0.55, 0.28], clamp); // 标签:咬合后从角标右上弹出(scaleX 展开 + 淡入) const labelT = interpolate(frame, [LOCK_END + 2, LABEL_END], [0, 1], { easing: Easing.out(Easing.back(1.8)), ...clamp, }); const showReticle = frame >= FLY_IN; const locked = frame >= LOCK_END; // 四个 L 角:位置 = 中心 ± (hw, hh),各自镜像 const corners = [ { x: TCX - hw, y: TCY - hh, sx: 1, sy: 1 }, { x: TCX + hw, y: TCY - hh, sx: -1, sy: 1 }, { x: TCX - hw, y: TCY + hh, sx: 1, sy: -1 }, { x: TCX + hw, y: TCY + hh, sx: -1, sy: -1 }, ]; return (
{/* 咬合微亮层:条件渲染,锁定前不存在 */} {locked && (
)} {showReticle && (
{corners.map((c, i) => (
{/* L 形角标:横臂 + 竖臂 */}
))} {/* 小标签:右上角标外侧弹出 */} {frame >= LOCK_END + 2 && (
TARGET · CARD_02
)}
)}
); };