|
| 1 | +import { useEffect, useState } from "react" |
| 2 | +import { AnimatePresence, motion } from "motion/react" |
1 | 3 | import { TypographyH1 } from "#shared/ui/typography" |
2 | | -import { motion } from "motion/react" |
3 | 4 |
|
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 | + |
5 | 39 | return ( |
6 | 40 | <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> |
15 | 43 | </div> |
16 | 44 | ) |
17 | 45 | } |
| 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 | +} |
0 commit comments