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: window.innerWidth / 2, y: window.innerHeight / 2, }); const animationRef = useRef(); const frameCountRef = useRef(0); 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, isAmbient = false ): Particle => { const angle = Math.random() * Math.PI * 2; const speed = isAmbient ? Math.random() * 0.5 + 0.2 : Math.random() * 2 + 1; const color = colors[Math.floor(Math.random() * colors.length)]; const spread = isAmbient ? 200 : 120; return { x: x + (Math.random() - 0.5) * spread, y: y + (Math.random() - 0.5) * spread, char: CHARACTERS[Math.floor(Math.random() * CHARACTERS.length)], opacity: isAmbient ? Math.random() * 0.4 + 0.1 : Math.random() * 0.8 + 0.2, targetX: x, targetY: y, vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed, life: 0, maxLife: isAmbient ? Math.random() * 120 + 80 : Math.random() * 80 + 50, size: Math.random() * 10 + 12, color, }; }; const handleMouseMove = (e: MouseEvent) => { mouseRef.current = { x: e.clientX, y: e.clientY }; for (let i = 0; i < 5; i++) { if (particlesRef.current.length < 400) { particlesRef.current.push(createParticle(e.clientX, e.clientY)); } } }; const animate = () => { ctx.fillStyle = "#000000"; ctx.fillRect(0, 0, canvas.width, canvas.height); frameCountRef.current++; if ( frameCountRef.current % 3 === 0 && particlesRef.current.length < 400 ) { const mx = mouseRef.current.x; const my = mouseRef.current.y; particlesRef.current.push(createParticle(mx, my, true)); } if ( frameCountRef.current % 8 === 0 && particlesRef.current.length < 400 ) { const rx = Math.random() * canvas.width; const ry = Math.random() * canvas.height; particlesRef.current.push(createParticle(rx, ry, true)); } 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) || 1; if (distance < 200) { const force = (200 - distance) / 200; p.vx += (dx / distance) * force * 0.4; p.vy += (dy / distance) * force * 0.4; } p.vx *= 0.97; p.vy *= 0.97; p.x += p.vx; p.y += p.vy; const lifeRatio = p.life / p.maxLife; const fadeOpacity = lifeRatio < 0.15 ? lifeRatio * 6.67 : lifeRatio > 0.7 ? (1 - lifeRatio) * 3.33 : 1; const distanceOpacity = Math.max(0.15, 1 - distance / 400); 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 [showHowItWorks, setShowHowItWorks] = useState(false); useEffect(() => { const timer = setTimeout(() => { setShowHowItWorks(true); }, 2000); return () => clearTimeout(timer); }, []); 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 (
); }