import { Capacitor } from "@capacitor/core"; import { useMemo } from "react"; import { checkForContentUpdates, contentPackHasUpdate, contentPackIsBundled, contentPackIsInstalled, installContentPack, removeContentPack, } from "../content/contentManager"; import { useContentManagerSnapshot } from "../content/useContentManager"; import { DualDisplayFrame } from "../components/DualDisplayFrame"; import { LazyLocalCompanionPanel } from "./LazyLocalCompanionPanel"; import { BrandMark, FrontSurface } from "./FrontSurface"; function formatBytes(bytes: number): string { if (bytes >= 1024 ** 3) return `${(bytes / 1024 ** 3).toFixed(2)} GB`; if (bytes >= 1024 ** 2) return `${(bytes / 1024 ** 2).toFixed(1)} MB`; if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)} KB`; return `${bytes} B`; } export function ContentManagerScreen({ onClose }: { readonly onClose: () => void }) { const state = useContentManagerSnapshot(); const native = Capacitor.isNativePlatform(); const packs = useMemo(() => state.manifest?.packs ?? state.bundledManifest?.packs ?? [], [state.bundledManifest, state.manifest]); const progress = state.totalBytes > 0 ? Math.min(1, state.downloadedBytes / state.totalBytes) : 0; return (
Main menu / DownloadsOffline content

Local-first installation

Choose what stays on this device

Installed content packs work without a server connection. Updates download to a temporary staging area, verify their SHA-256, and only then replace the active version.

{!native &&

Basic content is included. Optional packs can be downloaded for offline play and removed when you want the storage back.

}
{state.manifest ? `Catalog ${state.manifest.version}` : "Offline catalog unavailable"}
{state.installingPackId && (
{formatBytes(state.downloadedBytes)} / {formatBytes(state.totalBytes)}
)} {state.error &&

{state.error}

}
{packs.length === 0 &&

Connect once to discover optional downloads. Bundled starter content remains playable offline.

} {packs.map((pack) => { const installed = contentPackIsInstalled(pack.id); const update = contentPackHasUpdate(pack.id); const bundled = contentPackIsBundled(pack.id); const busy = state.installingPackId !== null; return (
{pack.label} {formatBytes(pack.downloadBytes ?? pack.bytes)} download / {pack.fileCount} files
{update ? "Update available" : bundled ? "Included — always available" : installed ? "Installed — offline ready" : "Not installed"}
{(!installed || update) && ( )} {installed && !bundled && ( )}
); })}
} bottom={} /> ); }