import { useEffect, useRef } from 'react'; import { cn } from '@/lib/utils'; import { sanitizeColor, safeBoolean, safeSelect } from './types'; import { useAnimationPause } from '@/hooks/useAnimationLoop'; interface Props { settings: Record; } const isMobile = typeof window !== 'undefined' && window.innerWidth < 768; function hexToRgbString(hex: string): string { hex = hex.replace('#', ''); if (hex.length === 3) hex = hex .split('') .map((c) => c + c) .join(''); const r = parseInt(hex.substring(0, 2), 16); const g = parseInt(hex.substring(2, 4), 16); const b = parseInt(hex.substring(4, 6), 16); if (isNaN(r) || isNaN(g) || isNaN(b)) return '129, 140, 248'; return `${r}, ${g}, ${b}`; } export default function BackgroundGradientAnimation({ settings }: Props) { const interactiveRef = useRef(null); const paused = useAnimationPause(); const firstColor = hexToRgbString(sanitizeColor(settings.firstColor, '#1271FF')); const secondColor = hexToRgbString(sanitizeColor(settings.secondColor, '#DD4AFF')); const thirdColor = hexToRgbString(sanitizeColor(settings.thirdColor, '#64DCFF')); const fourthColor = hexToRgbString(sanitizeColor(settings.fourthColor, '#C83232')); const fifthColor = hexToRgbString(sanitizeColor(settings.fifthColor, '#B4B432')); const pointerColor = hexToRgbString(sanitizeColor(settings.pointerColor, '#8C64FF')); const interactive = safeBoolean(settings.interactive, true); const size = safeSelect(settings.size, ['60%', '80%', '100%'] as const, '80%'); useEffect(() => { // Interactive mouse tracking — DESKTOP ONLY if (!interactive || !interactiveRef.current || isMobile) return; let curX = 0; let curY = 0; let tgX = 0; let tgY = 0; let animId = 0; let docHidden = document.hidden; let tgInactive = false; const isHidden = () => docHidden || tgInactive; const move = () => { curX += (tgX - curX) / 20; curY += (tgY - curY) / 20; if (interactiveRef.current) { interactiveRef.current.style.transform = `translate(${Math.round(curX)}px, ${Math.round(curY)}px)`; } animId = requestAnimationFrame(move); }; const start = () => { if (animId) return; animId = requestAnimationFrame(move); }; const stop = () => { if (animId) { cancelAnimationFrame(animId); animId = 0; } }; const handleMouse = (e: MouseEvent) => { tgX = e.clientX; tgY = e.clientY; }; const onVisibility = () => { docHidden = document.hidden; if (isHidden()) stop(); else start(); }; const tg = window.Telegram?.WebApp; const onActivated = () => { tgInactive = false; if (!isHidden()) start(); }; const onDeactivated = () => { tgInactive = true; stop(); }; window.addEventListener('mousemove', handleMouse); document.addEventListener('visibilitychange', onVisibility); if (tg) { tg.onEvent?.('activated', onActivated); tg.onEvent?.('deactivated', onDeactivated); } if (!isHidden()) start(); return () => { stop(); window.removeEventListener('mousemove', handleMouse); document.removeEventListener('visibilitychange', onVisibility); if (tg) { tg.offEvent?.('activated', onActivated); tg.offEvent?.('deactivated', onDeactivated); } }; }, [interactive]); // Mobile: use simpler blur (just CSS blur, no SVG filter) // Desktop: keep the goo SVG filter + CSS blur for full effect const filterStyle = isMobile ? 'blur(20px)' : 'url(#blurMe) blur(40px)'; return (
{/* SVG filter only rendered on desktop */} {!isMobile && ( )}
{[ { color: firstColor, anim: 'animate-move-vertical' }, { color: secondColor, anim: 'animate-move-in-circle' }, { color: thirdColor, anim: 'animate-move-in-circle-slow' }, { color: fourthColor, anim: 'animate-move-horizontal' }, { color: fifthColor, anim: 'animate-move-in-circle-fast' }, ].map((blob, i) => (
))} {interactive && !isMobile && (
)}
); }