diff --git a/ui/src/game/Game.svelte b/ui/src/game/Game.svelte
index 735c3ff..e5cc082 100644
--- a/ui/src/game/Game.svelte
+++ b/ui/src/game/Game.svelte
@@ -13,6 +13,7 @@
import { navigate } from '../lib/router.svelte';
import { app, handleError, showToast, markChatRead, seedChatUnread } from '../lib/app.svelte';
import { connection } from '../lib/connection.svelte';
+ import { offlineMode } from '../lib/offline.svelte';
import { GatewayError } from '../lib/client';
import { t, type MessageKey } from '../lib/i18n/index.svelte';
import type { EvalResult, MoveRecord, MoveResult, StateView, Tile } from '../lib/model';
@@ -52,6 +53,11 @@
// screen calls the game-loop methods (state/history/submit/pass/exchange/resign/hint/evaluate/
// draft) through it, so the same UI drives a local vs_ai game and an online one alike.
const source = $derived(gameSource(id));
+ // A local (offline) game plays entirely on-device, so it is always ready; an online game needs a
+ // live connection — and offline mode's kill switch refuses its calls — so its network actions are
+ // disabled while disconnected or in offline mode (which also stops the "something went wrong"
+ // toasts a blocked call would otherwise raise).
+ const netReady = $derived(isLocalGameId(id) || (connection.online && !offlineMode.active));
// Unsubscribes from a local game's robot-reply events (offline only; null for a network game).
let localUnsub: (() => void) | null = null;
@@ -1481,19 +1487,19 @@
/>
{#if !gameOver && placement.pending.length > 0 && !recallOverRack}
-
+
{/if}
{/snippet}
{#snippet controlButtons()}
-