feat: expand campaign and refine puzzle visuals and controls

This commit is contained in:
cabinw
2026-09-09 11:02:45 +08:00
parent 9f8990b579
commit 70b021d4cc
66 changed files with 41108 additions and 10553 deletions
+21
View File
@@ -0,0 +1,21 @@
import { MOTION } from './design-tokens.js';
import { routePoints } from './geometry.js';
// Timing follows actual path length, so a piece beside an exit has no fixed wait.
export function exitTiming(route) {
const points=routePoints({...route,end:null},{size:1,ox:0,oy:0});
const distance=points.slice(1).reduce((sum,p,i)=>sum+Math.hypot(p.x-points[i].x,p.y-points[i].y),0);
const travelMs=Math.max(MOTION.exitMinTravelMs,Math.min(MOTION.exitMaxTravelMs,distance*MOTION.exitMsPerUnit));
const shortDuration=travelMs+MOTION.exitEffectMs;
const longDuration=Math.min(MOTION.exitLongMaxMs,MOTION.exitLongBaseMs+route.steps.length*MOTION.exitLongCellMs);
// Blend into the original long-route pacing without slowing edge departures.
const blend=smoothMotion((route.steps.length-MOTION.exitLongStartCells)/(MOTION.exitLongFullCells-MOTION.exitLongStartCells));
const duration=shortDuration+(longDuration-shortDuration)*blend;
return {duration,travelFraction:(duration-MOTION.exitEffectMs)/duration};
}
// Zero endpoint velocity: a soft launch and a settled finish, without overshoot.
export function smoothMotion(value) {
const t=Math.max(0,Math.min(1,value));
return t*t*(3-2*t);
}