diff --git a/client/src/pages/home.tsx b/client/src/pages/home.tsx index abd71b7..7d1dd99 100644 --- a/client/src/pages/home.tsx +++ b/client/src/pages/home.tsx @@ -24,8 +24,9 @@ interface Particle { function ParticleBackground() { const canvasRef = useRef(null); const particlesRef = useRef([]); - const mouseRef = useRef({ x: 0, y: 0 }); + const mouseRef = useRef({ x: window.innerWidth / 2, y: window.innerHeight / 2 }); const animationRef = useRef(); + const frameCountRef = useRef(0); useEffect(() => { const canvas = canvasRef.current; @@ -51,21 +52,22 @@ function ParticleBackground() { "#cba6f7", ]; - const createParticle = (x: number, y: number): Particle => { + const createParticle = (x: number, y: number, isAmbient = false): Particle => { const angle = Math.random() * Math.PI * 2; - const speed = Math.random() * 2 + 1; + 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) * 100, - y: y + (Math.random() - 0.5) * 100, + x: x + (Math.random() - 0.5) * spread, + y: y + (Math.random() - 0.5) * spread, char: CHARACTERS[Math.floor(Math.random() * CHARACTERS.length)], - opacity: Math.random() * 0.8 + 0.2, + 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: Math.random() * 60 + 40, + maxLife: isAmbient ? Math.random() * 120 + 80 : Math.random() * 80 + 50, size: Math.random() * 10 + 12, color, }; @@ -74,8 +76,8 @@ function ParticleBackground() { const handleMouseMove = (e: MouseEvent) => { mouseRef.current = { x: e.clientX, y: e.clientY }; - for (let i = 0; i < 3; i++) { - if (particlesRef.current.length < 200) { + for (let i = 0; i < 5; i++) { + if (particlesRef.current.length < 400) { particlesRef.current.push(createParticle(e.clientX, e.clientY)); } } @@ -84,34 +86,48 @@ function ParticleBackground() { 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); + const distance = Math.sqrt(dx * dx + dy * dy) || 1; - if (distance < 150) { - const force = (150 - distance) / 150; - p.vx += (dx / distance) * force * 0.3; - p.vy += (dy / distance) * force * 0.3; + 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.96; - p.vy *= 0.96; + 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.2 - ? lifeRatio * 5 + const fadeOpacity = lifeRatio < 0.15 + ? lifeRatio * 6.67 : lifeRatio > 0.7 ? (1 - lifeRatio) * 3.33 : 1; - const distanceOpacity = Math.max(0, 1 - distance / 300); + const distanceOpacity = Math.max(0.15, 1 - distance / 400); const finalOpacity = p.opacity * fadeOpacity * distanceOpacity; if (finalOpacity > 0.01) { diff --git a/replit.md b/replit.md new file mode 100644 index 0000000..29bcc18 --- /dev/null +++ b/replit.md @@ -0,0 +1,84 @@ +# Terminal Portfolio Website + +## Overview + +A minimalist single-page portfolio website featuring an interactive particle background with terminal/matrix aesthetics. The site displays an SSH command for connecting to the portfolio owner, with cursor-following character effects using the Catppuccin Mocha color scheme. + +The application follows a full-stack architecture with a React frontend and Express backend, using TypeScript throughout. + +## User Preferences + +Preferred communication style: Simple, everyday language. + +## System Architecture + +### Frontend Architecture +- **Framework**: React 18 with TypeScript +- **Routing**: Wouter for lightweight client-side routing +- **Styling**: Tailwind CSS with custom Catppuccin Mocha theme variables +- **UI Components**: shadcn/ui component library (New York style variant) +- **State Management**: TanStack React Query for server state +- **Build Tool**: Vite with React plugin + +The frontend is a single-page application centered around an HTML5 canvas particle effect that follows mouse movement, displaying random terminal characters. The main interactive element is an SSH command display with a copy-to-clipboard button. + +### Backend Architecture +- **Runtime**: Node.js with Express +- **Language**: TypeScript (ESM modules) +- **API Pattern**: RESTful routes prefixed with `/api` +- **Storage**: Abstracted storage interface with in-memory implementation (MemStorage), designed for easy swap to database + +The server handles static file serving in production and proxies to Vite dev server in development. Routes are registered in `server/routes.ts`. + +### Data Layer +- **ORM**: Drizzle ORM with PostgreSQL dialect +- **Schema**: Defined in `shared/schema.ts` using Drizzle's table definitions +- **Validation**: Zod schemas generated from Drizzle schemas via drizzle-zod +- **Migrations**: Managed via `drizzle-kit push` + +Currently includes a basic users table for authentication scaffolding. + +### Project Structure +``` +client/ # React frontend + src/ + components/ # UI components (shadcn/ui) + pages/ # Route components + hooks/ # Custom React hooks + lib/ # Utilities and query client +server/ # Express backend + index.ts # Server entry point + routes.ts # API route definitions + storage.ts # Data access layer + vite.ts # Vite dev server integration +shared/ # Shared types and schemas + schema.ts # Drizzle database schema +``` + +### Design System +- **Theme**: Catppuccin Mocha (dark-only) +- **Typography**: JetBrains Mono monospace font +- **Colors**: CSS custom properties for consistent theming +- **Layout**: Full viewport single-screen design, centered content + +## External Dependencies + +### Database +- **PostgreSQL**: Primary database (configured via `DATABASE_URL` environment variable) +- **Drizzle ORM**: Database toolkit for type-safe queries + +### Frontend Libraries +- **Radix UI**: Headless UI primitives for accessible components +- **Lucide React**: Icon library +- **class-variance-authority**: Component variant management +- **embla-carousel-react**: Carousel functionality +- **react-day-picker**: Calendar component + +### Backend Libraries +- **express-session**: Session management +- **connect-pg-simple**: PostgreSQL session store + +### Build & Development +- **Vite**: Frontend build tool with HMR +- **esbuild**: Server bundling for production +- **tsx**: TypeScript execution for development \ No newline at end of file