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 */}