"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 (
);
}
const MemoFloatingOrb = memo(FloatingOrb);
export function FloatingOrbs() {
const { orbsRevealed } = useOnboarding();
const reduceMotion = useReducedMotion();
const [mounted, setMounted] = useState(false);
const [orbs, setOrbs] = useState>([]);
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 (
{orbs.map((orb) => (
))}
);
}