noz-studio/src/components/star-field.tsx

53 lines
1.8 KiB
TypeScript

"use client"
import { useEffect, useRef } from "react"
function makeStars(count: number, minOp: number, maxOp: number, minSize: number, maxSize: number, dark: boolean): string {
return Array.from({ length: count }, () => {
const x = (Math.random() * 100).toFixed(2)
const y = (Math.random() * 100).toFixed(2)
const op = (Math.random() * (maxOp - minOp) + minOp).toFixed(2)
const size = (minSize + Math.random() * (maxSize - minSize)).toFixed(1)
const color = dark
? `rgba(255,255,255,${op})`
: `rgba(80,70,60,${(parseFloat(op) * 0.35).toFixed(2)})`
return `${x}vw ${y}vh 0 ${size}px ${color}`
}).join(",")
}
export default function StarField() {
const dimRef = useRef<HTMLDivElement>(null)
const brightRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const generate = () => {
const dark = document.documentElement.classList.contains("dark")
if (dimRef.current) dimRef.current.style.boxShadow = makeStars(280, 0.25, 0.6, 1, 1.2, dark)
if (brightRef.current) brightRef.current.style.boxShadow = makeStars(40, 0.6, 1.0, 1.5, 2.5, dark)
}
generate()
const observer = new MutationObserver(generate)
observer.observe(document.documentElement, { attributes: true, attributeFilter: ["class"] })
return () => observer.disconnect()
}, [])
const base: React.CSSProperties = {
position: "absolute",
width: 1,
height: 1,
top: 0,
left: 0,
borderRadius: "50%",
}
return (
<div className="absolute inset-0 pointer-events-none" aria-hidden>
{/* Dim star layer */}
<div ref={dimRef} style={base} />
{/* Bright star layer — subtle pulse */}
<div ref={brightRef} style={{ ...base, animation: "star-pulse 6s ease-in-out infinite" }} />
</div>
)
}