|
@@ -111,8 +111,44 @@ function setLayout() {
|
|
|
emits('setLayout')
|
|
emits('setLayout')
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-function toggleTheme() {
|
|
|
|
|
- settingsStore.toggleTheme()
|
|
|
|
|
|
|
+async function toggleTheme(event) {
|
|
|
|
|
+ const x = event?.clientX || window.innerWidth / 2
|
|
|
|
|
+ const y = event?.clientY || window.innerHeight / 2
|
|
|
|
|
+ const wasDark = settingsStore.isDark
|
|
|
|
|
+
|
|
|
|
|
+ const isReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches
|
|
|
|
|
+ const isSupported = document.startViewTransition && !isReducedMotion
|
|
|
|
|
+
|
|
|
|
|
+ if (!isSupported) {
|
|
|
|
|
+ settingsStore.toggleTheme()
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ const transition = document.startViewTransition(async () => {
|
|
|
|
|
+ await new Promise((resolve) => setTimeout(resolve, 10))
|
|
|
|
|
+ settingsStore.toggleTheme()
|
|
|
|
|
+ await nextTick()
|
|
|
|
|
+ })
|
|
|
|
|
+ await transition.ready
|
|
|
|
|
+
|
|
|
|
|
+ const endRadius = Math.hypot(Math.max(x, window.innerWidth - x), Math.max(y, window.innerHeight - y))
|
|
|
|
|
+ const clipPath = [`circle(0px at ${x}px ${y}px)`, `circle(${endRadius}px at ${x}px ${y}px)`]
|
|
|
|
|
+ document.documentElement.animate(
|
|
|
|
|
+ {
|
|
|
|
|
+ clipPath: !wasDark ? [...clipPath].reverse() : clipPath
|
|
|
|
|
+ }, {
|
|
|
|
|
+ duration: 650,
|
|
|
|
|
+ easing: "cubic-bezier(0.4, 0, 0.2, 1)",
|
|
|
|
|
+ fill: "forwards",
|
|
|
|
|
+ pseudoElement: !wasDark ? "::view-transition-old(root)" : "::view-transition-new(root)"
|
|
|
|
|
+ }
|
|
|
|
|
+ )
|
|
|
|
|
+ await transition.finished
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.warn("View transition failed, falling back to immediate toggle:", error)
|
|
|
|
|
+ settingsStore.toggleTheme()
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|