mirror of
https://github.com/kennethnym/aris.git
synced 2026-03-20 09:01:19 +00:00
feat: init waitlist website (#60)
* feat: init waitlist website Co-authored-by: Ona <no-reply@ona.com> * feat[waitlist]: tweak copy Co-authored-by: Ona <no-reply@ona.com> * fix[waitlist]: reminify lottie json Co-authored-by: Ona <no-reply@ona.com> * feat[waitlist]: seo and preview stuff * chore[waitlist]: clean up * build[waitlist]: add fly.io config * feat(waitlist): add time-of-day greeting and duplicate email message Co-authored-by: Ona <no-reply@ona.com> * feat(waitlist): handle duplicate emails and send confirmation Co-authored-by: Ona <no-reply@ona.com> * chore: remove stray console.log Co-authored-by: Ona <no-reply@ona.com> * feat(waitlist): add privacy policy page Co-authored-by: Ona <no-reply@ona.com> * feat(waitlist): add footer with bottom progressive blur Co-authored-by: Ona <no-reply@ona.com> * feat(waitlist): add trouble message and improve error handling Co-authored-by: Ona <no-reply@ona.com> * fix(waitlist): fix timeOfDay logic, typo, and add audienceId Co-authored-by: Ona <no-reply@ona.com> * feat(waitlist): add .ico fallback favicon and style error page Co-authored-by: Ona <no-reply@ona.com> * chore(waitlist): add robots.txt, sitemap, clean dockerignore Co-authored-by: Ona <no-reply@ona.com> * feat(waitlist): add footer to privacy policy page Co-authored-by: Ona <no-reply@ona.com> * fix(waitlist): use segments instead of audienceId Co-authored-by: Ona <no-reply@ona.com> * fix[waitlist]: remove segmentId from dup check Co-authored-by: Ona <no-reply@ona.com> * fix(waitlist): reset logo animation on mouse leave Co-authored-by: Ona <no-reply@ona.com> --------- Co-authored-by: Ona <no-reply@ona.com>
This commit is contained in:
168
apps/waitlist-website/app/components/animated-logo.tsx
Normal file
168
apps/waitlist-website/app/components/animated-logo.tsx
Normal file
@@ -0,0 +1,168 @@
|
||||
import Lottie, { type LottieRef } from "lottie-react"
|
||||
import { useEffect, useRef, useState } from "react"
|
||||
|
||||
import { useColorScheme } from "~/hooks/use-color-scheme"
|
||||
import clickedAnimationDark from "~/lottie/clicked-dark.json"
|
||||
import clickedAnimationLight from "~/lottie/clicked-light.json"
|
||||
import loadingAnimationDark from "~/lottie/loading-dark.json"
|
||||
import loadingAnimationLight from "~/lottie/loading-light.json"
|
||||
import startLoadingAnimationDark from "~/lottie/start-loading-dark.json"
|
||||
import startLoadingAnimationLight from "~/lottie/start-loading-light.json"
|
||||
|
||||
export const AnimatedLogoState = {
|
||||
Idle: "idle",
|
||||
Loading: "loading",
|
||||
} as const
|
||||
export type AnimatedLogoState = (typeof AnimatedLogoState)[keyof typeof AnimatedLogoState]
|
||||
|
||||
interface AnimatedLogoProps {
|
||||
state: AnimatedLogoState
|
||||
className?: string
|
||||
}
|
||||
|
||||
interface Animation {
|
||||
loop: boolean
|
||||
reverse: boolean
|
||||
sticky: boolean
|
||||
data: unknown
|
||||
}
|
||||
|
||||
export function AnimatedLogo({ state, className }: AnimatedLogoProps) {
|
||||
const colorScheme = useColorScheme()
|
||||
const [animationQueue, setAnimationQueue] = useState<Animation[]>([])
|
||||
const lottieRef: LottieRef = useRef(null)
|
||||
|
||||
let currentAnimation: Animation
|
||||
let isIdle = false
|
||||
if (animationQueue.length === 0) {
|
||||
isIdle = true
|
||||
currentAnimation = {
|
||||
loop: false,
|
||||
reverse: false,
|
||||
sticky: false,
|
||||
data: colorScheme === "dark" ? startLoadingAnimationDark : startLoadingAnimationLight,
|
||||
}
|
||||
} else {
|
||||
isIdle = false
|
||||
currentAnimation = animationQueue[0]
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (state === AnimatedLogoState.Loading) {
|
||||
setAnimationQueue((queue) => [
|
||||
...queue,
|
||||
{
|
||||
loop: false,
|
||||
reverse: false,
|
||||
sticky: false,
|
||||
data: colorScheme === "dark" ? startLoadingAnimationDark : startLoadingAnimationLight,
|
||||
},
|
||||
{
|
||||
loop: true,
|
||||
reverse: false,
|
||||
sticky: false,
|
||||
data: colorScheme === "dark" ? loadingAnimationDark : loadingAnimationLight,
|
||||
},
|
||||
])
|
||||
} else if (state === AnimatedLogoState.Idle) {
|
||||
setAnimationQueue((queue) => {
|
||||
const last = queue.at(-1)
|
||||
if (!last) {
|
||||
return []
|
||||
}
|
||||
if (
|
||||
last.loop &&
|
||||
(last.data === loadingAnimationDark || last.data === loadingAnimationLight)
|
||||
) {
|
||||
return [
|
||||
...queue,
|
||||
{
|
||||
loop: false,
|
||||
sticky: false,
|
||||
reverse: false,
|
||||
data: colorScheme === "dark" ? loadingAnimationDark : loadingAnimationLight,
|
||||
},
|
||||
{
|
||||
loop: false,
|
||||
sticky: false,
|
||||
reverse: true,
|
||||
data: colorScheme === "dark" ? startLoadingAnimationDark : startLoadingAnimationLight,
|
||||
},
|
||||
]
|
||||
}
|
||||
return []
|
||||
})
|
||||
}
|
||||
}, [state])
|
||||
|
||||
useEffect(() => {
|
||||
if (!lottieRef.current) {
|
||||
return
|
||||
}
|
||||
if (currentAnimation.reverse) {
|
||||
const frames = lottieRef.current.getDuration(true)
|
||||
if (frames) {
|
||||
lottieRef.current.setDirection(-1)
|
||||
lottieRef.current.goToAndPlay(frames - 1, true)
|
||||
}
|
||||
} else if (!isIdle) {
|
||||
lottieRef.current.setDirection(1)
|
||||
lottieRef.current.play()
|
||||
}
|
||||
}, [currentAnimation])
|
||||
|
||||
function onComplete() {
|
||||
if (animationQueue.length > 0 && !animationQueue[0].sticky) {
|
||||
setAnimationQueue((queue) => queue.slice(1))
|
||||
}
|
||||
}
|
||||
|
||||
function onLoopComplete() {
|
||||
const current = animationQueue[0]
|
||||
const next = animationQueue[1]
|
||||
if (current && next && current.data === next.data && current.loop && !next.loop) {
|
||||
setAnimationQueue((queue) => queue.slice(2))
|
||||
}
|
||||
}
|
||||
|
||||
function onMouseDown() {
|
||||
if (state === AnimatedLogoState.Idle) {
|
||||
setAnimationQueue([
|
||||
{
|
||||
loop: false,
|
||||
sticky: true,
|
||||
reverse: false,
|
||||
data: colorScheme === "dark" ? clickedAnimationDark : clickedAnimationLight,
|
||||
},
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
function onMouseUp() {
|
||||
if (state === AnimatedLogoState.Idle) {
|
||||
setAnimationQueue((queue) => [
|
||||
{
|
||||
loop: false,
|
||||
sticky: false,
|
||||
reverse: true,
|
||||
data: colorScheme === "dark" ? clickedAnimationDark : clickedAnimationLight,
|
||||
},
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Lottie
|
||||
lottieRef={lottieRef}
|
||||
autoplay={false}
|
||||
loop={currentAnimation.loop}
|
||||
className={className}
|
||||
animationData={currentAnimation.data}
|
||||
onComplete={onComplete}
|
||||
onLoopComplete={onLoopComplete}
|
||||
onMouseDown={onMouseDown}
|
||||
onMouseUp={onMouseUp}
|
||||
onMouseLeave={onMouseUp}
|
||||
/>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user