veza/apps/web/src/components/ui/AstralBackground.tsx

142 lines
No EOL
4.8 KiB
TypeScript

import { useEffect, useRef } from 'react';
import { useUIStore } from '@/stores/ui';
/**
* Composant de fond animé "Astral/Ceramic"
* S'adapte intelligemment au thème (Étoiles en Dark, Particules d'encre en Light)
*/
export function AstralBackground() {
const canvasRef = useRef<HTMLCanvasElement>(null);
const { theme } = useUIStore(); // Hook pour détecter le changement de thème
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
// Détection du mode clair via la classe sur html/body ou le store
const isLightMode = document.documentElement.classList.contains('light');
let animationFrameId: number;
let particles: {
x: number;
y: number;
size: number;
speedX: number;
speedY: number;
opacity: number;
}[] = [];
// Configuration adaptative
const particleCount = window.innerWidth < 768 ? 20 : 50; // Performance mobile
const connectionDistance = 150;
// Couleurs basées sur le thème (Hardcoded pour perf canvas, mais aligné avec design-tokens)
const particleColor = isLightMode ? '14, 165, 233' : '102, 252, 241'; // Sky Blue vs Cyan Neon
const lineColor = isLightMode ? '148, 163, 184' : '102, 252, 241'; // Slate vs Cyan
const resize = () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
};
const createParticles = () => {
particles = [];
for (let i = 0; i < particleCount; i++) {
particles.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
size: Math.random() * (isLightMode ? 3 : 2), // Particules un peu plus grosses en light (effet encre)
speedX: (Math.random() - 0.5) * 0.2,
speedY: (Math.random() - 0.5) * 0.2,
opacity: Math.random() * 0.5 + 0.1,
});
}
};
const animate = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Update and draw particles
particles.forEach((p, i) => {
p.x += p.speedX;
p.y += p.speedY;
// Wrap around screen
if (p.x < 0) p.x = canvas.width;
if (p.x > canvas.width) p.x = 0;
if (p.y < 0) p.y = canvas.height;
if (p.y > canvas.height) p.y = 0;
// Draw particle
ctx.beginPath();
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
ctx.fillStyle = `rgba(${particleColor}, ${p.opacity})`;
ctx.fill();
// Draw connections
for (let j = i + 1; j < particles.length; j++) {
const p2 = particles[j];
const dx = p.x - p2.x;
const dy = p.y - p2.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < connectionDistance) {
ctx.beginPath();
// Lignes beaucoup plus subtiles en mode clair
const lineOpacity = isLightMode ? 0.05 : 0.1;
ctx.strokeStyle = `rgba(${lineColor}, ${lineOpacity * (1 - distance / connectionDistance)})`;
ctx.lineWidth = 0.5;
ctx.moveTo(p.x, p.y);
ctx.lineTo(p2.x, p2.y);
ctx.stroke();
}
}
});
animationFrameId = requestAnimationFrame(animate);
};
window.addEventListener('resize', resize);
resize();
createParticles();
animate();
return () => {
window.removeEventListener('resize', resize);
cancelAnimationFrame(animationFrameId);
};
}, [theme]); // Re-run effect when theme changes
return (
<div className="fixed inset-0 z-0 pointer-events-none overflow-hidden transition-colors duration-700">
{/* Background Layer 1: Solid Base */}
<div className="absolute inset-0 bg-background transition-colors duration-700" />
{/* Background Layer 2: Subtle Gradient Vignette */}
<div className="absolute inset-0 bg-gradient-to-b from-transparent via-transparent to-black/20 dark:to-black/80" />
{/* Nebulas - Optimized opacity for themes */}
<div className="absolute top-[-20%] left-[-10%] w-[60%] h-[60%] rounded-full bg-cyan/5 dark:bg-cyan/5 blur-[120px] animate-pulse" />
<div
className="absolute bottom-[-20%] right-[-10%] w-[60%] h-[60%] rounded-full bg-magenta/5 dark:bg-magenta/5 blur-[120px] animate-pulse"
style={{ animationDelay: '2s' }}
/>
{/* Star Field Canvas */}
<canvas ref={canvasRef} className="absolute inset-0 opacity-60 dark:opacity-40" />
{/* Grid Overlay - Very subtle, helps grounding */}
<div
className="absolute inset-0 opacity-[0.02] dark:opacity-[0.03]"
style={{
backgroundImage:
'linear-gradient(rgb(var(--sidebar-border)) 1px, transparent 1px)',
backgroundSize: '100px 100px',
}}
/>
</div>
);
}