// flyline-arc —— 飞线连接
// FakeDashboard A 微暗化(brightness 0.92),左上卡→右下卡一条贝塞尔弧线
// "打"过去(22f, out-cubic 生长),亮点头部领跑、亮头暗尾拖尾;到达帧目标卡
// 3px+ ink 描边脉冲 + 加深脉冲(白底禁提亮)。随后第二条线接力打到上中卡。
// 收尾真静止:全部动画在 f86 前结束,之后所有元素冻结。
import React, { useId } from 'react';
import { useCurrentFrame, interpolate, Easing } from 'remotion';
import { FakeDashboard } from '../../_fixtures/Fixtures';
type Pt = { x: number; y: number };
// 手写 cubic bezier 采样
const bez = (p0: Pt, p1: Pt, p2: Pt, p3: Pt, t: number): Pt => {
const u = 1 - t;
return {
x: u * u * u * p0.x + 3 * u * u * t * p1.x + 3 * u * t * t * p2.x + t * t * t * p3.x,
y: u * u * u * p0.y + 3 * u * u * t * p1.y + 3 * u * t * t * p2.y + t * t * t * p3.y,
};
};
const N = 100; // 采样段数
// 卡片几何(FakeDashboard variant A 实测网格)
const CARD_LT = { x: 256, y: 108, w: 524, h: 454, cx: 518, cy: 335 }; // 左上
const CARD_RB = { x: 1360, y: 590, w: 524, h: 454, cx: 1622, cy: 817 }; // 右下
const CARD_TM = { x: 808, y: 108, w: 524, h: 454, cx: 1070, cy: 335 }; // 上中
// 一条飞线:深色衬底 + 亮头暗尾白线分段 + 光头领跑(条件挂载)
const Flyline: React.FC<{
frame: number;
start: number; // 生长起始帧
haloId: string; // 光头径向渐变 ID(父组件按实例生成)
p0: Pt; p1: Pt; p2: Pt; p3: Pt;
}> = ({ frame, start, haloId, p0, p1, p2, p3 }) => {
const DUR = 22;
if (frame < start) return null;
const e = interpolate(frame, [start, start + DUR], [0, 1], {
easing: Easing.out(Easing.cubic),
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
const growing = frame < start + DUR;
// 到达后 10f 内尾部亮度线性抹匀 → 之后完全静止
const settle = interpolate(frame, [start + DUR, start + DUR + 10], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
const pts: Pt[] = [];
const nDrawn = Math.max(2, Math.ceil(e * N) + 1);
for (let i = 0; i < nDrawn; i++) {
const t = Math.min((i / N), e);
pts.push(bez(p0, p1, p2, p3, t));
}
const head = bez(p0, p1, p2, p3, e);
pts[pts.length - 1] = head;
const underlay = pts.map((p) => `${p.x.toFixed(1)},${p.y.toFixed(1)}`).join(' ');
// 亮头暗尾:每段按"离头距离"给 opacity;到达后 settle 抹匀到 1
const segs = [];
for (let i = 0; i < pts.length - 1; i++) {
const tSeg = Math.min(i / N, e) / Math.max(e, 0.001); // 0 尾 → 1 头
const grad = 0.4 + 0.6 * tSeg * tSeg;
const op = grad + (1 - grad) * settle;
segs.push(