export function humanFileSize(size: number) { if (size < 1024) return `${size} B`; const i = Math.min(4, Math.floor(Math.log(size) / Math.log(1024))); return `${(size / 1024 ** i).toFixed(2)} ${["B", "KiB", "MiB", "GiB", "TiB"][i]}`; } // TextDecoder-based UTF-8 check that works in both Bun and the browser. export function isValidUTF8(buf: ArrayBuffer | ArrayBufferView): boolean { try { new TextDecoder("utf-8", { fatal: true }).decode(buf); return true; } catch (_e) { return false; } } const MODE_STORAGE_KEY = "zbin-mode"; // Enables the (HTML-disabled) client-side radio now that JS is running, applies // the saved client/server preference (defaulting to client since JS is here), // and persists changes. Shared by the index (encrypt_mode) and show (decrypt_mode) pages. export function setupModeRadios(name: string) { const radios = document.querySelectorAll( `input[name="${name}"]`, ); if (radios.length === 0) return; const saved = localStorage.getItem(MODE_STORAGE_KEY) ?? "client"; for (const radio of radios) { radio.disabled = false; radio.checked = radio.value === saved; radio.addEventListener("change", () => { if (radio.checked) localStorage.setItem(MODE_STORAGE_KEY, radio.value); }); } } export function getMode(name: string): string { const checked = document.querySelector( `input[name="${name}"]:checked`, ); return checked?.value ?? "server"; }