diff --git a/client/src/components/canvas.tsx b/client/src/components/canvas.tsx index 87c0b5b..8ec9277 100644 --- a/client/src/components/canvas.tsx +++ b/client/src/components/canvas.tsx @@ -57,6 +57,21 @@ export function Canvas({ setZoom(1); }; + const handleWheel = (e: React.WheelEvent) => { + e.preventDefault(); + + const zoomFactor = 1.1; + const delta = e.deltaY; + + if (delta < 0) { + // Rein zoomen + setZoom(prev => Math.min(prev * zoomFactor, 3)); + } else { + // Raus zoomen + setZoom(prev => Math.max(prev / zoomFactor, 0.5)); + } + }; + useEffect(() => { setPixelSize(Math.max(2, 8 * zoom)); }, [zoom]); @@ -71,11 +86,12 @@ export function Canvas({
{/* Coordinate System Container */} -
+
{/* Top X-axis coordinates */}
{Array.from({ length: Math.ceil(canvasWidth / 10) }, (_, i) => ( @@ -124,7 +140,7 @@ export function Canvas({
void) { const [isConnected, setIsConnected] = useState(false); const [userCount, setUserCount] = useState(0); const ws = useRef(null); + const reconnectTimer = useRef(null); + const reconnectAttempts = useRef(0); + + const connect = () => { + try { + const protocol = window.location.protocol === "https:" ? "wss:" : "ws:"; + const wsUrl = `${protocol}//${window.location.host}/ws`; + + console.log(`Connecting to WebSocket: ${wsUrl}`); + ws.current = new WebSocket(wsUrl); + + ws.current.onopen = () => { + console.log("WebSocket connected"); + setIsConnected(true); + reconnectAttempts.current = 0; + }; + + ws.current.onmessage = (event) => { + try { + const message: WSMessage = JSON.parse(event.data); + + if (message.type === "user_count") { + setUserCount(message.data.count); + } + + onMessage(message); + } catch (error) { + console.error("Failed to parse WebSocket message:", error); + } + }; + + ws.current.onclose = (event) => { + console.log("WebSocket disconnected:", event.code, event.reason); + setIsConnected(false); + + // Automatically reconnect after a delay + if (reconnectAttempts.current < 5) { + const delay = Math.min(1000 * Math.pow(2, reconnectAttempts.current), 30000); + console.log(`Reconnecting in ${delay}ms...`); + reconnectTimer.current = setTimeout(() => { + reconnectAttempts.current++; + connect(); + }, delay); + } + }; + + ws.current.onerror = (error) => { + console.error("WebSocket error:", error); + setIsConnected(false); + }; + } catch (error) { + console.error("Failed to create WebSocket connection:", error); + setIsConnected(false); + } + }; useEffect(() => { - const protocol = window.location.protocol === "https:" ? "wss:" : "ws:"; - const wsUrl = `${protocol}//${window.location.host}/ws`; - - ws.current = new WebSocket(wsUrl); - - ws.current.onopen = () => { - setIsConnected(true); - }; - - ws.current.onmessage = (event) => { - try { - const message: WSMessage = JSON.parse(event.data); - - if (message.type === "user_count") { - setUserCount(message.data.count); - } - - onMessage(message); - } catch (error) { - console.error("Failed to parse WebSocket message:", error); - } - }; - - ws.current.onclose = () => { - setIsConnected(false); - }; - - ws.current.onerror = () => { - setIsConnected(false); - }; + connect(); return () => { + if (reconnectTimer.current) { + clearTimeout(reconnectTimer.current); + } if (ws.current) { ws.current.close(); } diff --git a/client/src/index.css b/client/src/index.css index 9c43758..e56af89 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -113,15 +113,7 @@ color: var(--muted); } -.pixel { - transition: all 0.1s ease; -} - -.pixel:hover { - transform: scale(1.1); - z-index: 10; - position: relative; -} +/* Entfernt - jetzt unten definiert */ @@ -139,6 +131,33 @@ 100% { background-position: 20px 20px; } } +/* Smooth scrolling für den gesamten Container */ +.scroll-smooth { + scroll-behavior: smooth; +} + +/* Canvas zoom transitions */ +.canvas-zoom { + transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1); +} + +/* Geschmeidiges Mausrad-Scrolling */ +.canvas-container { + scroll-behavior: smooth; +} + +/* Optimierte Pixel-Hover-Effekte */ +.pixel { + transition: transform 0.15s ease-out, box-shadow 0.15s ease-out; +} + +.pixel:hover { + transform: scale(1.1); + z-index: 10; + position: relative; + box-shadow: 0 0 8px rgba(255, 255, 255, 0.3); +} + /* Toast animations */ .toast-enter { animation: slideInRight 0.3s ease-out; diff --git a/exports/canvas-2025-08-18T12-34-32-373Z.svg b/exports/canvas-2025-08-18T12-34-32-373Z.svg new file mode 100644 index 0000000..d00a164 --- /dev/null +++ b/exports/canvas-2025-08-18T12-34-32-373Z.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/exports/canvas-2025-08-18T12-35-32-373Z.svg b/exports/canvas-2025-08-18T12-35-32-373Z.svg new file mode 100644 index 0000000..d00a164 --- /dev/null +++ b/exports/canvas-2025-08-18T12-35-32-373Z.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/exports/canvas-2025-08-18T12-36-32-373Z.svg b/exports/canvas-2025-08-18T12-36-32-373Z.svg new file mode 100644 index 0000000..6bdc3c1 --- /dev/null +++ b/exports/canvas-2025-08-18T12-36-32-373Z.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/exports/canvas-2025-08-18T12-37-32-373Z.svg b/exports/canvas-2025-08-18T12-37-32-373Z.svg new file mode 100644 index 0000000..0d6ad06 --- /dev/null +++ b/exports/canvas-2025-08-18T12-37-32-373Z.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/server/routes.ts b/server/routes.ts index 188cab4..4f1d37f 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -128,20 +128,42 @@ export async function registerRoutes(app: Express): Promise { }); } - wss.on('connection', (ws) => { + wss.on('connection', (ws, req) => { + console.log(`New WebSocket connection from ${req.socket.remoteAddress}`); connectedUsers.add(ws); broadcastUserCount(); - ws.on('close', () => { + // Send current user count immediately + ws.send(JSON.stringify({ + type: "user_count", + data: { count: connectedUsers.size }, + })); + + ws.on('close', (code, reason) => { + console.log(`WebSocket disconnected: ${code} ${reason}`); connectedUsers.delete(ws); broadcastUserCount(); }); - ws.on('error', () => { + ws.on('error', (error) => { + console.error('WebSocket error:', error); connectedUsers.delete(ws); broadcastUserCount(); }); + + ws.on('pong', () => { + // Keep connection alive + }); }); + // Keep connections alive with ping/pong + const pingInterval = setInterval(() => { + connectedUsers.forEach(ws => { + if (ws.readyState === WebSocket.OPEN) { + ws.ping(); + } + }); + }, 30000); + return httpServer; }