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:
@@ -24,8 +24,9 @@ interface Particle {
|
||||
function ParticleBackground() {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
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 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) {
|
||||
|
||||
84
replit.md
Normal file
84
replit.md
Normal 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
|
||||
Reference in New Issue
Block a user