// ===== demos/camera/steep-tilt-glide/SteepTiltGlide.tsx ===== // steep-tilt-glide v5(批次 14 #2)。用户意见(逐字): // "角度不对,你看看原片这个镜头的角度;以及镜头是固定的,但是页面本身 // 往其3d空间的横面方向移动的效果" // ——两点落实:①角度对照原片密帧校准(右缘近、左缘远的陡峭 rotateY, // 顶缘向右上扬、透视灭点在左中下);②运动模型改"物动镜不动": // perspective / perspectiveOrigin / rotateY / rotateZ 全程一个常数不动, // 动的只有页面自身沿其 3D 平面横向(局部 X 轴)的滑移 translateX。 // v4 的悬空贴落(FloatWrap 同形软影)保留。 import React, { useId } from 'react'; import { AbsoluteFill, interpolate, useCurrentFrame, Easing } from 'remotion'; const FONT = 'Helvetica, Arial, sans-serif'; const INK = '#2f2f36'; const easeFall = Easing.bezier(0.5, 0.05, 0.6, 1); // 加速贴落、末端软着陆 /* 悬浮 + 同形软影(GrazeFaceTour FloatWrap 模式) */ const FloatWrap: React.FC<{ h: number; children: React.ReactNode }> = ({ h, children }) => (
{h > 2 && (
{children}
)}
{children}
); /* land=贴合完成时刻(0..1),之前从 H 高度加速贴落 */ const liftOf = (t: number, land: number, H = 230) => { const FALL = 0.32; const p = Math.min(1, Math.max(0, (t - (land - FALL)) / FALL)); return (1 - easeFall(p)) * H; }; /* ClickUp 彩色小 logo(双 V 叠形近似) */ const CULogo: React.FC<{ size: number }> = ({ size }) => { // 渐变 ID 按实例生成,多实例同场不串引(useId 的 «:» 在 url() 里非法,需清洗) const uid = useId().replace(/[^a-zA-Z0-9]/g, ''); return ( ); }; /* Dropbox 蓝四菱形 glyph(原片 f45/f60 主区圆角灰砖里的图标) */ const DropboxGlyph: React.FC<{ size: number }> = ({ size }) => ( {[[50, 8, 27, 22], [50, 8, 73, 22], [50, 36, 27, 50], [50, 36, 73, 50]].map(() => null)} ); /* 页面:宽 6200 —— 左段=ClickUp3.0 顶栏+侧栏(用户截图三张全在此段), 右段=原片后程滑入的 Dropbox 砖、大 W 字、Product Management 列表 */ const PW = 6200; const PH = 2400; const Panel: React.FC<{ shade: number; t?: number }> = ({ shade, t = 1 }) => (
{/* 顶部 tab 条(贯穿整页;文字悬空贴落) */}
{[[4, 4], [23, 4], [4, 23], [23, 23]].map(([x, y], i) => ( ))}
Product analytics
ClickUp 3.0
{/* 顶栏右延的淡 tab(对齐 clickup04 顶栏 Widget brainstorm / Design system) */} {[['Widget brainstorm', 3050], ['Design system', 3900], ['Design', 4650]].map(([tb, x]) => (
{tb}
))}
{/* ClickUp logo 行 */}
ClickUp
{/* 主区圆角灰砖:Dropbox 图标 + 下拉 chevron(原片 f45–f60) */}
{/* Home 宽药丸(紫边细线横贯面板) */}
Home
{/* Inbox 行 */}
Inbox
{/* 更多侧栏行 */} {['Docs', 'Dashboards'].map((tb, i) => (
{tb}
))} {/* 主区轮播左箭头(原片 f60 中景的 "<") */}
{/* 大 W 字形(原片中程右上滑入的大字残段) */}
W
{/* 主区白色圆角卡群(右下淡白砖) */} {[[2620, 620, 640, 380], [2740, 1360, 720, 420], [2620, 1900, 560, 330]].map(([x, y, w, h], i) => (
))} {/* —— 右段:Product Management 列表(原片尾程满幅白页) —— */}
Product Management
{/* List / Board tabs */}
{[9, 20, 31].map((y) => ( ))}
List
Board
11
{/* IN PROGRESS chip */}
IN PROGRESS
TASK NAME
{/* 任务行 */} {[['New Feature Launch', '#3a3a42'], ['Roadmap Q3', '#55555e'], ['User Testing', '#83838d'], ['Bug Triage', '#b3b3bc']].map(([name, col], i) => (
{name}
))}
{/* 亮度:面板内黑罩(随进度揭亮) */}
{/* 左缘额外阴影(起始更暗,随亮度一同退去) */}
); /* 固定镜头 + 页面沿自身平面横向滑移。 * 镜头常数(对照原片密帧校准):perspective 1150、origin 24% 62%、 * rotateY(-38°)(右缘近、左缘远,顶缘向右上扬)+ rotateZ(-2°)。 * lx = 页面局部 X 平移(页面自己的横面方向),全程唯一动的量 */ // v7(批次 16):用户意见"改成60度吧"——rotY -53 → -60(精确定值) const CAM = { persp: 1100, origin: '30% 58%', rotY: -60, rotZ: -2, left: '40%', top: '15%', scale: 0.62 }; const PanelLayer: React.FC<{ lx: number; shade: number; opacity: number; t: number }> = ({ lx, shade, opacity, t }) => (
{/* 页面自身在其 3D 平面内横移(局部 X 轴) */}
); export const SteepTiltGlide: React.FC = () => { const frame = useCurrentFrame(); // 页面滑移(物动镜不动):局部 X 从 +140 → -2680,起步柔和后近匀速, // 全程不安定停死(bezier 尾段斜率不归零) const glide = Easing.bezier(0.3, 0.12, 0.72, 0.9); const lxAt = (f: number) => { const p = interpolate(f, [0, 120], [0, 1], { easing: glide, extrapolateLeft: 'clamp', extrapolateRight: 'clamp' }); return interpolate(p, [0, 1], [60, -4100]); }; const lx = lxAt(frame); // 面板黑罩:起始暗 → 约 1.4s 全亮(对齐原片节奏) const shade = interpolate(frame, [0, 18, 44], [0.7, 0.4, 0], { extrapolateRight: 'clamp' }); // 重影强度 ∝ 滑移速度(拖尾=过去时刻的局部位置,同一固定镜头下重投影) const speed = Math.abs(lxAt(frame - 1) - lxAt(frame + 1)) / 2; const g1 = Math.min(0.42, speed * 0.03); const g2 = Math.min(0.22, speed * 0.016); // 文字悬空贴落进程(v4 保留) const drop = interpolate(frame, [4, 100], [0, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' }); // 紫晕随亮度浮现 const glow = interpolate(frame, [14, 70], [0.15, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' }); return ( {/* 左侧紫晕 */} {/* 重影(先画,垫在本体后面;位置=过去时刻的页面局部位移) */} {g2 > 0.02 && } {g1 > 0.02 && } {/* 本体 */} {/* 暗角 */} ); };