let selectedProduct = null; // "stick" | "download" let selectedProductIds = new Set(); // "multiboot" let selectedDevice = null; let catalogItems = []; let libraryIds = new Set(); // Produkt-IDs die schon lokal (dauerhaft) vorhanden sind let mode = "stick"; // "stick" | "download" | "multiboot" const MULTIBOOT_MAX = 8; const step1Title = document.getElementById("step1Title"); const versionList = document.getElementById("versionList"); const versionDetail = document.getElementById("versionDetail"); const catalogStatus = document.getElementById("catalogStatus"); const deviceSection = document.getElementById("deviceSection"); const deviceList = document.getElementById("deviceList"); const confirmPath = document.getElementById("confirmPath"); const stickConfirm = document.getElementById("stickConfirm"); const downloadHint = document.getElementById("downloadHint"); const multibootHint = document.getElementById("multibootHint"); const step3Title = document.getElementById("step3Title"); const startBtn = document.getElementById("startBtn"); const progressCard = document.getElementById("progressCard"); const progressFill = document.getElementById("progressFill"); const progressPhase = document.getElementById("progressPhase"); const logEl = document.getElementById("log"); const libraryStatus = document.getElementById("libraryStatus"); const modeStickBtn = document.getElementById("modeStick"); const modeDownloadBtn = document.getElementById("modeDownload"); const modeMultibootBtn = document.getElementById("modeMultiboot"); function fmtSize(bytes) { if (!bytes) return "?"; const gb = bytes / 1e9; return gb >= 1 ? gb.toFixed(1) + " GB" : (bytes / 1e6).toFixed(0) + " MB"; } function setMode(next) { mode = next; modeStickBtn.classList.toggle("active", mode === "stick"); modeDownloadBtn.classList.toggle("active", mode === "download"); modeMultibootBtn.classList.toggle("active", mode === "multiboot"); const needsDevice = mode === "stick" || mode === "multiboot"; deviceSection.style.display = needsDevice ? "" : "none"; stickConfirm.style.display = needsDevice ? "" : "none"; downloadHint.style.display = mode === "download" ? "" : "none"; multibootHint.style.display = mode === "multiboot" ? "" : "none"; step1Title.textContent = mode === "multiboot" ? "1. macOS-Versionen (2-8 auswaehlen)" : "1. macOS-Version"; step3Title.textContent = mode === "download" ? "3. Herunterladen" : "3. Bestaetigen & Schreiben"; startBtn.textContent = mode === "download" ? "Image herunterladen" : (mode === "multiboot" ? "Multiboot-Stick erstellen" : "Stick erstellen"); renderVersionOptions(); updateStartEnabled(); } modeStickBtn.addEventListener("click", () => setMode("stick")); modeDownloadBtn.addEventListener("click", () => setMode("download")); modeMultibootBtn.addEventListener("click", () => setMode("multiboot")); async function loadLibrary() { const res = await fetch("/api/library"); const data = await res.json(); libraryIds = new Set((data.items || []).map((it) => it.id)); const count = (data.items || []).length; const size = fmtSize(data.total_bytes || 0); libraryStatus.textContent = count ? `${count} Installer bereits dauerhaft lokal vorhanden -- ${size} in ./downloads/library/` : "Noch keine Installer dauerhaft lokal gespeichert."; renderVersionOptions(); } function updateMultibootSummary() { const items = catalogItems.filter((i) => selectedProductIds.has(i.id)); const totalBytes = items.reduce((sum, i) => sum + (i.size_bytes || 0), 0); versionDetail.textContent = items.length ? `${items.length} Versionen ausgewaehlt, Original-Images zusammen ca. ${fmtSize(totalBytes)} -- Stick muss inkl. Partitions-Puffer entsprechend groesser sein.` : `Waehle 2-${MULTIBOOT_MAX} macOS-Versionen fuer den Multiboot-Stick aus.`; } function renderVersionOptions() { versionList.innerHTML = ""; for (const item of catalogItems) { const row = document.createElement("label"); row.className = "version-row"; const input = document.createElement("input"); const modeTag = item.mode === "recovery" ? " [experimentell]" : ""; const offlineTag = libraryIds.has(item.id) ? " -- bereits offline vorhanden" : ""; const labelText = `${item.title} ${item.version} - ${fmtSize(item.size_bytes)}${modeTag}${offlineTag}`; if (mode === "multiboot") { input.type = "checkbox"; input.checked = selectedProductIds.has(item.id); input.addEventListener("change", () => { if (input.checked) { if (selectedProductIds.size >= MULTIBOOT_MAX) { input.checked = false; return; } selectedProductIds.add(item.id); } else { selectedProductIds.delete(item.id); } row.classList.toggle("selected", input.checked); updateMultibootSummary(); updateStartEnabled(); }); row.classList.toggle("selected", input.checked); } else { input.type = "radio"; input.name = "versionPick"; input.checked = !!selectedProduct && selectedProduct.id === item.id; input.addEventListener("change", () => { selectedProduct = item; const modeText = item.mode === "recovery" ? "Big Sur+ Pfad: experimentell, Extraktion kann fehlschlagen." : "Klassischer dd-Pfad: robust und gut getestet."; const offlineText = libraryIds.has(item.id) ? " | bereits offline vorhanden, kein erneuter Download noetig." : ""; versionDetail.textContent = `${item.title} ${item.version} | ${fmtSize(item.size_bytes)} | ${modeText}${offlineText}`; document.querySelectorAll(".version-row").forEach((r) => r.classList.remove("selected")); row.classList.add("selected"); updateStartEnabled(); }); row.classList.toggle("selected", input.checked); } const span = document.createElement("span"); span.textContent = labelText; row.appendChild(input); row.appendChild(span); versionList.appendChild(row); } if (mode === "multiboot") updateMultibootSummary(); } async function loadVersions() { catalogStatus.textContent = "Lade Apple-Katalog..."; const res = await fetch("/api/versions"); const data = await res.json(); catalogItems = data.items || []; if (data.loading) { catalogStatus.textContent = "Katalog wird noch geladen, gleich nochmal versuchen..."; setTimeout(loadVersions, 3000); return; } if (data.error) { catalogStatus.textContent = "Fehler beim Laden: " + data.error; return; } catalogStatus.textContent = `${catalogItems.length} macOS-Installer gefunden (Katalog: ${data.catalog_url ? data.catalog_url.split("/").pop() : "?"})`; renderVersionOptions(); } async function loadDevices() { deviceList.innerHTML = "