mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-09-10 22:41:17 +00:00
- Mobile responsive - new toast design - web document render issue fix - posthog analytics - ui improvements
246 lines
6 KiB
TypeScript
246 lines
6 KiB
TypeScript
"use client"
|
|
|
|
import { motion, useReducedMotion } from "motion/react"
|
|
import { useEffect, useMemo, useState, memo } from "react"
|
|
import { useOnboarding } from "./onboarding-context"
|
|
|
|
interface OrbProps {
|
|
size: number
|
|
initialX: number
|
|
initialY: number
|
|
duration: number
|
|
delay: number
|
|
revealDelay: number
|
|
shouldReveal: boolean
|
|
color: {
|
|
primary: string
|
|
secondary: string
|
|
tertiary: string
|
|
}
|
|
}
|
|
|
|
function FloatingOrb({
|
|
size,
|
|
initialX,
|
|
initialY,
|
|
duration,
|
|
delay,
|
|
revealDelay,
|
|
shouldReveal,
|
|
color,
|
|
}: OrbProps) {
|
|
const blurPixels = Math.min(64, Math.max(24, Math.floor(size * 0.08)))
|
|
|
|
const gradient = useMemo(() => {
|
|
return `radial-gradient(circle, ${color.primary} 0%, ${color.secondary} 40%, ${color.tertiary} 70%, transparent 100%)`
|
|
}, [color.primary, color.secondary, color.tertiary])
|
|
|
|
const style = useMemo(() => {
|
|
return {
|
|
width: size,
|
|
height: size,
|
|
background: gradient,
|
|
filter: `blur(${blurPixels}px)`,
|
|
willChange: "transform, opacity",
|
|
mixBlendMode: "plus-lighter",
|
|
} as any
|
|
}, [size, gradient, blurPixels])
|
|
|
|
const initial = useMemo(() => {
|
|
return {
|
|
x: initialX,
|
|
y: initialY,
|
|
scale: 0,
|
|
opacity: 0,
|
|
}
|
|
}, [initialX, initialY])
|
|
|
|
const animate = useMemo(() => {
|
|
if (!shouldReveal) {
|
|
return {
|
|
x: initialX,
|
|
y: initialY,
|
|
scale: 0,
|
|
opacity: 0,
|
|
}
|
|
}
|
|
return {
|
|
x: [initialX, initialX + 200, initialX - 150, initialX + 100, initialX],
|
|
y: [initialY, initialY - 180, initialY + 120, initialY - 80, initialY],
|
|
scale: [0.8, 1.2, 0.9, 1.1, 0.8],
|
|
opacity: 0.7,
|
|
}
|
|
}, [shouldReveal, initialX, initialY])
|
|
|
|
const transition = useMemo(() => {
|
|
return {
|
|
x: {
|
|
duration: shouldReveal ? duration : 0,
|
|
repeat: shouldReveal ? Number.POSITIVE_INFINITY : 0,
|
|
ease: [0.42, 0, 0.58, 1],
|
|
delay: shouldReveal ? delay + revealDelay : 0,
|
|
},
|
|
y: {
|
|
duration: shouldReveal ? duration : 0,
|
|
repeat: shouldReveal ? Number.POSITIVE_INFINITY : 0,
|
|
ease: [0.42, 0, 0.58, 1],
|
|
delay: shouldReveal ? delay + revealDelay : 0,
|
|
},
|
|
scale: {
|
|
duration: shouldReveal ? duration : 0.8,
|
|
repeat: shouldReveal ? Number.POSITIVE_INFINITY : 0,
|
|
ease: shouldReveal ? [0.42, 0, 0.58, 1] : [0, 0, 0.58, 1],
|
|
delay: shouldReveal ? delay + revealDelay : revealDelay,
|
|
},
|
|
opacity: {
|
|
duration: 1.2,
|
|
ease: [0, 0, 0.58, 1],
|
|
delay: shouldReveal ? revealDelay : 0,
|
|
},
|
|
} as any
|
|
}, [shouldReveal, duration, delay, revealDelay])
|
|
|
|
return (
|
|
<motion.div
|
|
className="absolute rounded-full"
|
|
style={style}
|
|
initial={initial}
|
|
animate={animate}
|
|
transition={transition}
|
|
/>
|
|
)
|
|
}
|
|
|
|
const MemoFloatingOrb = memo(FloatingOrb)
|
|
|
|
export function FloatingOrbs() {
|
|
const { orbsRevealed } = useOnboarding()
|
|
const reduceMotion = useReducedMotion()
|
|
const [mounted, setMounted] = useState(false)
|
|
const [orbs, setOrbs] = useState<
|
|
Array<{
|
|
id: number
|
|
size: number
|
|
initialX: number
|
|
initialY: number
|
|
duration: number
|
|
delay: number
|
|
revealDelay: number
|
|
color: {
|
|
primary: string
|
|
secondary: string
|
|
tertiary: string
|
|
}
|
|
}>
|
|
>([])
|
|
|
|
useEffect(() => {
|
|
setMounted(true)
|
|
|
|
const screenWidth = typeof window !== "undefined" ? window.innerWidth : 1200
|
|
const screenHeight =
|
|
typeof window !== "undefined" ? window.innerHeight : 800
|
|
|
|
// Define edge zones (avoiding center)
|
|
const edgeThickness = Math.min(screenWidth, screenHeight) * 0.25 // 25% of smaller dimension
|
|
|
|
// Define rainbow color palette
|
|
const colorPalette = [
|
|
{
|
|
// Magenta
|
|
primary: "rgba(255, 0, 150, 0.6)",
|
|
secondary: "rgba(255, 100, 200, 0.4)",
|
|
tertiary: "rgba(255, 150, 220, 0.1)",
|
|
},
|
|
{
|
|
// Yellow
|
|
primary: "rgba(255, 235, 59, 0.6)",
|
|
secondary: "rgba(255, 245, 120, 0.4)",
|
|
tertiary: "rgba(255, 250, 180, 0.1)",
|
|
},
|
|
{
|
|
// Light Blue
|
|
primary: "rgba(100, 181, 246, 0.6)",
|
|
secondary: "rgba(144, 202, 249, 0.4)",
|
|
tertiary: "rgba(187, 222, 251, 0.1)",
|
|
},
|
|
{
|
|
// Orange (keeping original)
|
|
primary: "rgba(255, 154, 0, 0.6)",
|
|
secondary: "rgba(255, 206, 84, 0.4)",
|
|
tertiary: "rgba(255, 154, 0, 0.1)",
|
|
},
|
|
{
|
|
// Very Light Red/Pink
|
|
primary: "rgba(255, 138, 128, 0.6)",
|
|
secondary: "rgba(255, 171, 145, 0.4)",
|
|
tertiary: "rgba(255, 205, 210, 0.1)",
|
|
},
|
|
]
|
|
|
|
// Generate orb configurations positioned along edges
|
|
const newOrbs = Array.from({ length: 8 }, (_, i) => {
|
|
let x: number
|
|
let y: number
|
|
const zone = i % 4 // Rotate through 4 zones: top, right, bottom, left
|
|
|
|
switch (zone) {
|
|
case 0: // Top edge
|
|
x = Math.random() * screenWidth
|
|
y = Math.random() * edgeThickness
|
|
break
|
|
case 1: // Right edge
|
|
x = screenWidth - edgeThickness + Math.random() * edgeThickness
|
|
y = Math.random() * screenHeight
|
|
break
|
|
case 2: // Bottom edge
|
|
x = Math.random() * screenWidth
|
|
y = screenHeight - edgeThickness + Math.random() * edgeThickness
|
|
break
|
|
case 3: // Left edge
|
|
x = Math.random() * edgeThickness
|
|
y = Math.random() * screenHeight
|
|
break
|
|
default:
|
|
x = Math.random() * screenWidth
|
|
y = Math.random() * screenHeight
|
|
}
|
|
|
|
return {
|
|
id: i,
|
|
size: Math.random() * 300 + 200, // 200px to 500px
|
|
initialX: x,
|
|
initialY: y,
|
|
duration: Math.random() * 20 + 15, // 15-35 seconds (longer for more gentle movement)
|
|
delay: i * 0.4, // Staggered start for floating animation
|
|
revealDelay: i * 0.2, // Faster staggered reveal
|
|
color: colorPalette[i % colorPalette.length]!, // Cycle through rainbow colors
|
|
}
|
|
})
|
|
|
|
setOrbs(newOrbs)
|
|
}, [])
|
|
|
|
if (!mounted || orbs.length === 0) return null
|
|
|
|
return (
|
|
<div
|
|
className="fixed inset-0 pointer-events-none overflow-hidden"
|
|
style={{ isolation: "isolate", contain: "paint" }}
|
|
>
|
|
{orbs.map((orb) => (
|
|
<MemoFloatingOrb
|
|
key={orb.id}
|
|
size={orb.size}
|
|
initialX={orb.initialX}
|
|
initialY={orb.initialY}
|
|
duration={reduceMotion ? 0 : orb.duration}
|
|
delay={orb.delay}
|
|
revealDelay={orb.revealDelay}
|
|
shouldReveal={reduceMotion ? false : orbsRevealed}
|
|
color={orb.color}
|
|
/>
|
|
))}
|
|
</div>
|
|
)
|
|
}
|