import { useEffect, useRef, useState, useCallback } from "react"; import { Copy, Check } from "lucide-react"; import { Button } from "@/components/ui/button"; const SSH_COMMAND = "ssh portfolio@keshavanand.net"; const CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@#$%^&*()_+-=[]{}|;:,.<>?/\\~`"; interface Particle { x: number; y: number; char: string; opacity: number; targetX: number; targetY: number; vx: number; vy: number; life: number; maxLife: number; size: number; color: string; } function ParticleBackground() { const canvasRef = useRef(null); const particlesRef = useRef([]); const mouseRef = useRef({ x: 0, y: 0 }); const animationRef = useRef(); useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext("2d"); if (!ctx) return; const resizeCanvas = () => { canvas.width = window.innerWidth; canvas.height = window.innerHeight; }; resizeCanvas(); window.addEventListener("resize", resizeCanvas); const colors = [ "#a6e3a1", "#94e2d5", "#89dceb", "#74c7ec", "#89b4fa", "#cba6f7", ]; const createParticle = (x: number, y: number): Particle => { const angle = Math.random() * Math.PI * 2; const speed = Math.random() * 2 + 1; const color = colors[Math.floor(Math.random() * colors.length)]; return { x: x + (Math.random() - 0.5) * 100, y: y + (Math.random() - 0.5) * 100, char: CHARACTERS[Math.floor(Math.random() * CHARACTERS.length)], opacity: Math.random() * 0.8 + 0.2, targetX: x, targetY: y, vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed, life: 0, maxLife: Math.random() * 60 + 40, size: Math.random() * 10 + 12, color, }; }; const handleMouseMove = (e: MouseEvent) => { mouseRef.current = { x: e.clientX, y: e.clientY }; for (let i = 0; i < 3; i++) { if (particlesRef.current.length < 200) { particlesRef.current.push(createParticle(e.clientX, e.clientY)); } } }; const animate = () => { ctx.fillStyle = "#000000"; ctx.fillRect(0, 0, canvas.width, canvas.height); particlesRef.current = particlesRef.current.filter((p) => { p.life++; const dx = mouseRef.current.x - p.x; const dy = mouseRef.current.y - p.y; const distance = Math.sqrt(dx * dx + dy * dy); if (distance < 150) { const force = (150 - distance) / 150; p.vx += (dx / distance) * force * 0.3; p.vy += (dy / distance) * force * 0.3; } p.vx *= 0.96; p.vy *= 0.96; p.x += p.vx; p.y += p.vy; const lifeRatio = p.life / p.maxLife; const fadeOpacity = lifeRatio < 0.2 ? lifeRatio * 5 : lifeRatio > 0.7 ? (1 - lifeRatio) * 3.33 : 1; const distanceOpacity = Math.max(0, 1 - distance / 300); const finalOpacity = p.opacity * fadeOpacity * distanceOpacity; if (finalOpacity > 0.01) { ctx.font = `${p.size}px 'JetBrains Mono', monospace`; ctx.fillStyle = p.color; ctx.globalAlpha = finalOpacity; ctx.fillText(p.char, p.x, p.y); ctx.globalAlpha = 1; } return p.life < p.maxLife; }); animationRef.current = requestAnimationFrame(animate); }; window.addEventListener("mousemove", handleMouseMove); animate(); return () => { window.removeEventListener("resize", resizeCanvas); window.removeEventListener("mousemove", handleMouseMove); if (animationRef.current) { cancelAnimationFrame(animationRef.current); } }; }, []); return ( ); } function CommandBox() { const [copied, setCopied] = useState(false); const [isAnimating, setIsAnimating] = useState(false); const handleCopy = useCallback(async () => { try { await navigator.clipboard.writeText(SSH_COMMAND); setIsAnimating(true); setCopied(true); setTimeout(() => setIsAnimating(false), 150); setTimeout(() => setCopied(false), 2000); } catch (err) { console.error("Failed to copy:", err); } }, []); return (
$ {SSH_COMMAND}
); } export default function Home() { return (
); }