From 5e5b7894bb7e66fab7cc638d16d44be962309a66 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 3 Nov 2025 04:34:44 +0000 Subject: [PATCH 1/3] Initial plan From 12c818f22a080eec6efd7087681016d8837ca798 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 3 Nov 2025 04:41:55 +0000 Subject: [PATCH 2/3] Add reactive monochromatic gradient background with ripple effects Co-authored-by: Phlotonic <76844656+Phlotonic@users.noreply.github.com> --- src/App.css | 2 +- src/App.tsx | 2 + src/components/AnimatedGradientBackground.css | 58 ++++++++++++ src/components/AnimatedGradientBackground.tsx | 91 +++++++++++++++++++ 4 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 src/components/AnimatedGradientBackground.css create mode 100644 src/components/AnimatedGradientBackground.tsx diff --git a/src/App.css b/src/App.css index 0f06c55..c10ab04 100644 --- a/src/App.css +++ b/src/App.css @@ -13,7 +13,7 @@ body, html { /* Body and Background Enhancements */ body { font-family: 'Roboto', sans-serif; /* Modern font */ - background: linear-gradient(135deg, #1e3c72, #2a5298); /* Futuristic gradient background */ + /* Background is now handled by AnimatedGradientBackground component */ color: #e0e0e0; /* Light text for contrast */ overflow-x: hidden; /* Prevent horizontal scrolling */ line-height: 1.6; diff --git a/src/App.tsx b/src/App.tsx index ea8bdda..a6db668 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -14,6 +14,7 @@ import DeleteUser from './components/DeleteUser'; import NotFound from './components/NotFound'; import UserContext from './context/UserContext'; import OrderHistory from './components/OrderHistory'; +import AnimatedGradientBackground from './components/AnimatedGradientBackground'; import './App.css'; const queryClient = new QueryClient(); @@ -24,6 +25,7 @@ function App() { return ( + } /> diff --git a/src/components/AnimatedGradientBackground.css b/src/components/AnimatedGradientBackground.css new file mode 100644 index 0000000..4071857 --- /dev/null +++ b/src/components/AnimatedGradientBackground.css @@ -0,0 +1,58 @@ +/* Animated Gradient Background Styles */ + +.animated-gradient-background { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: -1; + transition: background 0.3s ease-out; +} + +.ripples-container { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: -1; + pointer-events: none; + overflow: hidden; +} + +.ripple { + position: absolute; + width: 0; + height: 0; + border-radius: 50%; + background: radial-gradient( + circle, + rgba(255, 255, 255, 0.3) 0%, + rgba(255, 255, 255, 0.1) 50%, + rgba(255, 255, 255, 0) 100% + ); + transform: translate(-50%, -50%); + animation: ripple-animation 1.5s ease-out forwards; +} + +@keyframes ripple-animation { + 0% { + width: 0; + height: 0; + opacity: 1; + } + 50% { + opacity: 0.6; + } + 100% { + width: 500px; + height: 500px; + opacity: 0; + } +} + +/* Ensure the background doesn't interfere with scrolling */ +body { + position: relative; +} diff --git a/src/components/AnimatedGradientBackground.tsx b/src/components/AnimatedGradientBackground.tsx new file mode 100644 index 0000000..08187b2 --- /dev/null +++ b/src/components/AnimatedGradientBackground.tsx @@ -0,0 +1,91 @@ +import React, { useState, useEffect, useRef } from 'react'; +import './AnimatedGradientBackground.css'; + +interface Ripple { + id: number; + x: number; + y: number; +} + +const AnimatedGradientBackground: React.FC = () => { + const [mousePosition, setMousePosition] = useState({ x: 50, y: 50 }); + const [ripples, setRipples] = useState([]); + const rippleIdRef = useRef(0); + + useEffect(() => { + const handleMouseMove = (e: MouseEvent) => { + const x = (e.clientX / window.innerWidth) * 100; + const y = (e.clientY / window.innerHeight) * 100; + setMousePosition({ x, y }); + }; + + const handleMouseClick = (e: MouseEvent) => { + const newRipple: Ripple = { + id: rippleIdRef.current++, + x: e.clientX, + y: e.clientY, + }; + + setRipples(prev => [...prev, newRipple]); + + // Remove ripple after animation completes + setTimeout(() => { + setRipples(prev => prev.filter(r => r.id !== newRipple.id)); + }, 1500); + }; + + window.addEventListener('mousemove', handleMouseMove); + window.addEventListener('click', handleMouseClick); + + return () => { + window.removeEventListener('mousemove', handleMouseMove); + window.removeEventListener('click', handleMouseClick); + }; + }, []); + + // Calculate gradient colors based on mouse position + const calculateGradient = () => { + // Using a monochromatic blue palette + const baseHue = 220; // Blue hue + const saturation = 70; + + // Vary lightness based on mouse position + const lightness1 = 15 + (mousePosition.x / 100) * 20; // 15-35% + const lightness2 = 25 + (mousePosition.y / 100) * 25; // 25-50% + const lightness3 = 20 + ((mousePosition.x + mousePosition.y) / 200) * 30; // 20-50% + + return ` + radial-gradient( + circle at ${mousePosition.x}% ${mousePosition.y}%, + hsl(${baseHue}, ${saturation}%, ${lightness1}%) 0%, + hsl(${baseHue}, ${saturation - 10}%, ${lightness2}%) 40%, + hsl(${baseHue}, ${saturation - 20}%, ${lightness3}%) 100% + ) + `; + }; + + return ( + <> +
+
+ {ripples.map(ripple => ( +
+ ))} +
+ + ); +}; + +export default AnimatedGradientBackground; From a8cf1336a7ddc8bd56d6ba9069a9c406017d47a6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 3 Nov 2025 04:43:02 +0000 Subject: [PATCH 3/3] Address code review: add memory leak prevention and throttle mouse events Co-authored-by: Phlotonic <76844656+Phlotonic@users.noreply.github.com> --- src/components/AnimatedGradientBackground.tsx | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/src/components/AnimatedGradientBackground.tsx b/src/components/AnimatedGradientBackground.tsx index 08187b2..1a37330 100644 --- a/src/components/AnimatedGradientBackground.tsx +++ b/src/components/AnimatedGradientBackground.tsx @@ -11,12 +11,20 @@ const AnimatedGradientBackground: React.FC = () => { const [mousePosition, setMousePosition] = useState({ x: 50, y: 50 }); const [ripples, setRipples] = useState([]); const rippleIdRef = useRef(0); + const timeoutIdsRef = useRef>(new Set()); + const rafIdRef = useRef(null); useEffect(() => { const handleMouseMove = (e: MouseEvent) => { - const x = (e.clientX / window.innerWidth) * 100; - const y = (e.clientY / window.innerHeight) * 100; - setMousePosition({ x, y }); + // Throttle mouse move updates using requestAnimationFrame + if (rafIdRef.current === null) { + rafIdRef.current = requestAnimationFrame(() => { + const x = (e.clientX / window.innerWidth) * 100; + const y = (e.clientY / window.innerHeight) * 100; + setMousePosition({ x, y }); + rafIdRef.current = null; + }); + } }; const handleMouseClick = (e: MouseEvent) => { @@ -28,10 +36,12 @@ const AnimatedGradientBackground: React.FC = () => { setRipples(prev => [...prev, newRipple]); - // Remove ripple after animation completes - setTimeout(() => { + // Remove ripple after animation completes, track timeout for cleanup + const timeoutId = setTimeout(() => { setRipples(prev => prev.filter(r => r.id !== newRipple.id)); + timeoutIdsRef.current.delete(timeoutId); }, 1500); + timeoutIdsRef.current.add(timeoutId); }; window.addEventListener('mousemove', handleMouseMove); @@ -40,6 +50,15 @@ const AnimatedGradientBackground: React.FC = () => { return () => { window.removeEventListener('mousemove', handleMouseMove); window.removeEventListener('click', handleMouseClick); + + // Clean up pending timeouts + timeoutIdsRef.current.forEach(id => clearTimeout(id)); + timeoutIdsRef.current.clear(); + + // Clean up pending animation frame + if (rafIdRef.current !== null) { + cancelAnimationFrame(rafIdRef.current); + } }; }, []);