diff --git a/frontend/src/app/pages/Dashboard/canvas/tiledGeometry.ts b/frontend/src/app/pages/Dashboard/canvas/tiledGeometry.ts index c5d0307a..905f1427 100644 --- a/frontend/src/app/pages/Dashboard/canvas/tiledGeometry.ts +++ b/frontend/src/app/pages/Dashboard/canvas/tiledGeometry.ts @@ -170,6 +170,12 @@ function stopObserving(): void { window.removeEventListener('resize', onWorkspaceChanged); } +// Entering a tile GLIDES instead of teleporting: one short transform transition on the registering +// frame only, removed on end so per-frame camera writes never fight an animation. Same curve as the +// tool-row motion so the whole app eases identically. +const ENTER_MS = 260; +const ENTER_EASE = 'cubic-bezier(0.32, 0.72, 0, 1)'; + export function registerTiledCard(id: string, zone: string, origin: { x: number; y: number }, cam: Camera): void { const el = document.querySelector(`[data-select-id="${CSS.escape(id)}"]`); if (!el) return; @@ -179,6 +185,8 @@ export function registerTiledCard(id: string, zone: string, origin: { x: number; // Tiling usually commits alongside chrome collapsing, so a cached workspace is untrustworthy here. workspace = null; lastCamera = cam; + el.style.transition = `transform ${ENTER_MS}ms ${ENTER_EASE}`; + window.setTimeout(() => { if (entries.get(id)?.el === el) el.style.transition = ''; }, ENTER_MS + 40); applyEntry(entry, cam); } diff --git a/frontend/src/app/pages/Dashboard/desktop/MinimizedStack.tsx b/frontend/src/app/pages/Dashboard/desktop/MinimizedStack.tsx index ee48ae23..bf2f60e6 100644 --- a/frontend/src/app/pages/Dashboard/desktop/MinimizedStack.tsx +++ b/frontend/src/app/pages/Dashboard/desktop/MinimizedStack.tsx @@ -58,7 +58,8 @@ function MinimizedStack({ browserCards, viewCards, outputs, selectedIds, onResto }; const tile = (entry: MinimizedEntry, zone: string): void => { restore(entry); - if (zone !== 'restore') dispatch(setTiledCard({ cardId: entry.id, zone })); + // Let the unpark PAINT before tiling: doing both in one frame teleported a half-built card into the zone (the janky minimized-to-fullscreen). Next frame, the tile glide takes it from its home to the zone. + if (zone !== 'restore') requestAnimationFrame(() => requestAnimationFrame(() => dispatch(setTiledCard({ cardId: entry.id, zone })))); }; return (