Skip to content

Commit 9b9e449

Browse files
committed
feat: improve splash screen by adding min duration and transition
1 parent fa93d62 commit 9b9e449

5 files changed

Lines changed: 99 additions & 25 deletions

File tree

solo.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: tilly
2-
icon: null
2+
icon: public/apple-touch-icon.png
33
processes:
44
Jazz Sync Server:
55
command: bunx jazz-run sync
Lines changed: 68 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,75 @@
1+
import { useEffect, useState } from "react"
2+
import { AnimatePresence, motion } from "motion/react"
13
import { TypographyH1 } from "#shared/ui/typography"
2-
import { motion } from "motion/react"
34

4-
export function SplashScreen() {
5+
export { SplashScreen, SplashScreenStatic, useSplashDelay }
6+
7+
declare global {
8+
interface Window {
9+
__pageLoadTime?: number
10+
}
11+
}
12+
13+
function useSplashDelay(minDurationMs = 700) {
14+
let [ready, setReady] = useState(() => {
15+
let loadTime = window.__pageLoadTime ?? Date.now()
16+
let elapsed = Date.now() - loadTime
17+
return elapsed >= minDurationMs
18+
})
19+
20+
useEffect(() => {
21+
if (ready) return
22+
23+
let loadTime = window.__pageLoadTime ?? Date.now()
24+
let elapsed = Date.now() - loadTime
25+
let remaining = Math.max(0, minDurationMs - elapsed)
26+
27+
let timer = setTimeout(() => setReady(true), remaining)
28+
return () => clearTimeout(timer)
29+
}, [minDurationMs, ready])
30+
31+
return ready
32+
}
33+
34+
function SplashScreenStatic() {
35+
useEffect(() => {
36+
document.getElementById("splash")?.remove()
37+
}, [])
38+
539
return (
640
<div className="flex min-h-screen flex-col items-center justify-center gap-4">
7-
<motion.img
8-
src="/app/icons/icon-192x192.png"
9-
className="size-24 rounded-lg"
10-
layoutId="logo"
11-
/>
12-
<motion.div layoutId="title">
13-
<TypographyH1>Tilly</TypographyH1>
14-
</motion.div>
41+
<img src="/app/icons/icon-192x192.png" className="size-24 rounded-lg" />
42+
<TypographyH1>Tilly</TypographyH1>
1543
</div>
1644
)
1745
}
46+
47+
function SplashScreen({ show }: { show?: boolean }) {
48+
return (
49+
<AnimatePresence>
50+
{show !== false && (
51+
<motion.div
52+
className="bg-background fixed inset-0 z-50 flex min-h-screen flex-col items-center justify-center gap-4"
53+
initial={{ opacity: 1 }}
54+
exit={{ opacity: 0 }}
55+
transition={{ duration: 0.3, ease: "easeOut" }}
56+
>
57+
<motion.img
58+
src="/app/icons/icon-192x192.png"
59+
className="size-24 rounded-lg"
60+
layoutId="logo"
61+
exit={{ scale: 0.9, opacity: 0 }}
62+
transition={{ duration: 0.3, ease: "easeOut" }}
63+
/>
64+
<motion.div
65+
layoutId="title"
66+
exit={{ opacity: 0 }}
67+
transition={{ duration: 0.3, ease: "easeOut" }}
68+
>
69+
<TypographyH1>Tilly</TypographyH1>
70+
</motion.div>
71+
</motion.div>
72+
)}
73+
</AnimatePresence>
74+
)
75+
}

src/app/main.tsx

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@ import { IntlProvider } from "#shared/intl/setup"
1111
import { messagesDe } from "#shared/intl/messages"
1212
import { useSyncRemindersToServiceWorker } from "#app/hooks/use-sync-reminders-to-sw"
1313
import { PWAContext, usePWAProvider } from "#app/lib/pwa"
14-
import { SplashScreen } from "./components/splash-screen"
14+
import {
15+
SplashScreen,
16+
SplashScreenStatic,
17+
useSplashDelay,
18+
} from "./components/splash-screen"
1519
import { Toaster } from "#shared/ui/sonner"
1620
import { MainErrorBoundary } from "#app/components/main-error-boundary"
1721

@@ -39,7 +43,7 @@ function JazzWithClerk() {
3943
clerk={clerk}
4044
AccountSchema={UserAccount}
4145
sync={syncConfig}
42-
fallback={<SplashScreen />}
46+
fallback={<SplashScreenStatic />}
4347
>
4448
<RouterWithJazz />
4549
<Toaster richColors />
@@ -52,8 +56,8 @@ function RouterWithJazz() {
5256
useSyncRemindersToServiceWorker()
5357
let me = useAccount(UserAccount, { resolve: { root: true } })
5458

55-
// Only show splash screen if account is still loading
56-
if (me.$jazz.loadingState === "loading") return <SplashScreen />
59+
let splashReady = useSplashDelay(700)
60+
let showSplash = me.$jazz.loadingState === "loading" || !splashReady
5761

5862
// Pass null for unauthenticated users, me object for authenticated users
5963
let contextMe
@@ -65,17 +69,19 @@ function RouterWithJazz() {
6569

6670
let locale = contextMe?.root?.language || "en"
6771

68-
if (locale === "de") {
69-
return (
70-
<IntlProvider messages={messagesDe} locale="de">
71-
<RouterProvider router={router} context={{ me: contextMe }} />
72-
</IntlProvider>
73-
)
74-
}
7572
return (
76-
<IntlProvider>
77-
<RouterProvider router={router} context={{ me: contextMe }} />
78-
</IntlProvider>
73+
<>
74+
<SplashScreen show={showSplash} />
75+
{locale === "de" ? (
76+
<IntlProvider messages={messagesDe} locale="de">
77+
<RouterProvider router={router} context={{ me: contextMe }} />
78+
</IntlProvider>
79+
) : (
80+
<IntlProvider>
81+
<RouterProvider router={router} context={{ me: contextMe }} />
82+
</IntlProvider>
83+
)}
84+
</>
7985
)
8086
}
8187

src/global.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/// <reference types="astro/client" />
2+
3+
interface Window {
4+
__pageLoadTime?: number
5+
}

src/pages/app/index.astro

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,15 @@ let metaDescription =
6666
<body
6767
class="selection:bg-primary selection:text-primary-foreground select-none bg-background text-foreground *:border-border overscroll-y-none"
6868
>
69+
<div id="splash" class="flex min-h-dvh flex-col items-center justify-center gap-4 bg-background">
70+
<img src="/app/icons/icon-192x192.png" class="size-24 rounded-lg" />
71+
<span class="text-4xl font-bold tracking-tight">Tilly</span>
72+
</div>
6973
<PWA client:only="react">
7074
<SplashScreen slot="fallback" />
7175
</PWA>
7276
<script>
77+
window.__pageLoadTime = Date.now()
7378
function updateThemeColor() {
7479
let meta = document.getElementById("theme-color")
7580
if (!meta) return

0 commit comments

Comments
 (0)