Enhance particle background with more characters and ambient effects

Update particle background to increase particle spawn rate on mouse move, add ambient particle spawning when idle, and increase max particle limit.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: beb495f8-3942-42fd-9d1b-db8f707d320f
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: c2b88424-5180-488e-b61b-c1eb8bc69a5c
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/804258ec-282b-434a-9b89-a7ccc1690e42/beb495f8-3942-42fd-9d1b-db8f707d320f/3Fb2Xuc
Replit-Helium-Checkpoint-Created: true
This commit is contained in:
keshavanandmusi
2025-12-14 23:58:20 +00:00
parent e35f2fff26
commit 5a5a23aaf8
2 changed files with 119 additions and 19 deletions

View File

@@ -24,8 +24,9 @@ interface Particle {
function ParticleBackground() { function ParticleBackground() {
const canvasRef = useRef<HTMLCanvasElement>(null); const canvasRef = useRef<HTMLCanvasElement>(null);
const particlesRef = useRef<Particle[]>([]); const particlesRef = useRef<Particle[]>([]);
const mouseRef = useRef({ x: 0, y: 0 }); const mouseRef = useRef({ x: window.innerWidth / 2, y: window.innerHeight / 2 });
const animationRef = useRef<number>(); const animationRef = useRef<number>();
const frameCountRef = useRef(0);
useEffect(() => { useEffect(() => {
const canvas = canvasRef.current; const canvas = canvasRef.current;
@@ -51,21 +52,22 @@ function ParticleBackground() {
"#cba6f7", "#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 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 color = colors[Math.floor(Math.random() * colors.length)];
const spread = isAmbient ? 200 : 120;
return { return {
x: x + (Math.random() - 0.5) * 100, x: x + (Math.random() - 0.5) * spread,
y: y + (Math.random() - 0.5) * 100, y: y + (Math.random() - 0.5) * spread,
char: CHARACTERS[Math.floor(Math.random() * CHARACTERS.length)], 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, targetX: x,
targetY: y, targetY: y,
vx: Math.cos(angle) * speed, vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed, vy: Math.sin(angle) * speed,
life: 0, life: 0,
maxLife: Math.random() * 60 + 40, maxLife: isAmbient ? Math.random() * 120 + 80 : Math.random() * 80 + 50,
size: Math.random() * 10 + 12, size: Math.random() * 10 + 12,
color, color,
}; };
@@ -74,8 +76,8 @@ function ParticleBackground() {
const handleMouseMove = (e: MouseEvent) => { const handleMouseMove = (e: MouseEvent) => {
mouseRef.current = { x: e.clientX, y: e.clientY }; mouseRef.current = { x: e.clientX, y: e.clientY };
for (let i = 0; i < 3; i++) { for (let i = 0; i < 5; i++) {
if (particlesRef.current.length < 200) { if (particlesRef.current.length < 400) {
particlesRef.current.push(createParticle(e.clientX, e.clientY)); particlesRef.current.push(createParticle(e.clientX, e.clientY));
} }
} }
@@ -84,34 +86,48 @@ function ParticleBackground() {
const animate = () => { const animate = () => {
ctx.fillStyle = "#000000"; ctx.fillStyle = "#000000";
ctx.fillRect(0, 0, canvas.width, canvas.height); 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) => { particlesRef.current = particlesRef.current.filter((p) => {
p.life++; p.life++;
const dx = mouseRef.current.x - p.x; const dx = mouseRef.current.x - p.x;
const dy = mouseRef.current.y - p.y; 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) { if (distance < 200) {
const force = (150 - distance) / 150; const force = (200 - distance) / 200;
p.vx += (dx / distance) * force * 0.3; p.vx += (dx / distance) * force * 0.4;
p.vy += (dy / distance) * force * 0.3; p.vy += (dy / distance) * force * 0.4;
} }
p.vx *= 0.96; p.vx *= 0.97;
p.vy *= 0.96; p.vy *= 0.97;
p.x += p.vx; p.x += p.vx;
p.y += p.vy; p.y += p.vy;
const lifeRatio = p.life / p.maxLife; const lifeRatio = p.life / p.maxLife;
const fadeOpacity = lifeRatio < 0.2 const fadeOpacity = lifeRatio < 0.15
? lifeRatio * 5 ? lifeRatio * 6.67
: lifeRatio > 0.7 : lifeRatio > 0.7
? (1 - lifeRatio) * 3.33 ? (1 - lifeRatio) * 3.33
: 1; : 1;
const distanceOpacity = Math.max(0, 1 - distance / 300); const distanceOpacity = Math.max(0.15, 1 - distance / 400);
const finalOpacity = p.opacity * fadeOpacity * distanceOpacity; const finalOpacity = p.opacity * fadeOpacity * distanceOpacity;
if (finalOpacity > 0.01) { if (finalOpacity > 0.01) {

84
replit.md Normal file
View File

@@ -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