From bda7b32080e1d98704844da06161917f2fe60dbe Mon Sep 17 00:00:00 2001 From: Eichenholz Date: Mon, 3 Aug 2026 19:54:25 +0200 Subject: [PATCH] ENH - clean app opening animations --- frontend/src/assets/main.css | 19 +++++++++++++------ frontend/src/components/AppIcon.vue | 21 +++++++++++++-------- frontend/src/types/apps.ts | 3 +-- frontend/src/views/PhoneAppWindow.vue | 3 ++- sky_phone/source/html/index.html | 4 ++-- 5 files changed, 31 insertions(+), 19 deletions(-) diff --git a/frontend/src/assets/main.css b/frontend/src/assets/main.css index 272a060..b0a7c2b 100644 --- a/frontend/src/assets/main.css +++ b/frontend/src/assets/main.css @@ -573,20 +573,27 @@ button { overflow: hidden; background: #000; transform-origin: 0 0; + backface-visibility: hidden; + will-change: transform, border-radius, opacity; +} +.app-window-enter-active { + transition: + transform 0.46s cubic-bezier(0.32, 0.72, 0, 1), + border-radius 0.46s cubic-bezier(0.32, 0.72, 0, 1), + opacity 0.18s ease-out; } -.app-window-enter-active, .app-window-leave-active { transition: - transform 0.48s cubic-bezier(0.22, 0.95, 0.28, 1), - opacity 0.35s, - border-radius 0.48s; + transform 0.4s cubic-bezier(0.32, 0, 0.67, 0), + border-radius 0.4s cubic-bezier(0.32, 0, 0.67, 0), + opacity 0.22s ease-in; } .app-window-enter-from, .app-window-leave-to { transform: translate(var(--launch-x), var(--launch-y)) scale(var(--launch-scale-x), var(--launch-scale-y)); - opacity: 0.15; - border-radius: 70px; + opacity: 0.72; + border-radius: var(--launch-radius); } .app-window-enter-to, .app-window-leave-from { diff --git a/frontend/src/components/AppIcon.vue b/frontend/src/components/AppIcon.vue index 1448f5b..3cb2ec8 100644 --- a/frontend/src/components/AppIcon.vue +++ b/frontend/src/components/AppIcon.vue @@ -24,16 +24,21 @@ const iconFailed = ref(false) function launch(event: MouseEvent): void { const button = event.currentTarget as HTMLElement const screen = button.closest('.phone-screen') - if (screen) { - const origin = button.getBoundingClientRect() + const icon = button.querySelector('.app-icon') + if (screen && icon) { + const origin = icon.getBoundingClientRect() const target = screen.getBoundingClientRect() + const scaleX = origin.width / target.width + const scaleY = origin.height / target.height + const screenScaleX = target.width / screen.clientWidth + const screenScaleY = target.height / screen.clientHeight + const iconRadius = Number.parseFloat(getComputedStyle(icon).borderRadius) phone.setLaunchOrigin({ - height: origin.height, - scaleX: origin.width / target.width, - scaleY: origin.height / target.height, - width: origin.width, - x: origin.left - target.left, - y: origin.top - target.top, + borderRadius: iconRadius / Math.min(scaleX, scaleY), + scaleX, + scaleY, + x: (origin.left - target.left) / screenScaleX, + y: (origin.top - target.top) / screenScaleY, }) } else { phone.setLaunchOrigin(null) diff --git a/frontend/src/types/apps.ts b/frontend/src/types/apps.ts index 34f2bf8..7f58c91 100644 --- a/frontend/src/types/apps.ts +++ b/frontend/src/types/apps.ts @@ -9,10 +9,9 @@ export type PhoneAppId = | 'settings' export type AppLaunchOrigin = { - height: number + borderRadius: number scaleX: number scaleY: number - width: number x: number y: number } diff --git a/frontend/src/views/PhoneAppWindow.vue b/frontend/src/views/PhoneAppWindow.vue index 1e84655..dd408c9 100644 --- a/frontend/src/views/PhoneAppWindow.vue +++ b/frontend/src/views/PhoneAppWindow.vue @@ -11,6 +11,7 @@ const app = computed(() => getPhoneApp(route.params.appId)) const launchStyle = computed(() => { const origin = phone.launchOrigin return { + '--launch-radius': `${origin?.borderRadius ?? 72}px`, '--launch-scale-x': origin?.scaleX ?? 0.82, '--launch-scale-y': origin?.scaleY ?? 0.82, '--launch-x': `${origin?.x ?? 35}px`, @@ -20,7 +21,7 @@ const launchStyle = computed(() => {