"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) => ( ))}
); }