// flyline-arc —— flyline connection
// FakeDashboard A slightly dimmed (brightness 0.92); a bezier arc "shoots" from the top-left card to the bottom-right card (22f, out-cubic growth),
// with a bright head leading and a bright-head/dark-tail trail; on the arrival frame the target card gets a 3px+ ink outline pulse + a darkening pulse (white background forbids brightening).
// Then a second line relays to the top-middle card. Ending in true stillness: all animation finishes before f86, then every element freezes.
import React, { useId } from 'react';
import { useCurrentFrame, interpolate, Easing } from 'remotion';
import { FakeDashboard } from '../../_fixtures/Fixtures';
type Pt = { x: number; y: number };
// Handwritten cubic bezier sampling
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; // sample segment count
// Card geometry (measured grid of FakeDashboard variant A)
const CARD_LT = { x: 256, y: 108, w: 524, h: 454, cx: 518, cy: 335 }; // top-left
const CARD_RB = { x: 1360, y: 590, w: 524, h: 454, cx: 1622, cy: 817 }; // bottom-right
const CARD_TM = { x: 808, y: 108, w: 524, h: 454, cx: 1070, cy: 335 }; // top-middle
// One flyline: dark underlay + white line segments with bright head / dark tail + leading glow head (conditionally mounted)
const Flyline: React.FC<{
frame: number;
start: number; // growth start frame
haloId: string; // radial gradient ID for the glow head (generated per instance by the parent)
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;
// Tail brightness evens out linearly within 10f after arrival → then fully static
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(' ');
// Bright head / dark tail: per-segment opacity by "distance from head"; settle evens it out to 1 after arrival
const segs = [];
for (let i = 0; i < pts.length - 1; i++) {
const tSeg = Math.min(i / N, e) / Math.max(e, 0.001); // 0 tail → 1 head
const grad = 0.4 + 0.6 * tSeg * tSeg;
const op = grad + (1 - grad) * settle;
segs.push(