[eric] windows: onboarding cursor eases via cursorStore-driven css transition (snaps while tracking, eases on moveTo) to match the mac spring

This commit is contained in:
Eric
2026-05-26 04:16:57 -07:00
parent 529d350cae
commit 9605454ebf
2 changed files with 11 additions and 8 deletions
@@ -67,10 +67,10 @@ const AgenticCursor = forwardRef<AgenticCursorHandle>((_props, ref) => {
const trackerRef = useRef<{ stop: () => void } | null>(null);
// Mirrored into cursorStore so popups follow without re-running through Framer's animation pipeline.
const writePos = (x: number, y: number, vis = true) => {
// Mirrored into cursorStore so popups follow without re-running through Framer's animation pipeline. `instant` controls the Windows CSS-transition fallback: true = snap (tracking), false = ease (moveTo/fadeOut). No-op on Mac.
const writePos = (x: number, y: number, vis = true, instant = true) => {
posRef.current = { x, y };
cursorStore.set({ x, y, visible: vis });
cursorStore.set({ x, y, visible: vis, instant });
};
const stopTrackingInternal = () => {
@@ -100,17 +100,18 @@ const AgenticCursor = forwardRef<AgenticCursorHandle>((_props, ref) => {
async moveTo(x, y, transition) {
// Stop prior tracker so it doesn't snap the cursor back to its old anchor mid-animation.
stopTrackingInternal();
// instant=false so the Windows CSS-transition eases here (mirrors Mac's spring controls.start).
writePos(x, y, true, false);
await controls.start({
x,
y,
transition: transition ?? SPRING,
});
writePos(x, y, true);
},
async fadeOut(to) {
stopTrackingInternal();
writePos(to.x, to.y, true, false);
await controls.start({ x: to.x, y: to.y, transition: SPRING });
writePos(to.x, to.y, true);
await controls.start({
opacity: 0,
scale: 0.5,
@@ -262,8 +263,8 @@ const AgenticCursor = forwardRef<AgenticCursorHandle>((_props, ref) => {
...(IS_WIN
? {
transform: `translate(${storePos.x}px, ${storePos.y}px)`,
// Closest CSS approximation of the Mac spring (stiffness 260, damping 26): a softly easing ~420ms cubic-bezier. Without this the cursor teleports because the shim strips Framer's spring runtime.
transition: 'transform 420ms cubic-bezier(0.22, 1, 0.36, 1)',
// Closest CSS approximation of the Mac spring (stiffness 260, damping 26): a softly easing ~420ms cubic-bezier for moveTo/fadeOut. Tracking sets instant=true so the cursor snaps to its target each frame instead of perpetually lagging behind.
transition: storePos.instant ? 'none' : 'transform 420ms cubic-bezier(0.22, 1, 0.36, 1)',
willChange: 'transform',
}
: null),
@@ -6,9 +6,11 @@ interface CursorPos {
x: number;
y: number;
visible: boolean;
// Windows-only: the motionWin shim strips Framer's spring, so the cursor wrapper eases via CSS transition. `instant` tells it to disable the transition for this update — set true while tracking a (mostly stationary) element so the cursor snaps like Mac's controls.set, false for moveTo/fadeOut so it eases like controls.start. Ignored on Mac (Framer drives the motion).
instant: boolean;
}
let state: CursorPos = { x: 0, y: 0, visible: false };
let state: CursorPos = { x: 0, y: 0, visible: false, instant: true };
let pendingState: CursorPos | null = null;
const listeners = new Set<() => void>();