// The deliberate offline MODE: a sticky, device-scoped reactive flag the app reads to gate the // network, tint the chrome blue and show only local games. It is the player's own choice (the // Settings toggle is the source of truth), distinct from connection.svelte.ts's transient // gateway-reachability signal. The pure persistence + readiness logic lives in offline.ts. import { loadOfflinePref, saveOfflinePref, offlinePreloadEligible } from './offline'; import { isStandalone } from './pwa'; import { insideTelegram } from './telegram'; import { insideVK } from './vk'; import type { Profile } from './model'; // Not named `state` (a svelte-check hazard: `$state` would then read as a store subscription). let active = $state(loadOfflinePref()); /** offlineMode exposes the reactive deliberate-offline flag; read it in markup / $derived. */ export const offlineMode = { /** active is true while the app is in deliberate offline mode. */ get active(): boolean { return active; }, }; /** setOfflineMode enters or leaves offline mode and persists the choice (device-scoped). */ export function setOfflineMode(on: boolean): void { active = on; saveOfflinePref(on); } // The dict-preload warning: true when a first-lobby background preload could not fetch every // enabled variant's dictionary (typically a poor connection), so offline mode may be incomplete. // The lobby shows a notice in place of the ad banner while it holds. let preloadWarn = $state(false); /** dictPreloadWarning exposes the reactive preload-failure flag; read it in markup / $derived. */ export const dictPreloadWarning = { /** active is true while a first-lobby dictionary preload has left a variant unavailable. */ get active(): boolean { return preloadWarn; }, }; /** setDictPreloadWarning raises or clears the in-lobby preload-failure notice. */ export function setDictPreloadWarning(on: boolean): void { preloadWarn = on; } // A preload runs at most once at a time; it finishes before a fresh trigger (a repeated lobby // mount or a variant-preference change) can start another. Module-scoped so mounts do not stack. let preloadInFlight = false; /** * kickDictPreload starts a background preload of the enabled variants' dictionaries for an * offline-capable install (a standalone web PWA with a confirmed email) while online, so a later * switch to offline mode already has the data. It is a no-op in a Telegram/VK mini-app, in a plain * browser tab, without a confirmed email, while offline, or when a preload is already running; * getDawg's caching makes a repeat run cheap. When warnOnFail is set (the first lobby entry), a * fetch failure raises the in-lobby notice, and a later successful run clears it. The dict loader * and generator are imported dynamically, so neither is pulled into the main bundle. */ export function kickDictPreload(prof: Profile | null, warnOnFail = false): void { if (preloadInFlight || !prof) return; const eligible = offlinePreloadEligible({ hasEmail: !!prof.email, standalone: isStandalone(), inTelegram: insideTelegram(), inVK: insideVK(), online: typeof navigator === 'undefined' || navigator.onLine !== false, }); if (!eligible) return; preloadInFlight = true; void import('./dict/preloadrun') .then((m) => m.runPreload(prof, warnOnFail)) .catch(() => { /* best-effort warmup — a failed dynamic import just leaves offline data unprimed */ }) .finally(() => { preloadInFlight = false; }); }