// dashboard-glow-highlight-pill — Glow Highlight Pill 金色胶囊指引(motion-lab 定稿转原生 Remotion) // 金字 "Ready." 悬于黑场,数据仪表盘(订单簿+K线+买卖面板,内容为占位数据)自底带透视升入后 // 保持缓慢 rotateX/rotateY 3D 漂移;金色光斑从右侧面板巡游至底部中央拉成胶囊,背景虚化压暗 // (仪表盘继续漂),辉光描边从底边逆时针描出 Focus Mode 弹窗轮廓,内容淡入后描边收敛为金色细框。 // 设计坐标 480×270(DesignStage raster="zoom" 等比放大),参数表数值以此坐标系标定。 // 验收:SSIM mean=0.9583/min=0.9510(BORDERLINE,对照条逐帧肉眼一致)。余差为近黑底 // 的 x264 暗场量化:把首帧自渲 PNG 过同参数 x264 后与原样片比对得 0.9977——内容已 // 像素级一致,直接比对的 -0.04 全部来自样片解码端的暗部压缩(2-30 电平带被压到 ~0.8x)。 import React from 'react'; import { DesignStage, E, lerp, rand, seg, useT } from '../../_fixtures/Motion'; export const DASHBOARD_GLOW_HIGHLIGHT_PILL_DURATION = 60; // 2000ms @30fps // ---- 订单簿行(上红下绿,渐变量条;种子与 effect.js 逐一对应) ---- const OB_ROWS = Array.from({ length: 7 }, (_, i) => ({ wRed: (30 + rand(i * 3 + 1) * 65).toFixed(0), wGreen: (30 + rand(i * 7 + 4) * 65).toFixed(0), priceRed: `1${(155.4 - i * 0.12).toFixed(2)}`, priceGreen: `1${(155.0 - i * 0.12).toFixed(2)}`, amtRed: (rand(i * 11) * 9 + 0.4).toFixed(3), amtGreen: (rand(i * 13 + 6) * 9 + 0.4).toFixed(3), })); // ---- K 线(确定性随机游走,dv 负偏置 → 整体上行;SVG y 轴向下) ---- const CANDLES: Array<{ x: number; hi: string; lo: string; yTop: string; h: string; col: string; volY: string }> = (() => { const out = []; let px = 104; for (let i = 0; i < 36; i++) { const dv = (rand(i * 2.7 + 9) - 0.6) * 13; const o = px; const c = px + dv; px = c; const hi = Math.min(o, c) - rand(i * 5.1) * 5; const lo = Math.max(o, c) + rand(i * 3.3) * 5; const up = c < o; out.push({ x: 6 + i * 7.6, hi: hi.toFixed(1), lo: lo.toFixed(1), yTop: Math.min(o, c).toFixed(1), h: Math.max(1.5, Math.abs(dv)).toFixed(1), col: up ? '#2bbf8a' : '#d6455a', volY: (128 - rand(i * 1.9 + 3) * 16).toFixed(1), }); } return out; })(); // ---- Focus Mode 弹窗几何(原片逐帧实测,注释见 effect.js) ---- const MW = 24.5, MH = 40, MCX = 48.4, MCY = 47; // % of root const MRAD = 5; // 弹窗圆角(px),描边共用 const RW = 480, RH = 270; const BW = (RW * MW) / 100, BH = (RH * MH) / 100; // 弹窗盒子像素尺寸 const BO = 0.5; // 描边压在 1px 边框中线上 // 起笔点 = 胶囊停住的位置(BLOB 末帧 x 44.1%),换算成盒内坐标 const SX = ((44.1 - (MCX - MW / 2)) / MW) * BW; // 描边路径:底边中点起、逆时针一整圈(与 effect.js 的 D 字符串逐字节一致) const TRACE_D = `M${SX.toFixed(1)} ${(BH - BO).toFixed(1)}` + ` L${(MRAD + BO).toFixed(1)} ${(BH - BO).toFixed(1)}` + ` A${MRAD} ${MRAD} 0 0 1 ${BO} ${(BH - MRAD - BO).toFixed(1)}` + ` L${BO} ${(MRAD + BO).toFixed(1)}` + ` A${MRAD} ${MRAD} 0 0 1 ${(MRAD + BO).toFixed(1)} ${BO}` + ` L${(BW - MRAD - BO).toFixed(1)} ${BO}` + ` A${MRAD} ${MRAD} 0 0 1 ${(BW - BO).toFixed(1)} ${(MRAD + BO).toFixed(1)}` + ` L${(BW - BO).toFixed(1)} ${(BH - MRAD - BO).toFixed(1)}` + ` A${MRAD} ${MRAD} 0 0 1 ${(BW - MRAD - BO).toFixed(1)} ${(BH - BO).toFixed(1)}` + ` L${SX.toFixed(1)} ${(BH - BO).toFixed(1)}`; // pathLength 归一化:dashoffset 直接按百分比走,免去 getTotalLength 运行时测量 const P_L = 100; // 关键帧插值(segments 间 inOutQuad;lerp 签名是 lerp(t, a, b)) const KF = (rows: number[][], t: number): number[] => { if (t <= rows[0][0]) return rows[0].slice(1); for (let i = 1; i < rows.length; i++) { if (t <= rows[i][0]) { const a = rows[i - 1], b = rows[i]; const p = E.inOutQuad((t - a[0]) / (b[0] - a[0])); return a.slice(1).map((_, k) => lerp(p, a[k + 1], b[k + 1])); } } return rows[rows.length - 1].slice(1); }; // ---- 逐帧校准的姿态关键帧:[t, rotateX, rotateY, translateX%, translateY%, scale] // 0.30 只有顶栏切片露在画面底部 → 0.365 基本满幅摆正 → 之后缓慢 3D 漂移; // 0.62-0.72 有一次明显镜头后拉(87% → 61.5% 宽),把画面让给弹窗。 const POSE = [ [0.3, 34.0, -13.0, -7.0, 47.0, 1.62], [0.322, 24.0, -11.0, -5.5, 30.0, 1.44], [0.345, 13.0, -8.0, -3.5, 14.0, 1.22], [0.365, 5.5, -5.0, -1.6, 4.0, 1.055], [0.42, 2.6, -3.0, -0.6, 0.4, 1.005], [0.5, 2.2, -2.0, -0.3, 0.0, 0.966], [0.62, 1.8, -0.6, 0.0, -0.5, 0.943], [0.67, 1.6, 0.6, -0.4, -0.8, 0.852], [0.72, 1.4, 1.4, -0.8, -1.1, 0.707], [1.0, 1.2, 2.6, -0.9, -1.3, 0.7], ]; // 光斑巡游关键帧 [t, x%, y%, w, h]:右侧面板圆斑 → 左下移动拉长 → 停在弹窗底边偏左(=描边起笔点) const BLOB = [ [0.4, 80.5, 42.0, 22, 22], [0.44, 79.7, 46.5, 27, 30], [0.5, 77.7, 56.3, 29, 36], [0.56, 71.1, 63.9, 56, 25], [0.61, 57.6, 65.2, 82, 21], [0.65, 44.1, 67.4, 96, 16], ]; const FS = (n: number): React.CSSProperties => ({ fontSize: n }); const rowBetween: React.CSSProperties = { display: 'flex', justifyContent: 'space-between' }; // 订单簿一行(红/绿共用结构,量条右对齐渐变) const ObRow: React.FC<{ w: string; price: string; amt: string; red?: boolean }> = ({ w, price, amt, red }) => (