// neon-frame-forerun v3 — additions on top of v2 per user feedback (five clickup04 screens):
// each component/text inside the frame initially hovers above the page (lifted in 3D), casting a soft
// same-shaped shadow on the panel while hovering, and they snap down in sync with the page
// lighting-up sequence (FloatWrap pattern, matching screenshot ③: tab/components hovering with
// offset shadows → ④ all snapped down). Already in v2: the strongly-perspective right-angle frame
// runs from the midpoint of its left edge in both directions, the panel brightens in place, and the
// background neon-tube frame cluster glows in the middle then dies out at the end.
import React, { useId } from 'react';
import { AbsoluteFill, useCurrentFrame, interpolate, Easing } from 'remotion';
const easeFall = Easing.bezier(0.5, 0.05, 0.6, 1); // accelerating fall, soft landing at the end
/* hover + same-shaped soft shadow: h=hover height(px). The body lifts up-left, leaving a blurred, darkened
* same-shaped shadow in place; when h→0 they coincide and the shadow disappears (see batch 11 GrazeFaceTour FloatWrap) */
const FloatWrap: React.FC<{ h: number; children: React.ReactNode }> = ({ h, children }) => (
{h > 1 && (
{children}
)}
{children}
);
/* land=snap-down completion moment (0..1), before that it accelerates down from height H */
const liftOf = (t: number, land: number, H: number) => {
const FALL = 0.3;
const p = Math.min(1, Math.max(0, (t - (land - FALL)) / FALL));
return (1 - easeFall(p)) * H;
};
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 = '#3c3c3a';
const mid = '#9a9a98';
const line = '#e3e3e1';
const PW = 1330;
const PH = 900;
const PL = 1000; // normalized path length
// right-angle rect path: starts from the midpoint of the left edge (matches screenshot ① left edge lighting first)
const FRAME_D = `M 0 ${PH / 2} L 0 0 L ${PW} 0 L ${PW} ${PH} L 0 ${PH} Z`;
const Chip: React.FC<{ w: number }> = ({ w }) => (
);
/* t = snap-down progress (0..1). Components snap down from the air in page-display order
* (sidebar top to bottom, main area top to bottom), staggered, casting same-shaped soft shadows
* while hovering (matches screenshot ③ ghost → ④⑤ snapped down) */
const GrayHome: React.FC<{ t?: number }> = ({ t = 1 }) => {
const L = (land: number, H = 72) => liftOf(t, land, H * 1.7);
return (
{[76, 56, 96, 110, 66, 60].map((w, i) => (
))}
{[104, 126, 96, 118, 88].map((w, i) => (
))}
{[0, 1, 2, 3, 4, 5, 6].map((i) => (
))}
{[54, 84, 50, 82].map((w, i) => (
))}
{[0, 1, 2, 3].map((i) => (
{[0, 1, 2].map((k) => )}
))}
);
};
// background neon-tube frames: large, bright, scattered with perspective (screenshots ③④)
type BgFrame = { x: number; y: number; w: number; h: number; hue: string; phase: number; period: number; skew: number };
const rng = mulberry32(20260718);
const HUES = ['#b06af0', '#e879c9', '#f0a35c', '#6a7df0', '#e0679a', '#8a5cf0', '#c06af0'];
const BG_FRAMES: BgFrame[] = Array.from({ length: 18 }).map(() => ({
x: rng() * 2000 - 120, y: rng() * 1100 - 60,
w: 160 + rng() * 480, h: 70 + rng() * 220,
hue: HUES[Math.floor(rng() * HUES.length)],
phase: rng() * 90, period: 55 + rng() * 70,
skew: -14 + rng() * 10,
}));
export const NeonFrameForerun: React.FC = () => {
const frame = useCurrentFrame();
// filter/gradient IDs generated per instance so multiple instances in one scene don't cross-reference (useId's «:» is invalid in url(), so it's sanitized)
const uid = useId().replace(/[^a-zA-Z0-9]/g, '');
// main frame trace: runs both ways from the midpoint of the left edge, taking shape over 26 frames (screenshot ①→②)
const trace = interpolate(frame, [2, 28], [0, 1], {
extrapolateLeft: 'clamp', extrapolateRight: 'clamp',
easing: Easing.bezier(0.3, 0.1, 0.3, 1),
});
// panel develops in place: after the frame forms, content goes from near-black to dark gray (screenshot ②), then to full bright (screenshot ③)
const lit = interpolate(frame, [24, 46, 78], [0.0, 0.3, 1], {
extrapolateLeft: 'clamp', extrapolateRight: 'clamp',
easing: Easing.bezier(0.35, 0, 0.3, 1),
});
// main frame's standalone stroke visibility: after the panel lights up it merges into the panel rim glow (after screenshot ③ the thin line fades out, the blurry glow remains)
const frameLine = interpolate(frame, [50, 90], [1, 0], {
extrapolateLeft: 'clamp', extrapolateRight: 'clamp',
});
const rimGlow = interpolate(frame, [28, 60, 108, 132], [0.9, 1, 0.75, 0.5], {
extrapolateLeft: 'clamp', extrapolateRight: 'clamp',
});
// background: fully lit in the middle (screenshots ③④) → dies out at the end (screenshot ⑤)
const bgLit = interpolate(frame, [8, 44, 96, 128], [0.15, 1, 0.85, 0.06], {
extrapolateLeft: 'clamp', extrapolateRight: 'clamp',
});
// strong perspective → relaxed: steep angles while the frame flies in, settling after the panel lights up (screenshot ① trapezoid → ③④ gentle tilt)
const settle = interpolate(frame, [0, 70], [0, 1], {
extrapolateLeft: 'clamp', extrapolateRight: 'clamp',
easing: Easing.out(Easing.cubic),
});
const rotY = -26 + 15 * settle;
const rotX = 7 - 3 * settle;
const rotZ = -9 + 4.5 * settle;
const scale = 0.94 + 0.1 * settle;
const headP = trace * (PL / 2);
// component snap-down progress: advances in sync with the page lighting (lit 24→78), finishing slightly later —
// "snap-down completes in sync with the page's reveal"
const drop = interpolate(frame, [34, 98], [0, 1], {
extrapolateLeft: 'clamp', extrapolateRight: 'clamp',
});
return (
{/* background neon-tube frame cluster */}
{/* main body: strongly-perspective tilted frame + panel */}
{/* panel: brightens in place from dark (no movement or scaling) */}
{/* panel rim glow (takes over once the frame light merges in, strongest at the bottom edge — matches the pink light on the bottom/right edges of screenshots ③④) */}
{/* neon stroked frame: runs both ways from the midpoint of the left edge, right-angle corners */}