feat: add smooth animations with gradient halos and pulsing dots

Introduce subtle black-and-white animations for a tech vibe.

Co-authored-by: Simon <85533298+handsomezhuzhu@users.noreply.github.com>
This commit is contained in:
v0
2026-02-04 15:28:23 +00:00
parent dc3c9110b9
commit d299f0cb46

View File

@@ -12,36 +12,66 @@ export function TechBackground() {
const ctx = canvas.getContext("2d") const ctx = canvas.getContext("2d")
if (!ctx) return if (!ctx) return
let animationFrameId: number
let time = 0
// Pulse points that will glow
const pulsePoints: { x: number; y: number; phase: number; speed: number }[] = []
const initPulsePoints = () => {
pulsePoints.length = 0
const gridSize = 60
for (let x = 0; x <= canvas.width; x += gridSize) {
for (let y = 0; y <= canvas.height; y += gridSize) {
if (Math.random() > 0.85) {
pulsePoints.push({
x,
y,
phase: Math.random() * Math.PI * 2,
speed: 0.02 + Math.random() * 0.02
})
}
}
}
}
const resize = () => { const resize = () => {
canvas.width = window.innerWidth canvas.width = window.innerWidth
canvas.height = window.innerHeight canvas.height = window.innerHeight
draw() initPulsePoints()
} }
const draw = () => { const draw = () => {
const isDark = document.documentElement.classList.contains("dark") const isDark = document.documentElement.classList.contains("dark")
time += 0.016
// Fill with base background color - pure black/white // Fill with base background color - pure black/white
ctx.fillStyle = isDark ? "#0a0a0a" : "#fafafa" ctx.fillStyle = isDark ? "#0a0a0a" : "#fafafa"
ctx.fillRect(0, 0, canvas.width, canvas.height) ctx.fillRect(0, 0, canvas.width, canvas.height)
// Animated gradient position
const gradientOffset1X = Math.sin(time * 0.3) * 100
const gradientOffset1Y = Math.cos(time * 0.2) * 100
const gradientOffset2X = Math.cos(time * 0.25) * 80
const gradientOffset2Y = Math.sin(time * 0.35) * 80
// Draw sophisticated gradient - top left corner glow (grayscale only) // Draw sophisticated gradient - top left corner glow (grayscale only)
const gradient1 = ctx.createRadialGradient( const gradient1 = ctx.createRadialGradient(
gradientOffset1X,
gradientOffset1Y,
0, 0,
0, gradientOffset1X,
0, gradientOffset1Y,
0,
0,
canvas.width * 0.8 canvas.width * 0.8
) )
if (isDark) { if (isDark) {
gradient1.addColorStop(0, "rgba(255, 255, 255, 0.03)") gradient1.addColorStop(0, "rgba(255, 255, 255, 0.04)")
gradient1.addColorStop(0.5, "rgba(255, 255, 255, 0.015)") gradient1.addColorStop(0.5, "rgba(255, 255, 255, 0.02)")
gradient1.addColorStop(1, "rgba(0, 0, 0, 0)") gradient1.addColorStop(1, "rgba(0, 0, 0, 0)")
} else { } else {
gradient1.addColorStop(0, "rgba(0, 0, 0, 0.02)") gradient1.addColorStop(0, "rgba(0, 0, 0, 0.025)")
gradient1.addColorStop(0.5, "rgba(0, 0, 0, 0.01)") gradient1.addColorStop(0.5, "rgba(0, 0, 0, 0.012)")
gradient1.addColorStop(1, "rgba(255, 255, 255, 0)") gradient1.addColorStop(1, "rgba(255, 255, 255, 0)")
} }
@@ -50,21 +80,21 @@ export function TechBackground() {
// Draw second gradient - bottom right corner glow (grayscale only) // Draw second gradient - bottom right corner glow (grayscale only)
const gradient2 = ctx.createRadialGradient( const gradient2 = ctx.createRadialGradient(
canvas.width, canvas.width + gradientOffset2X,
canvas.height, canvas.height + gradientOffset2Y,
0, 0,
canvas.width, canvas.width + gradientOffset2X,
canvas.height, canvas.height + gradientOffset2Y,
canvas.width * 0.6 canvas.width * 0.6
) )
if (isDark) { if (isDark) {
gradient2.addColorStop(0, "rgba(255, 255, 255, 0.025)") gradient2.addColorStop(0, "rgba(255, 255, 255, 0.03)")
gradient2.addColorStop(0.5, "rgba(255, 255, 255, 0.01)") gradient2.addColorStop(0.5, "rgba(255, 255, 255, 0.015)")
gradient2.addColorStop(1, "rgba(0, 0, 0, 0)") gradient2.addColorStop(1, "rgba(0, 0, 0, 0)")
} else { } else {
gradient2.addColorStop(0, "rgba(0, 0, 0, 0.015)") gradient2.addColorStop(0, "rgba(0, 0, 0, 0.02)")
gradient2.addColorStop(0.5, "rgba(0, 0, 0, 0.008)") gradient2.addColorStop(0.5, "rgba(0, 0, 0, 0.01)")
gradient2.addColorStop(1, "rgba(255, 255, 255, 0)") gradient2.addColorStop(1, "rgba(255, 255, 255, 0)")
} }
@@ -116,23 +146,44 @@ export function TechBackground() {
ctx.lineTo(canvas.width, y) ctx.lineTo(canvas.width, y)
ctx.stroke() ctx.stroke()
} }
// Draw animated pulse points at grid intersections
for (const point of pulsePoints) {
const pulse = Math.sin(time * point.speed * 60 + point.phase) * 0.5 + 0.5
const radius = 2 + pulse * 3
const opacity = 0.1 + pulse * 0.2
const glowGradient = ctx.createRadialGradient(
point.x, point.y, 0,
point.x, point.y, radius * 4
)
if (isDark) {
glowGradient.addColorStop(0, `rgba(255, 255, 255, ${opacity})`)
glowGradient.addColorStop(0.5, `rgba(255, 255, 255, ${opacity * 0.3})`)
glowGradient.addColorStop(1, "rgba(255, 255, 255, 0)")
} else {
glowGradient.addColorStop(0, `rgba(0, 0, 0, ${opacity * 0.8})`)
glowGradient.addColorStop(0.5, `rgba(0, 0, 0, ${opacity * 0.2})`)
glowGradient.addColorStop(1, "rgba(0, 0, 0, 0)")
}
ctx.fillStyle = glowGradient
ctx.beginPath()
ctx.arc(point.x, point.y, radius * 4, 0, Math.PI * 2)
ctx.fill()
}
animationFrameId = requestAnimationFrame(draw)
} }
resize() resize()
// Observe theme changes
const observer = new MutationObserver(() => {
draw() draw()
})
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ["class"],
})
window.addEventListener("resize", resize) window.addEventListener("resize", resize)
return () => { return () => {
observer.disconnect() cancelAnimationFrame(animationFrameId)
window.removeEventListener("resize", resize) window.removeEventListener("resize", resize)
} }
}, []) }, [])