diff --git a/api/src/games/ws-handler.ts b/api/src/games/ws-handler.ts index 57ea12a..b34d065 100644 --- a/api/src/games/ws-handler.ts +++ b/api/src/games/ws-handler.ts @@ -77,15 +77,19 @@ export function handleGameMessage(msg: GameWsClientMessage, ctx: WsContext): voi } const spectatorView = roomManager.getSpectatorView(msg.roomId); - ctx.publish(`room:${msg.roomId}`, JSON.stringify({ type: "GAME_UPDATE", roomId: msg.roomId, state: spectatorView })); + const updateMsg = JSON.stringify({ type: "GAME_UPDATE", roomId: msg.roomId, state: spectatorView }); + ctx.publish(`room:${msg.roomId}`, updateMsg); + ctx.send(updateMsg); if (result.gameOver) { - ctx.publish(`room:${msg.roomId}`, JSON.stringify({ + const endedMsg = JSON.stringify({ type: "GAME_ENDED", roomId: msg.roomId, winner: result.gameOver.winner, reason: result.gameOver.reason, - })); + }); + ctx.publish(`room:${msg.roomId}`, endedMsg); + ctx.send(endedMsg); ctx.publish("lobby", JSON.stringify({ type: "ROOM_LIST_UPDATE", rooms: roomManager.listRooms() })); } break; diff --git a/panel/src/components/Layout.tsx b/panel/src/components/Layout.tsx index f4231d2..a161919 100644 --- a/panel/src/components/Layout.tsx +++ b/panel/src/components/Layout.tsx @@ -13,8 +13,10 @@ import { ChevronRight, Gamepad2, Trophy, + Menu, + X, } from "lucide-react"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import { useLocation, useNavigate } from "react-router-dom"; import { cn } from "../lib/utils"; import type { AuthUser } from "../lib/useAuth"; @@ -54,6 +56,7 @@ export default function Layout({ children: React.ReactNode; }) { const [collapsed, setCollapsed] = useState(false); + const [mobileOpen, setMobileOpen] = useState(false); const location = useLocation(); const navigate = useNavigate(); @@ -63,6 +66,11 @@ export default function Layout({ ? `https://cdn.discordapp.com/avatars/${user.discordId}/${user.avatar}.png?size=64` : null; + // Close mobile drawer on route change + useEffect(() => { + setMobileOpen(false); + }, [location.pathname]); + function isActive(path: string): boolean { if (path === "/admin" && location.pathname === "/admin") return true; if (path === "/dashboard" && location.pathname === "/dashboard") return true; @@ -70,75 +78,123 @@ export default function Layout({ return false; } + function handleNav(path: string) { + navigate(path); + setMobileOpen(false); + } + + const sidebarContent = ( + <> + + +
+ {(!collapsed || mobileOpen) && ( +
+ {avatarUrl ? ( + {user.username} + ) : ( +
+ {user.username[0]?.toUpperCase()} +
+ )} +
+
{user.username}
+
+
+ )} +
+ + {/* Collapse toggle only on desktop */} + +
+
+ + ); + return (
+ {/* Mobile header bar */} +
+ +
Aurora
+
+ + {/* Mobile overlay */} + {mobileOpen && ( +
setMobileOpen(false)} + /> + )} + + {/* Sidebar - mobile drawer + desktop fixed */} -
-
+
+
{children}
diff --git a/panel/src/games/GameLobby.tsx b/panel/src/games/GameLobby.tsx index 784a3e1..0af74ec 100644 --- a/panel/src/games/GameLobby.tsx +++ b/panel/src/games/GameLobby.tsx @@ -49,20 +49,20 @@ export function GameLobby() { return (
-
-
+
+

Games

-

Browse and create game rooms

+

Browse and create game rooms

-
+
{showCreate && ( -
setShowCreate(false)}> -
e.stopPropagation()}> +
setShowCreate(false)}> +
e.stopPropagation()}>

Create a Room

{gameTypes.map(g => ( diff --git a/panel/src/games/GameRoom.tsx b/panel/src/games/GameRoom.tsx index 15ab19e..683cd60 100644 --- a/panel/src/games/GameRoom.tsx +++ b/panel/src/games/GameRoom.tsx @@ -50,11 +50,11 @@ export function GameRoom({ userId }: { userId: string }) { return (
-
-
- {plugin.icon} -
-

{plugin.name}

+
+
+ {plugin.icon} +
+

{plugin.name}

@@ -94,12 +94,13 @@ export function GameRoom({ userId }: { userId: string }) { )} {roomStatus === "waiting" && ( -
+
Waiting for players ({players.length}/2)
- Share this URL to invite: {window.location.href} + Share this URL to invite: + {window.location.href}
)} diff --git a/panel/src/games/chess/ChessBoard.tsx b/panel/src/games/chess/ChessBoard.tsx index 4d5ba7e..d1b7b81 100644 --- a/panel/src/games/chess/ChessBoard.tsx +++ b/panel/src/games/chess/ChessBoard.tsx @@ -1,4 +1,4 @@ -import { useState, useMemo } from "react"; +import { useState, useMemo, useRef, useEffect } from "react"; import { Chessboard } from "react-chessboard"; import { Chess } from "chess.js"; import type { GameUIProps } from "../registry"; @@ -15,6 +15,22 @@ export function ChessBoard({ state, myPlayerId, isSpectator, onAction, players } const chess = state as ChessState; const [promotionFrom, setPromotionFrom] = useState(null); const [promotionTo, setPromotionTo] = useState(null); + const containerRef = useRef(null); + const [boardWidth, setBoardWidth] = useState(400); + + // Responsive board sizing + useEffect(() => { + const container = containerRef.current; + if (!container) return; + + const observer = new ResizeObserver((entries) => { + const width = entries[0]?.contentRect.width ?? 400; + // Cap board at 400px, floor at 280px + setBoardWidth(Math.max(280, Math.min(400, width))); + }); + observer.observe(container); + return () => observer.disconnect(); + }, []); const game = useMemo(() => { if (!chess?.fen) return null; @@ -89,7 +105,6 @@ export function ChessBoard({ state, myPlayerId, isSpectator, onAction, players } // Highlight king in check const customSquareStyles: Record = {}; if (game.inCheck()) { - // Find the king square of the player in check const board = game.board(); const kingColor = game.turn(); for (let r = 0; r < 8; r++) { @@ -108,8 +123,9 @@ export function ChessBoard({ state, myPlayerId, isSpectator, onAction, players } } return ( -
-
+
+ {/* Board column */} +
{/* Opponent info */}
@@ -126,7 +142,7 @@ export function ChessBoard({ state, myPlayerId, isSpectator, onAction, players } onPromotionPieceSelect={handlePromotion} boardOrientation={boardOrientation} isDraggablePiece={isDraggablePiece} - boardWidth={400} + boardWidth={boardWidth} showPromotionDialog={promotionFrom !== null} promotionToSquare={promotionTo as any} animationDuration={200} @@ -153,11 +169,11 @@ export function ChessBoard({ state, myPlayerId, isSpectator, onAction, players }
- {/* Sidebar */} -
+ {/* Sidebar - stacks below on mobile */} +
Move History
-
+
{chess.moveHistory.length === 0 ? (
No moves yet
) : (