- Improve localStorage access in ActivityFeed, ChatWidget, and AdminPage with try-catch blocks to handle potential errors gracefully. - Update performance tracking in AnalyticsProvider and analytics.ts to ensure robust error handling and prevent failures from affecting user experience. - Refactor Web Vitals tracking to include error handling for observer initialization and data collection. - Ensure consistent handling of hydration mismatches in components like BackgroundBlobs and ChatWidget to improve rendering reliability.
173 lines
5.0 KiB
TypeScript
173 lines
5.0 KiB
TypeScript
"use client";
|
|
|
|
import { motion, useMotionValue, useTransform, useSpring } from "framer-motion";
|
|
import { useEffect, useState } from "react";
|
|
|
|
const BackgroundBlobs = () => {
|
|
const mouseX = useMotionValue(0);
|
|
const mouseY = useMotionValue(0);
|
|
|
|
const springConfig = { damping: 50, stiffness: 50, mass: 2 };
|
|
const springX = useSpring(mouseX, springConfig);
|
|
const springY = useSpring(mouseY, springConfig);
|
|
|
|
// Very subtle parallax offsets
|
|
const x1 = useTransform(springX, (value) => value / 30);
|
|
const y1 = useTransform(springY, (value) => value / 30);
|
|
|
|
const x2 = useTransform(springX, (value) => value / -25);
|
|
const y2 = useTransform(springY, (value) => value / -25);
|
|
|
|
const x3 = useTransform(springX, (value) => value / 20);
|
|
const y3 = useTransform(springY, (value) => value / 20);
|
|
|
|
const x4 = useTransform(springX, (value) => value / -35);
|
|
const y4 = useTransform(springY, (value) => value / -35);
|
|
|
|
const x5 = useTransform(springX, (value) => value / 15);
|
|
const y5 = useTransform(springY, (value) => value / 15);
|
|
|
|
// Prevent hydration mismatch
|
|
const [mounted, setMounted] = useState(false);
|
|
|
|
useEffect(() => {
|
|
setMounted(true);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (!mounted) return;
|
|
|
|
const handleMouseMove = (e: MouseEvent) => {
|
|
const x = e.clientX - window.innerWidth / 2;
|
|
const y = e.clientY - window.innerHeight / 2;
|
|
mouseX.set(x);
|
|
mouseY.set(y);
|
|
};
|
|
|
|
window.addEventListener("mousemove", handleMouseMove);
|
|
return () => window.removeEventListener("mousemove", handleMouseMove);
|
|
}, [mouseX, mouseY, mounted]);
|
|
|
|
if (!mounted) return null;
|
|
|
|
return (
|
|
<div className="fixed inset-0 overflow-hidden pointer-events-none z-0">
|
|
{/* Mint blob - top left */}
|
|
<motion.div
|
|
className="absolute top-[-10%] left-[-10%] w-[40vw] h-[40vw] bg-liquid-mint/40 rounded-full blur-[100px] mix-blend-multiply"
|
|
style={{ x: x1, y: y1 }}
|
|
animate={{
|
|
scale: [1, 1.15, 1],
|
|
rotate: [0, 45, 0],
|
|
}}
|
|
transition={{
|
|
duration: 40,
|
|
repeat: Infinity,
|
|
ease: "easeInOut",
|
|
repeatType: "reverse",
|
|
}}
|
|
/>
|
|
|
|
{/* Lavender blob - top right */}
|
|
<motion.div
|
|
className="absolute top-[10%] right-[-5%] w-[35vw] h-[35vw] bg-liquid-lavender/35 rounded-full blur-[100px] mix-blend-multiply"
|
|
style={{ x: x2, y: y2 }}
|
|
animate={{
|
|
scale: [1, 1.1, 1],
|
|
rotate: [0, -30, 0],
|
|
}}
|
|
transition={{
|
|
duration: 45,
|
|
repeat: Infinity,
|
|
ease: "easeInOut",
|
|
repeatType: "reverse",
|
|
}}
|
|
/>
|
|
|
|
{/* Rose blob - bottom left */}
|
|
<motion.div
|
|
className="absolute bottom-[-5%] left-[15%] w-[45vw] h-[45vw] bg-liquid-rose/35 rounded-full blur-[100px] mix-blend-multiply"
|
|
style={{ x: x3, y: y3 }}
|
|
animate={{
|
|
scale: [1, 1.2, 1],
|
|
rotate: [0, 60, 0],
|
|
}}
|
|
transition={{
|
|
duration: 50,
|
|
repeat: Infinity,
|
|
ease: "easeInOut",
|
|
repeatType: "reverse",
|
|
}}
|
|
/>
|
|
|
|
{/* Peach blob - middle right */}
|
|
<motion.div
|
|
className="absolute top-[40%] right-[10%] w-[30vw] h-[30vw] bg-orange-200/30 rounded-full blur-[120px] mix-blend-multiply"
|
|
style={{ x: x4, y: y4 }}
|
|
animate={{
|
|
scale: [1, 1.25, 1],
|
|
rotate: [0, -45, 0],
|
|
}}
|
|
transition={{
|
|
duration: 55,
|
|
repeat: Infinity,
|
|
ease: "easeInOut",
|
|
repeatType: "reverse",
|
|
}}
|
|
/>
|
|
|
|
{/* Blue blob - center */}
|
|
<motion.div
|
|
className="absolute top-[50%] left-[40%] w-[38vw] h-[38vw] bg-blue-200/30 rounded-full blur-[110px] mix-blend-multiply"
|
|
style={{ x: x5, y: y5 }}
|
|
animate={{
|
|
scale: [1, 1.18, 1],
|
|
rotate: [0, 90, 0],
|
|
}}
|
|
transition={{
|
|
duration: 48,
|
|
repeat: Infinity,
|
|
ease: "easeInOut",
|
|
repeatType: "reverse",
|
|
}}
|
|
/>
|
|
|
|
{/* Pink blob - bottom right */}
|
|
<motion.div
|
|
className="absolute bottom-[10%] right-[-8%] w-[32vw] h-[32vw] bg-pink-200/35 rounded-full blur-[100px] mix-blend-multiply"
|
|
animate={{
|
|
scale: [1, 1.12, 1],
|
|
rotate: [0, -60, 0],
|
|
x: [0, -20, 0],
|
|
y: [0, 20, 0],
|
|
}}
|
|
transition={{
|
|
duration: 43,
|
|
repeat: Infinity,
|
|
ease: "easeInOut",
|
|
repeatType: "reverse",
|
|
}}
|
|
/>
|
|
|
|
{/* Yellow-green blob - top center */}
|
|
<motion.div
|
|
className="absolute top-[5%] left-[45%] w-[28vw] h-[28vw] bg-lime-200/30 rounded-full blur-[115px] mix-blend-multiply"
|
|
animate={{
|
|
scale: [1, 1.22, 1],
|
|
rotate: [0, 75, 0],
|
|
x: [0, 15, 0],
|
|
y: [0, -15, 0],
|
|
}}
|
|
transition={{
|
|
duration: 52,
|
|
repeat: Infinity,
|
|
ease: "easeInOut",
|
|
repeatType: "reverse",
|
|
}}
|
|
/>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default BackgroundBlobs;
|