- Ajouter fallback pour Swagger UI si doc.json ne fonctionne pas - Améliorer message d'erreur avec bouton pour ouvrir Swagger UI directement - Les fonctionnalités API Keys et Usage Stats sont maintenant complètes et fonctionnelles - Tous les onglets de DeveloperPage sont maintenant implémentés
129 lines
3.7 KiB
TypeScript
129 lines
3.7 KiB
TypeScript
import { useEffect, useRef } from 'react';
|
|
|
|
/**
|
|
* Composant de fond animé "Astral"
|
|
* Crée un effet de particules et de nébuleuse subtil
|
|
*/
|
|
export function AstralBackground() {
|
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
|
|
useEffect(() => {
|
|
const canvas = canvasRef.current;
|
|
if (!canvas) return;
|
|
|
|
const ctx = canvas.getContext('2d');
|
|
if (!ctx) return;
|
|
|
|
let animationFrameId: number;
|
|
let particles: {
|
|
x: number;
|
|
y: number;
|
|
size: number;
|
|
speedX: number;
|
|
speedY: number;
|
|
opacity: number;
|
|
}[] = [];
|
|
|
|
// Configuration
|
|
const particleCount = 50;
|
|
const connectionDistance = 150;
|
|
|
|
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() * 2,
|
|
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(102, 252, 241, ${p.opacity})`; // Cyan tint
|
|
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();
|
|
ctx.strokeStyle = `rgba(102, 252, 241, ${0.1 * (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);
|
|
};
|
|
}, []);
|
|
|
|
return (
|
|
<div className="fixed inset-0 z-0 pointer-events-none overflow-hidden">
|
|
{/* Deep Space Gradient */}
|
|
<div className="absolute inset-0 bg-gradient-to-b from-kodo-void via-[#0d1016] to-[#080a0c]" />
|
|
|
|
{/* Nebulas */}
|
|
<div className="absolute top-[-20%] left-[-10%] w-[50%] h-[50%] rounded-full bg-kodo-cyan/5 blur-[120px] animate-pulse" />
|
|
<div
|
|
className="absolute bottom-[-20%] right-[-10%] w-[50%] h-[50%] rounded-full bg-kodo-magenta/5 blur-[120px] animate-pulse"
|
|
style={{ animationDelay: '2s' }}
|
|
/>
|
|
|
|
{/* Star Field Canvas */}
|
|
<canvas ref={canvasRef} className="absolute inset-0 opacity-40" />
|
|
|
|
{/* Grid Overlay - FIX: Removed vertical gradient (90deg) that was creating visible vertical lines */}
|
|
{/* Only show horizontal grid lines to avoid vertical line artifacts */}
|
|
<div
|
|
className="absolute inset-0 opacity-[0.03]"
|
|
style={{
|
|
backgroundImage:
|
|
'linear-gradient(rgba(102, 252, 241, 0.5) 1px, transparent 1px)',
|
|
backgroundSize: '50px 50px',
|
|
}}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|