diff --git a/src/components/GameTable.tsx b/src/components/GameTable.tsx index 6974203..f91178e 100644 --- a/src/components/GameTable.tsx +++ b/src/components/GameTable.tsx @@ -57,50 +57,27 @@ export function GameTable({ const mode = useGameStore((s) => s.mode); const seatPlayers = useGameStore((s) => s.seatPlayers); const { t } = useI18n(); - - // While waiting for the first server state broadcast, show a spinner instead - // of an empty green felt — prevents the "3 colored dots / stuck table" bug. - const connecting = mode === "online" && seatPlayers.length === 0; - if (connecting) { - return ( -
-
- -

{t("mm.searching")}

-
-
- ); - } + // All hooks must be declared unconditionally before any early return. const [askFf, setAskFf] = useState(false); - const sfx = useSoundStore((s) => s.sfx); const music = useSoundStore((s) => s.music); const toggleAll = useSoundStore((s) => s.toggleAll); - const muted = !sfx && !music; - - const exit = onExit ?? reset; const vw = useViewportWidth(); - // Pull the played-card pile inward on narrow screens so it clears the side stacks. - const trickScale = vw < 400 ? 0.82 : 1; - // Smaller played cards on phones so the center pile stays clear of the side seats. - const trickCardSize: "sm" | "md" = vw < 480 ? "sm" : "md"; + const playHuman = useGameStore((s) => s.playHuman); + const chooseTrump = useGameStore((s) => s.chooseTrump); const { phase, players, hakem, trump, turn, currentTrick } = game; - const legalMovesList = useMemo( () => (phase === "playing" && turn === 0 ? legalMoves(game, 0) : []), [phase, turn, game] ); const legalIds = new Set(legalMovesList.map((c) => c.id)); - // Keyboard shortcuts (desktop): 1–9 / 0 play the Nth playable card in hand - // order, Space/Enter play the first playable card, M mutes, F forfeits, - // Esc/Q quits. A floating hint lists them. - const playHuman = useGameStore((s) => s.playHuman); - const chooseTrump = useGameStore((s) => s.chooseTrump); + // Derived (non-hook) values — safe to compute after all hooks. + const muted = !sfx && !music; + const exit = onExit ?? reset; + const trickScale = vw < 400 ? 0.82 : 1; + const trickCardSize: "sm" | "md" = vw < 480 ? "sm" : "md"; + useEffect(() => { const onKey = (e: KeyboardEvent) => { const el = e.target as HTMLElement | null; @@ -133,6 +110,23 @@ export function GameTable({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [phase, turn, hakem, game.players, legalMovesList]); + // Connecting overlay: all hooks are already called above, so this early + // return is safe and won't cause a "more/fewer hooks" error on re-render. + if (mode === "online" && seatPlayers.length === 0) { + return ( +
+
+ +

{t("mm.searching")}

+
+
+ ); + } + return (
{/* Top HUD */}