function clampInt(n: number, min: number, max: number): number { if (!Number.isFinite(n)) return min; return Math.min(max, Math.max(min, Math.floor(n))); } function hashStringToInt32(input: string): number { // 简单可复现 hash:用于把 seed 映射为偏移量(不用于安全场景) let h = 0; for (let i = 0; i < input.length; i += 1) { // eslint-disable-next-line no-bitwise h = (h * 31 + input.charCodeAt(i)) | 0; } return h; } /** * 生成 t ∈ [0,1],用于 Color(t)=lerp(top,bottom,t) * * 说明: * - Home 没有 scroll,用“切换文案 step_index”模拟连续流动 * - 采用往返波形,避免从 1 回到 0 的突跳 */ export function computeTFromStep(args: { stepIndex: number; segments?: number; // N,默认 12 seed?: string; // 允许用 seed 做初始相位偏移(同一次冷启动内稳定) }): number { const N = clampInt(args.segments ?? 12, 3, 60); const stepIndex = clampInt(args.stepIndex, 0, 1_000_000_000); const period = 2 * (N - 1); const seed = String(args.seed ?? ''); const offset = seed ? Math.abs(hashStringToInt32(seed)) % period : 0; const phase = (stepIndex + offset) % period; const up = phase <= (N - 1); const pos = up ? phase : period - phase; return pos / (N - 1); }