diff --git a/frontend/app/components/workspace/CrmWorkspaceApp.vue b/frontend/app/components/workspace/CrmWorkspaceApp.vue index 40f3a6c..f5fd92c 100644 --- a/frontend/app/components/workspace/CrmWorkspaceApp.vue +++ b/frontend/app/components/workspace/CrmWorkspaceApp.vue @@ -2678,6 +2678,24 @@ function nextAnimationFrame() { }); } +function cloneElementStyleToFlyRect(source: HTMLElement, flyEl: HTMLElement) { + const s = getComputedStyle(source); + flyEl.style.borderColor = s.borderColor; + flyEl.style.borderWidth = s.borderWidth; + flyEl.style.borderStyle = s.borderStyle; + flyEl.style.backgroundColor = s.backgroundColor; + flyEl.style.borderRadius = s.borderRadius; + flyEl.style.boxShadow = s.boxShadow; +} + +function resetFlyRectStyle(flyEl: HTMLElement) { + flyEl.style.borderColor = ""; + flyEl.style.borderWidth = ""; + flyEl.style.borderStyle = ""; + flyEl.style.backgroundColor = ""; + flyEl.style.borderRadius = ""; + flyEl.style.boxShadow = ""; +} async function animateCalendarFlipTransition( _sourceElement: HTMLElement | null, @@ -2701,55 +2719,71 @@ async function animateCalendarFlipTransition( try { const wrapRect = wrapEl.getBoundingClientRect(); - // 1. Fade out current content + // 1. Fade out current scene content if (sceneEl) { await calendarTweenTo(sceneEl, { opacity: 0, duration: CALENDAR_FADE_DURATION, ease: "power2.in" }); } - // 2. Position fly rect at full viewport - const pad = 4; + // 2. Position fly rect at full viewport, styled like a card + const pad = 0; gsap.set(flyEl, { left: pad, top: pad, width: wrapRect.width - pad * 2, height: wrapRect.height - pad * 2, opacity: 1, - borderRadius: 14, }); + // Apply card-like styling: border + bg matching article cards + flyEl.style.borderRadius = "0.75rem"; + flyEl.style.borderWidth = "1px"; + flyEl.style.borderStyle = "solid"; + flyEl.style.borderColor = "color-mix(in oklab, var(--color-base-300) 100%, transparent)"; + flyEl.style.backgroundColor = "color-mix(in oklab, var(--color-base-100) 100%, transparent)"; + flyEl.style.boxShadow = ""; calendarFlyVisible.value = true; // 3. Switch to parent view apply(); await nextTick(); + await nextAnimationFrame(); // 4. Find target element in new view const targetElement = resolveTarget(); const targetRect = targetElement?.getBoundingClientRect() ?? null; if (targetElement && targetRect && targetRect.width >= 2 && targetRect.height >= 2) { + // Clone target's visual style to fly rect for seamless landing + cloneElementStyleToFlyRect(targetElement, flyEl); + // Hide target so there's no double + targetElement.style.opacity = "0"; + const tgtLeft = targetRect.left - wrapRect.left; const tgtTop = targetRect.top - wrapRect.top; - // 5. Animate fly rect → target element + // 5. Animate fly rect → target element bounds await calendarTweenTo(flyEl, { left: tgtLeft, top: tgtTop, width: targetRect.width, height: targetRect.height, - borderRadius: 12, duration: CALENDAR_FLY_DURATION, ease: CALENDAR_EASE, }); + + // Restore target visibility + targetElement.style.opacity = ""; } // 6. Hide fly rect, fade in content calendarFlyVisible.value = false; + resetFlyRectStyle(flyEl); if (sceneEl) { gsap.set(sceneEl, { opacity: 0 }); await calendarTweenTo(sceneEl, { opacity: 1, duration: 0.25, ease: "power2.out" }); } } finally { calendarFlyVisible.value = false; + resetFlyRectStyle(flyEl); calendarZoomBusy.value = false; } } @@ -2787,7 +2821,12 @@ async function animateCalendarZoomIntoSource( ).filter((el) => el !== sourceElement && !sourceElement.contains(el) && !el.contains(sourceElement)); await calendarTweenTo(siblings, { opacity: 0, duration: CALENDAR_FADE_DURATION, ease: "power2.in" }); - // 2. Position fly rect at source element + // 2. Fade out source element's inner content (keep border/bg visible) + const sourceChildren = Array.from(sourceElement.children) as HTMLElement[]; + await calendarTweenTo(sourceChildren, { opacity: 0, duration: 0.12, ease: "power2.in" }); + + // 3. Clone source visual style to fly-rect, position at source bounds + cloneElementStyleToFlyRect(sourceElement, flyEl); const srcLeft = sourceRect.left - wrapRect.left; const srcTop = sourceRect.top - wrapRect.top; gsap.set(flyEl, { @@ -2796,39 +2835,46 @@ async function animateCalendarZoomIntoSource( width: sourceRect.width, height: sourceRect.height, opacity: 1, - borderRadius: 12, }); + + // 4. Swap: hide source, show fly-rect (seamless — identical visual) + sourceElement.style.opacity = "0"; calendarFlyVisible.value = true; - // 3. Animate fly rect → full viewport - const pad = 4; + // 5. Animate fly-rect expanding to full viewport + const pad = 0; await calendarTweenTo(flyEl, { left: pad, top: pad, width: wrapRect.width - pad * 2, height: wrapRect.height - pad * 2, - borderRadius: 14, duration: CALENDAR_FLY_DURATION, ease: CALENDAR_EASE, }); - // 4. Switch content + // 6. Switch view content apply(); await nextTick(); - // 5. Hide fly rect, fade in new content + // 7. Hide fly-rect, fade in new content calendarFlyVisible.value = false; + resetFlyRectStyle(flyEl); if (sceneEl) { gsap.set(sceneEl, { opacity: 0 }); await calendarTweenTo(sceneEl, { opacity: 1, duration: 0.25, ease: "power2.out" }); } - // 6. Restore sibling opacity + // 8. Restore source + siblings + sourceElement.style.opacity = ""; + for (const child of sourceChildren) { + child.style.opacity = ""; + } for (const el of siblings) { el.style.opacity = ""; } } finally { calendarFlyVisible.value = false; + resetFlyRectStyle(flyEl); calendarZoomBusy.value = false; } } diff --git a/frontend/app/components/workspace/calendar/CrmCalendarPanel.vue b/frontend/app/components/workspace/calendar/CrmCalendarPanel.vue index 46c0b00..e685706 100644 --- a/frontend/app/components/workspace/calendar/CrmCalendarPanel.vue +++ b/frontend/app/components/workspace/calendar/CrmCalendarPanel.vue @@ -180,7 +180,7 @@ defineProps<{ >
Sat
-
+
-
+