Dvd thingy
This commit is contained in:
+5
-1
@@ -15,7 +15,11 @@ export default function RootLayout({
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className="antialiased bg-black">{children}</body>
|
||||
<body className="antialiased bg-black">
|
||||
<Background>
|
||||
<div className="relative h-screen overflow-y-scroll">{children}</div>
|
||||
</Background>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
+64
-171
@@ -1,4 +1,4 @@
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
|
||||
@@ -7,183 +7,76 @@ export default function Background({
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
||||
const animationRef = useRef<number>(0);
|
||||
const particlesRef = useRef<Particle[]>([]);
|
||||
const [dimensions, setDimensions] = useState<{ width: number; height: number }>({
|
||||
width: 0,
|
||||
height: 0,
|
||||
});
|
||||
|
||||
// Particle configuration
|
||||
const config = {
|
||||
particleCount: 400,
|
||||
maxDistance: 120,
|
||||
particleSpeed: 0.5,
|
||||
particleSize: 2,
|
||||
lineOpacity: 0.6,
|
||||
particleOpacity: 0.8,
|
||||
colors: {
|
||||
particles: "#f5304c", // blue-400
|
||||
connections: "#f5304c", // blue-500
|
||||
},
|
||||
};
|
||||
|
||||
// Particle class
|
||||
class Particle {
|
||||
x: number;
|
||||
y: number;
|
||||
vx: number;
|
||||
vy: number;
|
||||
size: number;
|
||||
|
||||
constructor(width: number, height: number) {
|
||||
this.x = Math.random() * width;
|
||||
this.y = Math.random() * height;
|
||||
this.vx = (Math.random() - 0.5) * config.particleSpeed;
|
||||
this.vy = (Math.random() - 0.5) * config.particleSpeed;
|
||||
this.size = config.particleSize + Math.random() * 2;
|
||||
}
|
||||
|
||||
update(width: number, height: number) {
|
||||
this.x += this.vx;
|
||||
this.y += this.vy;
|
||||
|
||||
// Bounce off edges
|
||||
if (this.x < 0 || this.x > width) {
|
||||
this.vx = -this.vx;
|
||||
this.x = Math.max(0, Math.min(width, this.x));
|
||||
}
|
||||
if (this.y < 0 || this.y > height) {
|
||||
this.vy = -this.vy;
|
||||
this.y = Math.max(0, Math.min(height, this.y));
|
||||
}
|
||||
}
|
||||
|
||||
draw(ctx: CanvasRenderingContext2D) {
|
||||
ctx.beginPath();
|
||||
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
|
||||
ctx.fillStyle = `${config.colors.particles}${Math.round(
|
||||
config.particleOpacity * 255
|
||||
)
|
||||
.toString(16)
|
||||
.padStart(2, "0")}`;
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize particles
|
||||
const initParticles = (width: number, height: number) => {
|
||||
particlesRef.current = [];
|
||||
for (let i = 0; i < config.particleCount; i++) {
|
||||
particlesRef.current.push(new Particle(width, height));
|
||||
}
|
||||
};
|
||||
|
||||
// Draw connections between nearby particles
|
||||
const drawConnections = (ctx: CanvasRenderingContext2D, particles: Particle[]) => {
|
||||
for (let i = 0; i < particles.length; i++) {
|
||||
for (let j = i + 1; j < particles.length; j++) {
|
||||
const dx = particles[i].x - particles[j].x;
|
||||
const dy = particles[i].y - particles[j].y;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (distance < config.maxDistance) {
|
||||
const opacity = 1 - distance / config.maxDistance;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(particles[i].x, particles[i].y);
|
||||
ctx.lineTo(particles[j].x, particles[j].y);
|
||||
ctx.strokeStyle = `${config.colors.connections}${Math.round(
|
||||
opacity * config.lineOpacity * 255
|
||||
)
|
||||
.toString(16)
|
||||
.padStart(2, "0")}`;
|
||||
ctx.lineWidth = 1;
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Animation loop
|
||||
const animate = () => {
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return;
|
||||
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (!ctx) return;
|
||||
|
||||
const { width, height } = dimensions;
|
||||
|
||||
// Clear canvas
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
|
||||
// Update and draw particles
|
||||
particlesRef.current.forEach((particle) => {
|
||||
particle.update(width, height);
|
||||
particle.draw(ctx);
|
||||
});
|
||||
|
||||
// Draw connections
|
||||
drawConnections(ctx, particlesRef.current);
|
||||
|
||||
animationRef.current = requestAnimationFrame(animate);
|
||||
};
|
||||
|
||||
// Handle window resize
|
||||
const handleResize = () => {
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas || !canvas.parentElement) return;
|
||||
|
||||
const rect = canvas.parentElement.getBoundingClientRect();
|
||||
const width = rect.width;
|
||||
const height = rect.height;
|
||||
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
canvas.width = width * dpr;
|
||||
canvas.height = height * dpr;
|
||||
canvas.style.width = `${width}px`;
|
||||
canvas.style.height = `${height}px`;
|
||||
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (ctx) {
|
||||
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset scaling before re-scaling
|
||||
ctx.scale(dpr, dpr);
|
||||
}
|
||||
|
||||
setDimensions({ width, height });
|
||||
};
|
||||
const [pos, setPos] = useState({ x: 100, y: 100 });
|
||||
const [vel, setVel] = useState({ x: 1, y: 1 });
|
||||
const dvdRef = useRef<HTMLImageElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
handleResize();
|
||||
window.addEventListener("resize", handleResize);
|
||||
let frame: number;
|
||||
let x = pos.x;
|
||||
let y = pos.y;
|
||||
let vx = vel.x;
|
||||
let vy = vel.y;
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("resize", handleResize);
|
||||
if (animationRef.current) {
|
||||
cancelAnimationFrame(animationRef.current);
|
||||
}
|
||||
const speed = 0.8;
|
||||
const size = 120;
|
||||
|
||||
// helper for collision detection
|
||||
const detectCollision = (
|
||||
nextX: number,
|
||||
nextY: number,
|
||||
w: number,
|
||||
h: number
|
||||
) => {
|
||||
let collidedX = false;
|
||||
let collidedY = false;
|
||||
|
||||
// Left or Right wall
|
||||
if (nextX <= 0 || nextX + size >= w) collidedX = true;
|
||||
|
||||
// Top or Bottom wall
|
||||
if (nextY <= 0 || nextY + size >= h) collidedY = true;
|
||||
|
||||
return { collidedX, collidedY };
|
||||
};
|
||||
|
||||
const animate = () => {
|
||||
const w = window.innerWidth;
|
||||
const h = window.innerHeight;
|
||||
|
||||
const nextX = x + vx * speed;
|
||||
const nextY = y + vy * speed;
|
||||
|
||||
const { collidedX, collidedY } = detectCollision(nextX, nextY, w, h);
|
||||
|
||||
if (collidedX) vx *= -1;
|
||||
if (collidedY) vy *= -1;
|
||||
|
||||
x += vx * speed;
|
||||
y += vy * speed;
|
||||
|
||||
setPos({ x, y });
|
||||
setVel({ x: vx, y: vy });
|
||||
|
||||
frame = requestAnimationFrame(animate);
|
||||
};
|
||||
|
||||
frame = requestAnimationFrame(animate);
|
||||
return () => cancelAnimationFrame(frame);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (dimensions.width && dimensions.height) {
|
||||
initParticles(dimensions.width, dimensions.height);
|
||||
animate();
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (animationRef.current) {
|
||||
cancelAnimationFrame(animationRef.current);
|
||||
}
|
||||
};
|
||||
}, [dimensions]);
|
||||
|
||||
return (
|
||||
<div className="relative w-full min-h-screen overflow-hidden bg-black">
|
||||
<canvas ref={canvasRef} className="absolute inset-0 w-full h-full" />
|
||||
|
||||
{children}
|
||||
<img
|
||||
ref={dvdRef}
|
||||
style={{
|
||||
transform: `translate(${pos.x}px, ${pos.y}px)`,
|
||||
}}
|
||||
src="/logo.png"
|
||||
alt="Logo"
|
||||
className="size-24 rounded-full border-slate-400 border"
|
||||
/>
|
||||
<div className="relative z-10">{children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user