// ===== demos/opening/icon-field-colorize/IconFieldColorize.tsx =====
// icon-field-colorize —— 灰阶图标原野错峰浮现(错峰淡入+微弹),停一拍后
// 蓝色整场极快扫翻,随后橙/绿/红三道色波依次向下扫过,终态四色横带。
// 对标 bear-app.mp4 0–3s(密帧核实:非同帧硬翻,是极快多道波纹翻色)。
import React from 'react';
import { AbsoluteFill, interpolate, useCurrentFrame } from 'remotion';
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;
};
// 16 个简笔图标(viewBox 0 0 24 24,纯填充),求可辨认不求精致
const SHAPES: ((c: string) => React.ReactNode)[] = [
(c) => ,
(c) => ,
(c) => ,
(c) => ,
(c) => ,
(c) => ,
(c) => <>>,
(c) => ,
(c) => ,
(c) => ,
(c) => <>>,
(c) => ,
(c) => ,
(c) => ,
(c) => <>>,
(c) => ,
];
const COLS = 17, ROWS = 10, CELL = 110, ICON = 44;
const GRAYS = ['#9a9a9a', '#7c7c7c', '#b0b0b0', '#8b8b8b'];
// 四道色波:蓝覆盖全场,橙/绿/红依次覆盖更低的行带 → 终态四色横带
const WAVES = [
{ color: '#5b67e8', fromRow: 0, start: 78 },
{ color: '#e7a03c', fromRow: 3, start: 90 },
{ color: '#34a56f', fromRow: 6, start: 100 },
{ color: '#e2606b', fromRow: 9, start: 110 },
];
export const IconFieldColorize: React.FC = () => {
const frame = useCurrentFrame();
const rand = mulberry32(20260718);
const icons: React.ReactNode[] = [];
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
const r1 = rand(), r2 = rand(), r3 = rand();
// —— 错峰浮现:分 10 批,批内再抖动
const batch = Math.floor(r1 * 10);
const t0 = 4 + batch * 4 + r2 * 3;
const ap = interpolate(frame, [t0, t0 + 8], [0, 1], {
extrapolateLeft: 'clamp', extrapolateRight: 'clamp',
});
if (ap <= 0) continue;
// 微弹:0.55 → 1.07 → 1
const easeOut = 1 - Math.pow(1 - ap, 3);
const appearScale = ap < 1 ? 0.55 + easeOut * 0.52 - Math.max(0, ap - 0.72) * 0.25 : 1;
// —— 色波:每道波从起始行向下快扫(行 1.4 帧 + 列微倾 + 抖动)
const gray = GRAYS[Math.floor(r2 * GRAYS.length)];
let color = gray;
let flipPhase = 0; // 最近一次翻色的进行度,用于 pop
for (const w of WAVES) {
if (r < w.fromRow) continue;
const arrive = w.start + (r - w.fromRow) * 1.4 + c * 0.25 + r3 * 1.5;
const p = interpolate(frame, [arrive, arrive + 3], [0, 1], {
extrapolateLeft: 'clamp', extrapolateRight: 'clamp',
});
if (p > 0) { color = w.color; flipPhase = p; }
}
const flipPop = flipPhase > 0 && flipPhase < 1
? 1 + Math.sin(flipPhase * Math.PI) * 0.22 : 1;
const shape = SHAPES[Math.floor(r3 * SHAPES.length) % SHAPES.length];
const x = 48 + c * CELL + (r % 2) * 26;
const y = 26 + r * CELL;
icons.push(
,
);
}
}
return {icons};
};