Rewrite with modern stack

Major refactor from simple HTML/JS app to modern full-stack TypeScript application:

## Architecture Changes
- Migrated to monorepo structure with workspaces (backend, frontend, shared)
- Backend: Node.js + Express + TypeScript + Socket.IO
- Frontend: Next.js 15.5 + React 19 + TypeScript + Tailwind CSS
- Shared: Common types and utilities across packages

## Key Features Implemented
- Real-time WebSocket collaboration via Socket.IO
- Virtual canvas with chunked loading for performance
- Modern UI with dark mode and responsive design
- Mock database system for easy development (Redis/PostgreSQL compatible)
- Comprehensive error handling and rate limiting
- User presence and cursor tracking
- Infinite canvas support with zoom/pan controls

## Performance Optimizations
- Canvas virtualization - only renders visible viewport
- Chunked pixel data loading (64x64 pixel chunks)
- Optimized WebSocket protocol
- Memory-efficient state management with Zustand

## Development Experience
- Full TypeScript support across all packages
- Hot reload for both frontend and backend
- Docker support for production deployment
- Comprehensive linting and formatting
- Automated development server startup

## Fixed Issues
- Corrected start script paths
- Updated environment configuration
- Fixed ESLint configuration issues
- Ensured all dependencies are properly installed
- Verified build process works correctly

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
martin 2025-08-22 19:28:05 +02:00
commit 98f290a662
69 changed files with 17771 additions and 1589 deletions

View file

@ -0,0 +1,80 @@
'use client';
import { useEffect, useState } from 'react';
import { motion } from 'framer-motion';
interface CooldownTimerProps {
isActive: boolean;
duration: number; // in seconds
onComplete: () => void;
}
export function CooldownTimer({ isActive, duration, onComplete }: CooldownTimerProps) {
const [timeLeft, setTimeLeft] = useState(0);
useEffect(() => {
if (isActive) {
setTimeLeft(duration);
const interval = setInterval(() => {
setTimeLeft((prev) => {
if (prev <= 1) {
clearInterval(interval);
// Use setTimeout to avoid setState during render
setTimeout(() => onComplete(), 0);
return 0;
}
return prev - 1;
});
}, 1000);
return () => clearInterval(interval);
}
}, [isActive, duration, onComplete]);
if (!isActive || timeLeft === 0) return null;
const progress = ((duration - timeLeft) / duration) * 100;
return (
<motion.div
className="fixed top-4 sm:top-6 left-1/2 transform -translate-x-1/2 z-50"
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.8 }}
transition={{
type: "spring",
stiffness: 400,
damping: 25
}}
>
<motion.div
className="bg-white/5 backdrop-blur-2xl rounded-full px-4 sm:px-6 py-2 sm:py-3 border border-white/20 shadow-lg ring-1 ring-white/10"
whileHover={{
scale: 1.05
}}
transition={{ type: "spring", stiffness: 500, damping: 25 }}
>
<div className="flex items-center gap-3 sm:gap-4">
<motion.div
className="text-white font-medium text-sm"
animate={{
color: timeLeft <= 3 ? "#f87171" : "#ffffff"
}}
transition={{ duration: 0.3 }}
>
{timeLeft}s
</motion.div>
<div className="w-16 sm:w-24 h-2 bg-white/20 rounded-full overflow-hidden">
<motion.div
className="h-full bg-gradient-to-r from-cyan-400 to-blue-500 rounded-full"
initial={{ width: 0 }}
animate={{ width: `${progress}%` }}
transition={{ duration: 0.5, ease: "easeOut" }}
/>
</div>
</div>
</motion.div>
</motion.div>
);
}