"use client"; import { cn } from "@/lib/utils"; import React, { useState, useEffect, useRef, RefObject, useCallback, } from "react"; interface StarProps { x: number; y: number; radius: number; opacity: number; twinkleSpeed: number | null; } interface StarBackgroundProps { starDensity?: number; allStarsTwinkle?: boolean; twinkleProbability?: number; minTwinkleSpeed?: number; maxTwinkleSpeed?: number; className?: string; } export const StarsBackground: React.FC = ({ starDensity = 0.00015, allStarsTwinkle = true, twinkleProbability = 0.7, minTwinkleSpeed = 0.5, maxTwinkleSpeed = 1, className, }) => { const [stars, setStars] = useState([]); const canvasRef: RefObject = useRef(null); const generateStars = useCallback( (width: number, height: number): StarProps[] => { const area = width * height; const numStars = Math.floor(area * starDensity); return Array.from({ length: numStars }, () => { const shouldTwinkle = allStarsTwinkle || Math.random() < twinkleProbability; return { x: Math.random() * width, y: Math.random() * height, radius: Math.random() * 0.05 + 0.5, opacity: Math.random() * 0.5 + 0.5, twinkleSpeed: shouldTwinkle ? minTwinkleSpeed + Math.random() * (maxTwinkleSpeed - minTwinkleSpeed) : null, }; }); }, [ starDensity, allStarsTwinkle, twinkleProbability, minTwinkleSpeed, maxTwinkleSpeed, ] ); useEffect(() => { const updateStars = () => { if (canvasRef.current) { const canvas = canvasRef.current; const ctx = canvas.getContext("2d"); if (!ctx) return; const { width, height } = canvas.getBoundingClientRect(); canvas.width = width; canvas.height = height; setStars(generateStars(width, height)); } }; updateStars(); const resizeObserver = new ResizeObserver(updateStars); if (canvasRef.current) { resizeObserver.observe(canvasRef.current); } return () => { if (canvasRef.current) { resizeObserver.unobserve(canvasRef.current); } }; }, [ starDensity, allStarsTwinkle, twinkleProbability, minTwinkleSpeed, maxTwinkleSpeed, generateStars, ]); useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext("2d"); if (!ctx) return; let animationFrameId: number; const render = () => { ctx.clearRect(0, 0, canvas.width, canvas.height); stars.forEach((star) => { ctx.beginPath(); ctx.arc(star.x, star.y, star.radius, 0, Math.PI * 2); ctx.fillStyle = `rgba(255, 255, 255, ${star.opacity})`; ctx.fill(); if (star.twinkleSpeed !== null) { star.opacity = 0.5 + Math.abs(Math.sin((Date.now() * 0.001) / star.twinkleSpeed) * 0.5); } }); animationFrameId = requestAnimationFrame(render); }; render(); return () => { cancelAnimationFrame(animationFrameId); }; }, [stars]); return ( ); };