   p  l  f  {"files":{"out":{"files":{"main":{"files":{"index.js":{"size":104927,"offset":"0","integrity":{"algorithm":"SHA256","hash":"0dd11255ff6efd963a12148ab82380611c711f64cb9d0938b5010bbdb2f1f577","blockSize":4194304,"blocks":["0dd11255ff6efd963a12148ab82380611c711f64cb9d0938b5010bbdb2f1f577"]}}}},"preload":{"files":{"index.js":{"size":1453,"offset":"104927","integrity":{"algorithm":"SHA256","hash":"6a21c5fbef28f7fae9bb1097f81abae891d70a374d1c4288ab05892c85bd854c","blockSize":4194304,"blocks":["6a21c5fbef28f7fae9bb1097f81abae891d70a374d1c4288ab05892c85bd854c"]}}}},"renderer":{"files":{"assets":{"files":{"index-BgkclzTg.css":{"size":49825,"offset":"106380","integrity":{"algorithm":"SHA256","hash":"76092f04f929d0b053589431d6ad8ead4d65885066ea4d782f03f910a8afcb76","blockSize":4194304,"blocks":["76092f04f929d0b053589431d6ad8ead4d65885066ea4d782f03f910a8afcb76"]}},"index-UR0P3Lhq.js":{"size":366696,"offset":"156205","integrity":{"algorithm":"SHA256","hash":"f0e8859c0d6b680b9a8e55d88554ffe13f4f5f7d7f200996ee7808abf8775879","blockSize":4194304,"blocks":["f0e8859c0d6b680b9a8e55d88554ffe13f4f5f7d7f200996ee7808abf8775879"]}}}},"index.html":{"size":1764,"offset":"522901","integrity":{"algorithm":"SHA256","hash":"dc09c68401dde240af199cf318f7600c4a9a59bae1ce9352108b18f4104de0cf","blockSize":4194304,"blocks":["dc09c68401dde240af199cf318f7600c4a9a59bae1ce9352108b18f4104de0cf"]}}}}}},"package.json":{"size":220,"offset":"524665","integrity":{"algorithm":"SHA256","hash":"d57eea336465ad9b561a16e5a7680de9afebf3f79f7770bed999575e57d8ab9f","blockSize":4194304,"blocks":["d57eea336465ad9b561a16e5a7680de9afebf3f79f7770bed999575e57d8ab9f"]}}}}  //#region \0rolldown/runtime.js
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __esmMin = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
var __exportAll = (all, no_symbols) => {
	let target = {};
	for (var name in all) __defProp(target, name, {
		get: all[name],
		enumerable: true
	});
	if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
	return target;
};
var __copyProps = (to, from, except, desc) => {
	if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
		key = keys[i];
		if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
			get: ((k) => from[k]).bind(null, key),
			enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
		});
	}
	return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
	value: mod,
	enumerable: true
}) : target, mod));
var __toCommonJS = (mod) => __hasOwnProp.call(mod, "module.exports") ? mod["module.exports"] : __copyProps(__defProp({}, "__esModule", { value: true }), mod);
//#endregion
let electron = require("electron");
let path = require("path");
let fs = require("fs");
let child_process = require("child_process");
child_process = __toESM(child_process);
let crypto = require("crypto");
let fs_promises = require("fs/promises");
let os = require("os");
let stream = require("stream");
let stream_promises = require("stream/promises");
//#region src/main/services/paths.ts
var paths_exports = /* @__PURE__ */ __exportAll({
	getResourcePath: () => getResourcePath,
	getUserDataPath: () => getUserDataPath
});
/**
* Bundled read-only resources (devices.json, commands/).
* In production: extracted to process.resourcesPath via extraResources.
* In dev: resolved from process.cwd().
* In E2E tests: SERENITY_TEST_RESOURCES env var redirects to a per-test tmp dir.
*/
function getResourcePath(...segments) {
	if (process.env.SERENITY_TEST_RESOURCES) return (0, path.join)(process.env.SERENITY_TEST_RESOURCES, ...segments);
	if (electron.app.isPackaged) return (0, path.join)(process.resourcesPath, ...segments);
	return (0, path.resolve)(process.cwd(), ...segments);
}
/**
* User-writable data (config.json, logs/, cache/).
* In production: %AppData%/SerenityInstaller/ (Windows) or ~/Library/Application Support/ (macOS).
* In dev: resolved from process.cwd().
* In E2E tests: SERENITY_TEST_USER_DATA env var redirects to a per-test tmp dir.
*/
function getUserDataPath(...segments) {
	if (process.env.SERENITY_TEST_USER_DATA) return (0, path.join)(process.env.SERENITY_TEST_USER_DATA, ...segments);
	if (electron.app.isPackaged) return (0, path.join)(electron.app.getPath("userData"), ...segments);
	return (0, path.resolve)(process.cwd(), ...segments);
}
var init_paths = __esmMin((() => {}));
//#endregion
//#region src/main/services/logger.ts
function ensureLogDir() {
	(0, fs.mkdirSync)(logDir, { recursive: true });
}
function initLogger(directory) {
	logDir = directory ?? getUserDataPath("logs");
	ensureLogDir();
	const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
	logFile = (0, path.join)(logDir, `serenity-${date}.log`);
}
function writeEntry(entry) {
	if (!logFile) initLogger();
	const line = JSON.stringify(entry) + "\n";
	try {
		(0, fs.appendFileSync)(logFile, line, "utf-8");
	} catch {}
}
function formatEntry(level, message, context) {
	return {
		timestamp: (/* @__PURE__ */ new Date()).toISOString(),
		level,
		message,
		...context && { context }
	};
}
function logInfo(message, context) {
	writeEntry(formatEntry("info", message, context));
}
function logWarn(message, context) {
	writeEntry(formatEntry("warn", message, context));
}
function logError(message, context) {
	writeEntry(formatEntry("error", message, context));
}
function logDebug(message, context) {
	writeEntry(formatEntry("debug", message, context));
}
var logDir, logFile;
var init_logger = __esmMin((() => {
	init_paths();
	logDir = "";
	logFile = "";
}));
//#endregion
//#region src/main/device-polling.ts
async function doPoll() {
	if (!pollingCtx || !pollSendToRenderer) return;
	lastPollAt = Date.now();
	try {
		const devices = await detectDevices();
		const serials = devices.map((d) => d.serial).sort();
		if (serials.join(",") !== lastKnownSerials.join(",")) {
			lastKnownSerials = serials;
			pollSendToRenderer({
				type: "devices-changed",
				devices
			});
			for (const [tabId, serial] of pollingCtx.abortSerials) if (!serials.includes(serial)) pollingCtx.abortControllers.get(tabId)?.abort();
		}
	} catch {
		if (lastKnownSerials.length > 0) {
			lastKnownSerials = [];
			pollSendToRenderer({
				type: "devices-changed",
				devices: []
			});
			for (const [tabId] of pollingCtx.abortSerials) pollingCtx.abortControllers.get(tabId)?.abort();
		}
	}
}
/**
* Called after each ADB command completes. If enough time has passed,
* piggyback a device poll on the existing ADB activity.
*/
function onAdbActivity() {
	if (!pollingCtx) return;
	if (Date.now() - lastPollAt >= MIN_POLL_INTERVAL_MS) doPoll();
	scheduleIdlePoll();
}
function scheduleIdlePoll() {
	if (idleTimer) clearTimeout(idleTimer);
	idleTimer = setTimeout(() => {
		doPoll();
		scheduleIdlePoll();
	}, MAX_POLL_INTERVAL_MS);
}
function startDevicePolling(ctx, sendToRenderer) {
	pollingCtx = ctx;
	pollSendToRenderer = sendToRenderer;
	doPoll();
	scheduleIdlePoll();
}
var MIN_POLL_INTERVAL_MS, MAX_POLL_INTERVAL_MS, lastPollAt, lastKnownSerials, pollingCtx, pollSendToRenderer, idleTimer;
var init_device_polling = __esmMin((() => {
	init_adb();
	MIN_POLL_INTERVAL_MS = 2e3;
	MAX_POLL_INTERVAL_MS = 5e3;
	lastPollAt = 0;
	lastKnownSerials = [];
	pollingCtx = null;
	pollSendToRenderer = null;
	idleTimer = null;
}));
//#endregion
//#region src/main/services/config.ts
/** Normalize a path: resolve ~, normalize separators */
function normPath(p) {
	if (!p) return "";
	return (0, path.resolve)(p);
}
function loadConfig(configPath) {
	if (cachedConfig) return cachedConfig;
	const filePath = configPath ?? getUserDataPath("config.json");
	logInfo("Loading config", { path: filePath });
	try {
		const raw = (0, fs.readFileSync)(filePath, "utf-8");
		const normalized = normalizeConfig(JSON.parse(raw));
		validateConfig(normalized);
		cachedConfig = normalized;
		return normalized;
	} catch (err) {
		logError("Failed to load config", {
			path: filePath,
			error: String(err)
		});
		throw new Error(`Cannot load config.json: ${err}`);
	}
}
/**
* Normalizes config keys from PascalCase (legacy C# format) to camelCase.
* Supports both formats so config.json can use either convention.
*/
function normalizeConfig(raw) {
	const mt = raw.mediaTransfer ?? raw.MediaTransfer;
	const tl = raw.transferLogging ?? raw.TransferLogging;
	const gh = raw.github ?? raw.Github;
	const pf = raw.productFilter ?? raw.ProductFilter;
	const pn = raw.packageNames ?? raw.PackageNames;
	return {
		mediaTransfer: {
			parentSourceDirVR: normPath(String(mt?.parentSourceDirVR ?? mt?.ParentSourceDirVR ?? "")),
			parentSourceDirTAB: normPath(String(mt?.parentSourceDirTAB ?? mt?.ParentSourceDirTAB ?? "")),
			sourceDirOldVR: normPath(String(mt?.sourceDirOldVR ?? mt?.SourceDirOldVR ?? "")),
			sourceDirOldTablet: normPath(String(mt?.sourceDirOldTablet ?? mt?.SourceDirOldTablet ?? "")),
			parentFileListDir: normPath(String(mt?.parentFileListDir ?? mt?.ParentFileListDir ?? "")),
			deviceTargets: mt?.deviceTargets ?? mt?.DeviceTargets ?? {},
			deviceReportPathVR: String(mt?.deviceReportPathVR ?? mt?.DeviceReportPathVR ?? "/storage/emulated/0/Android/data/com.IRIS.SerenityVR6/files/ConfigVR.log"),
			deviceReportPathTAB: String(mt?.deviceReportPathTAB ?? mt?.DeviceReportPathTAB ?? "/storage/emulated/0/Android/data/com.IRIS.SerenityManager/filesSerenity/ConfigTAB.log")
		},
		transferLogging: {
			logDirectory: String(tl?.logDirectory ?? tl?.LogDirectory ?? "./logs"),
			enableChecksum: Boolean(tl?.enableChecksum ?? tl?.EnableChecksum ?? true),
			maxRetries: Number(tl?.maxRetries ?? tl?.MaxRetries ?? 3),
			verifyAfterTransfer: Boolean(tl?.verifyAfterTransfer ?? tl?.VerifyAfterTransfer ?? true)
		},
		github: {
			owner: String(gh?.owner ?? gh?.Owner ?? ""),
			maxReleasesToShow: Number(gh?.maxReleasesToShow ?? gh?.MaxReleasesToShow ?? 10)
		},
		productFilter: { excludePatterns: pf?.excludePatterns ?? pf?.ExcludePatterns ?? [] },
		packageNames: pn ?? {},
		timeouts: {
			adbDefaultMs: Number(raw.timeouts?.adbDefaultMs ?? 3e4),
			md5TimeoutMs: Number(raw.timeouts?.md5TimeoutMs ?? 3e5),
			pushTimeoutMs: Number(raw.timeouts?.pushTimeoutMs ?? 6e5),
			installTimeoutMs: Number(raw.timeouts?.installTimeoutMs ?? 12e4)
		},
		performance: { maxConcurrentVerify: Number(raw.performance?.maxConcurrentVerify ?? 2) },
		webhooks: { resultDigestUrl: String(raw.webhooks?.resultDigestUrl ?? "") }
	};
}
function validateConfig(config) {
	if (!config.mediaTransfer.parentSourceDirVR) throw new Error("Missing parentSourceDirVR");
	if (!config.mediaTransfer.parentSourceDirTAB) throw new Error("Missing parentSourceDirTAB");
	if (!config.mediaTransfer.parentFileListDir) throw new Error("Missing parentFileListDir");
	if (!config.mediaTransfer.deviceTargets) throw new Error("Missing deviceTargets");
	if (!config.github.owner) throw new Error("Missing github.owner");
}
function getConfig() {
	if (!cachedConfig) return loadConfig();
	return cachedConfig;
}
function resetConfigCache() {
	cachedConfig = null;
}
var cachedConfig;
var init_config = __esmMin((() => {
	init_logger();
	init_paths();
	cachedConfig = null;
}));
//#endregion
//#region src/main/services/__fakes__/scenario-types.ts
function defaultScenario() {
	return {
		devices: [],
		deviceProps: {},
		storage: {},
		deviceFiles: {},
		md5: {},
		fileContents: {},
		releases: {},
		pushTriggers: [],
		installTriggers: []
	};
}
var init_scenario_types = __esmMin((() => {}));
//#endregion
//#region src/main/services/__fakes__/scenario.ts
function loadIfChanged() {
	const path = process.env.SERENITY_TEST_SCENARIO;
	if (!path) return;
	if (path !== cachedPath) {
		cachedPath = path;
		cachedMtimeMs = 0;
	}
	try {
		const stat = (0, fs.statSync)(path);
		if (stat.mtimeMs === cachedMtimeMs) return;
		const raw = (0, fs.readFileSync)(path, "utf-8");
		const parsed = JSON.parse(raw);
		cached = {
			...defaultScenario(),
			...parsed
		};
		cachedMtimeMs = stat.mtimeMs;
	} catch {}
}
function getState() {
	loadIfChanged();
	return cached;
}
function getCounters() {
	return counters;
}
function bumpCounter(key) {
	const before = counters[key];
	counters[key] = before + 1;
	return before;
}
function recordCall(kind, args) {
	recordedCalls.push({
		kind,
		args,
		at: Date.now()
	});
	if (recordedCalls.length > 5e3) recordedCalls.shift();
}
function getRecordedCalls() {
	return recordedCalls;
}
var cached, cachedMtimeMs, cachedPath, counters, recordedCalls;
var init_scenario = __esmMin((() => {
	init_scenario_types();
	cached = defaultScenario();
	cachedMtimeMs = 0;
	cachedPath = null;
	counters = {
		pushIndex: 0,
		installIndex: 0,
		devicesCallIndex: 0,
		shellCommandsRun: 0
	};
	recordedCalls = [];
	if (process.env.SERENITY_TEST_MODE === "1") globalThis.__serenityFake = {
		getState,
		getCounters,
		getRecordedCalls
	};
}));
//#endregion
//#region src/main/services/__fakes__/adb-fake.ts
var adb_fake_exports = /* @__PURE__ */ __exportAll({ runFakeAdb: () => runFakeAdb });
function hostMd5(filePath) {
	try {
		const stat = (0, fs.statSync)(filePath);
		const key = `${filePath}:${stat.mtimeMs}:${stat.size}`;
		const cached = hostMd5Cache.get(key);
		if (cached) return cached;
		const buf = (0, fs.readFileSync)(filePath);
		const hash = (0, crypto.createHash)("md5").update(buf).digest("hex");
		hostMd5Cache.set(key, hash);
		return hash;
	} catch {
		return "00000000000000000000000000000000";
	}
}
function makeAdbError(message, stderr, opts = {}) {
	const { AdbError } = (init_adb(), __toCommonJS(adb_exports));
	return new AdbError(message, 1, stderr, opts.isStorageFull ?? false, opts.isDisconnected ?? false);
}
function sleepAbortable(ms, signal) {
	return new Promise((resolve, reject) => {
		if (signal?.aborted) {
			reject(makeAdbError("Aborted", "", { isDisconnected: false }));
			return;
		}
		const timer = setTimeout(() => {
			cleanup();
			resolve();
		}, ms);
		const onAbort = () => {
			cleanup();
			reject(makeAdbError("Aborted", "", { isDisconnected: false }));
		};
		function cleanup() {
			clearTimeout(timer);
			signal?.removeEventListener("abort", onAbort);
		}
		signal?.addEventListener("abort", onAbort, { once: true });
	});
}
function formatBytesHuman(kb) {
	if (kb >= 1024 * 1024) return (kb / 1024 / 1024).toFixed(1) + "G";
	if (kb >= 1024) return (kb / 1024).toFixed(0) + "M";
	return kb + "K";
}
function renderDfLine(storage) {
	const total = formatBytesHuman(storage.totalKb);
	const used = formatBytesHuman(storage.usedKb);
	const avail = formatBytesHuman(storage.availableKb);
	const usedPct = storage.totalKb > 0 ? Math.round(storage.usedKb / storage.totalKb * 100) : 0;
	return `${storage.type === "internal" ? "/dev/fuse" : "/dev/block/sda1"}  ${total}  ${used}  ${avail}  ${usedPct}%  ${storage.path}`;
}
function renderDfAll(storages) {
	return ["Filesystem      Size  Used Avail Use% Mounted on", ...storages.map(renderDfLine)].join("\n") + "\n";
}
function renderLsLine(file) {
	return `-rw-rw-rw- 1 root root ${file.sizeBytes} 2024-01-01 00:00 ${file.name}`;
}
function renderDevicesList(devices) {
	return ["List of devices attached", ...devices.map((d) => {
		const extras = [];
		if (d.model) extras.push(`model:${d.model}`);
		if (d.product) extras.push(`product:${d.product}`);
		return `${d.serial}\t${d.status}` + (extras.length ? " " + extras.join(" ") : "");
	})].join("\n") + "\n";
}
function stripSerial(args) {
	if (args[0] === "-s" && args.length >= 2) return {
		serial: args[1],
		rest: args.slice(2)
	};
	return {
		serial: null,
		rest: args
	};
}
function parseQuoted(s) {
	const m = s.match(/^['"](.+)['"]$/s);
	return m ? m[1] : s;
}
function getFilesForDir(serial, dir) {
	const map = getState().deviceFiles[serial] ?? {};
	if (map[dir]) return map[dir];
	let bestKey = "";
	for (const key of Object.keys(map)) if (dir.startsWith(key) && key.length > bestKey.length) bestKey = key;
	return bestKey ? map[bestKey] : [];
}
function addFileToDir(serial, dir, file) {
	const state = getState();
	state.deviceFiles[serial] = state.deviceFiles[serial] ?? {};
	state.deviceFiles[serial][dir] = state.deviceFiles[serial][dir] ?? [];
	const existing = state.deviceFiles[serial][dir].findIndex((f) => f.name === file.name);
	if (existing >= 0) state.deviceFiles[serial][dir][existing] = file;
	else state.deviceFiles[serial][dir].push(file);
}
function removeFileFromDirs(serial, fullPath) {
	const map = getState().deviceFiles[serial] ?? {};
	const name = fullPath.substring(fullPath.lastIndexOf("/") + 1);
	for (const dir of Object.keys(map)) {
		const before = map[dir].length;
		map[dir] = map[dir].filter((f) => f.name !== name || !fullPath.startsWith(dir));
		if (map[dir].length < before) return true;
	}
	return false;
}
function fileExists(serial, fullPath) {
	const dir = fullPath.substring(0, fullPath.lastIndexOf("/"));
	const name = fullPath.substring(fullPath.lastIndexOf("/") + 1);
	return getFilesForDir(serial, dir).some((f) => f.name === name);
}
function md5For(serial, fullPath, sourcePathOverride) {
	const override = getState().md5[serial]?.[fullPath];
	if (override) return override;
	const dir = fullPath.substring(0, fullPath.lastIndexOf("/"));
	const name = fullPath.substring(fullPath.lastIndexOf("/") + 1);
	const found = getFilesForDir(serial, dir).find((f) => f.name === name);
	if (found?.md5) return found.md5;
	if (sourcePathOverride && (0, fs.existsSync)(sourcePathOverride)) return hostMd5(sourcePathOverride);
	const fromPushed = pushedSources.get(`${serial}:${fullPath}`);
	if (fromPushed && (0, fs.existsSync)(fromPushed)) return hostMd5(fromPushed);
	return null;
}
async function runFakeAdb(args, signal) {
	if (signal?.aborted) throw makeAdbError("Aborted", "", { isDisconnected: false });
	if (args[0] === "devices") {
		bumpCounter("devicesCallIndex");
		recordCall("devices", args);
		const state = getState();
		let devices = state.devices;
		if (state.disconnectAfterDevicesCalls !== void 0 && getCounters().devicesCallIndex > state.disconnectAfterDevicesCalls) devices = [];
		return {
			stdout: renderDevicesList(devices),
			stderr: ""
		};
	}
	const { serial, rest } = stripSerial(args);
	if (!serial) throw makeAdbError(`Unrecognized fake adb invocation: ${args.join(" ")}`, "");
	const state = getState();
	if (!state.devices.some((d) => d.serial === serial)) throw makeAdbError(`adb -s ${serial} ${rest.join(" ")}: device not found`, "error: device not found", { isDisconnected: true });
	const head = rest[0];
	if (head === "install") {
		const installIdx = bumpCounter("installIndex");
		const apkPath = rest[rest.length - 1];
		recordCall("install", [serial, ...rest]);
		if (state.defaultInstallDelayMs && state.defaultInstallDelayMs > 0) await sleepAbortable(state.defaultInstallDelayMs, signal);
		const trigger = state.installTriggers.find((t) => t.installIndex === installIdx);
		if (trigger) {
			if (trigger.action === "storage-full") throw makeAdbError(`adb install failed`, trigger.errorMessage || "adbd: No space left on device", { isStorageFull: true });
			if (trigger.action === "disconnect") throw makeAdbError(`adb install failed`, trigger.errorMessage || "error: device not found", { isDisconnected: true });
			if (trigger.action === "fail-generic") throw makeAdbError(`adb install failed`, trigger.errorMessage || "INSTALL_FAILED_INVALID_APK");
		}
		if (apkPath && !(0, fs.existsSync)(apkPath)) throw makeAdbError(`adb install failed`, `apk not found: ${apkPath}`);
		return {
			stdout: "Success\n",
			stderr: ""
		};
	}
	if (head === "push") {
		const pushIdx = bumpCounter("pushIndex");
		const sync = rest[1] === "--sync";
		const src = rest[sync ? 2 : 1];
		const dst = rest[sync ? 3 : 2];
		recordCall("push", [serial, ...rest]);
		if (state.defaultPushDelayMs && state.defaultPushDelayMs > 0) await sleepAbortable(state.defaultPushDelayMs, signal);
		const trigger = state.pushTriggers.find((t) => t.pushIndex === pushIdx);
		if (trigger) {
			if (trigger.action === "storage-full") throw makeAdbError(`adb push failed`, trigger.errorMessage || "adbd: No space left on device", { isStorageFull: true });
			if (trigger.action === "disconnect") {
				state.devices = [];
				throw makeAdbError(`adb push failed`, trigger.errorMessage || "error: device not found", { isDisconnected: true });
			}
			if (trigger.action === "fail-generic") throw makeAdbError(`adb push failed`, trigger.errorMessage || "unknown push error");
		}
		if (!(0, fs.existsSync)(src)) throw makeAdbError(`adb push failed`, `cannot stat '${src}': No such file or directory`);
		const sizeBytes = (0, fs.statSync)(src).size;
		const name = (0, path.basename)(dst);
		addFileToDir(serial, dst.substring(0, dst.lastIndexOf("/")), {
			name,
			sizeBytes,
			md5: hostMd5(src)
		});
		pushedSources.set(`${serial}:${dst}`, src);
		return {
			stdout: `${name}: 1 file pushed, 0 skipped.\n`,
			stderr: ""
		};
	}
	if (head === "shell") {
		recordCall("shell", [serial, ...rest]);
		return await handleShell(serial, rest.length === 2 ? rest[1] : rest.slice(1).join(" "), signal);
	}
	if (head === "reconnect" || head === "wait-for-device" || head === "start-server" || head === "kill-server") return {
		stdout: "",
		stderr: ""
	};
	recordCall("unknown", [serial, ...rest]);
	return {
		stdout: "",
		stderr: ""
	};
}
async function handleShell(serial, inner, signal) {
	const trimmed = inner.trim();
	const state = getState();
	bumpCounter("shellCommandsRun");
	if (state.shellDelayMs) {
		for (const [prefix, ms] of Object.entries(state.shellDelayMs)) if (trimmed.startsWith(prefix)) {
			await sleepAbortable(ms, signal);
			break;
		}
	}
	if (trimmed.startsWith("getprop ")) {
		const propName = trimmed.replace(/^getprop\s+/, "").trim();
		return {
			stdout: (state.deviceProps[serial]?.[propName] ?? "") + "\n",
			stderr: ""
		};
	}
	if (trimmed === "df -h" || trimmed === "df -h " || /^df\s+-h\b/.test(trimmed)) return {
		stdout: renderDfAll(state.storage[serial] ?? []),
		stderr: ""
	};
	if (trimmed.startsWith("df ")) {
		const path$2 = trimmed.replace(/^df\s+/, "").trim();
		const match = (state.storage[serial] ?? []).find((s) => path$2.startsWith(s.path) || s.path === path$2);
		if (match) return {
			stdout: renderDfAll([match]),
			stderr: ""
		};
		return {
			stdout: "",
			stderr: ""
		};
	}
	if (trimmed.startsWith("ls -l ")) {
		const files = getFilesForDir(serial, trimmed.replace(/^ls -l\s+/, "").trim());
		if (files.length === 0) return {
			stdout: "",
			stderr: ""
		};
		return {
			stdout: files.map(renderLsLine).join("\n") + "\n",
			stderr: ""
		};
	}
	if (trimmed.startsWith("md5sum ")) {
		const path$3 = parseQuoted(trimmed.replace(/^md5sum\s+/, "").trim());
		if (!fileExists(serial, path$3)) throw makeAdbError(`md5sum failed`, `md5sum: ${path$3}: No such file or directory`);
		const hash = md5For(serial, path$3);
		if (!hash) return {
			stdout: "",
			stderr: ""
		};
		return {
			stdout: `${hash}  ${path$3}\n`,
			stderr: ""
		};
	}
	if (trimmed.startsWith("test -f ")) {
		if (fileExists(serial, parseQuoted(trimmed.replace(/^test -f\s+/, "").replace(/\s*&&\s*echo EXISTS\s*$/, "").trim()))) return {
			stdout: "EXISTS\n",
			stderr: ""
		};
		throw makeAdbError("test -f failed", "");
	}
	if (trimmed.startsWith("rm -f ")) {
		const path$4 = parseQuoted(trimmed.replace(/^rm -f\s+/, "").trim());
		removeFileFromDirs(serial, path$4);
		pushedSources.delete(`${serial}:${path$4}`);
		return {
			stdout: "",
			stderr: ""
		};
	}
	if (trimmed.startsWith("rm ")) {
		removeFileFromDirs(serial, parseQuoted(trimmed.replace(/^rm\s+/, "").trim()));
		return {
			stdout: "",
			stderr: ""
		};
	}
	if (trimmed.startsWith("mkdir ")) return {
		stdout: "",
		stderr: ""
	};
	if (trimmed.startsWith("cat ")) {
		const path$5 = parseQuoted(trimmed.replace(/^cat\s+/, "").trim());
		const content = state.fileContents[serial]?.[path$5];
		if (content === void 0) throw makeAdbError("cat failed", `cat: ${path$5}: No such file or directory`);
		return {
			stdout: content,
			stderr: ""
		};
	}
	if (trimmed.startsWith("monkey ")) return {
		stdout: "Events injected: 1\n",
		stderr: ""
	};
	if (trimmed.startsWith("am ")) return {
		stdout: "",
		stderr: ""
	};
	if (/^(settings|pm|echo|input|service|dumpsys|sleep|busybox|sync|sh|true|false)\b/.test(trimmed)) return {
		stdout: "",
		stderr: ""
	};
	return {
		stdout: "",
		stderr: ""
	};
}
var hostMd5Cache, pushedSources;
var init_adb_fake = __esmMin((() => {
	init_scenario();
	hostMd5Cache = /* @__PURE__ */ new Map();
	pushedSources = /* @__PURE__ */ new Map();
	if (process.env.SERENITY_TEST_MODE === "1") {
		globalThis.__fakeAdb = {
			counters: getCounters,
			state: getState,
			pushedSources: () => Array.from(pushedSources.entries())
		};
		Object.defineProperty(globalThis, "__fakeAdbPushCount", {
			get() {
				return getCounters().pushIndex;
			},
			configurable: true
		});
	}
}));
//#endregion
//#region src/main/services/adb.ts
var adb_exports = /* @__PURE__ */ __exportAll({
	AdbError: () => AdbError,
	configureDevice: () => configureDevice,
	deleteDeviceFile: () => deleteDeviceFile,
	detectDevices: () => detectDevices,
	detectModel: () => detectModel,
	detectStorageLocations: () => detectStorageLocations,
	fileExistsOnDevice: () => fileExistsOnDevice,
	forceStopApp: () => forceStopApp,
	getDeviceMd5: () => getDeviceMd5,
	getExistingFiles: () => getExistingFiles,
	getStorageInfo: () => getStorageInfo,
	installApk: () => installApk,
	launchApp: () => launchApp,
	pushFile: () => pushFile,
	readDeviceFile: () => readDeviceFile,
	shellCommand: () => shellCommand
});
function getTimeout(key) {
	try {
		return getConfig().timeouts[key];
	} catch {}
	return {
		adbDefaultMs: 3e4,
		md5TimeoutMs: 3e5,
		pushTimeoutMs: 6e5,
		installTimeoutMs: 12e4
	}[key];
}
function validateSerial(serial) {
	if (!SERIAL_REGEX.test(serial)) throw new Error(`Invalid device serial: ${serial}`);
}
function classifyError(stderr, exitCode) {
	const lower = stderr.toLowerCase();
	return {
		isStorageFull: STORAGE_FULL_PATTERNS.some((p) => lower.includes(p)),
		isDisconnected: DISCONNECTED_PATTERNS.some((p) => lower.includes(p))
	};
}
function adb(args, timeout, signal) {
	const effectiveTimeout = timeout ?? getTimeout("adbDefaultMs");
	if (process.env.SERENITY_TEST_MODE === "1") return (init_adb_fake(), __toCommonJS(adb_fake_exports)).runFakeAdb(args, signal);
	return new Promise((resolve, reject) => {
		if (signal?.aborted) {
			reject(new AdbError("Aborted", null, "", false, false));
			return;
		}
		if (args[0] !== "devices") logDebug("adb command", { args });
		const child = child_process.execFile("adb", args, { timeout: effectiveTimeout }, (error, stdout, stderr) => {
			if (error) {
				const exitCode = error.code ?? null;
				const { isStorageFull, isDisconnected } = classifyError(stderr, exitCode);
				reject(new AdbError(`adb ${args.join(" ")} failed: ${stderr || error.message}`, typeof exitCode === "number" ? exitCode : 1, stderr, isStorageFull, isDisconnected));
				return;
			}
			if (args[0] !== "devices") onAdbActivity();
			resolve({
				stdout: stdout ?? "",
				stderr: stderr ?? ""
			});
		});
		if (signal) {
			const onAbort = () => {
				child.kill("SIGTERM");
			};
			signal.addEventListener("abort", onAbort, { once: true });
			child.on("exit", () => signal.removeEventListener("abort", onAbort));
		}
	});
}
function adbForDevice(serial, args, timeout, signal) {
	validateSerial(serial);
	return adb([
		"-s",
		serial,
		...args
	], timeout, signal);
}
async function detectDevices() {
	logInfo("Detecting devices");
	const { stdout } = await adb(["devices", "-l"]);
	const lines = stdout.trim().split("\n").slice(1);
	const devices = [];
	for (const line of lines) {
		const trimmed = line.trim();
		if (!trimmed) continue;
		const parts = trimmed.split(/\s+/);
		const serial = parts[0];
		const status = parts[1];
		if (!serial || !status) continue;
		const info = {
			serial,
			status
		};
		for (const part of parts.slice(2)) {
			const [key, value] = part.split(":");
			if (key === "model") info.model = value;
			if (key === "product") info.product = value;
		}
		devices.push(info);
	}
	logInfo("Devices detected", { count: devices.length });
	return devices;
}
async function detectModel(serial) {
	validateSerial(serial);
	const { stdout } = await adbForDevice(serial, [
		"shell",
		"getprop",
		"ro.product.name"
	]);
	return stdout.trim();
}
async function installApk(serial, apkPath) {
	validateSerial(serial);
	logInfo("Installing APK", {
		serial,
		apkPath
	});
	await adbForDevice(serial, [
		"install",
		"-r",
		apkPath
	], getTimeout("installTimeoutMs"));
	logInfo("APK installed", { serial });
}
async function pushFile(serial, src, dst, sync = false, signal) {
	validateSerial(serial);
	const { stdout } = await adbForDevice(serial, sync ? [
		"push",
		"--sync",
		src,
		dst
	] : [
		"push",
		src,
		dst
	], getTimeout("pushTimeoutMs"), signal);
	return { skipped: sync && /0 files? pushed/i.test(stdout) };
}
async function shellCommand(serial, command) {
	validateSerial(serial);
	const { stdout } = await adbForDevice(serial, ["shell", command.join(" ")]);
	return stdout.trim();
}
async function configureDevice(serial, commands, vars) {
	validateSerial(serial);
	const successes = [];
	const failures = [];
	const NON_CRITICAL_PREFIXES = ["uninstall", "shell pm disable"];
	for (const rawLine of commands) {
		const line = rawLine.trim();
		if (!line || line.startsWith("#")) continue;
		let expanded = line;
		for (const [key, value] of Object.entries(vars)) expanded = expanded.replaceAll(`\${${key}}`, value);
		const parts = expanded.split(/\s+/);
		const isNonCritical = NON_CRITICAL_PREFIXES.some((p) => expanded.startsWith(p));
		try {
			logInfo("Running device command", {
				serial,
				command: expanded
			});
			await adbForDevice(serial, parts);
			successes.push(expanded);
		} catch (err) {
			if (isNonCritical) {
				logWarn("Non-critical command failed, continuing", {
					command: expanded,
					error: String(err)
				});
				failures.push(expanded);
			} else {
				logError("Critical command failed", {
					command: expanded,
					error: String(err)
				});
				throw err;
			}
		}
	}
	return {
		successes,
		failures
	};
}
async function detectStorageLocations(serial) {
	validateSerial(serial);
	logInfo("Detecting storage", { serial });
	const locations = [];
	try {
		const { stdout } = await adbForDevice(serial, [
			"shell",
			"df",
			"-h"
		]);
		const lines = stdout.trim().split("\n");
		for (const line of lines) if (line.includes("/storage/emulated")) locations.push(parseStorageLine(line, "internal", "/storage/emulated/0", "Stockage interne"));
		else {
			const sdMatch = line.match(/\/storage\/([A-Z0-9]{4}-[A-Z0-9]{4})\b/);
			if (sdMatch) {
				const mountPath = `/storage/${sdMatch[1]}`;
				locations.push(parseStorageLine(line, "sdcard", mountPath, `Carte SD (${sdMatch[1]})`));
			}
		}
	} catch (err) {
		logWarn("Could not detect storage via df", { error: String(err) });
	}
	if (locations.length === 0) locations.push({
		type: "internal",
		path: "/storage/emulated/0",
		displayName: "Stockage interne"
	});
	return locations;
}
function parseStorageLine(line, type, path, displayName) {
	const parts = line.trim().split(/\s+/);
	return {
		type,
		path,
		displayName,
		totalBytes: parseSize(parts[1]),
		usedBytes: parseSize(parts[2]),
		availableBytes: parseSize(parts[3]),
		lastCheckedAt: (/* @__PURE__ */ new Date()).toISOString()
	};
}
function parseSize(sizeStr) {
	if (!sizeStr) return void 0;
	const match = sizeStr.match(/^([\d.]+)([KMGTP]?)$/i);
	if (!match) return void 0;
	const value = parseFloat(match[1]);
	const unit = match[2].toUpperCase();
	return Math.floor(value * ({
		"": 1,
		K: 1024,
		M: 1024 ** 2,
		G: 1024 ** 3,
		T: 1024 ** 4
	}[unit] ?? 1));
}
async function getStorageInfo(serial, storagePath) {
	validateSerial(serial);
	try {
		const { stdout } = await adbForDevice(serial, [
			"shell",
			"df",
			storagePath
		]);
		const lines = stdout.trim().split("\n");
		for (const line of lines) if (line.includes(storagePath) || lines.indexOf(line) > 0) {
			const parts = line.trim().split(/\s+/);
			const total = parseSize(parts[1]);
			const available = parseSize(parts[3]);
			if (total && available) return {
				totalBytes: total,
				availableBytes: available
			};
		}
	} catch {}
	return null;
}
async function getExistingFiles(serial, dir) {
	validateSerial(serial);
	const files = /* @__PURE__ */ new Map();
	try {
		const { stdout } = await adbForDevice(serial, [
			"shell",
			"ls",
			"-l",
			dir
		]);
		const lines = stdout.trim().split("\n");
		for (const line of lines) {
			const parts = line.trim().split(/\s+/);
			if (parts.length >= 7) {
				const size = parseInt(parts[4], 10);
				const name = parts.slice(7).join(" ");
				if (name && !isNaN(size)) files.set(name, size);
			}
		}
	} catch {
		logDebug("Could not list existing files", { dir });
	}
	return files;
}
async function getDeviceMd5(serial, filePath) {
	validateSerial(serial);
	const cleanPath = filePath.replace(/[\r\n]+/g, "");
	const pathsToTry = [cleanPath];
	if (cleanPath.includes("%")) try {
		const decoded = decodeURIComponent(cleanPath);
		if (decoded !== cleanPath) pathsToTry.push(decoded);
	} catch {}
	const MAX_RETRIES = 3;
	for (const tryPath of pathsToTry) for (let attempt = 0; attempt < MAX_RETRIES; attempt++) try {
		if (attempt > 0) await new Promise((r) => setTimeout(r, 1e3 * attempt));
		const { stdout, stderr } = await adbForDevice(serial, ["shell", "md5sum '" + tryPath + "'"], getTimeout("md5TimeoutMs"));
		const hash = stdout.trim().split(/\s+/)[0];
		if (hash && /^[a-f0-9]{32}$/i.test(hash)) {
			if (tryPath !== cleanPath) logInfo("MD5 succeeded with URL-decoded path", {
				original: cleanPath,
				decoded: tryPath
			});
			if (attempt > 0) logInfo("MD5 succeeded after retry", {
				filePath: tryPath,
				attempt
			});
			return hash;
		}
		logWarn("MD5 output not a valid hash", {
			filePath: tryPath,
			stdout: stdout.trim(),
			stderr: stderr.trim()
		});
		break;
	} catch (err) {
		const adbErr = err;
		if (adbErr.stderr && adbErr.stderr.trim()) {
			logDebug("MD5 command error", {
				filePath: tryPath,
				stderr: adbErr.stderr.trim(),
				attempt
			});
			break;
		}
		logDebug("MD5 connection failed, will retry", {
			filePath: tryPath,
			attempt
		});
	}
	logWarn("MD5 failed on all path variants", {
		filePath: cleanPath,
		tried: pathsToTry
	});
	return null;
}
async function fileExistsOnDevice(serial, filePath) {
	const cleanPath = filePath.replace(/[\r\n]+/g, "");
	const pathsToTry = [cleanPath];
	if (cleanPath.includes("%")) try {
		const decoded = decodeURIComponent(cleanPath);
		if (decoded !== cleanPath) pathsToTry.push(decoded);
	} catch {}
	for (const tryPath of pathsToTry) try {
		await adbForDevice(serial, ["shell", "test -f '" + tryPath + "' && echo EXISTS"]);
		return true;
	} catch {}
	return false;
}
async function wakeDevice(serial) {
	try {
		await adbForDevice(serial, [
			"shell",
			"input",
			"keyevent",
			"224"
		]);
		await new Promise((r) => setTimeout(r, 300));
	} catch (err) {
		logWarn("wakeDevice failed, continuing anyway", {
			serial,
			error: String(err)
		});
	}
}
async function launchApp(serial, packageName) {
	validateSerial(serial);
	logInfo("Launching app", {
		serial,
		packageName
	});
	await wakeDevice(serial);
	await adbForDevice(serial, [
		"shell",
		"monkey",
		"-p",
		packageName,
		"-c",
		"android.intent.category.LAUNCHER",
		"1"
	]);
}
async function forceStopApp(serial, packageName) {
	validateSerial(serial);
	logInfo("Force stopping app", {
		serial,
		packageName
	});
	await adbForDevice(serial, [
		"shell",
		"am",
		"force-stop",
		packageName
	]);
}
async function deleteDeviceFile(serial, devicePath) {
	validateSerial(serial);
	try {
		await adbForDevice(serial, ["shell", "rm -f '" + devicePath + "'"]);
		logInfo("Deleted file on device", {
			serial,
			devicePath
		});
		return true;
	} catch (err) {
		logWarn("Failed to delete file on device", {
			devicePath,
			error: String(err)
		});
		return false;
	}
}
async function readDeviceFile(serial, devicePath) {
	validateSerial(serial);
	try {
		const { stdout } = await adbForDevice(serial, ["exec-out", "cat '" + devicePath + "'"]);
		return stdout.replace(/\r\n/g, "\n");
	} catch (err) {
		logWarn("readDeviceFile failed", {
			devicePath,
			error: String(err)
		});
		return null;
	}
}
var SERIAL_REGEX, STORAGE_FULL_PATTERNS, DISCONNECTED_PATTERNS, AdbError;
var init_adb = __esmMin((() => {
	init_logger();
	init_device_polling();
	init_config();
	SERIAL_REGEX = /^[a-zA-Z0-9:._-]+$/;
	STORAGE_FULL_PATTERNS = [
		"no space left",
		"not enough space",
		"insufficient storage",
		"disk full",
		"enospc"
	];
	DISCONNECTED_PATTERNS = [
		"device not found",
		"device offline",
		"no devices",
		"error: closed"
	];
	AdbError = class extends Error {
		constructor(message, exitCode, stderr, isStorageFull = false, isDisconnected = false) {
			super(message);
			this.exitCode = exitCode;
			this.stderr = stderr;
			this.isStorageFull = isStorageFull;
			this.isDisconnected = isDisconnected;
			this.name = "AdbError";
		}
	};
}));
//#endregion
//#region src/main/services/__fakes__/gh-fake.ts
var gh_fake_exports = /* @__PURE__ */ __exportAll({ runFakeGh: () => runFakeGh });
function repoFromArgs(args) {
	const idx = args.indexOf("-R");
	if (idx >= 0 && args[idx + 1]) {
		const slug = args[idx + 1];
		return slug.includes("/") ? slug.split("/")[1] : slug;
	}
	return "";
}
function findTag(args) {
	const subIdx = args.indexOf("release");
	if (subIdx < 0) return null;
	const sub = args[subIdx + 1];
	if (sub === "view" || sub === "download") return args[subIdx + 2] ?? null;
	return null;
}
async function runFakeGh(args) {
	recordCall("gh", args);
	const state = getState();
	if (args[0] === "release" && args[1] === "list") {
		const repo = repoFromArgs(args);
		const releases = state.releases[repo] ?? [];
		return JSON.stringify(releases.map((r) => ({
			tagName: r.tagName,
			name: r.name,
			publishedAt: r.publishedAt
		})));
	}
	if (args[0] === "release" && args[1] === "view") {
		const repo = repoFromArgs(args);
		const tag = findTag(args);
		const release = (state.releases[repo] ?? []).find((r) => r.tagName === tag);
		if (!release) throw new Error(`gh release view: tag ${tag} not found in ${repo}`);
		return (release.apkName ?? `${repo}-${tag}.apk`) + "\n";
	}
	if (args[0] === "release" && args[1] === "download") {
		const repo = repoFromArgs(args);
		const tag = findTag(args);
		const release = (state.releases[repo] ?? []).find((r) => r.tagName === tag);
		if (!release) throw new Error(`gh release download: tag ${tag} not found in ${repo}`);
		const apkName = release.apkName ?? `${repo}-${tag}.apk`;
		const dirIdx = args.indexOf("-D");
		const dir = dirIdx >= 0 ? args[dirIdx + 1] : process.cwd();
		(0, fs.mkdirSync)(dir, { recursive: true });
		const apkPath = (0, path.join)(dir, apkName);
		if (!(0, fs.existsSync)(apkPath)) (0, fs.writeFileSync)(apkPath, "FAKE_APK_CONTENT_FOR_E2E_TESTS", "utf-8");
		return "";
	}
	throw new Error(`fake gh: unsupported invocation: ${args.join(" ")}`);
}
var init_gh_fake = __esmMin((() => {
	init_scenario();
}));
//#endregion
//#region src/main/services/github.ts
init_logger();
init_config();
init_paths();
var CACHE_DIR = getUserDataPath("cache");
var RELEASE_CACHE_TTL_MS = 300 * 1e3;
var releaseCache = /* @__PURE__ */ new Map();
function execGh(args) {
	if (process.env.SERENITY_TEST_MODE === "1") return (init_gh_fake(), __toCommonJS(gh_fake_exports)).runFakeGh(args);
	return new Promise((resolve, reject) => {
		child_process.execFile("gh", args, { timeout: 3e4 }, (error, stdout, stderr) => {
			if (error) {
				if (error.code === "ENOENT") {
					reject(/* @__PURE__ */ new Error("GitHub CLI (gh) introuvable. Installez-le depuis https://cli.github.com"));
					return;
				}
				reject(/* @__PURE__ */ new Error(`gh ${args.join(" ")} failed: ${stderr || error.message}`));
				return;
			}
			resolve(stdout);
		});
	});
}
async function fetchReleases(repo, maxCount) {
	const config = getConfig();
	const owner = config.github.owner;
	const limit = maxCount ?? config.github.maxReleasesToShow ?? 10;
	const cacheKey = `${owner}/${repo}:${limit}`;
	const cached = releaseCache.get(cacheKey);
	if (cached && Date.now() - cached.fetchedAt < RELEASE_CACHE_TTL_MS) {
		logInfo("Releases cache hit", {
			repo,
			age: Math.round((Date.now() - cached.fetchedAt) / 1e3) + "s"
		});
		return cached.data;
	}
	logInfo("Fetching releases", {
		owner,
		repo,
		limit
	});
	const stdout = await execGh([
		"release",
		"list",
		"-R",
		`${owner}/${repo}`,
		"-L",
		String(limit),
		"--json",
		"tagName,publishedAt,name"
	]);
	const releases = JSON.parse(stdout);
	releaseCache.set(cacheKey, {
		data: releases,
		fetchedAt: Date.now()
	});
	logInfo("Releases fetched and cached", { count: releases.length });
	return releases;
}
async function downloadApk(repo, tag) {
	const owner = getConfig().github.owner;
	const tagDir = (0, path.join)(CACHE_DIR, tag);
	(0, fs.mkdirSync)(tagDir, { recursive: true });
	logInfo("Downloading APK", {
		owner,
		repo,
		tag
	});
	const apkName = (await execGh([
		"release",
		"view",
		tag,
		"-R",
		`${owner}/${repo}`,
		"--json",
		"assets",
		"--jq",
		".assets[].name"
	])).trim().split("\n").find((n) => n.endsWith(".apk"));
	if (!apkName) throw new Error(`No APK found in release ${tag}`);
	const apkPath = (0, path.join)(tagDir, apkName);
	const sha256Path = apkPath + ".sha256";
	if ((0, fs.existsSync)(apkPath) && (0, fs.existsSync)(sha256Path)) {
		if ((0, fs.readFileSync)(sha256Path, "utf-8").trim() === await hashFile(apkPath)) {
			logInfo("APK cache hit", {
				tag,
				apkName
			});
			return apkPath;
		}
		logWarn("APK cache hash mismatch, re-downloading", { tag });
	}
	try {
		await execGh([
			"release",
			"download",
			tag,
			"-R",
			`${owner}/${repo}`,
			"-p",
			apkName,
			"-D",
			tagDir,
			"--clobber"
		]);
	} catch (err) {
		if ((0, fs.existsSync)(apkPath)) try {
			(0, fs.unlinkSync)(apkPath);
		} catch {}
		logError("APK download failed", {
			tag,
			error: String(err)
		});
		throw err;
	}
	const hash = await hashFile(apkPath);
	(0, fs.writeFileSync)(sha256Path, hash, "utf-8");
	logInfo("APK downloaded and cached", {
		tag,
		apkName,
		sha256: hash
	});
	return apkPath;
}
function hashFile(filePath) {
	return new Promise((resolve, reject) => {
		const hash = (0, crypto.createHash)("sha256");
		const stream = (0, fs.createReadStream)(filePath);
		stream.on("data", (chunk) => hash.update(chunk));
		stream.on("end", () => resolve(hash.digest("hex")));
		stream.on("error", reject);
	});
}
//#endregion
//#region src/main/services/assets.ts
init_config();
init_logger();
var FILE_PREFIXES = [
	"1_",
	"2_",
	"0_",
	""
];
function discoverSerenityFiles(productDir) {
	const envPath = (0, path.join)(productDir, "serenityFileName.env");
	if (!(0, fs.existsSync)(envPath)) return [];
	const results = [];
	const envStat = (0, fs.statSync)(envPath);
	results.push({
		fileName: "serenityFileName.env",
		sourcePath: envPath,
		size: envStat.size
	});
	const referencedName = (0, fs.readFileSync)(envPath, "utf-8").trim();
	if (referencedName) {
		const refPath = (0, path.join)(productDir, referencedName);
		if ((0, fs.existsSync)(refPath)) {
			const refStat = (0, fs.statSync)(refPath);
			results.push({
				fileName: referencedName,
				sourcePath: refPath,
				size: refStat.size
			});
		}
	}
	return results;
}
function getRequirementsPath(product, deviceType) {
	const config = getConfig();
	const fileName = deviceType === "PICOG3" || deviceType === "QUEST3" ? "video_requirements.txt" : "viewers_requirements.txt";
	return (0, path.join)(config.mediaTransfer.parentFileListDir, product, "VideoAssetsRequirements", fileName);
}
function getSourceDirs(deviceType) {
	const config = getConfig();
	const isVR = deviceType === "PICOG3" || deviceType === "QUEST3";
	return {
		primary: isVR ? config.mediaTransfer.parentSourceDirVR : config.mediaTransfer.parentSourceDirTAB,
		old: isVR ? config.mediaTransfer.sourceDirOldVR : config.mediaTransfer.sourceDirOldTablet
	};
}
function parseRequirementsFile(filePath) {
	if (!(0, fs.existsSync)(filePath)) throw new Error(`Requirements file not found: ${filePath}`);
	return (0, fs.readFileSync)(filePath, "utf-8").split(/\r?\n/).map((line) => line.replace(/[\r\n]+/g, "").trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
}
function findFileWithPrefixes(fileName, primaryDir, oldDir, extraDirs = []) {
	let best = null;
	const allMatches = [];
	for (const dir of [
		primaryDir,
		oldDir,
		...extraDirs
	]) {
		if (!(0, fs.existsSync)(dir)) continue;
		for (const prefix of FILE_PREFIXES) {
			const candidate = (0, path.join)(dir, prefix + fileName);
			try {
				if ((0, fs.existsSync)(candidate)) {
					const stat = (0, fs.statSync)(candidate);
					allMatches.push(prefix + fileName);
					if (!best) best = {
						path: candidate,
						size: stat.size
					};
				}
			} catch {}
		}
	}
	if (!best) return null;
	const duplicates = allMatches.length > 1 ? allMatches : void 0;
	return {
		...best,
		duplicates
	};
}
function calculateAssets(product, quality, deviceType, extraDirs = []) {
	logInfo("Calculating assets", {
		product,
		quality,
		deviceType,
		extraDirs: extraDirs.length
	});
	const reqPath = getRequirementsPath(product, deviceType);
	console.log("[assets] Requirements path:", reqPath);
	const requiredFiles = parseRequirementsFile(reqPath);
	console.log("[assets] Required files count:", requiredFiles.length);
	const { primary, old } = getSourceDirs(deviceType);
	const qualityDir = (0, path.join)(primary, quality);
	const oldDir = old;
	console.log("[assets] Searching in qualityDir:", qualityDir, "oldDir:", oldDir, "extraDirs:", extraDirs);
	const resolvedFiles = /* @__PURE__ */ new Map();
	const missingFiles = [];
	const sourceDuplicates = [];
	let totalSize = 0;
	for (const fileName of requiredFiles) {
		const found = findFileWithPrefixes(fileName, qualityDir, oldDir, extraDirs);
		if (found) {
			resolvedFiles.set(fileName, found);
			totalSize += found.size;
			if (found.duplicates) sourceDuplicates.push({
				baseName: fileName,
				variants: found.duplicates
			});
		} else {
			missingFiles.push(fileName);
			if (missingFiles.length <= 3) console.log("[assets] Missing file:", fileName, "| searched in:", qualityDir, oldDir);
		}
	}
	const serenityFiles = discoverSerenityFiles((0, path.join)(getConfig().mediaTransfer.parentFileListDir, product));
	const serenitySize = serenityFiles.reduce((sum, f) => sum + f.size, 0);
	logInfo("Asset calculation complete", {
		total: requiredFiles.length,
		resolved: resolvedFiles.size,
		missing: missingFiles.length,
		serenityFiles: serenityFiles.length,
		totalSizeBytes: totalSize + serenitySize
	});
	return {
		requiredFiles,
		resolvedFiles,
		missingFiles,
		serenityFiles,
		sourceDuplicates,
		totalSize: totalSize + serenitySize,
		totalCount: requiredFiles.length + serenityFiles.length
	};
}
function loadProducts(deviceType) {
	const config = getConfig();
	const reqDir = config.mediaTransfer.parentFileListDir;
	if (!reqDir || !(0, fs.existsSync)(reqDir)) {
		logWarn("Product directory not found", { path: reqDir || "(empty)" });
		return [];
	}
	const { readdirSync } = require("fs");
	const entries = readdirSync(reqDir, { withFileTypes: true });
	const excludePatterns = config.productFilter?.excludePatterns ?? [];
	return entries.filter((e) => e.isDirectory()).map((e) => e.name).filter((name) => !excludePatterns.some((pattern) => name.includes(pattern))).sort();
}
//#endregion
//#region src/main/services/session.ts
init_logger();
init_paths();
function getIncompleteDir() {
	return getUserDataPath("logs", "incomplete");
}
function getCompletedDir() {
	return getUserDataPath("logs", "completed");
}
function ensureDirs() {
	(0, fs.mkdirSync)(getIncompleteDir(), { recursive: true });
	(0, fs.mkdirSync)(getCompletedDir(), { recursive: true });
}
function sessionFilePath(sessionId, completed) {
	return (0, path.join)(completed ? getCompletedDir() : getIncompleteDir(), `${sessionId}.json`);
}
function generateSessionId(deviceId, product, quality) {
	return `${(/* @__PURE__ */ new Date()).toISOString().replace(/[-:T.]/g, "").slice(0, 14)}_${deviceId.replace(/[^a-zA-Z0-9]/g, "")}_${product}_${quality}`;
}
function saveSession(session) {
	ensureDirs();
	session.lastUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
	const isComplete = session.phase === "completed" || session.phase === "failed";
	(0, fs.writeFileSync)(sessionFilePath(session.sessionId, isComplete), JSON.stringify(session, null, 2), "utf-8");
}
function completeSession(session) {
	ensureDirs();
	session.completedAt = (/* @__PURE__ */ new Date()).toISOString();
	session.lastUpdatedAt = session.completedAt;
	const incompletePath = sessionFilePath(session.sessionId, false);
	(0, fs.writeFileSync)(sessionFilePath(session.sessionId, true), JSON.stringify(session, null, 2), "utf-8");
	if ((0, fs.existsSync)(incompletePath)) {
		const { unlinkSync } = require("fs");
		unlinkSync(incompletePath);
	}
}
function findIncompleteSession(deviceId, product, quality) {
	ensureDirs();
	const files = (0, fs.readdirSync)(getIncompleteDir()).filter((f) => f.endsWith(".json"));
	for (const file of files) try {
		const raw = (0, fs.readFileSync)((0, path.join)(getIncompleteDir(), file), "utf-8");
		const session = JSON.parse(raw);
		if (session.deviceId === deviceId && session.product === product && session.quality === quality) {
			const hours = (Date.now() - new Date(session.lastUpdatedAt).getTime()) / (1e3 * 60 * 60);
			if (hours > 24) logWarn("Found stale session (>24h)", {
				sessionId: session.sessionId,
				hours: Math.round(hours)
			});
			return session;
		}
	} catch {
		logWarn("Could not parse session file", { file });
	}
	return null;
}
//#endregion
//#region src/main/services/devices.ts
init_logger();
init_paths();
var cachedDevices = null;
function loadBundledDevices() {
	const filePath = getResourcePath("devices.json");
	try {
		const raw = (0, fs.readFileSync)(filePath, "utf-8");
		const parsed = JSON.parse(raw);
		return Array.isArray(parsed.devices) ? parsed.devices : [];
	} catch (err) {
		logError("Failed to load bundled devices", {
			path: filePath,
			error: String(err)
		});
		return [];
	}
}
function loadCustomDevices() {
	const filePath = getUserDataPath("custom-devices.json");
	logInfo("Loading custom devices", {
		path: filePath,
		exists: (0, fs.existsSync)(filePath)
	});
	if (!(0, fs.existsSync)(filePath)) return [];
	try {
		const raw = (0, fs.readFileSync)(filePath, "utf-8");
		const parsed = JSON.parse(raw);
		return Array.isArray(parsed.devices) ? parsed.devices : [];
	} catch (err) {
		logWarn("Failed to load custom devices", {
			path: filePath,
			error: String(err)
		});
		return [];
	}
}
function loadDevices(devicesPath) {
	if (cachedDevices) return cachedDevices;
	if (devicesPath) {
		const raw = (0, fs.readFileSync)(devicesPath, "utf-8");
		cachedDevices = JSON.parse(raw).devices;
		return cachedDevices;
	}
	const bundled = loadBundledDevices();
	const custom = loadCustomDevices();
	const merged = /* @__PURE__ */ new Map();
	for (const p of bundled) merged.set(p.model, p);
	for (const p of custom) merged.set(p.model, {
		...p,
		_custom: true
	});
	cachedDevices = Array.from(merged.values());
	logInfo("Devices loaded", {
		bundled: bundled.length,
		custom: custom.length,
		total: cachedDevices.length
	});
	return cachedDevices;
}
function saveCustomProfile(profile) {
	const filePath = getUserDataPath("custom-devices.json");
	(0, fs.mkdirSync)((0, path.dirname)(filePath), { recursive: true });
	const updated = [...loadCustomDevices().filter((p) => p.model !== profile.model), profile];
	(0, fs.writeFileSync)(filePath, JSON.stringify({ devices: updated }, null, 2), "utf-8");
	logInfo("Custom profile saved", {
		model: profile.model,
		path: filePath
	});
	resetDevicesCache();
}
function deleteCustomProfile(model) {
	const filePath = getUserDataPath("custom-devices.json");
	if (!(0, fs.existsSync)(filePath)) return;
	const filtered = loadCustomDevices().filter((p) => p.model !== model);
	(0, fs.writeFileSync)(filePath, JSON.stringify({ devices: filtered }, null, 2), "utf-8");
	logInfo("Custom profile deleted", { model });
	resetDevicesCache();
}
function findProfileByModel(model) {
	const devices = loadDevices();
	const normalized = model.toLowerCase().trim();
	return devices.find((d) => d.model.toLowerCase() === normalized || d.aliases.some((a) => a.toLowerCase() === normalized));
}
function resetDevicesCache() {
	cachedDevices = null;
}
//#endregion
//#region src/main/services/transfer.ts
init_adb();
init_logger();
init_config();
var BACKOFF_MS = [
	0,
	2e3,
	5e3,
	1e4
];
async function executeTransfer(opts) {
	const { session, serial, emit, signal, enableVerification = true } = opts;
	const startTime = Date.now();
	let filesTransferred = 0;
	let filesSkipped = 0;
	let filesFailed = 0;
	let filesMissing = 0;
	let bytesTransferred = 0;
	const allFiles = [...session.serenityFiles, ...session.mediaFiles];
	try {
		await shellCommand(serial, [
			"mkdir",
			"-p",
			session.deviceTargetPath
		]);
	} catch {}
	let maxConcurrentVerify = 2;
	try {
		maxConcurrentVerify = getConfig().performance.maxConcurrentVerify;
	} catch {}
	const MAX_CONCURRENT_VERIFY = maxConcurrentVerify;
	const verifyQueue = [];
	let activeVerifications = 0;
	let filesVerified = 0;
	let filesVerifyFailed = 0;
	function queueVerification(record) {
		const run = async () => {
			while (activeVerifications >= MAX_CONCURRENT_VERIFY) await sleep(100);
			activeVerifications++;
			try {
				if (await verifyFile(record, serial, emit, session)) filesVerified++;
				else filesVerifyFailed++;
				try {
					saveSession(session);
				} catch {}
			} finally {
				activeVerifications--;
			}
		};
		verifyQueue.push(run());
	}
	for (let i = 0; i < allFiles.length; i++) {
		if (signal?.aborted) {
			logInfo("Transfer aborted by user");
			break;
		}
		const record = allFiles[i];
		emit({
			type: "file-status",
			fileName: record.fileName,
			status: record.status
		});
		if (record.status === "completed" || record.status === "skipped" || record.status === "verified") {
			filesSkipped++;
			continue;
		}
		if (record.status === "missing") {
			filesMissing++;
			emit({
				type: "file-skip",
				fileName: record.fileName,
				reason: "Fichier source manquant"
			});
			continue;
		}
		emit({
			type: "file-status",
			fileName: record.fileName,
			status: "transferring"
		});
		if (await transferFile(record, session, serial, emit, signal)) {
			filesTransferred++;
			bytesTransferred += record.fileSizeBytes;
			session.totalBytesTransferred += record.fileSizeBytes;
			emit({
				type: "file-status",
				fileName: record.fileName,
				status: "completed"
			});
			if (enableVerification && record.sourcePath) queueVerification(record);
		} else if (record.status === "skipped") {
			filesSkipped++;
			emit({
				type: "file-status",
				fileName: record.fileName,
				status: "skipped"
			});
			if (enableVerification && record.sourcePath) queueVerification(record);
		} else {
			filesFailed++;
			emit({
				type: "file-status",
				fileName: record.fileName,
				status: record.status
			});
			if (record.errorMessage === "Appareil déconnecté") {
				logInfo("Device disconnected, aborting remaining transfers");
				break;
			}
		}
		saveSession(session);
		emit({
			type: "file-complete",
			fileName: record.fileName,
			size: record.fileSizeBytes,
			completed: i + 1,
			total: allFiles.length
		});
	}
	if (verifyQueue.length > 0 && !signal?.aborted) try {
		const deviceFiles = await getExistingFiles(serial, session.deviceTargetPath);
		logInfo("Files on device before verification", {
			dir: session.deviceTargetPath,
			count: deviceFiles.size,
			sample: [...deviceFiles.keys()].slice(0, 5)
		});
	} catch {}
	if (verifyQueue.length > 0 && !signal?.aborted) {
		emit({
			type: "progress",
			phase: "verifying",
			icon: "🔍",
			message: `Vérification MD5 de ${verifyQueue.length} fichier(s)...`
		});
		await Promise.all(verifyQueue);
	}
	const durationSeconds = Math.round((Date.now() - startTime) / 1e3);
	return {
		filesTransferred,
		filesSkipped,
		filesFailed,
		filesMissing,
		bytesTransferred,
		durationSeconds
	};
}
async function verifyFile(record, serial, emit, session) {
	try {
		record.status = "verifying";
		emit({
			type: "file-status",
			fileName: record.fileName,
			status: "verifying"
		});
		const hostMd5 = await computeHostMd5Stream(record.sourcePath);
		record.hostMd5 = hostMd5;
		const candidatePaths = [record.targetPath];
		if (session?.storageMode === "both" && session.storagePaths.internal && session.storagePaths.sdcard) {
			const fileName = record.targetPath.substring(record.targetPath.lastIndexOf("/") + 1);
			const internalPath = path.posix.join(session.storagePaths.internal, fileName);
			const sdcardPath = path.posix.join(session.storagePaths.sdcard, fileName);
			if (!candidatePaths.includes(internalPath)) candidatePaths.push(internalPath);
			if (!candidatePaths.includes(sdcardPath)) candidatePaths.push(sdcardPath);
		}
		let deviceMd5 = null;
		let verifiedPath = record.targetPath;
		for (const path$1 of candidatePaths) {
			const md5 = await getDeviceMd5(serial, path$1);
			if (md5) {
				if (md5 === hostMd5) {
					deviceMd5 = md5;
					verifiedPath = path$1;
					break;
				}
				if (!deviceMd5) {
					deviceMd5 = md5;
					verifiedPath = path$1;
				}
			}
		}
		record.deviceMd5 = deviceMd5 ?? void 0;
		if (verifiedPath !== record.targetPath) {
			logInfo("File found at alternate path", {
				fileName: record.fileName,
				from: record.targetPath,
				to: verifiedPath
			});
			record.targetPath = verifiedPath;
		}
		if (!deviceMd5) {
			record.status = "verify-failed";
			logWarn("Device MD5 unavailable on all paths", {
				fileName: record.fileName,
				tried: candidatePaths
			});
			emit({
				type: "file-verified",
				fileName: record.fileName,
				match: false,
				hostMd5,
				deviceMd5: `n/a (${candidatePaths.join(", ")})`
			});
			emit({
				type: "file-status",
				fileName: record.fileName,
				status: "verify-failed"
			});
			return false;
		}
		const match = hostMd5 === deviceMd5;
		record.status = match ? "verified" : "verify-failed";
		emit({
			type: "file-verified",
			fileName: record.fileName,
			match,
			hostMd5,
			deviceMd5
		});
		emit({
			type: "file-status",
			fileName: record.fileName,
			status: record.status
		});
		if (!match) {
			console.error(`[verify] MD5 mismatch: ${record.fileName} host=${hostMd5} device=${deviceMd5} at ${verifiedPath}`);
			record.errorMessage = `MD5 mismatch: host=${hostMd5} device=${deviceMd5}`;
		}
		return match;
	} catch (err) {
		console.error(`[verify] Error verifying ${record.fileName}:`, err);
		record.status = "verify-failed";
		emit({
			type: "file-status",
			fileName: record.fileName,
			status: "verify-failed"
		});
		return false;
	}
}
async function transferFile(record, session, serial, emit, signal) {
	record.status = "transferring";
	record.startedAt = (/* @__PURE__ */ new Date()).toISOString();
	for (let attempt = 0; attempt <= record.maxRetries; attempt++) {
		record.retryCount = attempt;
		if (attempt > 0) {
			const delay = BACKOFF_MS[attempt] ?? BACKOFF_MS[BACKOFF_MS.length - 1];
			logInfo("Retrying file transfer", {
				fileName: record.fileName,
				attempt,
				delay
			});
			await sleep(delay);
		}
		try {
			const forceOverwrite = record.fileName === "serenityFileName.env";
			if (!forceOverwrite && session.transferPolicy === "skip-existing") {
				if (await fileExistsOnDevice(serial, record.targetPath)) {
					record.status = "skipped";
					emit({
						type: "file-skip",
						fileName: record.fileName,
						reason: "Existe déjà sur l'appareil"
					});
					return false;
				}
			}
			const useSync = !forceOverwrite && session.transferPolicy === "update-different";
			console.log(`[transfer] Pushing: ${record.sourcePath} -> ${record.targetPath} (sync=${useSync})`);
			if ((await pushFile(serial, record.sourcePath, record.targetPath, useSync, signal)).skipped) {
				record.status = "skipped";
				emit({
					type: "file-skip",
					fileName: record.fileName,
					reason: "Identique (sync)"
				});
				return false;
			}
			record.status = "completed";
			record.completedAt = (/* @__PURE__ */ new Date()).toISOString();
			return true;
		} catch (err) {
			const adbErr = err;
			console.error(`[transfer] Failed: ${record.fileName} (attempt ${attempt}):`, adbErr.message);
			if (signal?.aborted) {
				console.log(`[transfer] Aborted, cleaning up incomplete file: ${record.targetPath}`);
				try {
					await shellCommand(serial, [
						"rm",
						"-f",
						record.targetPath
					]);
				} catch {}
				record.status = "failed";
				record.errorMessage = "Annulé par l'utilisateur";
				emit({
					type: "file-error",
					fileName: record.fileName,
					error: "Annulé"
				});
				return false;
			}
			if (adbErr.isStorageFull) {
				if (session.storageMode === "both" && session.storagePaths.sdcard) {
					const primaryPath = session.storagePaths[session.storagePriority === "sdcard-first" ? "sdcard" : "internal"] ?? session.deviceTargetPath;
					const fallbackPath = session.storagePaths[session.storagePriority === "sdcard-first" ? "internal" : "sdcard"];
					if (fallbackPath && record.targetPath.startsWith(primaryPath)) {
						const fallbackTarget = record.targetPath.replace(primaryPath, fallbackPath);
						const fromLabel = session.storagePriority === "sdcard-first" ? "carte SD" : "stockage interne";
						const toLabel = session.storagePriority === "sdcard-first" ? "stockage interne" : "carte SD";
						logInfo(`Storage full on ${fromLabel}, falling back to ${toLabel}`, {
							fileName: record.fileName,
							from: record.targetPath,
							to: fallbackTarget
						});
						emit({
							type: "file-error",
							fileName: record.fileName,
							error: `Stockage plein, bascule vers ${toLabel}`
						});
						const fallbackDir = fallbackTarget.substring(0, fallbackTarget.lastIndexOf("/"));
						try {
							await shellCommand(serial, [
								"mkdir",
								"-p",
								fallbackDir
							]);
						} catch {}
						record.targetPath = fallbackTarget;
						record.status = "transferring";
						try {
							if ((await pushFile(serial, record.sourcePath, fallbackTarget, false, signal)).skipped) {
								record.status = "skipped";
								emit({
									type: "file-skip",
									fileName: record.fileName,
									reason: "Identique (sync)"
								});
								return false;
							}
							record.status = "completed";
							record.completedAt = (/* @__PURE__ */ new Date()).toISOString();
							return true;
						} catch (fallbackErr) {
							const fallbackAdbErr = fallbackErr;
							logError("Fallback storage also failed", {
								fileName: record.fileName,
								error: fallbackAdbErr.message
							});
						}
					}
				}
				record.status = "failed";
				record.errorMessage = "Stockage plein";
				emit({
					type: "file-error",
					fileName: record.fileName,
					error: "Stockage plein"
				});
				emit({
					type: "storage-full",
					context: {
						currentStorage: {
							type: "internal",
							path: session.deviceTargetPath,
							displayName: "Stockage interne"
						},
						bytesNeeded: record.fileSizeBytes,
						bytesAvailable: 0,
						fileName: record.fileName
					}
				});
				return false;
			}
			if (adbErr.isDisconnected) {
				record.status = "failed";
				record.errorMessage = "Appareil déconnecté";
				emit({
					type: "file-error",
					fileName: record.fileName,
					error: "Appareil déconnecté"
				});
				return false;
			}
			if (attempt === record.maxRetries) {
				record.status = "failed";
				record.errorMessage = adbErr.message;
				record.exitCode = adbErr.exitCode;
				emit({
					type: "file-error",
					fileName: record.fileName,
					error: adbErr.message
				});
				return false;
			}
		}
	}
	return false;
}
var md5Cache = null;
function getMd5CachePath() {
	try {
		const { getUserDataPath } = (init_paths(), __toCommonJS(paths_exports));
		return getUserDataPath("cache", "md5-cache.json");
	} catch {
		return (0, path.join)(process.cwd(), "cache", "md5-cache.json");
	}
}
function loadMd5Cache() {
	if (md5Cache) return md5Cache;
	const cachePath = getMd5CachePath();
	try {
		if ((0, fs.existsSync)(cachePath)) {
			md5Cache = JSON.parse((0, fs.readFileSync)(cachePath, "utf-8"));
			return md5Cache;
		}
	} catch {}
	md5Cache = {};
	return md5Cache;
}
function saveMd5Cache() {
	const cachePath = getMd5CachePath();
	try {
		(0, fs.mkdirSync)((0, path.dirname)(cachePath), { recursive: true });
		(0, fs.writeFileSync)(cachePath, JSON.stringify(md5Cache, null, 2), "utf-8");
	} catch {}
}
function md5CacheKey(filePath) {
	try {
		const stat = (0, fs.statSync)(filePath);
		return `${filePath}:${stat.mtimeMs}:${stat.size}`;
	} catch {
		return null;
	}
}
async function computeHostMd5Stream(filePath) {
	const cache = loadMd5Cache();
	const key = md5CacheKey(filePath);
	if (key && cache[key]) return cache[key];
	const hash = await new Promise((resolve, reject) => {
		const h = (0, crypto.createHash)("md5");
		const stream = (0, fs.createReadStream)(filePath);
		stream.on("data", (chunk) => h.update(chunk));
		stream.on("end", () => resolve(h.digest("hex")));
		stream.on("error", reject);
	});
	if (key) {
		cache[key] = hash;
		saveMd5Cache();
	}
	return hash;
}
function sleep(ms) {
	return new Promise((resolve) => setTimeout(resolve, ms));
}
//#endregion
//#region src/main/execution-orchestrator.ts
init_adb();
init_config();
init_logger();
init_paths();
function createExecutionContext() {
	return {
		abortControllers: /* @__PURE__ */ new Map(),
		abortSerials: /* @__PURE__ */ new Map()
	};
}
function emitForTab(tabId, sendToRenderer) {
	return (event) => {
		sendToRenderer({
			...event,
			tabId
		});
	};
}
async function handleStartExecution(plan, ctx, sendToRenderer) {
	console.log("[exec] Starting execution", {
		serial: plan.selectedDeviceId,
		product: plan.product,
		quality: plan.quality,
		skipApk: plan.skipApk,
		skipFiles: plan.skipFiles,
		releaseTag: plan.releaseTag,
		requiredFiles: plan.requiredFiles?.length,
		missingFiles: plan.missingFiles?.length
	});
	const serial = plan.selectedDeviceId;
	const tabId = plan.tabId ?? "default";
	const controller = new AbortController();
	ctx.abortControllers.set(tabId, controller);
	ctx.abortSerials.set(tabId, serial);
	const emit = emitForTab(tabId, sendToRenderer);
	try {
		if (!plan.skipApk && plan.deviceProfile) {
			let apkPath;
			if (plan.localApkPath) {
				apkPath = plan.localApkPath;
				console.log("[exec] Using local APK:", apkPath);
				emit({
					type: "progress",
					tabId,
					phase: "download",
					icon: "✓",
					message: "APK local sélectionné"
				});
			} else if (plan.releaseTag) {
				emit({
					type: "progress",
					tabId,
					phase: "download",
					icon: "⬇",
					message: "Téléchargement de l'APK..."
				});
				apkPath = await downloadApk(plan.deviceProfile.repo, plan.releaseTag);
			} else apkPath = "";
			if (apkPath) {
				emit({
					type: "progress",
					tabId,
					phase: "install",
					icon: "📦",
					message: "Installation de l'APK..."
				});
				try {
					await installApk(serial, apkPath);
				} catch (installErr) {
					const adbErr = installErr;
					if (adbErr.isStorageFull) throw new Error("Espace insuffisant sur l'appareil pour installer l'APK. Libérez de l'espace et réessayez.");
					if (adbErr.isDisconnected) throw new Error("Appareil déconnecté pendant l'installation de l'APK.");
					throw new Error(`Échec de l\'installation de l\'APK: ${adbErr.message}`);
				}
			}
		}
		if (plan.deviceProfile?.commandFile) {
			emit({
				type: "progress",
				tabId,
				phase: "configure",
				icon: "⚙",
				message: "Configuration de l'appareil..."
			});
			await configureDevice(serial, (0, fs.readFileSync)(getResourcePath("commands", plan.deviceProfile.commandFile), "utf-8").split("\n"), {
				DEVICE_NAME: plan.deviceName,
				NETWORK_NAME: plan.networkName,
				TARGET_DIR: plan.deviceProfile.targetDir,
				DEVICE_SERIAL: serial,
				PACKAGE_NAME: plan.deviceProfile.packageName,
				REPO_NAME: plan.deviceProfile.repo
			});
			if (plan.deviceProfile.type === "PICOG3" || plan.deviceProfile.type === "QUEST3") {
				const csvContent = `device_name;${plan.networkName}\nref;${plan.deviceName}\n`;
				const tmpPath = (0, path.join)((0, os.tmpdir)(), `settings-${serial}-${Date.now()}.csv`);
				const targetPath = path.posix.join(plan.deviceProfile.targetDir, "settings.csv");
				try {
					await (0, fs_promises.writeFile)(tmpPath, csvContent, "utf-8");
					await pushFile(serial, tmpPath, targetPath);
					emit({
						type: "progress",
						tabId,
						phase: "configure",
						icon: "⚙",
						message: "Fichier settings.csv créé"
					});
				} catch (err) {
					throw new Error(`Échec de la création du fichier settings.csv: ${err instanceof Error ? err.message : String(err)}`);
				} finally {
					await (0, fs_promises.unlink)(tmpPath).catch(() => {});
				}
			}
		}
		if (!plan.skipFiles && plan.product && plan.quality) {
			emit({
				type: "progress",
				tabId,
				phase: "transfer",
				icon: "📁",
				message: "Transfert des fichiers..."
			});
			const sessionId = generateSessionId(serial, plan.product, plan.quality);
			const internalTargetDir = plan.deviceProfile?.targetDir ?? "";
			const storagePaths = { internal: internalTargetDir };
			const sdLoc = plan.storageLocations.find((l) => l.type === "sdcard");
			if (sdLoc) {
				const relativePath = internalTargetDir.replace(/^\/storage\/emulated\/0/, "");
				storagePaths.sdcard = sdLoc.path + relativePath;
			}
			let effectiveTargetDir = internalTargetDir;
			if (plan.storageMode === "sdcard" && storagePaths.sdcard) effectiveTargetDir = storagePaths.sdcard;
			else if (plan.storageMode === "both" && plan.storagePriority === "sdcard-first" && storagePaths.sdcard) effectiveTargetDir = storagePaths.sdcard;
			logInfo("Storage configuration", {
				mode: plan.storageMode,
				priority: plan.storagePriority,
				effectiveTarget: effectiveTargetDir,
				internal: storagePaths.internal,
				sdcard: storagePaths.sdcard ?? "none",
				storageLocationsCount: plan.storageLocations?.length ?? 0
			});
			const sdLabel = storagePaths.sdcard ? storagePaths.sdcard.match(/\/storage\/([^/]+)/)?.[1] ?? "SD" : "aucune";
			emit({
				type: "progress",
				tabId,
				phase: "transfer",
				icon: "💾",
				message: `Cible: ${effectiveTargetDir.includes("emulated") ? "Interne" : sdLabel} | Interne: ${storagePaths.internal} | SD: ${storagePaths.sdcard ?? "aucune"}`
			});
			const session = {
				sessionId,
				createdAt: (/* @__PURE__ */ new Date()).toISOString(),
				lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString(),
				deviceId: serial,
				deviceModel: plan.detectedDeviceModel ?? "",
				deviceType: plan.deviceProfile?.type ?? "PICOG3",
				product: plan.product,
				quality: plan.quality,
				transferPolicy: plan.transferPolicy,
				deviceTargetPath: effectiveTargetDir,
				storageMode: plan.storageMode,
				storagePriority: plan.storagePriority ?? "internal-first",
				storagePaths,
				phase: "transferring-media",
				serenityFiles: buildSerenityFileRecords(plan, effectiveTargetDir),
				mediaFiles: (() => {
					const records = buildFileRecords(plan, effectiveTargetDir);
					const pending = records.filter((r) => r.status === "pending");
					const missing = records.filter((r) => r.status === "missing");
					console.log(`[exec] File records: ${records.length} total, ${pending.length} pending, ${missing.length} missing`);
					return records;
				})(),
				totalBytesPlanned: plan.totalAssetSize,
				totalBytesTransferred: 0,
				requirementsFileHash: ""
			};
			saveSession(session);
			const stats = await executeTransfer({
				session,
				serial,
				emit,
				signal: controller.signal,
				enableVerification: plan.enableVerification ?? true
			});
			const wasAborted = controller.signal.aborted;
			session.phase = wasAborted || stats.filesFailed > 0 ? "failed" : "completed";
			completeSession(session);
			emit({
				type: "execution-done",
				tabId,
				success: !wasAborted && stats.filesFailed === 0,
				message: wasAborted ? "Installation annulée par l'utilisateur" : stats.filesFailed === 0 ? "Installation terminée avec succès" : `Installation terminée avec ${stats.filesFailed} erreur(s)`,
				stats
			});
		} else {
			const wasAborted = controller.signal.aborted;
			emit({
				type: "execution-done",
				tabId,
				success: !wasAborted,
				message: wasAborted ? "Installation annulée par l'utilisateur" : "Installation terminée avec succès",
				stats: {
					filesTransferred: 0,
					filesSkipped: 0,
					filesFailed: 0,
					filesMissing: 0,
					bytesTransferred: 0,
					durationSeconds: 0
				}
			});
		}
	} catch (err) {
		console.error("[exec] Execution failed:", err);
		emit({
			type: "execution-done",
			tabId,
			success: false,
			message: `Erreur: ${err instanceof Error ? err.message : String(err)}`,
			stats: {
				filesTransferred: 0,
				filesSkipped: 0,
				filesFailed: 0,
				filesMissing: 0,
				bytesTransferred: 0,
				durationSeconds: 0
			}
		});
	} finally {
		ctx.abortControllers.delete(tabId);
		ctx.abortSerials.delete(tabId);
	}
}
function handleStopExecution(tabId, ctx) {
	const id = tabId ?? "default";
	const controller = ctx.abortControllers.get(id);
	if (controller) {
		controller.abort();
		logInfo("Execution stop requested", { tabId: id });
	}
}
function buildSerenityFileRecords(plan, effectiveTargetDir) {
	const maxRetries = getConfig().transferLogging.maxRetries;
	const targetDir = effectiveTargetDir ?? plan.deviceProfile?.targetDir ?? "";
	return (plan.serenityFiles ?? []).map((sf) => ({
		fileName: sf.fileName,
		sourcePath: sf.sourcePath,
		targetPath: path.posix.join(targetDir, sf.fileName),
		status: "pending",
		fileSizeBytes: sf.size,
		retryCount: 0,
		maxRetries
	}));
}
function buildFileRecords(plan, effectiveTargetDir) {
	const config = getConfig();
	const maxRetries = config.transferLogging.maxRetries;
	const deviceType = plan.deviceProfile?.type ?? "PICOG3";
	const isVR = deviceType === "PICOG3" || deviceType === "QUEST3";
	const primaryDir = isVR ? config.mediaTransfer.parentSourceDirVR : config.mediaTransfer.parentSourceDirTAB;
	const oldDir = isVR ? config.mediaTransfer.sourceDirOldVR : config.mediaTransfer.sourceDirOldTablet;
	const qualityDir = plan.quality ? (0, path.join)(primaryDir, plan.quality) : primaryDir;
	const extraDirs = plan.extraSearchDirs ?? [];
	const serenityNames = new Set((plan.serenityFiles ?? []).map((f) => f.fileName));
	return plan.requiredFiles.filter((f) => !serenityNames.has(f)).map((fileName) => {
		const isMissing = plan.missingFiles.includes(fileName);
		let sourcePath = "";
		let fileSizeBytes = 0;
		if (!isMissing) {
			const found = findFileWithPrefixes(fileName, qualityDir, oldDir, extraDirs);
			if (found) {
				sourcePath = found.path;
				fileSizeBytes = found.size;
			}
		}
		const actualName = sourcePath ? (0, path.basename)(sourcePath) : fileName;
		return {
			fileName: actualName,
			sourcePath,
			targetPath: path.posix.join(effectiveTargetDir ?? plan.deviceProfile?.targetDir ?? "", actualName),
			status: isMissing || !sourcePath ? "missing" : "pending",
			fileSizeBytes,
			retryCount: 0,
			maxRetries
		};
	});
}
//#endregion
//#region src/main/services/update-helpers/win-swap.ts
function generateWinSwapBat(args) {
	const { pendingPath, targetPath, exePath } = args;
	return [
		"@echo off",
		"setlocal",
		"set \"TARGET=" + targetPath + "\"",
		"set \"PENDING=" + pendingPath + "\"",
		"set \"APP_EXE=" + exePath + "\"",
		"set RETRY=0",
		":loop",
		"move /Y \"%PENDING%\" \"%TARGET%\" >nul 2>&1",
		"if errorlevel 1 (",
		"    set /a RETRY+=1",
		"    if %RETRY% GEQ 30 goto giveup",
		"    timeout /t 1 /nobreak >nul",
		"    goto loop",
		")",
		"start \"\" \"%APP_EXE%\"",
		"(goto) 2>nul & del \"%~f0\"",
		"exit /b 0",
		":giveup",
		"echo Echec du remplacement apres 30 tentatives.",
		"echo Fichier de mise a jour: %PENDING%",
		"echo Cible: %TARGET%",
		"echo Vous pouvez tenter manuellement: move \"%PENDING%\" \"%TARGET%\"",
		"pause",
		"exit /b 1",
		""
	].join("\r\n");
}
//#endregion
//#region src/main/services/update-helpers/mac-swap.ts
function generateMacSwapSh(args) {
	const { pendingPath, targetPath, appBundlePath } = args;
	return [
		"#!/bin/bash",
		"TARGET=" + shellQuote(targetPath),
		"PENDING=" + shellQuote(pendingPath),
		"APP_PATH=" + shellQuote(appBundlePath),
		"for i in $(seq 1 30); do",
		"  if mv -f \"$PENDING\" \"$TARGET\" 2>/dev/null; then",
		"    open \"$APP_PATH\"",
		"    rm -- \"$0\"",
		"    exit 0",
		"  fi",
		"  sleep 1",
		"done",
		"osascript -e \"display alert \\\"SerenityInstaller — mise à jour interrompue\\\" message \\\"Impossible de remplacer app.asar après 30 tentatives. Fichier prêt: $PENDING\\\"\"",
		"exit 1",
		""
	].join("\n");
}
function shellQuote(s) {
	return "'" + s.replace(/'/g, "'\\''") + "'";
}
//#endregion
//#region src/main/services/update-helpers/index.ts
init_logger();
function spawnSwapHelper(args) {
	const tempDir = electron.app.getPath("temp");
	const stamp = Date.now();
	if (process.platform === "win32") {
		const exePath = process.execPath;
		const scriptPath = (0, path.join)(tempDir, `serenity-update-${stamp}.bat`);
		(0, fs.writeFileSync)(scriptPath, generateWinSwapBat({
			pendingPath: args.pendingPath,
			targetPath: args.targetPath,
			exePath
		}), "utf-8");
		logInfo("Spawning Windows swap helper", { scriptPath });
		(0, child_process.spawn)("cmd.exe", ["/c", scriptPath], {
			detached: true,
			stdio: "ignore",
			windowsHide: true
		}).unref();
		return;
	}
	if (process.platform === "darwin") {
		const appBundlePath = findAppBundle(process.execPath);
		if (!appBundlePath) {
			logError("Could not locate .app bundle from execPath", { execPath: process.execPath });
			throw new Error("Bundle .app introuvable");
		}
		const scriptPath = (0, path.join)(tempDir, `serenity-update-${stamp}.sh`);
		(0, fs.writeFileSync)(scriptPath, generateMacSwapSh({
			pendingPath: args.pendingPath,
			targetPath: args.targetPath,
			appBundlePath
		}), "utf-8");
		(0, fs.chmodSync)(scriptPath, 493);
		logInfo("Spawning macOS swap helper", {
			scriptPath,
			appBundlePath
		});
		(0, child_process.spawn)("/bin/bash", [scriptPath], {
			detached: true,
			stdio: "ignore"
		}).unref();
		return;
	}
	throw new Error(`Plateforme non supportée: ${process.platform}`);
}
function findAppBundle(execPath) {
	const match = execPath.match(/^(.+\.app)\//);
	return match ? match[1] : null;
}
//#endregion
//#region src/main/services/updater.ts
init_paths();
init_logger();
var GITEA_API = "https://git.rballage.com/api/v1";
var OWNER = "Serenity";
var REPO = "SerenityInstallerReleases";
var MANIFEST_ASSET_NAME = "update-manifest.json";
var state = {
	phase: "idle",
	received: 0,
	total: 0,
	speedBps: 0
};
var activeController = null;
var sendToRenderer$1 = () => {};
var lastLaunchedVersionFile = "";
function registerUpdater(send) {
	sendToRenderer$1 = send;
	lastLaunchedVersionFile = getUserDataPath("last-launched-version.json");
	emitAppliedIfVersionChanged();
}
function getUpdateState() {
	return { ...state };
}
function abortInFlightDownload() {
	if (activeController) {
		activeController.abort();
		activeController = null;
	}
}
function setState(patch) {
	state = {
		...state,
		...patch
	};
}
function emit(event) {
	try {
		sendToRenderer$1(event);
	} catch (err) {
		logError("Updater event emission failed", { error: String(err) });
	}
}
function emitError(phase, error) {
	setState({
		phase: "error",
		error,
		errorPhase: phase
	});
	emit({
		type: "update-download-error",
		phase: phase ?? "fetch",
		error
	});
}
function getEnvironmentBlocker() {
	if (!electron.app.isPackaged) return "Mise à jour désactivée en mode développement";
	if (process.platform === "darwin" && process.execPath.includes("/AppTranslocation/")) return "Veuillez déplacer SerenityInstaller dans le dossier Applications avant de mettre à jour";
	return null;
}
function getAsarPaths() {
	const targetPath = (0, path.join)(process.resourcesPath, "app.asar");
	const pendingDir = getUserDataPath("pending");
	return {
		targetPath,
		pendingDir,
		pendingPath: (0, path.join)(pendingDir, "app.asar.new")
	};
}
function canWriteResources(targetPath) {
	try {
		const probe = (0, path.join)((0, path.dirname)(targetPath), ".serenity-write-probe");
		(0, fs.writeFileSync)(probe, "");
		(0, fs.unlinkSync)(probe);
		return true;
	} catch {
		return false;
	}
}
function authHeader() {
	return {};
}
async function fetchManifest() {
	try {
		const releaseResponse = await electron.net.fetch(`${GITEA_API}/repos/${OWNER}/${REPO}/releases/latest`, { headers: authHeader() });
		if (!releaseResponse.ok) {
			logError("Latest release fetch failed", { status: releaseResponse.status });
			return null;
		}
		const manifestAsset = (await releaseResponse.json()).assets?.find((a) => a.name === MANIFEST_ASSET_NAME);
		if (!manifestAsset) {
			logError("update-manifest.json missing from release assets");
			return null;
		}
		const manifestResponse = await electron.net.fetch(manifestAsset.browser_download_url, { headers: authHeader() });
		if (!manifestResponse.ok) {
			logError("Manifest fetch failed", { status: manifestResponse.status });
			return null;
		}
		const manifest = await manifestResponse.json();
		if (!manifest.version || !manifest.asar?.url || !manifest.asar?.sha256) {
			logError("Manifest malformed", { manifest });
			return null;
		}
		return manifest;
	} catch (err) {
		logError("fetchManifest error", { error: err instanceof Error ? err.message : String(err) });
		return null;
	}
}
async function startDownload() {
	if (state.phase === "downloading" || state.phase === "verifying" || state.phase === "applying") {
		logInfo("Download already in flight; ignoring");
		return;
	}
	const blocker = getEnvironmentBlocker();
	if (blocker) {
		emitError("environment", blocker);
		return;
	}
	if (!state.asar) {
		emitError("fetch", "Aucune mise à jour disponible");
		return;
	}
	const paths = getAsarPaths();
	if (!paths) {
		emitError("environment", "Chemins introuvables");
		return;
	}
	if (!canWriteResources(paths.targetPath)) {
		emitError("permission", "Permission refusée sur le dossier de l'app. Réinstallez SerenityInstaller dans un emplacement où vous avez les droits d'écriture.");
		return;
	}
	try {
		(0, fs.mkdirSync)(paths.pendingDir, { recursive: true });
	} catch (err) {
		emitError("write", `Impossible de créer le dossier pending: ${String(err)}`);
		return;
	}
	const partPath = paths.pendingPath + ".part";
	if ((0, fs.existsSync)(partPath)) try {
		(0, fs.unlinkSync)(partPath);
	} catch {}
	const asar = state.asar;
	setState({
		phase: "downloading",
		received: 0,
		total: asar.size,
		speedBps: 0,
		error: void 0,
		errorPhase: void 0
	});
	activeController = new AbortController();
	const startTime = Date.now();
	let lastEmit = startTime;
	let received = 0;
	try {
		const response = await electron.net.fetch(asar.url, {
			headers: authHeader(),
			signal: activeController.signal
		});
		if (!response.ok) throw new Error(`HTTP ${response.status}`);
		if (!response.body) throw new Error("Corps de réponse manquant");
		const headerTotal = Number(response.headers.get("content-length") ?? "0");
		const total = headerTotal > 0 ? headerTotal : asar.size;
		setState({ total });
		const hash = (0, crypto.createHash)("sha256");
		const reader = stream.Readable.fromWeb(response.body);
		const writeStream = (0, fs.createWriteStream)(partPath);
		reader.on("data", (chunk) => {
			hash.update(chunk);
			received += chunk.length;
			const now = Date.now();
			if (now - lastEmit >= 200) {
				const elapsed = (now - startTime) / 1e3;
				const speedBps = elapsed > 0 ? Math.round(received / elapsed) : 0;
				setState({
					received,
					speedBps
				});
				emit({
					type: "update-download-progress",
					received,
					total,
					speedBps
				});
				lastEmit = now;
			}
		});
		await (0, stream_promises.pipeline)(reader, writeStream);
		setState({
			phase: "verifying",
			received,
			speedBps: 0
		});
		const digest = hash.digest("hex");
		if (digest.toLowerCase() !== asar.sha256.toLowerCase()) {
			try {
				(0, fs.unlinkSync)(partPath);
			} catch {}
			throw new Error(`Empreinte SHA256 invalide (attendu ${asar.sha256.slice(0, 12)}…, obtenu ${digest.slice(0, 12)}…)`);
		}
		if ((0, fs.existsSync)(paths.pendingPath)) try {
			(0, fs.unlinkSync)(paths.pendingPath);
		} catch {}
		(0, fs.renameSync)(partPath, paths.pendingPath);
		setState({
			phase: "ready",
			received,
			total
		});
		emit({
			type: "update-download-complete",
			version: state.version ?? ""
		});
		activeController = null;
		logInfo("Update download complete", {
			version: state.version,
			sizeBytes: received
		});
	} catch (err) {
		const message = err instanceof Error ? err.message : String(err);
		if (activeController?.signal.aborted) {
			logInfo("Download aborted by user");
			setState({
				phase: "available",
				received: 0,
				total: state.total
			});
			try {
				if ((0, fs.existsSync)(partPath)) (0, fs.unlinkSync)(partPath);
			} catch {}
		} else {
			logError("Download failed", { error: message });
			try {
				if ((0, fs.existsSync)(partPath)) (0, fs.unlinkSync)(partPath);
			} catch {}
			emitError(message.includes("SHA256") ? "verify" : "fetch", message);
		}
		activeController = null;
	}
}
function cancelDownload() {
	abortInFlightDownload();
}
function applyUpdate() {
	if (state.phase !== "ready") {
		logInfo("applyUpdate called outside ready state", { phase: state.phase });
		return;
	}
	const paths = getAsarPaths();
	if (!paths || !(0, fs.existsSync)(paths.pendingPath)) {
		emit({
			type: "update-apply-error",
			error: "Fichier de mise à jour introuvable"
		});
		return;
	}
	try {
		persistLastLaunchedVersion({ pendingVersion: state.version ?? "" });
		spawnSwapHelper({
			pendingPath: paths.pendingPath,
			targetPath: paths.targetPath
		});
		setState({ phase: "applying" });
		setTimeout(() => electron.app.quit(), 250);
	} catch (err) {
		const message = err instanceof Error ? err.message : String(err);
		logError("applyUpdate failed", { error: message });
		emit({
			type: "update-apply-error",
			error: message
		});
	}
}
function readVersionRecord() {
	try {
		if (!(0, fs.existsSync)(lastLaunchedVersionFile)) return null;
		const raw = require("fs").readFileSync(lastLaunchedVersionFile, "utf-8");
		return JSON.parse(raw);
	} catch {
		return null;
	}
}
function persistLastLaunchedVersion(patch) {
	try {
		(0, fs.mkdirSync)((0, path.dirname)(lastLaunchedVersionFile), { recursive: true });
		const merged = {
			...readVersionRecord() ?? { lastLaunchedVersion: electron.app.getVersion() },
			...patch
		};
		(0, fs.writeFileSync)(lastLaunchedVersionFile, JSON.stringify(merged, null, 2), "utf-8");
	} catch (err) {
		logError("Failed to persist version record", { error: String(err) });
	}
}
function emitAppliedIfVersionChanged() {
	if (!electron.app.isPackaged) return;
	const current = electron.app.getVersion();
	const record = readVersionRecord();
	if (record) {
		const previous = record.lastLaunchedVersion;
		if (previous && previous !== current) {
			emit({
				type: "update-applied",
				previousVersion: previous,
				currentVersion: current
			});
			logInfo("Update applied since last launch", {
				previous,
				current
			});
		}
	}
	persistLastLaunchedVersion({
		lastLaunchedVersion: current,
		pendingVersion: void 0
	});
}
function setAvailableUpdate(info) {
	setState({
		phase: info.requiresManualUpdate ? "manual-required" : "available",
		version: info.version,
		asar: info.asar,
		fullPackageUrl: info.fullPackageUrl,
		releasedAt: info.releasedAt,
		notes: info.notes,
		total: info.asar.size,
		received: 0,
		error: void 0,
		errorPhase: void 0
	});
	emit({
		type: "update-available",
		version: info.version,
		asar: info.asar,
		fullPackageUrl: info.fullPackageUrl,
		requiresManualUpdate: info.requiresManualUpdate,
		releasedAt: info.releasedAt,
		notes: info.notes
	});
}
function markUpToDate() {
	setState({
		phase: "idle",
		version: void 0,
		asar: void 0,
		received: 0,
		total: 0
	});
	emit({
		type: "update-check-result",
		upToDate: true
	});
}
//#endregion
//#region src/main/services/version-checker.ts
init_logger();
function sendToAllWindows(event) {
	for (const win of electron.BrowserWindow.getAllWindows()) win.webContents.send("ipc-event", event);
}
var MIN_CHECK_INTERVAL_MS = 6e4;
var lastManualCheckAt = 0;
/**
* @param manual — true when triggered by user click (throttled at 60s),
*                 false for automatic startup check (never throttled)
*/
async function checkForUpdates(manual = false) {
	if (manual) {
		const now = Date.now();
		if (now - lastManualCheckAt < MIN_CHECK_INTERVAL_MS) {
			logInfo("Update check throttled", { secondsSinceLast: Math.round((now - lastManualCheckAt) / 1e3) });
			return;
		}
		lastManualCheckAt = now;
	}
	const currentVersion = electron.app.getVersion();
	logInfo("Checking for updates", { currentVersion });
	const manifest = await fetchManifest();
	if (!manifest) return;
	logInfo("Version check result", {
		currentVersion,
		latestVersion: manifest.version
	});
	if (!isNewer(manifest.version, currentVersion)) {
		markUpToDate();
		return;
	}
	const requiresManualUpdate = manifest.minRuntimeVersion ? isNewer(manifest.minRuntimeVersion, currentVersion) : false;
	if (requiresManualUpdate && !manifest.fullPackage?.url) logError("Manual update required but no fullPackage url in manifest");
	setAvailableUpdate({
		version: manifest.version,
		asar: manifest.asar,
		fullPackageUrl: manifest.fullPackage?.url,
		requiresManualUpdate,
		releasedAt: manifest.releasedAt,
		notes: manifest.notes
	});
	sendToAllWindows({
		type: "update-check-result",
		upToDate: false
	});
}
function isNewer(latest, current) {
	const l = latest.split(".").map(Number);
	const c = current.split(".").map(Number);
	for (let i = 0; i < Math.max(l.length, c.length); i++) {
		const lv = l[i] ?? 0;
		const cv = c[i] ?? 0;
		if (lv > cv) return true;
		if (lv < cv) return false;
	}
	return false;
}
//#endregion
//#region src/main/ipc-handlers.ts
init_adb();
init_config();
init_logger();
init_paths();
init_device_polling();
function sendToRenderer(event) {
	const windows = electron.BrowserWindow.getAllWindows();
	for (const win of windows) win.webContents.send("ipc-event", event);
}
function registerIpcHandlers() {
	initLogger();
	logInfo("Registering IPC handlers");
	registerUpdater(sendToRenderer);
	const ctx = createExecutionContext();
	electron.ipcMain.handle("detect-devices", () => detectDevices());
	electron.ipcMain.handle("detect-model", (_e, serial) => detectModel(serial));
	electron.ipcMain.handle("match-profile", (_e, model) => findProfileByModel(model) ?? null);
	electron.ipcMain.handle("list-profiles", () => loadDevices());
	electron.ipcMain.handle("save-custom-profile", (_e, profile) => {
		saveCustomProfile(profile);
		return true;
	});
	electron.ipcMain.handle("delete-custom-profile", (_e, model) => {
		deleteCustomProfile(model);
		return true;
	});
	electron.ipcMain.handle("list-command-files", () => {
		const commandsDir = getResourcePath("commands");
		try {
			const { readdirSync } = require("fs");
			return readdirSync(commandsDir).filter((f) => f.endsWith(".txt"));
		} catch {
			return [];
		}
	});
	electron.ipcMain.handle("load-products", (_e, deviceType) => {
		logInfo("load-products called", {
			deviceType,
			parentFileListDir: getConfig().mediaTransfer.parentFileListDir
		});
		try {
			const result = loadProducts(deviceType);
			logInfo("load-products result", { count: result.length });
			return result;
		} catch (err) {
			logError("load-products failed", { error: String(err) });
			throw err;
		}
	});
	electron.ipcMain.handle("load-qualities", (_e, deviceType) => {
		const config = getConfig();
		const baseDir = deviceType === "PICOG3" || deviceType === "QUEST3" ? config.mediaTransfer.parentSourceDirVR : config.mediaTransfer.parentSourceDirTAB;
		logInfo("load-qualities called", {
			deviceType,
			baseDir
		});
		try {
			const { readdirSync } = require("fs");
			return readdirSync(baseDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
		} catch (err) {
			logError("load-qualities failed", {
				error: String(err),
				baseDir
			});
			return [];
		}
	});
	electron.ipcMain.handle("calculate-assets", async (_e, product, quality, deviceType, extraDirs, serial, transferPolicy, sdcardTargetDir) => {
		const result = calculateAssets(product, quality, deviceType, extraDirs ?? []);
		let existingOnDeviceSize = 0;
		let existingOnDeviceCount = 0;
		let netTransferSize = result.totalSize;
		const duplicateFiles = [];
		if (serial && transferPolicy) try {
			const targetDir = getConfig().mediaTransfer.deviceTargets[deviceType] ?? "";
			if (targetDir) {
				const onInternal = await getExistingFiles(serial, targetDir);
				console.log(`[assets] Found ${onInternal.size} files on internal in ${targetDir}`);
				let onSdcard = /* @__PURE__ */ new Map();
				if (sdcardTargetDir) try {
					onSdcard = await getExistingFiles(serial, sdcardTargetDir);
					console.log(`[assets] Found ${onSdcard.size} files on SD card in ${sdcardTargetDir}`);
				} catch {}
				for (const [fileName, resolved] of result.resolvedFiles) {
					const prefixes = [
						"1_",
						"2_",
						"0_",
						""
					];
					function findInMap(map) {
						for (const prefix of prefixes) {
							const s = map.get(prefix + fileName);
							if (s !== void 0) return {
								found: true,
								size: s
							};
						}
						const direct = map.get(fileName);
						if (direct !== void 0) return {
							found: true,
							size: direct
						};
						return {
							found: false,
							size: 0
						};
					}
					const internalHit = findInMap(onInternal);
					const sdcardHit = sdcardTargetDir ? findInMap(onSdcard) : {
						found: false,
						size: 0
					};
					if (internalHit.found && sdcardHit.found) duplicateFiles.push(fileName);
					const foundOnDevice = internalHit.found || sdcardHit.found;
					const foundSize = internalHit.found ? internalHit.size : sdcardHit.size;
					if (foundOnDevice) {
						existingOnDeviceCount++;
						existingOnDeviceSize += resolved.size;
						if (transferPolicy === "skip-existing") netTransferSize -= resolved.size;
						else if (transferPolicy === "update-different" && foundSize === resolved.size) netTransferSize -= resolved.size;
					}
				}
			}
		} catch (err) {
			console.warn("[assets] Could not query device files:", err);
		}
		const { basename } = require("path");
		const serenityFileNames = result.serenityFiles.map((f) => f.fileName);
		const fileSizes = {};
		const resolvedNames = [];
		for (const [bareName, resolved] of result.resolvedFiles) {
			const actualName = basename(resolved.path);
			fileSizes[actualName] = resolved.size;
			resolvedNames.push(actualName);
		}
		for (const missing of result.missingFiles) resolvedNames.push(missing);
		for (const sf of result.serenityFiles) fileSizes[sf.fileName] = sf.size;
		return {
			requiredFiles: [...serenityFileNames, ...resolvedNames],
			missingFiles: result.missingFiles,
			serenityFiles: result.serenityFiles,
			fileSizes,
			totalSize: result.totalSize,
			totalCount: result.totalCount,
			existingOnDeviceSize,
			existingOnDeviceCount,
			netTransferSize: Math.max(0, netTransferSize),
			duplicateFiles,
			sourceDuplicates: result.sourceDuplicates
		};
	});
	electron.ipcMain.handle("detect-storage", (_e, serial) => detectStorageLocations(serial));
	electron.ipcMain.handle("fetch-releases", (_e, repo) => fetchReleases(repo));
	electron.ipcMain.handle("find-incomplete-session", (_e, deviceId, product, quality) => findIncompleteSession(deviceId, product, quality));
	electron.ipcMain.handle("start-execution", (_e, plan) => handleStartExecution(plan, ctx, sendToRenderer));
	electron.ipcMain.handle("stop-execution", (_e, tabId) => handleStopExecution(tabId, ctx));
	electron.ipcMain.handle("select-local-apk", async () => {
		const win = electron.BrowserWindow.getAllWindows()[0];
		if (!win) return null;
		const result = await electron.dialog.showOpenDialog(win, {
			title: "Sélectionner un APK",
			filters: [{
				name: "APK",
				extensions: ["apk"]
			}],
			properties: ["openFile"]
		});
		return result.canceled ? null : result.filePaths[0];
	});
	electron.ipcMain.handle("select-directory", async () => {
		const win = electron.BrowserWindow.getAllWindows()[0];
		if (!win) return null;
		const result = await electron.dialog.showOpenDialog(win, {
			title: "Sélectionner un dossier",
			properties: ["openDirectory"]
		});
		return result.canceled ? null : result.filePaths[0];
	});
	electron.ipcMain.handle("select-file", async () => {
		const win = electron.BrowserWindow.getAllWindows()[0];
		if (!win) return null;
		const result = await electron.dialog.showOpenDialog(win, {
			title: "Sélectionner le fichier manquant",
			properties: ["openFile"]
		});
		return result.canceled ? null : result.filePaths[0];
	});
	electron.ipcMain.handle("get-config", () => getConfig());
	electron.ipcMain.handle("save-config", (_e, configData) => {
		const configPath = getUserDataPath("config.json");
		(0, fs.mkdirSync)((0, path.dirname)(configPath), { recursive: true });
		const { writeFileSync } = require("fs");
		writeFileSync(configPath, JSON.stringify(configData, null, 4), "utf-8");
		resetConfigCache();
		logInfo("Config saved", { path: configPath });
		return true;
	});
	electron.ipcMain.handle("get-config-path", () => getUserDataPath("config.json"));
	electron.ipcMain.handle("get-app-version", () => {
		const { app } = require("electron");
		return app.getVersion();
	});
	electron.ipcMain.handle("check-for-updates", () => checkForUpdates(true));
	electron.ipcMain.handle("updater:start-download", () => startDownload());
	electron.ipcMain.handle("updater:cancel-download", () => cancelDownload());
	electron.ipcMain.handle("updater:apply-update", () => applyUpdate());
	electron.ipcMain.handle("updater:get-state", () => getUpdateState());
	electron.ipcMain.handle("open-external", (_e, url) => electron.shell.openExternal(url));
	electron.ipcMain.handle("show-item-in-folder", (_e, filePath) => electron.shell.showItemInFolder(filePath));
	electron.ipcMain.handle("launch-app", (_e, serial, packageName) => launchApp(serial, packageName));
	electron.ipcMain.handle("force-stop-app", (_e, serial, packageName) => forceStopApp(serial, packageName));
	electron.ipcMain.handle("read-device-file", (_e, serial, devicePath) => readDeviceFile(serial, devicePath));
	electron.ipcMain.handle("get-device-report-path", (_e, deviceType) => {
		const config = getConfig();
		return deviceType === "PICOG3" || deviceType === "QUEST3" ? config.mediaTransfer.deviceReportPathVR : config.mediaTransfer.deviceReportPathTAB;
	});
	electron.ipcMain.handle("delete-device-file", (_e, serial, devicePath) => deleteDeviceFile(serial, devicePath));
	electron.ipcMain.handle("file-exists-on-device", (_e, serial, filePath) => fileExistsOnDevice(serial, filePath));
	electron.ipcMain.handle("scan-device-duplicates", async (_e, serial, internalDir, sdcardDir) => {
		const FILE_PREFIX_PATTERN = /^(?:0_|1_|2_)(.+)$/;
		function stripPrefix(name) {
			const m = name.match(FILE_PREFIX_PATTERN);
			return m ? m[1] : name;
		}
		const internalFiles = await getExistingFiles(serial, internalDir);
		let sdcardFiles = /* @__PURE__ */ new Map();
		if (sdcardDir) try {
			sdcardFiles = await getExistingFiles(serial, sdcardDir);
		} catch {}
		const internalCount = internalFiles.size;
		const sdcardCount = sdcardFiles.size;
		const crossStorage = [];
		for (const [name, size] of internalFiles) {
			const sdSize = sdcardFiles.get(name);
			if (sdSize !== void 0) crossStorage.push({
				baseName: name,
				files: [{
					fileName: name,
					path: internalDir + "/" + name,
					storage: "internal",
					size
				}, {
					fileName: name,
					path: sdcardDir + "/" + name,
					storage: "sdcard",
					size: sdSize
				}]
			});
		}
		const byBase = /* @__PURE__ */ new Map();
		for (const [name, size] of internalFiles) {
			const base = stripPrefix(name);
			if (!byBase.has(base)) byBase.set(base, []);
			byBase.get(base).push({
				fileName: name,
				path: internalDir + "/" + name,
				storage: "internal",
				size
			});
		}
		for (const [name, size] of sdcardFiles) {
			const base = stripPrefix(name);
			if (!byBase.has(base)) byBase.set(base, []);
			byBase.get(base).push({
				fileName: name,
				path: sdcardDir + "/" + name,
				storage: "sdcard",
				size
			});
		}
		const prefixVariants = [];
		for (const [base, files] of byBase) if (new Set(files.map((f) => f.fileName)).size > 1) prefixVariants.push({
			baseName: base,
			files
		});
		return {
			crossStorage,
			prefixVariants,
			totalFiles: internalCount + sdcardCount,
			internalCount,
			sdcardCount
		};
	});
	electron.ipcMain.handle("transfer-single-file", async (_e, serial, sourcePath, targetPath) => {
		console.log(`[exec] Transfer single file: ${sourcePath} -> ${targetPath}`);
		await pushFile(serial, sourcePath, targetPath, false);
		return true;
	});
	electron.ipcMain.handle("clear-apk-cache", () => {
		const cacheDir = getUserDataPath("cache");
		try {
			const { rmSync, readdirSync } = require("fs");
			const entries = readdirSync(cacheDir);
			for (const entry of entries) {
				if (entry === "md5-cache.json") continue;
				try {
					rmSync((0, path.join)(cacheDir, entry), {
						recursive: true,
						force: true
					});
				} catch {}
			}
			logInfo("APK cache cleared", {
				dir: cacheDir,
				count: entries.length
			});
			return entries.length;
		} catch {
			return 0;
		}
	});
	electron.ipcMain.handle("clear-completed-sessions", () => {
		const dir = getUserDataPath("logs", "completed");
		try {
			const { rmSync, readdirSync } = require("fs");
			const files = readdirSync(dir).filter((f) => f.endsWith(".json"));
			for (const f of files) try {
				rmSync((0, path.join)(dir, f));
			} catch {}
			logInfo("Completed sessions cleared", { count: files.length });
			return files.length;
		} catch {
			return 0;
		}
	});
	electron.ipcMain.handle("clear-incomplete-sessions", () => {
		const dir = getUserDataPath("logs", "incomplete");
		try {
			const { rmSync, readdirSync } = require("fs");
			const files = readdirSync(dir).filter((f) => f.endsWith(".json"));
			for (const f of files) try {
				rmSync((0, path.join)(dir, f));
			} catch {}
			logInfo("Incomplete sessions cleared", { count: files.length });
			return files.length;
		} catch {
			return 0;
		}
	});
	electron.ipcMain.handle("send-result-digest", async (_e, url, digest) => {
		if (!url || !url.startsWith("http")) return {
			success: false,
			error: "URL invalide"
		};
		try {
			logInfo("Sending result digest", { url });
			const response = await electron.net.fetch(url, {
				method: "POST",
				headers: { "Content-Type": "application/json" },
				body: JSON.stringify(digest)
			});
			if (response.ok) {
				logInfo("Result digest sent", {
					url,
					status: response.status
				});
				return {
					success: true,
					status: response.status
				};
			}
			logError("Result digest failed", {
				url,
				status: response.status
			});
			return {
				success: false,
				status: response.status,
				error: `HTTP ${response.status}`
			};
		} catch (err) {
			const msg = err instanceof Error ? err.message : String(err);
			logError("Result digest error", {
				url,
				error: msg
			});
			return {
				success: false,
				error: msg
			};
		}
	});
	electron.ipcMain.handle("clear-md5-cache", () => {
		const cachePath = getUserDataPath("cache", "md5-cache.json");
		try {
			const { unlinkSync } = require("fs");
			unlinkSync(cachePath);
			logInfo("MD5 cache cleared");
			return true;
		} catch {
			return false;
		}
	});
	startDevicePolling(ctx, sendToRenderer);
}
//#endregion
//#region src/main/index.ts
init_paths();
if (process.env.SERENITY_TEST_USER_DATA) electron.app.setPath("userData", process.env.SERENITY_TEST_USER_DATA);
var gotLock = electron.app.requestSingleInstanceLock();
if (!gotLock) electron.app.quit();
function createWindow() {
	const mainWindow = new electron.BrowserWindow({
		width: 960,
		height: 700,
		minWidth: 800,
		minHeight: 600,
		backgroundColor: "#1A1A1A",
		webPreferences: {
			preload: (0, path.join)(__dirname, "../preload/index.js"),
			contextIsolation: true,
			nodeIntegration: false
		}
	});
	if (process.env.ELECTRON_RENDERER_URL) mainWindow.loadURL(process.env.ELECTRON_RENDERER_URL);
	else mainWindow.loadFile((0, path.join)(__dirname, "../renderer/index.html"));
}
function bootstrapUserData() {
	const userConfig = getUserDataPath("config.json");
	if (!(0, fs.existsSync)(userConfig)) {
		const bundledConfig = getResourcePath("config.json");
		if ((0, fs.existsSync)(bundledConfig)) {
			(0, fs.mkdirSync)((0, path.dirname)(userConfig), { recursive: true });
			(0, fs.copyFileSync)(bundledConfig, userConfig);
		}
	}
}
if (gotLock) {
	electron.app.on("second-instance", () => {
		const windows = electron.BrowserWindow.getAllWindows();
		if (windows.length > 0) {
			const win = windows[0];
			if (win.isMinimized()) win.restore();
			win.focus();
		}
	});
	electron.app.whenReady().then(() => {
		bootstrapUserData();
		registerIpcHandlers();
		createWindow();
		checkForUpdates();
		electron.app.on("activate", () => {
			if (electron.BrowserWindow.getAllWindows().length === 0) createWindow();
		});
	});
	electron.app.on("before-quit", () => {
		abortInFlightDownload();
	});
	electron.app.on("window-all-closed", () => {
		if (process.platform !== "darwin") electron.app.quit();
	});
}
//#endregion
let electron = require("electron");
//#region src/preload/index.ts
var INVOKE_CHANNELS = [
	"detect-devices",
	"detect-model",
	"match-profile",
	"load-products",
	"load-qualities",
	"calculate-assets",
	"detect-storage",
	"fetch-releases",
	"start-execution",
	"stop-execution",
	"find-incomplete-session",
	"select-local-apk",
	"select-directory",
	"get-config",
	"save-config",
	"transfer-single-file",
	"select-file",
	"get-config-path",
	"get-app-version",
	"open-external",
	"show-item-in-folder",
	"list-profiles",
	"save-custom-profile",
	"delete-custom-profile",
	"list-command-files",
	"check-for-updates",
	"updater:start-download",
	"updater:cancel-download",
	"updater:apply-update",
	"updater:get-state",
	"launch-app",
	"force-stop-app",
	"read-device-file",
	"get-device-report-path",
	"delete-device-file",
	"scan-device-duplicates",
	"file-exists-on-device",
	"clear-apk-cache",
	"clear-completed-sessions",
	"clear-incomplete-sessions",
	"clear-md5-cache",
	"send-result-digest"
];
electron.contextBridge.exposeInMainWorld("api", {
	invoke: (channel, ...args) => {
		if (!INVOKE_CHANNELS.includes(channel)) throw new Error(`IPC channel not allowed: ${channel}`);
		return electron.ipcRenderer.invoke(channel, ...args);
	},
	onEvent: (callback) => {
		electron.ipcRenderer.on("ipc-event", (_event, data) => callback(data));
	},
	removeEventListeners: () => {
		electron.ipcRenderer.removeAllListeners("ipc-event");
	}
});
//#endregion

.strip[data-v-cc9807ea] {
  padding: 8px 24px;
  background: #252525;
  border-bottom: 1px solid #3A3A3A;
  font-size: 12px;
  color: #AAAAAA;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.strip-row[data-v-cc9807ea] {
  display: flex;
  gap: 16px;
}
.strip-item[data-v-cc9807ea] {
  white-space: nowrap;
}

.steps[data-v-428a5599] {
  display: flex;
  align-items: center;
  gap: 0;
  padding: 0 24px;
}
.step-dot[data-v-428a5599] {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 2px solid #3A3A3A;
  background: transparent;
  color: #888888;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.2s;
  flex-shrink: 0;
}
.step-dot[data-v-428a5599]:disabled {
  cursor: default;
}
.step-active[data-v-428a5599] {
  border-color: #00D7FF;
  color: #00D7FF;
  background: rgba(0, 215, 255, 0.1);
}
.step-done[data-v-428a5599] {
  border-color: #00CC66;
  color: #00CC66;
  background: rgba(0, 204, 102, 0.1);
}
.step-line[data-v-428a5599] {
  flex: 1;
  height: 2px;
  background: #3A3A3A;
  min-width: 20px;
}
.line-done[data-v-428a5599] {
  background: #00CC66;
}

.overlay[data-v-05d58638] {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;
}
.dialog[data-v-05d58638] {
  background: #2D2D2D;
  border: 1px solid #3A3A3A;
  border-radius: 12px;
  padding: 24px;
  min-width: 320px;
  max-width: 400px;
}
.dialog-message[data-v-05d58638] {
  font-size: 14px;
  margin-bottom: 20px;
  line-height: 1.5;
}
.dialog-actions[data-v-05d58638] {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}
.btn[data-v-05d58638] {
  padding: 8px 20px;
  border-radius: 6px;
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  border: none;
}
.btn-no[data-v-05d58638] {
  background: #3A3A3A;
  color: #EEEEEE;
}
.btn-no[data-v-05d58638]:hover {
  background: #4A4A4A;
}
.btn-yes[data-v-05d58638] {
  background: #FF4444;
  color: #EEEEEE;
}
.btn-yes[data-v-05d58638]:hover {
  opacity: 0.85;
}

.update-banner[data-v-711d1e06] {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 6px 24px;
  background: rgba(0, 215, 255, 0.1);
  border-bottom: 1px solid #00D7FF;
  color: #00D7FF;
  font-size: 12px;
  flex-shrink: 0;
}
.update-banner.phase-error[data-v-711d1e06] {
  background: rgba(255, 68, 68, 0.1);
  border-bottom-color: #FF4444;
  color: #FF4444;
}
.update-banner.phase-manual-required[data-v-711d1e06] {
  background: rgba(255, 165, 0, 0.1);
  border-bottom-color: #FFA500;
  color: #FFA500;
}
.msg[data-v-711d1e06] { user-select: text;
}
.msg.error[data-v-711d1e06] { font-weight: 500;
}
.btn-update[data-v-711d1e06],
.btn-cancel[data-v-711d1e06] {
  padding: 2px 12px;
  border-radius: 4px;
  font-size: 11px;
  font-weight: 600;
  cursor: pointer;
  border: 1px solid currentColor;
  background: transparent;
  color: inherit;
  transition: background 0.15s;
}
.btn-update[data-v-711d1e06]:hover { background: rgba(0, 215, 255, 0.2);
}
.update-banner.phase-error .btn-update[data-v-711d1e06]:hover { background: rgba(255, 68, 68, 0.2);
}
.update-banner.phase-manual-required .btn-update[data-v-711d1e06]:hover { background: rgba(255, 165, 0, 0.2);
}
.btn-cancel[data-v-711d1e06] { opacity: 0.7;
}
.btn-cancel[data-v-711d1e06]:hover { opacity: 1; background: rgba(255, 255, 255, 0.05);
}
.progress[data-v-711d1e06] {
  position: absolute;
  left: 0;
  bottom: 0;
  height: 2px;
  background: #00D7FF;
  transition: width 0.2s linear;
}

.layout[data-v-8d50af5b] {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
  background: #1A1A1A;
  color: #EEEEEE;
}
.header[data-v-8d50af5b] {
  padding: 12px 24px;
  border-bottom: 1px solid #3A3A3A;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.title[data-v-8d50af5b] {
  font-size: 18px;
  font-weight: 600;
  color: #00D7FF;
  margin: 0;
}
.version[data-v-8d50af5b] {
  font-size: 11px;
  font-weight: 400;
  color: #666666;
  cursor: pointer;
  transition: color 0.2s;
}
.version[data-v-8d50af5b]:hover {
  color: #00D7FF;
}
.up-to-date[data-v-8d50af5b],
.applied-toast[data-v-8d50af5b] {
  font-size: 11px;
  font-weight: 400;
  color: #00CC66;
  margin-left: 6px;
  animation: fade-in-8d50af5b 0.2s ease;
}
@keyframes fade-in-8d50af5b {
from { opacity: 0;
}
to { opacity: 1;
}
}
.step-bar[data-v-8d50af5b] {
  padding: 12px 0;
  border-bottom: 1px solid #3A3A3A;
  flex-shrink: 0;
}
.content[data-v-8d50af5b] {
  flex: 1;
  overflow-y: auto;
  padding: 24px;
}
.footer[data-v-8d50af5b] {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 24px;
  border-top: 1px solid #3A3A3A;
  flex-shrink: 0;
}
.footer-right[data-v-8d50af5b] {
  display: flex;
  gap: 8px;
}
.btn[data-v-8d50af5b] {
  padding: 8px 20px;
  border-radius: 6px;
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  border: none;
  transition: opacity 0.2s;
}
.btn[data-v-8d50af5b]:disabled {
  opacity: 0.4;
  cursor: default;
}
.btn-primary[data-v-8d50af5b] {
  background: #00D7FF;
  color: #1A1A1A;
}
.btn-primary[data-v-8d50af5b]:hover:not(:disabled) {
  opacity: 0.85;
}
.btn-secondary[data-v-8d50af5b] {
  background: #2D2D2D;
  color: #EEEEEE;
  border: 1px solid #3A3A3A;
}
.btn-secondary[data-v-8d50af5b]:hover:not(:disabled) {
  background: #3A3A3A;
}
.btn-cancel[data-v-8d50af5b] {
  background: transparent;
  color: #FF4444;
  border: 1px solid #FF4444;
}
.btn-cancel[data-v-8d50af5b]:hover {
  background: rgba(255, 68, 68, 0.1);
}
.overlay[data-v-8d50af5b] {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;
}
.dialog[data-v-8d50af5b] {
  background: #2D2D2D;
  border: 1px solid #3A3A3A;
  border-radius: 12px;
  padding: 24px;
  min-width: 320px;
  max-width: 400px;
}
.dialog-message[data-v-8d50af5b] {
  font-size: 14px;
  margin-bottom: 20px;
  line-height: 1.5;
}
.dialog-actions[data-v-8d50af5b] {
  display: flex;
  justify-content: flex-end;
}
.btn-cancel-dialog[data-v-8d50af5b] {
  padding: 8px 20px;
  border-radius: 6px;
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  border: none;
  background: #3A3A3A;
  color: #EEEEEE;
}
.btn-cancel-dialog[data-v-8d50af5b]:hover {
  background: #4A4A4A;
}

.tab-bar[data-v-45ce84c2] {
  display: flex;
  align-items: stretch;
  gap: 0;
  background: #1A1A1A;
  border-bottom: 1px solid #3A3A3A;
  padding: 0 8px;
  overflow-x: auto;
  flex-shrink: 0;
}
.tab[data-v-45ce84c2] {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 14px;
  font-size: 12px;
  color: #AAAAAA;
  cursor: pointer;
  border-bottom: 2px solid transparent;
  transition: all 0.15s;
  white-space: nowrap;
  position: relative;
}
.tab[data-v-45ce84c2]:hover {
  color: #EEEEEE;
  background: #252525;
}
.tab-active[data-v-45ce84c2] {
  color: #EEEEEE;
  border-bottom-color: #00D7FF;
  background: #252525;
}
.tab-spinner[data-v-45ce84c2] {
  color: #00D7FF;
  animation: spin-45ce84c2 1s linear infinite;
}
@keyframes spin-45ce84c2 {
from { transform: rotate(0deg);
}
to { transform: rotate(360deg);
}
}
.tab-label[data-v-45ce84c2] {
  max-width: 150px;
  overflow: hidden;
  text-overflow: ellipsis;
}
.tab-close[data-v-45ce84c2] {
  background: none;
  border: none;
  color: #888888;
  font-size: 14px;
  cursor: pointer;
  padding: 0 2px;
  line-height: 1;
  border-radius: 3px;
}
.tab-close[data-v-45ce84c2]:hover {
  color: #FF4444;
  background: rgba(255, 68, 68, 0.1);
}
.tab-add[data-v-45ce84c2] {
  background: none;
  border: none;
  color: #888888;
  font-size: 18px;
  cursor: pointer;
  padding: 4px 10px;
  transition: color 0.15s;
}
.tab-add[data-v-45ce84c2]:hover {
  color: #00D7FF;
}

.page[data-v-96b899c5] {
  display: flex;
  flex-direction: column;
  gap: 20px;
  max-width: 640px;
  margin: 0 auto;
}
.scan-state[data-v-96b899c5] {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 48px 0 24px;
  gap: 12px;
}
.pulse-wrap[data-v-96b899c5] {
  position: relative;
  width: 64px;
  height: 64px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.pulse-icon[data-v-96b899c5] { font-size: 32px;
}
.pulse-ring[data-v-96b899c5] {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 2px solid #00D7FF;
  animation: pulse-96b899c5 2s ease-in-out infinite;
}
@keyframes pulse-96b899c5 {
0% { transform: scale(0.8); opacity: 1;
}
100% { transform: scale(1.5); opacity: 0;
}
}
.scan-text[data-v-96b899c5] { color: #EEEEEE; font-size: 15px;
}
.scan-hint[data-v-96b899c5] { color: #888888; font-size: 12px;
}
.error-box[data-v-96b899c5] {
  text-align: center;
  color: #FF4444;
  padding: 32px;
}
.btn-retry[data-v-96b899c5] {
  margin-top: 12px;
  padding: 8px 20px;
  background: #2D2D2D;
  border: 1px solid #3A3A3A;
  border-radius: 6px;
  color: #EEEEEE;
  cursor: pointer;
  font-size: 13px;
}
.section h3[data-v-96b899c5] {
  font-size: 12px;
  color: #888888;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  margin-bottom: 8px;
}
.device-grid[data-v-96b899c5] {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 8px;
}
.device-card[data-v-96b899c5] {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px;
  background: #2D2D2D;
  border-radius: 8px;
  cursor: pointer;
  border: 2px solid transparent;
  transition: border-color 0.15s;
}
.device-card[data-v-96b899c5]:hover:not(.device-offline):not(.device-claimed) {
  border-color: #3A3A3A;
}
.device-selected[data-v-96b899c5] {
  border-color: #00D7FF !important;
  background: rgba(0, 215, 255, 0.04);
}
.device-offline[data-v-96b899c5], .device-claimed[data-v-96b899c5] {
  opacity: 0.5;
  cursor: default;
}
.device-icon[data-v-96b899c5] { font-size: 14px; width: 20px; text-align: center;
}
.icon-ok[data-v-96b899c5] { color: #00CC66;
}
.icon-warn[data-v-96b899c5] { color: #FFB800;
}
.icon-off[data-v-96b899c5] { color: #FF4444;
}
.device-info[data-v-96b899c5] { flex: 1; display: flex; flex-direction: column;
}
.device-serial[data-v-96b899c5] { font-weight: 500; font-size: 13px;
}
.device-model[data-v-96b899c5] { font-size: 11px; color: #AAAAAA;
}
.device-badge[data-v-96b899c5] {
  font-size: 10px;
  padding: 2px 8px;
  border-radius: 10px;
  background: #1A1A1A;
  color: #AAAAAA;
}
.badge-ok[data-v-96b899c5] { color: #00CC66;
}
.badge-claimed[data-v-96b899c5] { color: #FFB800; background: rgba(255, 184, 0, 0.1);
}

.page[data-v-748880d8] {
  max-width: 560px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 24px;
}
.device-header[data-v-748880d8] {
  text-align: center;
  padding: 20px;
  background: #2D2D2D;
  border-radius: 10px;
}
.device-type[data-v-748880d8] {
  font-size: 20px;
  font-weight: 600;
  color: #EEEEEE;
  margin-bottom: 4px;
}
.device-serial[data-v-748880d8] {
  font-size: 12px;
  font-family: 'SF Mono', 'Fira Code', monospace;
  color: #888888;
}
.storage-info[data-v-748880d8] {
  display: flex;
  gap: 12px;
}
.storage-item[data-v-748880d8] {
  flex: 1;
  background: #2D2D2D;
  border-radius: 8px;
  padding: 12px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.storage-label[data-v-748880d8] {
  font-size: 11px;
  color: #AAAAAA;
  font-weight: 500;
}
.storage-bar-mini[data-v-748880d8] {
  height: 6px;
  border-radius: 3px;
  background: #1A1A1A;
  overflow: hidden;
}
.storage-bar-fill[data-v-748880d8] {
  height: 100%;
  background: #00D7FF;
  border-radius: 3px;
  transition: width 0.3s ease;
}
.storage-detail[data-v-748880d8] {
  font-size: 11px;
  color: #888888;
}
.section[data-v-748880d8] {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
h3[data-v-748880d8] {
  font-size: 14px;
  color: #AAAAAA;
  margin: 0;
}
.warn-text[data-v-748880d8] {
  color: #FFB800;
  font-size: 13px;
  text-align: center;
}
.profile-grid[data-v-748880d8] {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}
.profile-btn[data-v-748880d8] {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 16px 12px;
  background: #2D2D2D;
  border: 1px solid #3A3A3A;
  border-radius: 8px;
  color: #EEEEEE;
  cursor: pointer;
  transition: border-color 0.2s;
}
.profile-btn[data-v-748880d8]:hover { border-color: #00D7FF;
}
.profile-btn-new[data-v-748880d8] {
  border-style: dashed;
  color: #00D7FF;
}
.profile-name[data-v-748880d8] {
  font-size: 14px;
  font-weight: 600;
}
.profile-desc[data-v-748880d8] {
  font-size: 11px;
  color: #888888;
}
.form-grid[data-v-748880d8] {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}
.full-width[data-v-748880d8] {
  grid-column: 1 / -1;
}
.form-actions[data-v-748880d8] {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 4px;
}
.btn-cancel-form[data-v-748880d8] {
  padding: 8px 16px;
  background: #2D2D2D;
  border: 1px solid #3A3A3A;
  border-radius: 6px;
  color: #EEEEEE;
  font-size: 12px;
  cursor: pointer;
}
.btn-save-form[data-v-748880d8] {
  padding: 8px 16px;
  background: #00D7FF;
  color: #1A1A1A;
  border: none;
  border-radius: 6px;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
}
.btn-save-form[data-v-748880d8]:disabled { opacity: 0.4; cursor: default;
}
.btn-save-form[data-v-748880d8]:hover:not(:disabled) { opacity: 0.85;
}
.btn-edit-profile[data-v-748880d8] {
  background: none;
  border: none;
  color: #00D7FF;
  font-size: 11px;
  cursor: pointer;
  padding: 0;
  text-align: left;
}
.btn-edit-profile[data-v-748880d8]:hover { text-decoration: underline;
}
.form-row[data-v-748880d8] {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
}
.field label[data-v-748880d8] {
  display: block;
  font-size: 12px;
  color: #AAAAAA;
  margin-bottom: 6px;
}
.input[data-v-748880d8] {
  width: 100%;
  padding: 10px 12px;
  background: #2D2D2D;
  border: 1px solid #3A3A3A;
  border-radius: 6px;
  color: #EEEEEE;
  font-size: 13px;
  outline: none;
  transition: border-color 0.2s;
}
.input[data-v-748880d8]:focus { border-color: #00D7FF;
}
.input-readonly[data-v-748880d8] { color: #888888; cursor: default;
}
.input[data-v-748880d8]::placeholder { color: #666666;
}
select.input[data-v-748880d8] {
  appearance: auto;
}
.single-col[data-v-748880d8] {
  grid-template-columns: 1fr;
}
.required[data-v-748880d8] {
  color: #FF4444;
}

.card[data-v-ef30aa81] {
  background: #2D2D2D;
  border-radius: 8px;
  padding: 16px;
}
.card-title[data-v-ef30aa81] {
  font-size: 14px;
  font-weight: 600;
  color: #AAAAAA;
  margin-bottom: 12px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  font-size: 11px;
}

.storage-bar-container[data-v-aa1c52ef] {
  margin-top: 8px;
}
.storage-track[data-v-aa1c52ef] {
  display: flex;
  height: 12px;
  border-radius: 6px;
  overflow: hidden;
  background: #1A1A1A;
}
.seg[data-v-aa1c52ef] {
  transition: width 0.3s ease;
}
.seg-used[data-v-aa1c52ef] { background: #888888;
}
.seg-planned[data-v-aa1c52ef] { background: #00D7FF;
}
.seg-margin[data-v-aa1c52ef] { background: #FFB800;
}
.storage-legend[data-v-aa1c52ef] {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 8px;
  font-size: 11px;
  color: #AAAAAA;
}
.legend-item[data-v-aa1c52ef] {
  display: flex;
  align-items: center;
  gap: 4px;
}
.dot[data-v-aa1c52ef] {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  display: inline-block;
}
.dot-used[data-v-aa1c52ef] { background: #888888;
}
.dot-planned[data-v-aa1c52ef] { background: #00D7FF;
}
.dot-margin[data-v-aa1c52ef] { background: #FFB800;
}
.dot-free[data-v-aa1c52ef] { background: #3A3A3A;
}
.storage-warning[data-v-aa1c52ef] {
  margin-top: 8px;
  color: #FF4444;
  font-size: 12px;
  font-weight: 500;
}

.page[data-v-143c2a56] {
  max-width: 560px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 20px;
}
.skip-row[data-v-143c2a56] {
  padding-bottom: 8px;
  border-bottom: 1px solid #3A3A3A;
}
.checkbox-label[data-v-143c2a56] {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  font-size: 14px;
}
.section h3[data-v-143c2a56] {
  font-size: 14px;
  color: #AAAAAA;
  margin-bottom: 8px;
}
.radio-list[data-v-143c2a56] {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.radio-list.horizontal[data-v-143c2a56] {
  flex-direction: row;
  gap: 12px;
}
.radio-item[data-v-143c2a56] {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  background: #2D2D2D;
  border-radius: 6px;
  cursor: pointer;
  border: 1px solid transparent;
  transition: border-color 0.2s;
  font-size: 13px;
}
.radio-item[data-v-143c2a56]:hover {
  border-color: #3A3A3A;
}
.radio-selected[data-v-143c2a56] {
  border-color: #00D7FF;
  background: rgba(0, 215, 255, 0.05);
}
.radio-item input[type="radio"][data-v-143c2a56] {
  accent-color: #00D7FF;
}
.pol-label[data-v-143c2a56] {
  font-weight: 500;
  display: block;
}
.pol-desc[data-v-143c2a56] {
  font-size: 11px;
  color: #888888;
}
.summary-row[data-v-143c2a56] {
  display: flex;
  justify-content: space-between;
  padding: 6px 0;
  font-size: 13px;
}
.summary-row + .summary-row[data-v-143c2a56] {
  border-top: 1px solid #3A3A3A;
}
.text-warn[data-v-143c2a56] { color: #FFB800;
}
.text-success[data-v-143c2a56] { color: #00CC66;
}
.text-primary[data-v-143c2a56] { color: #00D7FF; font-weight: 600;
}
.summary-hint[data-v-143c2a56] {
  font-size: 10px;
  color: #888888;
  margin-top: 4px;
  font-style: italic;
}
.loading[data-v-143c2a56] {
  color: #888888;
  font-size: 13px;
  padding: 12px;
  text-align: center;
}
.empty[data-v-143c2a56] {
  color: #888888;
  font-size: 13px;
  padding: 8px;
}
.resume-banner[data-v-143c2a56] {
  background: rgba(0, 215, 255, 0.08);
  border: 1px solid #00D7FF;
  border-radius: 6px;
  padding: 10px 14px;
  font-size: 13px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.resume-hint[data-v-143c2a56] {
  color: #AAAAAA;
  font-size: 11px;
}
.extra-dir[data-v-143c2a56] {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  background: #2D2D2D;
  border-radius: 6px;
  margin-bottom: 6px;
}
.extra-dir-path[data-v-143c2a56] {
  flex: 1;
  font-size: 11px;
  font-family: 'SF Mono', 'Fira Code', monospace;
  color: #AAAAAA;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.extra-dir-remove[data-v-143c2a56] {
  background: none;
  border: none;
  color: #FF4444;
  font-size: 16px;
  cursor: pointer;
  padding: 0 4px;
}
.btn-add-dir[data-v-143c2a56] {
  width: 100%;
  padding: 8px;
  background: transparent;
  border: 1px dashed #3A3A3A;
  border-radius: 6px;
  color: #AAAAAA;
  font-size: 12px;
  cursor: pointer;
  text-align: center;
  transition: border-color 0.2s, color 0.2s;
}
.btn-add-dir[data-v-143c2a56]:hover {
  border-color: #00D7FF;
  color: #00D7FF;
}
.extra-dir-hint[data-v-143c2a56] {
  font-size: 11px;
  color: #FFB800;
  margin-top: 6px;
}
.verification-row[data-v-143c2a56] {
  padding-top: 4px;
}
.source-dup-banner[data-v-143c2a56] {
  display: flex;
  gap: 8px;
  padding: 10px 14px;
  background: rgba(255, 184, 0, 0.08);
  border: 1px solid #FFB800;
  border-radius: 6px;
  font-size: 13px;
  color: #FFB800;
}
.dup-icon[data-v-143c2a56] { font-size: 16px; flex-shrink: 0;
}
.dup-hint[data-v-143c2a56] { display: block; font-size: 11px; color: #AAAAAA; margin-top: 2px;
}
.dup-details[data-v-143c2a56] { margin-top: 6px; font-size: 11px;
}
.dup-details summary[data-v-143c2a56] { cursor: pointer; color: #AAAAAA;
}
.dup-entry[data-v-143c2a56] {
  display: flex;
  gap: 8px;
  padding: 2px 0;
  font-family: 'SF Mono', 'Fira Code', monospace;
  font-size: 10px;
}
.dup-base[data-v-143c2a56] { color: #EEEEEE; flex-shrink: 0;
}
.dup-variants[data-v-143c2a56] { color: #888888;
}
.priority-section[data-v-143c2a56] {
  margin-top: 8px;
}
.priority-section h4[data-v-143c2a56] {
  font-size: 12px;
  color: #888888;
  margin-bottom: 6px;
}
.radio-item.small[data-v-143c2a56] {
  padding: 6px 10px;
  font-size: 12px;
}
.duplicate-banner[data-v-143c2a56] {
  display: flex;
  gap: 8px;
  padding: 10px 14px;
  background: rgba(255, 184, 0, 0.08);
  border: 1px solid #FFB800;
  border-radius: 6px;
  font-size: 13px;
  color: #FFB800;
  margin-top: 8px;
}
.duplicate-icon[data-v-143c2a56] {
  font-size: 16px;
  flex-shrink: 0;
}
.duplicate-details[data-v-143c2a56] {
  margin-top: 4px;
  font-size: 11px;
}
.duplicate-details summary[data-v-143c2a56] {
  cursor: pointer;
  color: #AAAAAA;
}
.duplicate-details ul[data-v-143c2a56] {
  margin: 4px 0 0 16px;
  padding: 0;
  max-height: 120px;
  overflow-y: auto;
  color: #AAAAAA;
}
.duplicate-details li[data-v-143c2a56] {
  font-family: 'SF Mono', 'Fira Code', monospace;
  font-size: 10px;
  padding: 1px 0;
}

.page[data-v-e9597d84] {
  max-width: 520px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.skip-row[data-v-e9597d84] {
  padding-bottom: 8px;
  border-bottom: 1px solid #3A3A3A;
}
.checkbox-label[data-v-e9597d84] {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  font-size: 14px;
}
h3[data-v-e9597d84] {
  font-size: 14px;
  color: #AAAAAA;
  margin-bottom: 8px;
}
.release-card[data-v-e9597d84] {
  cursor: pointer;
  margin-bottom: 6px;
  border: 2px solid transparent;
  transition: border-color 0.2s;
}
.release-card[data-v-e9597d84]:hover {
  border-color: #3A3A3A;
}
.release-selected[data-v-e9597d84] {
  border-color: #00D7FF !important;
}
.release-row[data-v-e9597d84] {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.release-info[data-v-e9597d84] {
  display: flex;
  flex-direction: column;
}
.release-tag[data-v-e9597d84] {
  font-weight: 600;
  font-size: 14px;
  color: #00D7FF;
}
.release-name[data-v-e9597d84] {
  font-size: 12px;
  color: #AAAAAA;
}
.release-date[data-v-e9597d84] {
  font-size: 12px;
  color: #888888;
}
.btn-local[data-v-e9597d84] {
  padding: 10px 16px;
  background: #2D2D2D;
  border: 1px dashed #3A3A3A;
  border-radius: 6px;
  color: #AAAAAA;
  font-size: 13px;
  cursor: pointer;
  text-align: center;
  transition: border-color 0.2s;
}
.btn-local[data-v-e9597d84]:hover {
  border-color: #00D7FF;
  color: #00D7FF;
}
.local-apk-banner[data-v-e9597d84] {
  background: rgba(0, 204, 102, 0.08);
  border: 1px solid #00CC66;
  border-radius: 6px;
  padding: 8px 14px;
  font-size: 12px;
  color: #00CC66;
}
.loading[data-v-e9597d84], .empty[data-v-e9597d84] {
  text-align: center;
  color: #888888;
  padding: 24px;
  font-size: 13px;
}
.error[data-v-e9597d84] {
  text-align: center;
  color: #FF4444;
  padding: 24px;
}

.page[data-v-1c979fea] {
  max-width: 640px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.cards-grid[data-v-1c979fea] {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}
.card-wide[data-v-1c979fea] {
  grid-column: 1 / -1;
}
.content-grid[data-v-1c979fea] {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0 24px;
}
.summary-card[data-v-1c979fea] {
  cursor: pointer;
  border: 1px solid transparent;
  transition: border-color 0.15s;
  position: relative;
}
.summary-card[data-v-1c979fea]:hover:not(.muted) {
  border-color: #00D7FF;
}
.summary-card[data-v-1c979fea]:hover:not(.muted)::after {
  content: '✎';
  position: absolute;
  top: 10px;
  right: 12px;
  font-size: 12px;
  color: #00D7FF;
  opacity: 0.6;
}
.muted[data-v-1c979fea] {
  opacity: 0.4;
  cursor: default;
}
.muted-text[data-v-1c979fea] {
  font-size: 12px;
  color: #888888;
}
.row[data-v-1c979fea] {
  display: flex;
  justify-content: space-between;
  padding: 3px 0;
  font-size: 12px;
}
.row + .row[data-v-1c979fea] {
  border-top: 1px solid #3A3A3A;
}
.label[data-v-1c979fea] { color: #AAAAAA;
}
.mono[data-v-1c979fea] { font-family: 'SF Mono', 'Fira Code', monospace; font-size: 11px;
}
.text-primary[data-v-1c979fea] { color: #00D7FF; font-weight: 600;
}
.warning-box[data-v-1c979fea] {
  background: rgba(255, 184, 0, 0.08);
  border: 1px solid #FFB800;
  border-radius: 6px;
  padding: 8px 14px;
  color: #FFB800;
  font-size: 12px;
}
.btn-launch[data-v-1c979fea] {
  padding: 14px 40px;
  background: #00CC66;
  color: #1A1A1A;
  border: none;
  border-radius: 8px;
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
  transition: opacity 0.2s;
  align-self: center;
}
.btn-launch[data-v-1c979fea]:hover { opacity: 0.85;
}

.progress-container[data-v-36196173] {
  width: 100%;
}
.progress-header[data-v-36196173] {
  display: flex;
  justify-content: space-between;
  margin-bottom: 6px;
  font-size: 12px;
}
.secondary-header[data-v-36196173] {
  margin-bottom: 0;
  margin-top: 4px;
}
.progress-label[data-v-36196173] {
  color: #AAAAAA;
}
.secondary-label[data-v-36196173] {
  color: #888888;
  font-size: 11px;
}
.progress-pct[data-v-36196173] {
  color: #00D7FF;
  font-weight: 600;
}
.secondary-pct[data-v-36196173] {
  color: #00CC66;
  font-size: 11px;
}
.progress-track[data-v-36196173] {
  height: 6px;
  background: #1A1A1A;
  border-radius: 3px;
  overflow: hidden;
  position: relative;
}
.progress-fill[data-v-36196173] {
  height: 100%;
  background: #00D7FF;
  border-radius: 3px;
  transition: width 0.3s ease;
  position: absolute;
  top: 0;
  left: 0;
}
.progress-fill-secondary[data-v-36196173] {
  height: 100%;
  background: #00CC66;
  border-radius: 3px;
  transition: width 0.3s ease;
  position: absolute;
  top: 0;
  left: 0;
}

.log-panel[data-v-370f839d] {
  background: #1A1A1A;
  border: 1px solid #3A3A3A;
  border-radius: 8px;
  padding: 8px;
  overflow-y: auto;
  font-family: 'SF Mono', 'Fira Code', monospace;
  font-size: 11px;
  flex: 1;
  min-height: 0;
}
.log-spacer[data-v-370f839d] {
  position: relative;
}
.log-visible[data-v-370f839d] {
  position: absolute;
  left: 0;
  right: 0;
}
.log-entry[data-v-370f839d] {
  display: flex;
  gap: 8px;
  height: 18px;
  box-sizing: border-box;
  align-items: center;
}
.log-time[data-v-370f839d] {
  color: #888888;
  flex-shrink: 0;
}
.log-msg[data-v-370f839d] {
  color: #EEEEEE;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.log-error .log-msg[data-v-370f839d] { color: #FF4444;
}
.log-warn .log-msg[data-v-370f839d] { color: #FFB800;
}
.log-success .log-msg[data-v-370f839d] { color: #00CC66;
}
.log-info .log-msg[data-v-370f839d] { color: #EEEEEE;
}
.log-empty[data-v-370f839d] {
  color: #888888;
  text-align: center;
  padding: 16px;
}

.file-list[data-v-72115ca5] {
  overflow-y: auto;
  font-size: 11px;
  font-family: 'SF Mono', 'Fira Code', monospace;
  flex: 1;
  min-height: 0;
}
.file-list-spacer[data-v-72115ca5] {
  position: relative;
}
.file-list-visible[data-v-72115ca5] {
  position: absolute;
  left: 0;
  right: 0;
}
.file-row[data-v-72115ca5] {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 3px 4px;
  border-bottom: 1px solid #2A2A2A;
  height: 22px;
  box-sizing: border-box;
}
.file-icon[data-v-72115ca5] {
  width: 18px;
  text-align: center;
  flex-shrink: 0;
  font-size: 10px;
}
.file-name[data-v-72115ca5] {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: #AAAAAA;
}
.file-size[data-v-72115ca5] {
  font-size: 9px;
  color: #666666;
  flex-shrink: 0;
  white-space: nowrap;
}
.file-badge[data-v-72115ca5] {
  font-size: 9px;
  padding: 1px 6px;
  border-radius: 8px;
  flex-shrink: 0;
  background: #1A1A1A;
}
.st-pending .file-icon[data-v-72115ca5] { color: #888888;
}
.st-active .file-icon[data-v-72115ca5] { color: #00D7FF;
}
.st-active .file-name[data-v-72115ca5] { color: #EEEEEE;
}
.st-active .file-badge[data-v-72115ca5] { color: #00D7FF;
}
.st-done .file-icon[data-v-72115ca5] { color: #00CC66;
}
.st-done .file-badge[data-v-72115ca5] { color: #00CC66;
}
.st-verified .file-icon[data-v-72115ca5] { color: #00CC66;
}
.st-verified .file-badge[data-v-72115ca5] { color: #00CC66; background: rgba(0,204,102,0.1);
}
.st-verifying .file-icon[data-v-72115ca5] { color: #00D7FF;
}
.st-verifying .file-badge[data-v-72115ca5] { color: #00D7FF;
}
.st-verify-failed .file-icon[data-v-72115ca5] { color: #FF4444;
}
.st-verify-failed .file-name[data-v-72115ca5] { color: #FF4444;
}
.st-verify-failed .file-badge[data-v-72115ca5] { color: #FF4444; background: rgba(255,68,68,0.1);
}
.st-failed .file-icon[data-v-72115ca5] { color: #FF4444;
}
.st-failed .file-name[data-v-72115ca5] { color: #FF4444;
}
.st-failed .file-badge[data-v-72115ca5] { color: #FF4444;
}
.st-skipped .file-icon[data-v-72115ca5] { color: #888888;
}
.st-skipped .file-badge[data-v-72115ca5] { color: #888888;
}
.st-missing .file-icon[data-v-72115ca5] { color: #FFB800;
}
.st-missing .file-name[data-v-72115ca5] { color: #FFB800;
}
.st-missing .file-badge[data-v-72115ca5] { color: #FFB800;
}

.exec[data-v-e338f3f7] {
  display: flex;
  flex-direction: column;
  height: 100%;
  gap: 12px;
}

/* Top section */
.exec-top[data-v-e338f3f7] {
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.stats-bar[data-v-e338f3f7] {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: #AAAAAA;
  flex-wrap: wrap;
}
.stat[data-v-e338f3f7] { white-space: nowrap;
}
.stat-eta[data-v-e338f3f7] { color: #00D7FF;
}
.stat-muted[data-v-e338f3f7] { color: #888888;
}
.stat-error[data-v-e338f3f7] { color: #FF4444;
}
.stat-sep[data-v-e338f3f7] { color: #3A3A3A;
}
.stats-spacer[data-v-e338f3f7] { flex: 1;
}
.btn-stop[data-v-e338f3f7] {
  padding: 5px 16px;
  background: transparent;
  border: 1px solid #FF4444;
  color: #FF4444;
  border-radius: 5px;
  font-size: 12px;
  cursor: pointer;
  transition: background 0.2s;
  white-space: nowrap;
}
.btn-stop[data-v-e338f3f7]:hover {
  background: rgba(255, 68, 68, 0.1);
}
.btn-results[data-v-e338f3f7] {
  padding: 5px 16px;
  background: #00D7FF;
  color: #1A1A1A;
  border: none;
  border-radius: 5px;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
}
.btn-results[data-v-e338f3f7]:hover { opacity: 0.85;
}

/* Middle section */
.exec-body[data-v-e338f3f7] {
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}
.exec-left[data-v-e338f3f7], .exec-right[data-v-e338f3f7] {
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-height: 0;
}
.phases-row[data-v-e338f3f7] {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  flex-shrink: 0;
}
.phase-chip[data-v-e338f3f7] {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 4px 10px;
  border-radius: 12px;
  font-size: 11px;
  background: #2D2D2D;
}
.phase-icon[data-v-e338f3f7] { font-size: 10px;
}
.phase-done[data-v-e338f3f7] { color: #00CC66;
}
.phase-active[data-v-e338f3f7] { color: #00D7FF; font-weight: 600; background: rgba(0, 215, 255, 0.08);
}
.phase-pending[data-v-e338f3f7] { color: #888888;
}
.files-card[data-v-e338f3f7] {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
.files-card[data-v-e338f3f7] .file-list {
  flex: 1;
  max-height: none;
}
.journal-card[data-v-e338f3f7] {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
.journal-card[data-v-e338f3f7] .log-panel {
  flex: 1;
  max-height: none;
}

/* Bottom config bar */
.exec-footer[data-v-e338f3f7] {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 0;
  border-top: 1px solid #3A3A3A;
  font-size: 11px;
  color: #888888;
}
.footer-sep[data-v-e338f3f7] { color: #3A3A3A;
}
.disconnect-banner[data-v-e338f3f7] {
  background: rgba(255, 68, 68, 0.1);
  border: 1px solid #FF4444;
  border-radius: 6px;
  padding: 8px 14px;
  color: #FF4444;
  font-size: 12px;
  text-align: center;
}

.dup-manager[data-v-80353fd1] {
  max-width: 700px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  max-height: 75vh;
}
.dup-body[data-v-80353fd1] {
  flex: 1;
  overflow-y: auto;
  min-height: 0;
}
.dup-header[data-v-80353fd1] {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 16px;
}
.dup-header h2[data-v-80353fd1] { font-size: 18px; margin: 0;
}
.dup-header-actions[data-v-80353fd1] { display: flex; gap: 8px;
}
.btn-refresh[data-v-80353fd1], .btn-close[data-v-80353fd1] {
  padding: 6px 14px;
  background: #2D2D2D;
  border: 1px solid #3A3A3A;
  border-radius: 6px;
  color: #EEEEEE;
  font-size: 12px;
  cursor: pointer;
}
.btn-refresh[data-v-80353fd1]:hover { border-color: #00D7FF; color: #00D7FF;
}
.btn-close[data-v-80353fd1]:hover { border-color: #FF4444; color: #FF4444;
}
.btn-refresh[data-v-80353fd1]:disabled { opacity: 0.5;
}
.dup-loading[data-v-80353fd1], .dup-empty[data-v-80353fd1] {
  text-align: center;
  color: #888888;
  padding: 32px;
  font-size: 13px;
}
.dup-error[data-v-80353fd1] {
  text-align: center;
  color: #FF4444;
  padding: 24px;
}
.dup-stats[data-v-80353fd1] {
  font-size: 12px;
  color: #AAAAAA;
  padding: 8px 12px;
  background: #252525;
  border-radius: 6px;
  margin-bottom: 16px;
}
.dup-section[data-v-80353fd1] { margin-bottom: 20px;
}
.dup-section h3[data-v-80353fd1] { font-size: 14px; color: #00D7FF; margin: 0 0 4px 0;
}
.dup-section-desc[data-v-80353fd1] { font-size: 11px; color: #888888; margin: 0;
}
.dup-section-header[data-v-80353fd1] {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 12px;
  margin-bottom: 10px;
}
.dup-section-toolbar[data-v-80353fd1] {
  display: flex;
  gap: 4px;
  flex-shrink: 0;
}
.btn-toolbar[data-v-80353fd1] {
  padding: 3px 8px;
  background: #252525;
  border: 1px solid #3A3A3A;
  border-radius: 4px;
  color: #AAAAAA;
  font-size: 10px;
  cursor: pointer;
  white-space: nowrap;
}
.btn-toolbar[data-v-80353fd1]:hover { border-color: #00D7FF; color: #00D7FF;
}
.dup-group[data-v-80353fd1] {
  background: #2D2D2D;
  border-radius: 8px;
  padding: 10px 12px;
  margin-bottom: 8px;
}
.dup-group-header[data-v-80353fd1] {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 6px;
}
.dup-group-name[data-v-80353fd1] {
  font-family: 'SF Mono', 'Fira Code', monospace;
  font-size: 12px;
  color: #EEEEEE;
  font-weight: 600;
  flex: 1;
}
.dup-group-count[data-v-80353fd1] { font-size: 10px; color: #888888;
}
.btn-select-others[data-v-80353fd1] {
  padding: 2px 8px;
  background: transparent;
  border: 1px solid #3A3A3A;
  border-radius: 4px;
  color: #AAAAAA;
  font-size: 9px;
  cursor: pointer;
}
.btn-select-others[data-v-80353fd1]:hover { border-color: #FFB800; color: #FFB800;
}
.dup-file[data-v-80353fd1] {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 3px 0;
  font-size: 11px;
  font-family: 'SF Mono', 'Fira Code', monospace;
}
.dup-file input[type="checkbox"][data-v-80353fd1] { accent-color: #FFB800; flex-shrink: 0;
}
.dup-del-icon[data-v-80353fd1] { width: 13px; text-align: center; color: #FF4444; flex-shrink: 0; font-size: 10px;
}
.dup-file-name[data-v-80353fd1] { flex: 1; color: #AAAAAA; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.dup-deleted .dup-file-name[data-v-80353fd1] { text-decoration: line-through; color: #555555;
}
.dup-badge[data-v-80353fd1] {
  padding: 1px 6px;
  border-radius: 8px;
  font-size: 9px;
  font-family: -apple-system, sans-serif;
  flex-shrink: 0;
}
.badge-internal[data-v-80353fd1] { background: rgba(0, 215, 255, 0.1); color: #00D7FF;
}
.badge-sdcard[data-v-80353fd1] { background: rgba(255, 184, 0, 0.1); color: #FFB800;
}
.dup-file-size[data-v-80353fd1] { font-size: 9px; color: #666666; flex-shrink: 0;
}
.btn-delete-one[data-v-80353fd1] {
  padding: 1px 8px;
  background: transparent;
  border: 1px solid #FF4444;
  border-radius: 4px;
  color: #FF4444;
  font-size: 9px;
  cursor: pointer;
  flex-shrink: 0;
}
.btn-delete-one[data-v-80353fd1]:hover { background: rgba(255,68,68,0.1);
}
.btn-delete-one[data-v-80353fd1]:disabled { opacity: 0.5;
}
.dup-deleted-label[data-v-80353fd1] { font-size: 9px; color: #555555; flex-shrink: 0;
}
.dup-batch[data-v-80353fd1] {
  flex-shrink: 0;
  padding: 12px 0 0 0;
  border-top: 1px solid #3A3A3A;
  display: flex;
  justify-content: center;
  gap: 8px;
}
.btn-batch-clear[data-v-80353fd1] {
  padding: 10px 20px;
  background: #2D2D2D;
  border: 1px solid #3A3A3A;
  border-radius: 6px;
  color: #AAAAAA;
  font-size: 13px;
  cursor: pointer;
}
.btn-batch-clear[data-v-80353fd1]:hover { border-color: #00D7FF; color: #00D7FF;
}
.btn-batch-delete[data-v-80353fd1] {
  padding: 10px 24px;
  background: #FF4444;
  color: #FFFFFF;
  border: none;
  border-radius: 6px;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}
.btn-batch-delete[data-v-80353fd1]:hover { opacity: 0.85;
}

.page[data-v-0ce378f8] {
  max-width: 900px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

/* Banner */
.result-banner[data-v-0ce378f8] {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 16px 20px;
  border-radius: 10px;
}
.banner-success[data-v-0ce378f8] { background: rgba(0, 204, 102, 0.08); border: 1px solid #00CC66;
}
.banner-warning[data-v-0ce378f8] { background: rgba(255, 184, 0, 0.08); border: 1px solid #FFB800;
}
.banner-error[data-v-0ce378f8] { background: rgba(255, 68, 68, 0.08); border: 1px solid #FF4444;
}
.banner-icon[data-v-0ce378f8] { font-size: 28px;
}
.banner-success .banner-icon[data-v-0ce378f8] { color: #00CC66;
}
.banner-warning .banner-icon[data-v-0ce378f8] { color: #FFB800;
}
.banner-error .banner-icon[data-v-0ce378f8] { color: #FF4444;
}
.banner-text h2[data-v-0ce378f8] { font-size: 16px; margin: 0;
}
.banner-success h2[data-v-0ce378f8] { color: #00CC66;
}
.banner-warning h2[data-v-0ce378f8] { color: #FFB800;
}
.banner-error h2[data-v-0ce378f8] { color: #FF4444;
}
.banner-sub[data-v-0ce378f8] { font-size: 12px; color: #AAAAAA;
}

/* Summary card */
.summary-card[data-v-0ce378f8] {
  background: #2D2D2D;
  border-radius: 8px;
  padding: 14px 18px;
}
.summary-grid[data-v-0ce378f8] {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px 24px;
}
.summary-item[data-v-0ce378f8] {
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.summary-label[data-v-0ce378f8] {
  font-size: 10px;
  color: #888888;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}
.summary-value[data-v-0ce378f8] {
  font-size: 13px;
  color: #EEEEEE;
}
.summary-value.mono[data-v-0ce378f8] {
  font-family: 'SF Mono', 'Fira Code', monospace;
  font-size: 11px;
}
.summary-value.dimmed[data-v-0ce378f8] {
  color: #666666;
  font-style: italic;
}

/* Stats grid */
.stats-grid[data-v-0ce378f8] {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
  gap: 8px;
}
.stat-tile[data-v-0ce378f8] {
  background: #2D2D2D;
  border-radius: 8px;
  padding: 12px;
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.stat-value[data-v-0ce378f8] { font-size: 18px; font-weight: 700; color: #EEEEEE;
}
.stat-label[data-v-0ce378f8] { font-size: 10px; color: #888888; text-transform: uppercase; letter-spacing: 0.5px;
}
.text-success[data-v-0ce378f8] { color: #00CC66;
}
.text-warn[data-v-0ce378f8] { color: #FFB800;
}
.text-error[data-v-0ce378f8] { color: #FF4444;
}

/* Missing files */
.missing-list[data-v-0ce378f8] {
  font-size: 11px;
  font-family: 'SF Mono', 'Fira Code', monospace;
  max-height: 200px;
  overflow-y: auto;
  user-select: text;
}
.missing-item[data-v-0ce378f8] {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px 2px;
  border-bottom: 1px solid #3A3A3A;
  color: #FFB800;
}
.missing-resolved[data-v-0ce378f8] { color: #00CC66;
}
.missing-resolving[data-v-0ce378f8] { color: #00D7FF;
}
.missing-icon[data-v-0ce378f8] { width: 14px; text-align: center; flex-shrink: 0; font-size: 10px;
}
.missing-name[data-v-0ce378f8] { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.btn-resolve[data-v-0ce378f8] {
  padding: 2px 8px;
  background: #2D2D2D;
  border: 1px solid #3A3A3A;
  border-radius: 4px;
  color: #AAAAAA;
  font-size: 10px;
  cursor: pointer;
  flex-shrink: 0;
}
.btn-resolve[data-v-0ce378f8]:hover { border-color: #00D7FF; color: #00D7FF;
}
.missing-done[data-v-0ce378f8] { font-size: 10px; color: #00CC66;
}
.missing-progress[data-v-0ce378f8] { font-size: 10px; color: #00D7FF;
}

/* Actions */
.actions[data-v-0ce378f8] {
  display: flex;
  justify-content: center;
  gap: 10px;
}
.btn-primary[data-v-0ce378f8] {
  padding: 10px 28px;
  background: #00D7FF;
  color: #1A1A1A;
  border: none;
  border-radius: 8px;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
}
.btn-primary[data-v-0ce378f8]:hover { opacity: 0.85;
}
.btn-secondary[data-v-0ce378f8] {
  padding: 10px 28px;
  background: #2D2D2D;
  color: #EEEEEE;
  border: 1px solid #3A3A3A;
  border-radius: 8px;
  font-size: 14px;
  cursor: pointer;
}
.btn-secondary[data-v-0ce378f8]:hover { border-color: #00D7FF;
}
.banner-reason[data-v-0ce378f8] { color: #FF8888;
}

/* Error details */
.error-list[data-v-0ce378f8] {
  font-size: 11px;
  font-family: 'SF Mono', 'Fira Code', monospace;
  max-height: 200px;
  overflow-y: auto;
}
.error-item[data-v-0ce378f8] {
  display: flex;
  align-items: baseline;
  gap: 6px;
  padding: 4px 2px;
  border-bottom: 1px solid #3A3A3A;
  color: #FF4444;
}
.error-file-icon[data-v-0ce378f8] { width: 14px; text-align: center; flex-shrink: 0; font-size: 10px;
}
.error-file-name[data-v-0ce378f8] {
  flex-shrink: 0;
  max-width: 200px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: #EEEEEE;
}
.error-file-reason[data-v-0ce378f8] {
  flex: 1;
  color: #FF8888;
  font-size: 10px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* App verification */
.app-verify[data-v-0ce378f8] {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.app-verify-actions[data-v-0ce378f8] {
  display: flex;
  gap: 8px;
}
.btn-launch[data-v-0ce378f8] {
  padding: 8px 18px;
  background: #00D7FF;
  color: #1A1A1A;
  border: none;
  border-radius: 6px;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
}
.btn-launch[data-v-0ce378f8]:disabled { opacity: 0.5; cursor: default;
}
.btn-launch[data-v-0ce378f8]:hover:not(:disabled) { opacity: 0.85;
}
.btn-refresh-report[data-v-0ce378f8] {
  padding: 8px 14px;
  background: #2D2D2D;
  border: 1px solid #3A3A3A;
  border-radius: 6px;
  color: #AAAAAA;
  font-size: 12px;
  cursor: pointer;
}
.btn-refresh-report[data-v-0ce378f8]:hover:not(:disabled) { border-color: #00D7FF; color: #00D7FF;
}
.btn-refresh-report[data-v-0ce378f8]:disabled { opacity: 0.5;
}
.report-status[data-v-0ce378f8] {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  color: #00D7FF;
}
.report-spinner[data-v-0ce378f8] {
  animation: spin-0ce378f8 1s linear infinite;
  display: inline-block;
}
@keyframes spin-0ce378f8 {
from { transform: rotate(0deg);
}
to { transform: rotate(360deg);
}
}
.report-error[data-v-0ce378f8] {
  font-size: 12px;
  color: #FF4444;
  padding: 8px 12px;
  background: rgba(255, 68, 68, 0.06);
  border-radius: 6px;
}
.report-content pre[data-v-0ce378f8] {
  font-family: 'SF Mono', 'Fira Code', monospace;
  font-size: 11px;
  color: #CCCCCC;
  background: #1A1A1A;
  border-radius: 6px;
  padding: 10px 12px;
  max-height: 300px;
  overflow-y: auto;
  white-space: pre-wrap;
  word-break: break-word;
  margin: 0;
}
.report-hint[data-v-0ce378f8] {
  font-size: 11px;
  color: #888888;
  font-style: italic;
  text-align: center;
  padding: 8px;
}

/* Asset re-verification */
.asset-verify[data-v-0ce378f8] {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.btn-reverify[data-v-0ce378f8] {
  padding: 8px 16px;
  background: #2D2D2D;
  border: 1px solid #3A3A3A;
  border-radius: 6px;
  color: #EEEEEE;
  font-size: 12px;
  cursor: pointer;
  align-self: flex-start;
}
.btn-reverify[data-v-0ce378f8]:hover:not(:disabled) { border-color: #00D7FF; color: #00D7FF;
}
.btn-reverify[data-v-0ce378f8]:disabled { opacity: 0.5;
}
.asset-verify-result[data-v-0ce378f8] {
  font-size: 12px;
  display: flex;
  gap: 6px;
  align-items: center;
}
.av-present[data-v-0ce378f8] { color: #00CC66;
}
.av-missing[data-v-0ce378f8] { color: #FF4444;
}
.av-ok[data-v-0ce378f8] { color: #00CC66; font-weight: 600;
}
.av-sep[data-v-0ce378f8] { color: #3A3A3A;
}
.av-missing-list[data-v-0ce378f8] {
  font-size: 11px;
  font-family: 'SF Mono', 'Fira Code', monospace;
  max-height: 150px;
  overflow-y: auto;
}
.av-missing-item[data-v-0ce378f8] {
  display: flex;
  gap: 6px;
  padding: 2px 0;
  color: #FFB800;
}
.av-missing-icon[data-v-0ce378f8] { width: 14px; text-align: center;
}
.modal-overlay[data-v-0ce378f8] {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 200;
}
.modal-panel[data-v-0ce378f8] {
  background: #1A1A1A;
  border: 1px solid #3A3A3A;
  border-radius: 12px;
  padding: 24px;
  width: 90%;
  max-width: 720px;
  max-height: 85vh;
  overflow-y: auto;
}
.no-verify-banner[data-v-0ce378f8] {
  padding: 10px 14px;
  background: rgba(255, 184, 0, 0.08);
  border: 1px solid #FFB800;
  border-radius: 6px;
  font-size: 12px;
  color: #FFB800;
  text-align: center;
}

.settings[data-v-a4cc71f4] {
  max-width: 640px;
  margin: 0 auto;
}
.settings-header[data-v-a4cc71f4] {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
}
h2[data-v-a4cc71f4] { font-size: 18px;
}
.btn-close[data-v-a4cc71f4] {
  padding: 6px 16px;
  background: #2D2D2D;
  border: 1px solid #3A3A3A;
  border-radius: 6px;
  color: #EEEEEE;
  font-size: 12px;
  cursor: pointer;
}
.section[data-v-a4cc71f4] {
  margin-bottom: 24px;
}
h3[data-v-a4cc71f4] {
  font-size: 13px;
  color: #AAAAAA;
  margin-bottom: 12px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}
.path-field[data-v-a4cc71f4] {
  margin-bottom: 12px;
}
.path-field label[data-v-a4cc71f4] {
  display: block;
  font-size: 12px;
  color: #AAAAAA;
  margin-bottom: 4px;
}
.path-row[data-v-a4cc71f4] {
  display: flex;
  gap: 8px;
}
.input[data-v-a4cc71f4] {
  flex: 1;
  padding: 8px 10px;
  background: #2D2D2D;
  border: 1px solid #3A3A3A;
  border-radius: 6px;
  color: #EEEEEE;
  font-size: 12px;
  font-family: 'SF Mono', 'Fira Code', monospace;
  outline: none;
  min-width: 0;
  width: 100%;
  box-sizing: border-box;
}
.input[data-v-a4cc71f4]:focus { border-color: #00D7FF;
}
.btn-browse[data-v-a4cc71f4] {
  padding: 8px 14px;
  background: #2D2D2D;
  border: 1px solid #3A3A3A;
  border-radius: 6px;
  color: #AAAAAA;
  font-size: 12px;
  cursor: pointer;
  white-space: nowrap;
}
.btn-browse[data-v-a4cc71f4]:hover { border-color: #00D7FF; color: #00D7FF;
}
.path-field > .input[data-v-a4cc71f4] {
  width: 100%;
}
.actions[data-v-a4cc71f4] {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}
.btn-cancel[data-v-a4cc71f4] {
  padding: 10px 24px;
  background: #2D2D2D;
  border: 1px solid #3A3A3A;
  border-radius: 6px;
  color: #EEEEEE;
  font-size: 13px;
  cursor: pointer;
}
.btn-cancel[data-v-a4cc71f4]:hover { border-color: #FF4444; color: #FF4444;
}
.field-hint[data-v-a4cc71f4] {
  font-size: 10px;
  color: #888888;
  margin: 4px 0 0 0;
  font-style: italic;
}
.number-fields[data-v-a4cc71f4] {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}
.number-field label[data-v-a4cc71f4] {
  display: block;
  font-size: 12px;
  color: #AAAAAA;
  margin-bottom: 4px;
}
.input-num[data-v-a4cc71f4] {
  width: 100%;
  padding: 8px 10px;
  background: #2D2D2D;
  border: 1px solid #3A3A3A;
  border-radius: 6px;
  color: #EEEEEE;
  font-size: 13px;
  font-family: 'SF Mono', 'Fira Code', monospace;
  outline: none;
  box-sizing: border-box;
}
.input-num[data-v-a4cc71f4]:focus { border-color: #00D7FF;
}
.cache-actions[data-v-a4cc71f4] {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.cache-row[data-v-a4cc71f4] {
  display: flex;
  align-items: center;
  gap: 10px;
}
.cache-label[data-v-a4cc71f4] {
  flex: 1;
  font-size: 12px;
  color: #AAAAAA;
}
.btn-cache[data-v-a4cc71f4] {
  padding: 5px 14px;
  background: #2D2D2D;
  border: 1px solid #3A3A3A;
  border-radius: 5px;
  color: #EEEEEE;
  font-size: 11px;
  cursor: pointer;
  white-space: nowrap;
}
.btn-cache[data-v-a4cc71f4]:hover { border-color: #FF4444; color: #FF4444;
}
.cache-status[data-v-a4cc71f4] {
  font-size: 11px;
  color: #00CC66;
  min-width: 80px;
}
.btn-save[data-v-a4cc71f4] {
  padding: 10px 24px;
  background: #00D7FF;
  color: #1A1A1A;
  border: none;
  border-radius: 6px;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}
.btn-save[data-v-a4cc71f4]:disabled { opacity: 0.5;
}
.btn-save[data-v-a4cc71f4]:hover:not(:disabled) { opacity: 0.85;
}
.config-path[data-v-a4cc71f4] {
  margin-top: 16px;
  font-size: 10px;
  color: #666666;
  font-family: 'SF Mono', 'Fira Code', monospace;
  word-break: break-all;
}
.config-link[data-v-a4cc71f4] {
  color: #00D7FF;
  text-decoration: none;
  cursor: pointer;
}
.config-link[data-v-a4cc71f4]:hover {
  text-decoration: underline;
}
.loading[data-v-a4cc71f4] {
  color: #888888;
  text-align: center;
  padding: 40px;
}

.app-root[data-v-8f84d028] {
  display: flex;
  flex-direction: column;
  height: 100vh;
}
.tab-content[data-v-8f84d028] {
  flex: 1;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.btn-settings[data-v-8f84d028] {
  background: none;
  border: none;
  color: #AAAAAA;
  font-size: 18px;
  cursor: pointer;
  padding: 4px 8px;
  border-radius: 4px;
  transition: color 0.2s;
}
.btn-settings[data-v-8f84d028]:hover {
  color: #00D7FF;
}
.modal-overlay[data-v-8f84d028] {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 200;
}
.modal-panel[data-v-8f84d028] {
  background: #1A1A1A;
  border: 1px solid #3A3A3A;
  border-radius: 12px;
  padding: 24px;
  width: 90%;
  max-width: 640px;
  max-height: 85vh;
  overflow-y: auto;
}
//#region node_modules/@vue/shared/dist/shared.esm-bundler.js
/**
* @vue/shared v3.5.31
* (c) 2018-present Yuxi (Evan) You and Vue contributors
* @license MIT
**/
/* @__NO_SIDE_EFFECTS__ */
function makeMap(str) {
	const map = /* @__PURE__ */ Object.create(null);
	for (const key of str.split(",")) map[key] = 1;
	return (val) => val in map;
}
var EMPTY_OBJ = {};
var EMPTY_ARR = [];
var NOOP = () => {};
var NO = () => false;
var isOn = (key) => key.charCodeAt(0) === 111 && key.charCodeAt(1) === 110 && (key.charCodeAt(2) > 122 || key.charCodeAt(2) < 97);
var isModelListener = (key) => key.startsWith("onUpdate:");
var extend = Object.assign;
var remove = (arr, el) => {
	const i = arr.indexOf(el);
	if (i > -1) arr.splice(i, 1);
};
var hasOwnProperty$1 = Object.prototype.hasOwnProperty;
var hasOwn = (val, key) => hasOwnProperty$1.call(val, key);
var isArray = Array.isArray;
var isMap = (val) => toTypeString(val) === "[object Map]";
var isSet = (val) => toTypeString(val) === "[object Set]";
var isDate = (val) => toTypeString(val) === "[object Date]";
var isFunction = (val) => typeof val === "function";
var isString = (val) => typeof val === "string";
var isSymbol = (val) => typeof val === "symbol";
var isObject = (val) => val !== null && typeof val === "object";
var isPromise = (val) => {
	return (isObject(val) || isFunction(val)) && isFunction(val.then) && isFunction(val.catch);
};
var objectToString = Object.prototype.toString;
var toTypeString = (value) => objectToString.call(value);
var toRawType = (value) => {
	return toTypeString(value).slice(8, -1);
};
var isPlainObject$1 = (val) => toTypeString(val) === "[object Object]";
var isIntegerKey = (key) => isString(key) && key !== "NaN" && key[0] !== "-" && "" + parseInt(key, 10) === key;
var isReservedProp = /* @__PURE__ */ makeMap(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted");
var cacheStringFunction = (fn) => {
	const cache = /* @__PURE__ */ Object.create(null);
	return ((str) => {
		return cache[str] || (cache[str] = fn(str));
	});
};
var camelizeRE = /-\w/g;
var camelize = cacheStringFunction((str) => {
	return str.replace(camelizeRE, (c) => c.slice(1).toUpperCase());
});
var hyphenateRE = /\B([A-Z])/g;
var hyphenate = cacheStringFunction((str) => str.replace(hyphenateRE, "-$1").toLowerCase());
var capitalize = cacheStringFunction((str) => {
	return str.charAt(0).toUpperCase() + str.slice(1);
});
var toHandlerKey = cacheStringFunction((str) => {
	return str ? `on${capitalize(str)}` : ``;
});
var hasChanged = (value, oldValue) => !Object.is(value, oldValue);
var invokeArrayFns = (fns, ...arg) => {
	for (let i = 0; i < fns.length; i++) fns[i](...arg);
};
var def = (obj, key, value, writable = false) => {
	Object.defineProperty(obj, key, {
		configurable: true,
		enumerable: false,
		writable,
		value
	});
};
var looseToNumber = (val) => {
	const n = parseFloat(val);
	return isNaN(n) ? val : n;
};
var _globalThis;
var getGlobalThis = () => {
	return _globalThis || (_globalThis = typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : {});
};
function normalizeStyle(value) {
	if (isArray(value)) {
		const res = {};
		for (let i = 0; i < value.length; i++) {
			const item = value[i];
			const normalized = isString(item) ? parseStringStyle(item) : normalizeStyle(item);
			if (normalized) for (const key in normalized) res[key] = normalized[key];
		}
		return res;
	} else if (isString(value) || isObject(value)) return value;
}
var listDelimiterRE = /;(?![^(]*\))/g;
var propertyDelimiterRE = /:([^]+)/;
var styleCommentRE = /\/\*[^]*?\*\//g;
function parseStringStyle(cssText) {
	const ret = {};
	cssText.replace(styleCommentRE, "").split(listDelimiterRE).forEach((item) => {
		if (item) {
			const tmp = item.split(propertyDelimiterRE);
			tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim());
		}
	});
	return ret;
}
function normalizeClass(value) {
	let res = "";
	if (isString(value)) res = value;
	else if (isArray(value)) for (let i = 0; i < value.length; i++) {
		const normalized = normalizeClass(value[i]);
		if (normalized) res += normalized + " ";
	}
	else if (isObject(value)) {
		for (const name in value) if (value[name]) res += name + " ";
	}
	return res.trim();
}
var specialBooleanAttrs = `itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly`;
var isSpecialBooleanAttr = /* @__PURE__ */ makeMap(specialBooleanAttrs);
specialBooleanAttrs + "";
function includeBooleanAttr(value) {
	return !!value || value === "";
}
function looseCompareArrays(a, b) {
	if (a.length !== b.length) return false;
	let equal = true;
	for (let i = 0; equal && i < a.length; i++) equal = looseEqual(a[i], b[i]);
	return equal;
}
function looseEqual(a, b) {
	if (a === b) return true;
	let aValidType = isDate(a);
	let bValidType = isDate(b);
	if (aValidType || bValidType) return aValidType && bValidType ? a.getTime() === b.getTime() : false;
	aValidType = isSymbol(a);
	bValidType = isSymbol(b);
	if (aValidType || bValidType) return a === b;
	aValidType = isArray(a);
	bValidType = isArray(b);
	if (aValidType || bValidType) return aValidType && bValidType ? looseCompareArrays(a, b) : false;
	aValidType = isObject(a);
	bValidType = isObject(b);
	if (aValidType || bValidType) {
		if (!aValidType || !bValidType) return false;
		if (Object.keys(a).length !== Object.keys(b).length) return false;
		for (const key in a) {
			const aHasKey = a.hasOwnProperty(key);
			const bHasKey = b.hasOwnProperty(key);
			if (aHasKey && !bHasKey || !aHasKey && bHasKey || !looseEqual(a[key], b[key])) return false;
		}
	}
	return String(a) === String(b);
}
function looseIndexOf(arr, val) {
	return arr.findIndex((item) => looseEqual(item, val));
}
var isRef$1 = (val) => {
	return !!(val && val["__v_isRef"] === true);
};
var toDisplayString = (val) => {
	return isString(val) ? val : val == null ? "" : isArray(val) || isObject(val) && (val.toString === objectToString || !isFunction(val.toString)) ? isRef$1(val) ? toDisplayString(val.value) : JSON.stringify(val, replacer, 2) : String(val);
};
var replacer = (_key, val) => {
	if (isRef$1(val)) return replacer(_key, val.value);
	else if (isMap(val)) return { [`Map(${val.size})`]: [...val.entries()].reduce((entries, [key, val2], i) => {
		entries[stringifySymbol(key, i) + " =>"] = val2;
		return entries;
	}, {}) };
	else if (isSet(val)) return { [`Set(${val.size})`]: [...val.values()].map((v) => stringifySymbol(v)) };
	else if (isSymbol(val)) return stringifySymbol(val);
	else if (isObject(val) && !isArray(val) && !isPlainObject$1(val)) return String(val);
	return val;
};
var stringifySymbol = (v, i = "") => {
	var _a;
	return isSymbol(v) ? `Symbol(${(_a = v.description) != null ? _a : i})` : v;
};
//#endregion
//#region node_modules/@vue/reactivity/dist/reactivity.esm-bundler.js
/**
* @vue/reactivity v3.5.31
* (c) 2018-present Yuxi (Evan) You and Vue contributors
* @license MIT
**/
var activeEffectScope;
var EffectScope = class {
	constructor(detached = false) {
		this.detached = detached;
		/**
		* @internal
		*/
		this._active = true;
		/**
		* @internal track `on` calls, allow `on` call multiple times
		*/
		this._on = 0;
		/**
		* @internal
		*/
		this.effects = [];
		/**
		* @internal
		*/
		this.cleanups = [];
		this._isPaused = false;
		this.__v_skip = true;
		this.parent = activeEffectScope;
		if (!detached && activeEffectScope) this.index = (activeEffectScope.scopes || (activeEffectScope.scopes = [])).push(this) - 1;
	}
	get active() {
		return this._active;
	}
	pause() {
		if (this._active) {
			this._isPaused = true;
			let i, l;
			if (this.scopes) for (i = 0, l = this.scopes.length; i < l; i++) this.scopes[i].pause();
			for (i = 0, l = this.effects.length; i < l; i++) this.effects[i].pause();
		}
	}
	/**
	* Resumes the effect scope, including all child scopes and effects.
	*/
	resume() {
		if (this._active) {
			if (this._isPaused) {
				this._isPaused = false;
				let i, l;
				if (this.scopes) for (i = 0, l = this.scopes.length; i < l; i++) this.scopes[i].resume();
				for (i = 0, l = this.effects.length; i < l; i++) this.effects[i].resume();
			}
		}
	}
	run(fn) {
		if (this._active) {
			const currentEffectScope = activeEffectScope;
			try {
				activeEffectScope = this;
				return fn();
			} finally {
				activeEffectScope = currentEffectScope;
			}
		}
	}
	/**
	* This should only be called on non-detached scopes
	* @internal
	*/
	on() {
		if (++this._on === 1) {
			this.prevScope = activeEffectScope;
			activeEffectScope = this;
		}
	}
	/**
	* This should only be called on non-detached scopes
	* @internal
	*/
	off() {
		if (this._on > 0 && --this._on === 0) {
			activeEffectScope = this.prevScope;
			this.prevScope = void 0;
		}
	}
	stop(fromParent) {
		if (this._active) {
			this._active = false;
			let i, l;
			for (i = 0, l = this.effects.length; i < l; i++) this.effects[i].stop();
			this.effects.length = 0;
			for (i = 0, l = this.cleanups.length; i < l; i++) this.cleanups[i]();
			this.cleanups.length = 0;
			if (this.scopes) {
				for (i = 0, l = this.scopes.length; i < l; i++) this.scopes[i].stop(true);
				this.scopes.length = 0;
			}
			if (!this.detached && this.parent && !fromParent) {
				const last = this.parent.scopes.pop();
				if (last && last !== this) {
					this.parent.scopes[this.index] = last;
					last.index = this.index;
				}
			}
			this.parent = void 0;
		}
	}
};
function effectScope(detached) {
	return new EffectScope(detached);
}
function getCurrentScope() {
	return activeEffectScope;
}
function onScopeDispose(fn, failSilently = false) {
	if (activeEffectScope) activeEffectScope.cleanups.push(fn);
}
var activeSub;
var pausedQueueEffects = /* @__PURE__ */ new WeakSet();
var ReactiveEffect = class {
	constructor(fn) {
		this.fn = fn;
		/**
		* @internal
		*/
		this.deps = void 0;
		/**
		* @internal
		*/
		this.depsTail = void 0;
		/**
		* @internal
		*/
		this.flags = 5;
		/**
		* @internal
		*/
		this.next = void 0;
		/**
		* @internal
		*/
		this.cleanup = void 0;
		this.scheduler = void 0;
		if (activeEffectScope && activeEffectScope.active) activeEffectScope.effects.push(this);
	}
	pause() {
		this.flags |= 64;
	}
	resume() {
		if (this.flags & 64) {
			this.flags &= -65;
			if (pausedQueueEffects.has(this)) {
				pausedQueueEffects.delete(this);
				this.trigger();
			}
		}
	}
	/**
	* @internal
	*/
	notify() {
		if (this.flags & 2 && !(this.flags & 32)) return;
		if (!(this.flags & 8)) batch(this);
	}
	run() {
		if (!(this.flags & 1)) return this.fn();
		this.flags |= 2;
		cleanupEffect(this);
		prepareDeps(this);
		const prevEffect = activeSub;
		const prevShouldTrack = shouldTrack;
		activeSub = this;
		shouldTrack = true;
		try {
			return this.fn();
		} finally {
			cleanupDeps(this);
			activeSub = prevEffect;
			shouldTrack = prevShouldTrack;
			this.flags &= -3;
		}
	}
	stop() {
		if (this.flags & 1) {
			for (let link = this.deps; link; link = link.nextDep) removeSub(link);
			this.deps = this.depsTail = void 0;
			cleanupEffect(this);
			this.onStop && this.onStop();
			this.flags &= -2;
		}
	}
	trigger() {
		if (this.flags & 64) pausedQueueEffects.add(this);
		else if (this.scheduler) this.scheduler();
		else this.runIfDirty();
	}
	/**
	* @internal
	*/
	runIfDirty() {
		if (isDirty(this)) this.run();
	}
	get dirty() {
		return isDirty(this);
	}
};
var batchDepth = 0;
var batchedSub;
var batchedComputed;
function batch(sub, isComputed = false) {
	sub.flags |= 8;
	if (isComputed) {
		sub.next = batchedComputed;
		batchedComputed = sub;
		return;
	}
	sub.next = batchedSub;
	batchedSub = sub;
}
function startBatch() {
	batchDepth++;
}
function endBatch() {
	if (--batchDepth > 0) return;
	if (batchedComputed) {
		let e = batchedComputed;
		batchedComputed = void 0;
		while (e) {
			const next = e.next;
			e.next = void 0;
			e.flags &= -9;
			e = next;
		}
	}
	let error;
	while (batchedSub) {
		let e = batchedSub;
		batchedSub = void 0;
		while (e) {
			const next = e.next;
			e.next = void 0;
			e.flags &= -9;
			if (e.flags & 1) try {
				e.trigger();
			} catch (err) {
				if (!error) error = err;
			}
			e = next;
		}
	}
	if (error) throw error;
}
function prepareDeps(sub) {
	for (let link = sub.deps; link; link = link.nextDep) {
		link.version = -1;
		link.prevActiveLink = link.dep.activeLink;
		link.dep.activeLink = link;
	}
}
function cleanupDeps(sub) {
	let head;
	let tail = sub.depsTail;
	let link = tail;
	while (link) {
		const prev = link.prevDep;
		if (link.version === -1) {
			if (link === tail) tail = prev;
			removeSub(link);
			removeDep(link);
		} else head = link;
		link.dep.activeLink = link.prevActiveLink;
		link.prevActiveLink = void 0;
		link = prev;
	}
	sub.deps = head;
	sub.depsTail = tail;
}
function isDirty(sub) {
	for (let link = sub.deps; link; link = link.nextDep) if (link.dep.version !== link.version || link.dep.computed && (refreshComputed(link.dep.computed) || link.dep.version !== link.version)) return true;
	if (sub._dirty) return true;
	return false;
}
function refreshComputed(computed) {
	if (computed.flags & 4 && !(computed.flags & 16)) return;
	computed.flags &= -17;
	if (computed.globalVersion === globalVersion) return;
	computed.globalVersion = globalVersion;
	if (!computed.isSSR && computed.flags & 128 && (!computed.deps && !computed._dirty || !isDirty(computed))) return;
	computed.flags |= 2;
	const dep = computed.dep;
	const prevSub = activeSub;
	const prevShouldTrack = shouldTrack;
	activeSub = computed;
	shouldTrack = true;
	try {
		prepareDeps(computed);
		const value = computed.fn(computed._value);
		if (dep.version === 0 || hasChanged(value, computed._value)) {
			computed.flags |= 128;
			computed._value = value;
			dep.version++;
		}
	} catch (err) {
		dep.version++;
		throw err;
	} finally {
		activeSub = prevSub;
		shouldTrack = prevShouldTrack;
		cleanupDeps(computed);
		computed.flags &= -3;
	}
}
function removeSub(link, soft = false) {
	const { dep, prevSub, nextSub } = link;
	if (prevSub) {
		prevSub.nextSub = nextSub;
		link.prevSub = void 0;
	}
	if (nextSub) {
		nextSub.prevSub = prevSub;
		link.nextSub = void 0;
	}
	if (dep.subs === link) {
		dep.subs = prevSub;
		if (!prevSub && dep.computed) {
			dep.computed.flags &= -5;
			for (let l = dep.computed.deps; l; l = l.nextDep) removeSub(l, true);
		}
	}
	if (!soft && !--dep.sc && dep.map) dep.map.delete(dep.key);
}
function removeDep(link) {
	const { prevDep, nextDep } = link;
	if (prevDep) {
		prevDep.nextDep = nextDep;
		link.prevDep = void 0;
	}
	if (nextDep) {
		nextDep.prevDep = prevDep;
		link.nextDep = void 0;
	}
}
var shouldTrack = true;
var trackStack = [];
function pauseTracking() {
	trackStack.push(shouldTrack);
	shouldTrack = false;
}
function resetTracking() {
	const last = trackStack.pop();
	shouldTrack = last === void 0 ? true : last;
}
function cleanupEffect(e) {
	const { cleanup } = e;
	e.cleanup = void 0;
	if (cleanup) {
		const prevSub = activeSub;
		activeSub = void 0;
		try {
			cleanup();
		} finally {
			activeSub = prevSub;
		}
	}
}
var globalVersion = 0;
var Link = class {
	constructor(sub, dep) {
		this.sub = sub;
		this.dep = dep;
		this.version = dep.version;
		this.nextDep = this.prevDep = this.nextSub = this.prevSub = this.prevActiveLink = void 0;
	}
};
var Dep = class {
	constructor(computed) {
		this.computed = computed;
		this.version = 0;
		/**
		* Link between this dep and the current active effect
		*/
		this.activeLink = void 0;
		/**
		* Doubly linked list representing the subscribing effects (tail)
		*/
		this.subs = void 0;
		/**
		* For object property deps cleanup
		*/
		this.map = void 0;
		this.key = void 0;
		/**
		* Subscriber counter
		*/
		this.sc = 0;
		/**
		* @internal
		*/
		this.__v_skip = true;
	}
	track(debugInfo) {
		if (!activeSub || !shouldTrack || activeSub === this.computed) return;
		let link = this.activeLink;
		if (link === void 0 || link.sub !== activeSub) {
			link = this.activeLink = new Link(activeSub, this);
			if (!activeSub.deps) activeSub.deps = activeSub.depsTail = link;
			else {
				link.prevDep = activeSub.depsTail;
				activeSub.depsTail.nextDep = link;
				activeSub.depsTail = link;
			}
			addSub(link);
		} else if (link.version === -1) {
			link.version = this.version;
			if (link.nextDep) {
				const next = link.nextDep;
				next.prevDep = link.prevDep;
				if (link.prevDep) link.prevDep.nextDep = next;
				link.prevDep = activeSub.depsTail;
				link.nextDep = void 0;
				activeSub.depsTail.nextDep = link;
				activeSub.depsTail = link;
				if (activeSub.deps === link) activeSub.deps = next;
			}
		}
		return link;
	}
	trigger(debugInfo) {
		this.version++;
		globalVersion++;
		this.notify(debugInfo);
	}
	notify(debugInfo) {
		startBatch();
		try {
			for (let link = this.subs; link; link = link.prevSub) if (link.sub.notify()) link.sub.dep.notify();
		} finally {
			endBatch();
		}
	}
};
function addSub(link) {
	link.dep.sc++;
	if (link.sub.flags & 4) {
		const computed = link.dep.computed;
		if (computed && !link.dep.subs) {
			computed.flags |= 20;
			for (let l = computed.deps; l; l = l.nextDep) addSub(l);
		}
		const currentTail = link.dep.subs;
		if (currentTail !== link) {
			link.prevSub = currentTail;
			if (currentTail) currentTail.nextSub = link;
		}
		link.dep.subs = link;
	}
}
var targetMap = /* @__PURE__ */ new WeakMap();
var ITERATE_KEY = /* @__PURE__ */ Symbol("");
var MAP_KEY_ITERATE_KEY = /* @__PURE__ */ Symbol("");
var ARRAY_ITERATE_KEY = /* @__PURE__ */ Symbol("");
function track(target, type, key) {
	if (shouldTrack && activeSub) {
		let depsMap = targetMap.get(target);
		if (!depsMap) targetMap.set(target, depsMap = /* @__PURE__ */ new Map());
		let dep = depsMap.get(key);
		if (!dep) {
			depsMap.set(key, dep = new Dep());
			dep.map = depsMap;
			dep.key = key;
		}
		dep.track();
	}
}
function trigger(target, type, key, newValue, oldValue, oldTarget) {
	const depsMap = targetMap.get(target);
	if (!depsMap) {
		globalVersion++;
		return;
	}
	const run = (dep) => {
		if (dep) dep.trigger();
	};
	startBatch();
	if (type === "clear") depsMap.forEach(run);
	else {
		const targetIsArray = isArray(target);
		const isArrayIndex = targetIsArray && isIntegerKey(key);
		if (targetIsArray && key === "length") {
			const newLength = Number(newValue);
			depsMap.forEach((dep, key2) => {
				if (key2 === "length" || key2 === ARRAY_ITERATE_KEY || !isSymbol(key2) && key2 >= newLength) run(dep);
			});
		} else {
			if (key !== void 0 || depsMap.has(void 0)) run(depsMap.get(key));
			if (isArrayIndex) run(depsMap.get(ARRAY_ITERATE_KEY));
			switch (type) {
				case "add":
					if (!targetIsArray) {
						run(depsMap.get(ITERATE_KEY));
						if (isMap(target)) run(depsMap.get(MAP_KEY_ITERATE_KEY));
					} else if (isArrayIndex) run(depsMap.get("length"));
					break;
				case "delete":
					if (!targetIsArray) {
						run(depsMap.get(ITERATE_KEY));
						if (isMap(target)) run(depsMap.get(MAP_KEY_ITERATE_KEY));
					}
					break;
				case "set":
					if (isMap(target)) run(depsMap.get(ITERATE_KEY));
					break;
			}
		}
	}
	endBatch();
}
function getDepFromReactive(object, key) {
	const depMap = targetMap.get(object);
	return depMap && depMap.get(key);
}
function reactiveReadArray(array) {
	const raw = /* @__PURE__ */ toRaw(array);
	if (raw === array) return raw;
	track(raw, "iterate", ARRAY_ITERATE_KEY);
	return /* @__PURE__ */ isShallow(array) ? raw : raw.map(toReactive);
}
function shallowReadArray(arr) {
	track(arr = /* @__PURE__ */ toRaw(arr), "iterate", ARRAY_ITERATE_KEY);
	return arr;
}
function toWrapped(target, item) {
	if (/* @__PURE__ */ isReadonly(target)) return /* @__PURE__ */ isReactive(target) ? toReadonly(toReactive(item)) : toReadonly(item);
	return toReactive(item);
}
var arrayInstrumentations = {
	__proto__: null,
	[Symbol.iterator]() {
		return iterator(this, Symbol.iterator, (item) => toWrapped(this, item));
	},
	concat(...args) {
		return reactiveReadArray(this).concat(...args.map((x) => isArray(x) ? reactiveReadArray(x) : x));
	},
	entries() {
		return iterator(this, "entries", (value) => {
			value[1] = toWrapped(this, value[1]);
			return value;
		});
	},
	every(fn, thisArg) {
		return apply(this, "every", fn, thisArg, void 0, arguments);
	},
	filter(fn, thisArg) {
		return apply(this, "filter", fn, thisArg, (v) => v.map((item) => toWrapped(this, item)), arguments);
	},
	find(fn, thisArg) {
		return apply(this, "find", fn, thisArg, (item) => toWrapped(this, item), arguments);
	},
	findIndex(fn, thisArg) {
		return apply(this, "findIndex", fn, thisArg, void 0, arguments);
	},
	findLast(fn, thisArg) {
		return apply(this, "findLast", fn, thisArg, (item) => toWrapped(this, item), arguments);
	},
	findLastIndex(fn, thisArg) {
		return apply(this, "findLastIndex", fn, thisArg, void 0, arguments);
	},
	forEach(fn, thisArg) {
		return apply(this, "forEach", fn, thisArg, void 0, arguments);
	},
	includes(...args) {
		return searchProxy(this, "includes", args);
	},
	indexOf(...args) {
		return searchProxy(this, "indexOf", args);
	},
	join(separator) {
		return reactiveReadArray(this).join(separator);
	},
	lastIndexOf(...args) {
		return searchProxy(this, "lastIndexOf", args);
	},
	map(fn, thisArg) {
		return apply(this, "map", fn, thisArg, void 0, arguments);
	},
	pop() {
		return noTracking(this, "pop");
	},
	push(...args) {
		return noTracking(this, "push", args);
	},
	reduce(fn, ...args) {
		return reduce(this, "reduce", fn, args);
	},
	reduceRight(fn, ...args) {
		return reduce(this, "reduceRight", fn, args);
	},
	shift() {
		return noTracking(this, "shift");
	},
	some(fn, thisArg) {
		return apply(this, "some", fn, thisArg, void 0, arguments);
	},
	splice(...args) {
		return noTracking(this, "splice", args);
	},
	toReversed() {
		return reactiveReadArray(this).toReversed();
	},
	toSorted(comparer) {
		return reactiveReadArray(this).toSorted(comparer);
	},
	toSpliced(...args) {
		return reactiveReadArray(this).toSpliced(...args);
	},
	unshift(...args) {
		return noTracking(this, "unshift", args);
	},
	values() {
		return iterator(this, "values", (item) => toWrapped(this, item));
	}
};
function iterator(self, method, wrapValue) {
	const arr = shallowReadArray(self);
	const iter = arr[method]();
	if (arr !== self && !/* @__PURE__ */ isShallow(self)) {
		iter._next = iter.next;
		iter.next = () => {
			const result = iter._next();
			if (!result.done) result.value = wrapValue(result.value);
			return result;
		};
	}
	return iter;
}
var arrayProto = Array.prototype;
function apply(self, method, fn, thisArg, wrappedRetFn, args) {
	const arr = shallowReadArray(self);
	const needsWrap = arr !== self && !/* @__PURE__ */ isShallow(self);
	const methodFn = arr[method];
	if (methodFn !== arrayProto[method]) {
		const result2 = methodFn.apply(self, args);
		return needsWrap ? toReactive(result2) : result2;
	}
	let wrappedFn = fn;
	if (arr !== self) {
		if (needsWrap) wrappedFn = function(item, index) {
			return fn.call(this, toWrapped(self, item), index, self);
		};
		else if (fn.length > 2) wrappedFn = function(item, index) {
			return fn.call(this, item, index, self);
		};
	}
	const result = methodFn.call(arr, wrappedFn, thisArg);
	return needsWrap && wrappedRetFn ? wrappedRetFn(result) : result;
}
function reduce(self, method, fn, args) {
	const arr = shallowReadArray(self);
	const needsWrap = arr !== self && !/* @__PURE__ */ isShallow(self);
	let wrappedFn = fn;
	let wrapInitialAccumulator = false;
	if (arr !== self) {
		if (needsWrap) {
			wrapInitialAccumulator = args.length === 0;
			wrappedFn = function(acc, item, index) {
				if (wrapInitialAccumulator) {
					wrapInitialAccumulator = false;
					acc = toWrapped(self, acc);
				}
				return fn.call(this, acc, toWrapped(self, item), index, self);
			};
		} else if (fn.length > 3) wrappedFn = function(acc, item, index) {
			return fn.call(this, acc, item, index, self);
		};
	}
	const result = arr[method](wrappedFn, ...args);
	return wrapInitialAccumulator ? toWrapped(self, result) : result;
}
function searchProxy(self, method, args) {
	const arr = /* @__PURE__ */ toRaw(self);
	track(arr, "iterate", ARRAY_ITERATE_KEY);
	const res = arr[method](...args);
	if ((res === -1 || res === false) && /* @__PURE__ */ isProxy(args[0])) {
		args[0] = /* @__PURE__ */ toRaw(args[0]);
		return arr[method](...args);
	}
	return res;
}
function noTracking(self, method, args = []) {
	pauseTracking();
	startBatch();
	const res = (/* @__PURE__ */ toRaw(self))[method].apply(self, args);
	endBatch();
	resetTracking();
	return res;
}
var isNonTrackableKeys = /* @__PURE__ */ makeMap(`__proto__,__v_isRef,__isVue`);
var builtInSymbols = new Set(/* @__PURE__ */ Object.getOwnPropertyNames(Symbol).filter((key) => key !== "arguments" && key !== "caller").map((key) => Symbol[key]).filter(isSymbol));
function hasOwnProperty(key) {
	if (!isSymbol(key)) key = String(key);
	const obj = /* @__PURE__ */ toRaw(this);
	track(obj, "has", key);
	return obj.hasOwnProperty(key);
}
var BaseReactiveHandler = class {
	constructor(_isReadonly = false, _isShallow = false) {
		this._isReadonly = _isReadonly;
		this._isShallow = _isShallow;
	}
	get(target, key, receiver) {
		if (key === "__v_skip") return target["__v_skip"];
		const isReadonly2 = this._isReadonly, isShallow2 = this._isShallow;
		if (key === "__v_isReactive") return !isReadonly2;
		else if (key === "__v_isReadonly") return isReadonly2;
		else if (key === "__v_isShallow") return isShallow2;
		else if (key === "__v_raw") {
			if (receiver === (isReadonly2 ? isShallow2 ? shallowReadonlyMap : readonlyMap : isShallow2 ? shallowReactiveMap : reactiveMap).get(target) || Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver)) return target;
			return;
		}
		const targetIsArray = isArray(target);
		if (!isReadonly2) {
			let fn;
			if (targetIsArray && (fn = arrayInstrumentations[key])) return fn;
			if (key === "hasOwnProperty") return hasOwnProperty;
		}
		const res = Reflect.get(target, key, /* @__PURE__ */ isRef(target) ? target : receiver);
		if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) return res;
		if (!isReadonly2) track(target, "get", key);
		if (isShallow2) return res;
		if (/* @__PURE__ */ isRef(res)) {
			const value = targetIsArray && isIntegerKey(key) ? res : res.value;
			return isReadonly2 && isObject(value) ? /* @__PURE__ */ readonly(value) : value;
		}
		if (isObject(res)) return isReadonly2 ? /* @__PURE__ */ readonly(res) : /* @__PURE__ */ reactive(res);
		return res;
	}
};
var MutableReactiveHandler = class extends BaseReactiveHandler {
	constructor(isShallow2 = false) {
		super(false, isShallow2);
	}
	set(target, key, value, receiver) {
		let oldValue = target[key];
		const isArrayWithIntegerKey = isArray(target) && isIntegerKey(key);
		if (!this._isShallow) {
			const isOldValueReadonly = /* @__PURE__ */ isReadonly(oldValue);
			if (!/* @__PURE__ */ isShallow(value) && !/* @__PURE__ */ isReadonly(value)) {
				oldValue = /* @__PURE__ */ toRaw(oldValue);
				value = /* @__PURE__ */ toRaw(value);
			}
			if (!isArrayWithIntegerKey && /* @__PURE__ */ isRef(oldValue) && !/* @__PURE__ */ isRef(value)) if (isOldValueReadonly) return true;
			else {
				oldValue.value = value;
				return true;
			}
		}
		const hadKey = isArrayWithIntegerKey ? Number(key) < target.length : hasOwn(target, key);
		const result = Reflect.set(target, key, value, /* @__PURE__ */ isRef(target) ? target : receiver);
		if (target === /* @__PURE__ */ toRaw(receiver)) {
			if (!hadKey) trigger(target, "add", key, value);
			else if (hasChanged(value, oldValue)) trigger(target, "set", key, value, oldValue);
		}
		return result;
	}
	deleteProperty(target, key) {
		const hadKey = hasOwn(target, key);
		const oldValue = target[key];
		const result = Reflect.deleteProperty(target, key);
		if (result && hadKey) trigger(target, "delete", key, void 0, oldValue);
		return result;
	}
	has(target, key) {
		const result = Reflect.has(target, key);
		if (!isSymbol(key) || !builtInSymbols.has(key)) track(target, "has", key);
		return result;
	}
	ownKeys(target) {
		track(target, "iterate", isArray(target) ? "length" : ITERATE_KEY);
		return Reflect.ownKeys(target);
	}
};
var ReadonlyReactiveHandler = class extends BaseReactiveHandler {
	constructor(isShallow2 = false) {
		super(true, isShallow2);
	}
	set(target, key) {
		return true;
	}
	deleteProperty(target, key) {
		return true;
	}
};
var mutableHandlers = /* @__PURE__ */ new MutableReactiveHandler();
var readonlyHandlers = /* @__PURE__ */ new ReadonlyReactiveHandler();
var shallowReactiveHandlers = /* @__PURE__ */ new MutableReactiveHandler(true);
var toShallow = (value) => value;
var getProto = (v) => Reflect.getPrototypeOf(v);
function createIterableMethod(method, isReadonly2, isShallow2) {
	return function(...args) {
		const target = this["__v_raw"];
		const rawTarget = /* @__PURE__ */ toRaw(target);
		const targetIsMap = isMap(rawTarget);
		const isPair = method === "entries" || method === Symbol.iterator && targetIsMap;
		const isKeyOnly = method === "keys" && targetIsMap;
		const innerIterator = target[method](...args);
		const wrap = isShallow2 ? toShallow : isReadonly2 ? toReadonly : toReactive;
		!isReadonly2 && track(rawTarget, "iterate", isKeyOnly ? MAP_KEY_ITERATE_KEY : ITERATE_KEY);
		return extend(Object.create(innerIterator), { next() {
			const { value, done } = innerIterator.next();
			return done ? {
				value,
				done
			} : {
				value: isPair ? [wrap(value[0]), wrap(value[1])] : wrap(value),
				done
			};
		} });
	};
}
function createReadonlyMethod(type) {
	return function(...args) {
		return type === "delete" ? false : type === "clear" ? void 0 : this;
	};
}
function createInstrumentations(readonly, shallow) {
	const instrumentations = {
		get(key) {
			const target = this["__v_raw"];
			const rawTarget = /* @__PURE__ */ toRaw(target);
			const rawKey = /* @__PURE__ */ toRaw(key);
			if (!readonly) {
				if (hasChanged(key, rawKey)) track(rawTarget, "get", key);
				track(rawTarget, "get", rawKey);
			}
			const { has } = getProto(rawTarget);
			const wrap = shallow ? toShallow : readonly ? toReadonly : toReactive;
			if (has.call(rawTarget, key)) return wrap(target.get(key));
			else if (has.call(rawTarget, rawKey)) return wrap(target.get(rawKey));
			else if (target !== rawTarget) target.get(key);
		},
		get size() {
			const target = this["__v_raw"];
			!readonly && track(/* @__PURE__ */ toRaw(target), "iterate", ITERATE_KEY);
			return target.size;
		},
		has(key) {
			const target = this["__v_raw"];
			const rawTarget = /* @__PURE__ */ toRaw(target);
			const rawKey = /* @__PURE__ */ toRaw(key);
			if (!readonly) {
				if (hasChanged(key, rawKey)) track(rawTarget, "has", key);
				track(rawTarget, "has", rawKey);
			}
			return key === rawKey ? target.has(key) : target.has(key) || target.has(rawKey);
		},
		forEach(callback, thisArg) {
			const observed = this;
			const target = observed["__v_raw"];
			const rawTarget = /* @__PURE__ */ toRaw(target);
			const wrap = shallow ? toShallow : readonly ? toReadonly : toReactive;
			!readonly && track(rawTarget, "iterate", ITERATE_KEY);
			return target.forEach((value, key) => {
				return callback.call(thisArg, wrap(value), wrap(key), observed);
			});
		}
	};
	extend(instrumentations, readonly ? {
		add: createReadonlyMethod("add"),
		set: createReadonlyMethod("set"),
		delete: createReadonlyMethod("delete"),
		clear: createReadonlyMethod("clear")
	} : {
		add(value) {
			const target = /* @__PURE__ */ toRaw(this);
			const proto = getProto(target);
			const rawValue = /* @__PURE__ */ toRaw(value);
			const valueToAdd = !shallow && !/* @__PURE__ */ isShallow(value) && !/* @__PURE__ */ isReadonly(value) ? rawValue : value;
			if (!(proto.has.call(target, valueToAdd) || hasChanged(value, valueToAdd) && proto.has.call(target, value) || hasChanged(rawValue, valueToAdd) && proto.has.call(target, rawValue))) {
				target.add(valueToAdd);
				trigger(target, "add", valueToAdd, valueToAdd);
			}
			return this;
		},
		set(key, value) {
			if (!shallow && !/* @__PURE__ */ isShallow(value) && !/* @__PURE__ */ isReadonly(value)) value = /* @__PURE__ */ toRaw(value);
			const target = /* @__PURE__ */ toRaw(this);
			const { has, get } = getProto(target);
			let hadKey = has.call(target, key);
			if (!hadKey) {
				key = /* @__PURE__ */ toRaw(key);
				hadKey = has.call(target, key);
			}
			const oldValue = get.call(target, key);
			target.set(key, value);
			if (!hadKey) trigger(target, "add", key, value);
			else if (hasChanged(value, oldValue)) trigger(target, "set", key, value, oldValue);
			return this;
		},
		delete(key) {
			const target = /* @__PURE__ */ toRaw(this);
			const { has, get } = getProto(target);
			let hadKey = has.call(target, key);
			if (!hadKey) {
				key = /* @__PURE__ */ toRaw(key);
				hadKey = has.call(target, key);
			}
			const oldValue = get ? get.call(target, key) : void 0;
			const result = target.delete(key);
			if (hadKey) trigger(target, "delete", key, void 0, oldValue);
			return result;
		},
		clear() {
			const target = /* @__PURE__ */ toRaw(this);
			const hadItems = target.size !== 0;
			const oldTarget = void 0;
			const result = target.clear();
			if (hadItems) trigger(target, "clear", void 0, void 0, oldTarget);
			return result;
		}
	});
	[
		"keys",
		"values",
		"entries",
		Symbol.iterator
	].forEach((method) => {
		instrumentations[method] = createIterableMethod(method, readonly, shallow);
	});
	return instrumentations;
}
function createInstrumentationGetter(isReadonly2, shallow) {
	const instrumentations = createInstrumentations(isReadonly2, shallow);
	return (target, key, receiver) => {
		if (key === "__v_isReactive") return !isReadonly2;
		else if (key === "__v_isReadonly") return isReadonly2;
		else if (key === "__v_raw") return target;
		return Reflect.get(hasOwn(instrumentations, key) && key in target ? instrumentations : target, key, receiver);
	};
}
var mutableCollectionHandlers = { get: /* @__PURE__ */ createInstrumentationGetter(false, false) };
var shallowCollectionHandlers = { get: /* @__PURE__ */ createInstrumentationGetter(false, true) };
var readonlyCollectionHandlers = { get: /* @__PURE__ */ createInstrumentationGetter(true, false) };
var reactiveMap = /* @__PURE__ */ new WeakMap();
var shallowReactiveMap = /* @__PURE__ */ new WeakMap();
var readonlyMap = /* @__PURE__ */ new WeakMap();
var shallowReadonlyMap = /* @__PURE__ */ new WeakMap();
function targetTypeMap(rawType) {
	switch (rawType) {
		case "Object":
		case "Array": return 1;
		case "Map":
		case "Set":
		case "WeakMap":
		case "WeakSet": return 2;
		default: return 0;
	}
}
function getTargetType(value) {
	return value["__v_skip"] || !Object.isExtensible(value) ? 0 : targetTypeMap(toRawType(value));
}
/* @__NO_SIDE_EFFECTS__ */
function reactive(target) {
	if (/* @__PURE__ */ isReadonly(target)) return target;
	return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap);
}
/* @__NO_SIDE_EFFECTS__ */
function shallowReactive(target) {
	return createReactiveObject(target, false, shallowReactiveHandlers, shallowCollectionHandlers, shallowReactiveMap);
}
/* @__NO_SIDE_EFFECTS__ */
function readonly(target) {
	return createReactiveObject(target, true, readonlyHandlers, readonlyCollectionHandlers, readonlyMap);
}
function createReactiveObject(target, isReadonly2, baseHandlers, collectionHandlers, proxyMap) {
	if (!isObject(target)) return target;
	if (target["__v_raw"] && !(isReadonly2 && target["__v_isReactive"])) return target;
	const targetType = getTargetType(target);
	if (targetType === 0) return target;
	const existingProxy = proxyMap.get(target);
	if (existingProxy) return existingProxy;
	const proxy = new Proxy(target, targetType === 2 ? collectionHandlers : baseHandlers);
	proxyMap.set(target, proxy);
	return proxy;
}
/* @__NO_SIDE_EFFECTS__ */
function isReactive(value) {
	if (/* @__PURE__ */ isReadonly(value)) return /* @__PURE__ */ isReactive(value["__v_raw"]);
	return !!(value && value["__v_isReactive"]);
}
/* @__NO_SIDE_EFFECTS__ */
function isReadonly(value) {
	return !!(value && value["__v_isReadonly"]);
}
/* @__NO_SIDE_EFFECTS__ */
function isShallow(value) {
	return !!(value && value["__v_isShallow"]);
}
/* @__NO_SIDE_EFFECTS__ */
function isProxy(value) {
	return value ? !!value["__v_raw"] : false;
}
/* @__NO_SIDE_EFFECTS__ */
function toRaw(observed) {
	const raw = observed && observed["__v_raw"];
	return raw ? /* @__PURE__ */ toRaw(raw) : observed;
}
function markRaw(value) {
	if (!hasOwn(value, "__v_skip") && Object.isExtensible(value)) def(value, "__v_skip", true);
	return value;
}
var toReactive = (value) => isObject(value) ? /* @__PURE__ */ reactive(value) : value;
var toReadonly = (value) => isObject(value) ? /* @__PURE__ */ readonly(value) : value;
/* @__NO_SIDE_EFFECTS__ */
function isRef(r) {
	return r ? r["__v_isRef"] === true : false;
}
/* @__NO_SIDE_EFFECTS__ */
function ref(value) {
	return createRef(value, false);
}
function createRef(rawValue, shallow) {
	if (/* @__PURE__ */ isRef(rawValue)) return rawValue;
	return new RefImpl(rawValue, shallow);
}
var RefImpl = class {
	constructor(value, isShallow2) {
		this.dep = new Dep();
		this["__v_isRef"] = true;
		this["__v_isShallow"] = false;
		this._rawValue = isShallow2 ? value : /* @__PURE__ */ toRaw(value);
		this._value = isShallow2 ? value : toReactive(value);
		this["__v_isShallow"] = isShallow2;
	}
	get value() {
		this.dep.track();
		return this._value;
	}
	set value(newValue) {
		const oldValue = this._rawValue;
		const useDirectValue = this["__v_isShallow"] || /* @__PURE__ */ isShallow(newValue) || /* @__PURE__ */ isReadonly(newValue);
		newValue = useDirectValue ? newValue : /* @__PURE__ */ toRaw(newValue);
		if (hasChanged(newValue, oldValue)) {
			this._rawValue = newValue;
			this._value = useDirectValue ? newValue : toReactive(newValue);
			this.dep.trigger();
		}
	}
};
function unref(ref2) {
	return /* @__PURE__ */ isRef(ref2) ? ref2.value : ref2;
}
var shallowUnwrapHandlers = {
	get: (target, key, receiver) => key === "__v_raw" ? target : unref(Reflect.get(target, key, receiver)),
	set: (target, key, value, receiver) => {
		const oldValue = target[key];
		if (/* @__PURE__ */ isRef(oldValue) && !/* @__PURE__ */ isRef(value)) {
			oldValue.value = value;
			return true;
		} else return Reflect.set(target, key, value, receiver);
	}
};
function proxyRefs(objectWithRefs) {
	return /* @__PURE__ */ isReactive(objectWithRefs) ? objectWithRefs : new Proxy(objectWithRefs, shallowUnwrapHandlers);
}
/* @__NO_SIDE_EFFECTS__ */
function toRefs(object) {
	const ret = isArray(object) ? new Array(object.length) : {};
	for (const key in object) ret[key] = propertyToRef(object, key);
	return ret;
}
var ObjectRefImpl = class {
	constructor(_object, key, _defaultValue) {
		this._object = _object;
		this._defaultValue = _defaultValue;
		this["__v_isRef"] = true;
		this._value = void 0;
		this._key = isSymbol(key) ? key : String(key);
		this._raw = /* @__PURE__ */ toRaw(_object);
		let shallow = true;
		let obj = _object;
		if (!isArray(_object) || isSymbol(this._key) || !isIntegerKey(this._key)) do
			shallow = !/* @__PURE__ */ isProxy(obj) || /* @__PURE__ */ isShallow(obj);
		while (shallow && (obj = obj["__v_raw"]));
		this._shallow = shallow;
	}
	get value() {
		let val = this._object[this._key];
		if (this._shallow) val = unref(val);
		return this._value = val === void 0 ? this._defaultValue : val;
	}
	set value(newVal) {
		if (this._shallow && /* @__PURE__ */ isRef(this._raw[this._key])) {
			const nestedRef = this._object[this._key];
			if (/* @__PURE__ */ isRef(nestedRef)) {
				nestedRef.value = newVal;
				return;
			}
		}
		this._object[this._key] = newVal;
	}
	get dep() {
		return getDepFromReactive(this._raw, this._key);
	}
};
function propertyToRef(source, key, defaultValue) {
	return new ObjectRefImpl(source, key, defaultValue);
}
var ComputedRefImpl = class {
	constructor(fn, setter, isSSR) {
		this.fn = fn;
		this.setter = setter;
		/**
		* @internal
		*/
		this._value = void 0;
		/**
		* @internal
		*/
		this.dep = new Dep(this);
		/**
		* @internal
		*/
		this.__v_isRef = true;
		/**
		* @internal
		*/
		this.deps = void 0;
		/**
		* @internal
		*/
		this.depsTail = void 0;
		/**
		* @internal
		*/
		this.flags = 16;
		/**
		* @internal
		*/
		this.globalVersion = globalVersion - 1;
		/**
		* @internal
		*/
		this.next = void 0;
		this.effect = this;
		this["__v_isReadonly"] = !setter;
		this.isSSR = isSSR;
	}
	/**
	* @internal
	*/
	notify() {
		this.flags |= 16;
		if (!(this.flags & 8) && activeSub !== this) {
			batch(this, true);
			return true;
		}
	}
	get value() {
		const link = this.dep.track();
		refreshComputed(this);
		if (link) link.version = this.dep.version;
		return this._value;
	}
	set value(newValue) {
		if (this.setter) this.setter(newValue);
	}
};
/* @__NO_SIDE_EFFECTS__ */
function computed$1(getterOrOptions, debugOptions, isSSR = false) {
	let getter;
	let setter;
	if (isFunction(getterOrOptions)) getter = getterOrOptions;
	else {
		getter = getterOrOptions.get;
		setter = getterOrOptions.set;
	}
	return new ComputedRefImpl(getter, setter, isSSR);
}
var INITIAL_WATCHER_VALUE = {};
var cleanupMap = /* @__PURE__ */ new WeakMap();
var activeWatcher = void 0;
function onWatcherCleanup(cleanupFn, failSilently = false, owner = activeWatcher) {
	if (owner) {
		let cleanups = cleanupMap.get(owner);
		if (!cleanups) cleanupMap.set(owner, cleanups = []);
		cleanups.push(cleanupFn);
	}
}
function watch$1(source, cb, options = EMPTY_OBJ) {
	const { immediate, deep, once, scheduler, augmentJob, call } = options;
	const reactiveGetter = (source2) => {
		if (deep) return source2;
		if (/* @__PURE__ */ isShallow(source2) || deep === false || deep === 0) return traverse(source2, 1);
		return traverse(source2);
	};
	let effect;
	let getter;
	let cleanup;
	let boundCleanup;
	let forceTrigger = false;
	let isMultiSource = false;
	if (/* @__PURE__ */ isRef(source)) {
		getter = () => source.value;
		forceTrigger = /* @__PURE__ */ isShallow(source);
	} else if (/* @__PURE__ */ isReactive(source)) {
		getter = () => reactiveGetter(source);
		forceTrigger = true;
	} else if (isArray(source)) {
		isMultiSource = true;
		forceTrigger = source.some((s) => /* @__PURE__ */ isReactive(s) || /* @__PURE__ */ isShallow(s));
		getter = () => source.map((s) => {
			if (/* @__PURE__ */ isRef(s)) return s.value;
			else if (/* @__PURE__ */ isReactive(s)) return reactiveGetter(s);
			else if (isFunction(s)) return call ? call(s, 2) : s();
		});
	} else if (isFunction(source)) if (cb) getter = call ? () => call(source, 2) : source;
	else getter = () => {
		if (cleanup) {
			pauseTracking();
			try {
				cleanup();
			} finally {
				resetTracking();
			}
		}
		const currentEffect = activeWatcher;
		activeWatcher = effect;
		try {
			return call ? call(source, 3, [boundCleanup]) : source(boundCleanup);
		} finally {
			activeWatcher = currentEffect;
		}
	};
	else getter = NOOP;
	if (cb && deep) {
		const baseGetter = getter;
		const depth = deep === true ? Infinity : deep;
		getter = () => traverse(baseGetter(), depth);
	}
	const scope = getCurrentScope();
	const watchHandle = () => {
		effect.stop();
		if (scope && scope.active) remove(scope.effects, effect);
	};
	if (once && cb) {
		const _cb = cb;
		cb = (...args) => {
			_cb(...args);
			watchHandle();
		};
	}
	let oldValue = isMultiSource ? new Array(source.length).fill(INITIAL_WATCHER_VALUE) : INITIAL_WATCHER_VALUE;
	const job = (immediateFirstRun) => {
		if (!(effect.flags & 1) || !effect.dirty && !immediateFirstRun) return;
		if (cb) {
			const newValue = effect.run();
			if (deep || forceTrigger || (isMultiSource ? newValue.some((v, i) => hasChanged(v, oldValue[i])) : hasChanged(newValue, oldValue))) {
				if (cleanup) cleanup();
				const currentWatcher = activeWatcher;
				activeWatcher = effect;
				try {
					const args = [
						newValue,
						oldValue === INITIAL_WATCHER_VALUE ? void 0 : isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE ? [] : oldValue,
						boundCleanup
					];
					oldValue = newValue;
					call ? call(cb, 3, args) : cb(...args);
				} finally {
					activeWatcher = currentWatcher;
				}
			}
		} else effect.run();
	};
	if (augmentJob) augmentJob(job);
	effect = new ReactiveEffect(getter);
	effect.scheduler = scheduler ? () => scheduler(job, false) : job;
	boundCleanup = (fn) => onWatcherCleanup(fn, false, effect);
	cleanup = effect.onStop = () => {
		const cleanups = cleanupMap.get(effect);
		if (cleanups) {
			if (call) call(cleanups, 4);
			else for (const cleanup2 of cleanups) cleanup2();
			cleanupMap.delete(effect);
		}
	};
	if (cb) if (immediate) job(true);
	else oldValue = effect.run();
	else if (scheduler) scheduler(job.bind(null, true), true);
	else effect.run();
	watchHandle.pause = effect.pause.bind(effect);
	watchHandle.resume = effect.resume.bind(effect);
	watchHandle.stop = watchHandle;
	return watchHandle;
}
function traverse(value, depth = Infinity, seen) {
	if (depth <= 0 || !isObject(value) || value["__v_skip"]) return value;
	seen = seen || /* @__PURE__ */ new Map();
	if ((seen.get(value) || 0) >= depth) return value;
	seen.set(value, depth);
	depth--;
	if (/* @__PURE__ */ isRef(value)) traverse(value.value, depth, seen);
	else if (isArray(value)) for (let i = 0; i < value.length; i++) traverse(value[i], depth, seen);
	else if (isSet(value) || isMap(value)) value.forEach((v) => {
		traverse(v, depth, seen);
	});
	else if (isPlainObject$1(value)) {
		for (const key in value) traverse(value[key], depth, seen);
		for (const key of Object.getOwnPropertySymbols(value)) if (Object.prototype.propertyIsEnumerable.call(value, key)) traverse(value[key], depth, seen);
	}
	return value;
}
//#endregion
//#region node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js
/**
* @vue/runtime-core v3.5.31
* (c) 2018-present Yuxi (Evan) You and Vue contributors
* @license MIT
**/
function callWithErrorHandling(fn, instance, type, args) {
	try {
		return args ? fn(...args) : fn();
	} catch (err) {
		handleError(err, instance, type);
	}
}
function callWithAsyncErrorHandling(fn, instance, type, args) {
	if (isFunction(fn)) {
		const res = callWithErrorHandling(fn, instance, type, args);
		if (res && isPromise(res)) res.catch((err) => {
			handleError(err, instance, type);
		});
		return res;
	}
	if (isArray(fn)) {
		const values = [];
		for (let i = 0; i < fn.length; i++) values.push(callWithAsyncErrorHandling(fn[i], instance, type, args));
		return values;
	}
}
function handleError(err, instance, type, throwInDev = true) {
	const contextVNode = instance ? instance.vnode : null;
	const { errorHandler, throwUnhandledErrorInProduction } = instance && instance.appContext.config || EMPTY_OBJ;
	if (instance) {
		let cur = instance.parent;
		const exposedInstance = instance.proxy;
		const errorInfo = `https://vuejs.org/error-reference/#runtime-${type}`;
		while (cur) {
			const errorCapturedHooks = cur.ec;
			if (errorCapturedHooks) {
				for (let i = 0; i < errorCapturedHooks.length; i++) if (errorCapturedHooks[i](err, exposedInstance, errorInfo) === false) return;
			}
			cur = cur.parent;
		}
		if (errorHandler) {
			pauseTracking();
			callWithErrorHandling(errorHandler, null, 10, [
				err,
				exposedInstance,
				errorInfo
			]);
			resetTracking();
			return;
		}
	}
	logError(err, type, contextVNode, throwInDev, throwUnhandledErrorInProduction);
}
function logError(err, type, contextVNode, throwInDev = true, throwInProd = false) {
	if (throwInProd) throw err;
	else console.error(err);
}
var queue = [];
var flushIndex = -1;
var pendingPostFlushCbs = [];
var activePostFlushCbs = null;
var postFlushIndex = 0;
var resolvedPromise = /* @__PURE__ */ Promise.resolve();
var currentFlushPromise = null;
function nextTick(fn) {
	const p = currentFlushPromise || resolvedPromise;
	return fn ? p.then(this ? fn.bind(this) : fn) : p;
}
function findInsertionIndex(id) {
	let start = flushIndex + 1;
	let end = queue.length;
	while (start < end) {
		const middle = start + end >>> 1;
		const middleJob = queue[middle];
		const middleJobId = getId(middleJob);
		if (middleJobId < id || middleJobId === id && middleJob.flags & 2) start = middle + 1;
		else end = middle;
	}
	return start;
}
function queueJob(job) {
	if (!(job.flags & 1)) {
		const jobId = getId(job);
		const lastJob = queue[queue.length - 1];
		if (!lastJob || !(job.flags & 2) && jobId >= getId(lastJob)) queue.push(job);
		else queue.splice(findInsertionIndex(jobId), 0, job);
		job.flags |= 1;
		queueFlush();
	}
}
function queueFlush() {
	if (!currentFlushPromise) currentFlushPromise = resolvedPromise.then(flushJobs);
}
function queuePostFlushCb(cb) {
	if (!isArray(cb)) {
		if (activePostFlushCbs && cb.id === -1) activePostFlushCbs.splice(postFlushIndex + 1, 0, cb);
		else if (!(cb.flags & 1)) {
			pendingPostFlushCbs.push(cb);
			cb.flags |= 1;
		}
	} else pendingPostFlushCbs.push(...cb);
	queueFlush();
}
function flushPreFlushCbs(instance, seen, i = flushIndex + 1) {
	for (; i < queue.length; i++) {
		const cb = queue[i];
		if (cb && cb.flags & 2) {
			if (instance && cb.id !== instance.uid) continue;
			queue.splice(i, 1);
			i--;
			if (cb.flags & 4) cb.flags &= -2;
			cb();
			if (!(cb.flags & 4)) cb.flags &= -2;
		}
	}
}
function flushPostFlushCbs(seen) {
	if (pendingPostFlushCbs.length) {
		const deduped = [...new Set(pendingPostFlushCbs)].sort((a, b) => getId(a) - getId(b));
		pendingPostFlushCbs.length = 0;
		if (activePostFlushCbs) {
			activePostFlushCbs.push(...deduped);
			return;
		}
		activePostFlushCbs = deduped;
		for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) {
			const cb = activePostFlushCbs[postFlushIndex];
			if (cb.flags & 4) cb.flags &= -2;
			if (!(cb.flags & 8)) cb();
			cb.flags &= -2;
		}
		activePostFlushCbs = null;
		postFlushIndex = 0;
	}
}
var getId = (job) => job.id == null ? job.flags & 2 ? -1 : Infinity : job.id;
function flushJobs(seen) {
	try {
		for (flushIndex = 0; flushIndex < queue.length; flushIndex++) {
			const job = queue[flushIndex];
			if (job && !(job.flags & 8)) {
				if (job.flags & 4) job.flags &= -2;
				callWithErrorHandling(job, job.i, job.i ? 15 : 14);
				if (!(job.flags & 4)) job.flags &= -2;
			}
		}
	} finally {
		for (; flushIndex < queue.length; flushIndex++) {
			const job = queue[flushIndex];
			if (job) job.flags &= -2;
		}
		flushIndex = -1;
		queue.length = 0;
		flushPostFlushCbs(seen);
		currentFlushPromise = null;
		if (queue.length || pendingPostFlushCbs.length) flushJobs(seen);
	}
}
var currentRenderingInstance = null;
var currentScopeId = null;
function setCurrentRenderingInstance(instance) {
	const prev = currentRenderingInstance;
	currentRenderingInstance = instance;
	currentScopeId = instance && instance.type.__scopeId || null;
	return prev;
}
function withCtx(fn, ctx = currentRenderingInstance, isNonScopedSlot) {
	if (!ctx) return fn;
	if (fn._n) return fn;
	const renderFnWithContext = (...args) => {
		if (renderFnWithContext._d) setBlockTracking(-1);
		const prevInstance = setCurrentRenderingInstance(ctx);
		let res;
		try {
			res = fn(...args);
		} finally {
			setCurrentRenderingInstance(prevInstance);
			if (renderFnWithContext._d) setBlockTracking(1);
		}
		return res;
	};
	renderFnWithContext._n = true;
	renderFnWithContext._c = true;
	renderFnWithContext._d = true;
	return renderFnWithContext;
}
function withDirectives(vnode, directives) {
	if (currentRenderingInstance === null) return vnode;
	const instance = getComponentPublicInstance(currentRenderingInstance);
	const bindings = vnode.dirs || (vnode.dirs = []);
	for (let i = 0; i < directives.length; i++) {
		let [dir, value, arg, modifiers = EMPTY_OBJ] = directives[i];
		if (dir) {
			if (isFunction(dir)) dir = {
				mounted: dir,
				updated: dir
			};
			if (dir.deep) traverse(value);
			bindings.push({
				dir,
				instance,
				value,
				oldValue: void 0,
				arg,
				modifiers
			});
		}
	}
	return vnode;
}
function invokeDirectiveHook(vnode, prevVNode, instance, name) {
	const bindings = vnode.dirs;
	const oldBindings = prevVNode && prevVNode.dirs;
	for (let i = 0; i < bindings.length; i++) {
		const binding = bindings[i];
		if (oldBindings) binding.oldValue = oldBindings[i].value;
		let hook = binding.dir[name];
		if (hook) {
			pauseTracking();
			callWithAsyncErrorHandling(hook, instance, 8, [
				vnode.el,
				binding,
				vnode,
				prevVNode
			]);
			resetTracking();
		}
	}
}
function provide(key, value) {
	if (currentInstance) {
		let provides = currentInstance.provides;
		const parentProvides = currentInstance.parent && currentInstance.parent.provides;
		if (parentProvides === provides) provides = currentInstance.provides = Object.create(parentProvides);
		provides[key] = value;
	}
}
function inject(key, defaultValue, treatDefaultAsFactory = false) {
	const instance = getCurrentInstance();
	if (instance || currentApp) {
		let provides = currentApp ? currentApp._context.provides : instance ? instance.parent == null || instance.ce ? instance.vnode.appContext && instance.vnode.appContext.provides : instance.parent.provides : void 0;
		if (provides && key in provides) return provides[key];
		else if (arguments.length > 1) return treatDefaultAsFactory && isFunction(defaultValue) ? defaultValue.call(instance && instance.proxy) : defaultValue;
	}
}
function hasInjectionContext() {
	return !!(getCurrentInstance() || currentApp);
}
var ssrContextKey = /* @__PURE__ */ Symbol.for("v-scx");
var useSSRContext = () => {
	{
		const ctx = inject(ssrContextKey);
		if (!ctx) {}
		return ctx;
	}
};
function watch(source, cb, options) {
	return doWatch(source, cb, options);
}
function doWatch(source, cb, options = EMPTY_OBJ) {
	const { immediate, deep, flush, once } = options;
	const baseWatchOptions = extend({}, options);
	const runsImmediately = cb && immediate || !cb && flush !== "post";
	let ssrCleanup;
	if (isInSSRComponentSetup) {
		if (flush === "sync") {
			const ctx = useSSRContext();
			ssrCleanup = ctx.__watcherHandles || (ctx.__watcherHandles = []);
		} else if (!runsImmediately) {
			const watchStopHandle = () => {};
			watchStopHandle.stop = NOOP;
			watchStopHandle.resume = NOOP;
			watchStopHandle.pause = NOOP;
			return watchStopHandle;
		}
	}
	const instance = currentInstance;
	baseWatchOptions.call = (fn, type, args) => callWithAsyncErrorHandling(fn, instance, type, args);
	let isPre = false;
	if (flush === "post") baseWatchOptions.scheduler = (job) => {
		queuePostRenderEffect(job, instance && instance.suspense);
	};
	else if (flush !== "sync") {
		isPre = true;
		baseWatchOptions.scheduler = (job, isFirstRun) => {
			if (isFirstRun) job();
			else queueJob(job);
		};
	}
	baseWatchOptions.augmentJob = (job) => {
		if (cb) job.flags |= 4;
		if (isPre) {
			job.flags |= 2;
			if (instance) {
				job.id = instance.uid;
				job.i = instance;
			}
		}
	};
	const watchHandle = watch$1(source, cb, baseWatchOptions);
	if (isInSSRComponentSetup) {
		if (ssrCleanup) ssrCleanup.push(watchHandle);
		else if (runsImmediately) watchHandle();
	}
	return watchHandle;
}
function instanceWatch(source, value, options) {
	const publicThis = this.proxy;
	const getter = isString(source) ? source.includes(".") ? createPathGetter(publicThis, source) : () => publicThis[source] : source.bind(publicThis, publicThis);
	let cb;
	if (isFunction(value)) cb = value;
	else {
		cb = value.handler;
		options = value;
	}
	const reset = setCurrentInstance(this);
	const res = doWatch(getter, cb.bind(publicThis), options);
	reset();
	return res;
}
function createPathGetter(ctx, path) {
	const segments = path.split(".");
	return () => {
		let cur = ctx;
		for (let i = 0; i < segments.length && cur; i++) cur = cur[segments[i]];
		return cur;
	};
}
var TeleportEndKey = /* @__PURE__ */ Symbol("_vte");
var isTeleport = (type) => type.__isTeleport;
var leaveCbKey = /* @__PURE__ */ Symbol("_leaveCb");
function setTransitionHooks(vnode, hooks) {
	if (vnode.shapeFlag & 6 && vnode.component) {
		vnode.transition = hooks;
		setTransitionHooks(vnode.component.subTree, hooks);
	} else if (vnode.shapeFlag & 128) {
		vnode.ssContent.transition = hooks.clone(vnode.ssContent);
		vnode.ssFallback.transition = hooks.clone(vnode.ssFallback);
	} else vnode.transition = hooks;
}
/* @__NO_SIDE_EFFECTS__ */
function defineComponent(options, extraOptions) {
	return isFunction(options) ? extend({ name: options.name }, extraOptions, { setup: options }) : options;
}
function markAsyncBoundary(instance) {
	instance.ids = [
		instance.ids[0] + instance.ids[2]++ + "-",
		0,
		0
	];
}
function isTemplateRefKey(refs, key) {
	let desc;
	return !!((desc = Object.getOwnPropertyDescriptor(refs, key)) && !desc.configurable);
}
var pendingSetRefMap = /* @__PURE__ */ new WeakMap();
function setRef(rawRef, oldRawRef, parentSuspense, vnode, isUnmount = false) {
	if (isArray(rawRef)) {
		rawRef.forEach((r, i) => setRef(r, oldRawRef && (isArray(oldRawRef) ? oldRawRef[i] : oldRawRef), parentSuspense, vnode, isUnmount));
		return;
	}
	if (isAsyncWrapper(vnode) && !isUnmount) {
		if (vnode.shapeFlag & 512 && vnode.type.__asyncResolved && vnode.component.subTree.component) setRef(rawRef, oldRawRef, parentSuspense, vnode.component.subTree);
		return;
	}
	const refValue = vnode.shapeFlag & 4 ? getComponentPublicInstance(vnode.component) : vnode.el;
	const value = isUnmount ? null : refValue;
	const { i: owner, r: ref } = rawRef;
	const oldRef = oldRawRef && oldRawRef.r;
	const refs = owner.refs === EMPTY_OBJ ? owner.refs = {} : owner.refs;
	const setupState = owner.setupState;
	const rawSetupState = /* @__PURE__ */ toRaw(setupState);
	const canSetSetupRef = setupState === EMPTY_OBJ ? NO : (key) => {
		if (isTemplateRefKey(refs, key)) return false;
		return hasOwn(rawSetupState, key);
	};
	const canSetRef = (ref2, key) => {
		if (key && isTemplateRefKey(refs, key)) return false;
		return true;
	};
	if (oldRef != null && oldRef !== ref) {
		invalidatePendingSetRef(oldRawRef);
		if (isString(oldRef)) {
			refs[oldRef] = null;
			if (canSetSetupRef(oldRef)) setupState[oldRef] = null;
		} else if (/* @__PURE__ */ isRef(oldRef)) {
			const oldRawRefAtom = oldRawRef;
			if (canSetRef(oldRef, oldRawRefAtom.k)) oldRef.value = null;
			if (oldRawRefAtom.k) refs[oldRawRefAtom.k] = null;
		}
	}
	if (isFunction(ref)) callWithErrorHandling(ref, owner, 12, [value, refs]);
	else {
		const _isString = isString(ref);
		const _isRef = /* @__PURE__ */ isRef(ref);
		if (_isString || _isRef) {
			const doSet = () => {
				if (rawRef.f) {
					const existing = _isString ? canSetSetupRef(ref) ? setupState[ref] : refs[ref] : canSetRef(ref) || !rawRef.k ? ref.value : refs[rawRef.k];
					if (isUnmount) isArray(existing) && remove(existing, refValue);
					else if (!isArray(existing)) if (_isString) {
						refs[ref] = [refValue];
						if (canSetSetupRef(ref)) setupState[ref] = refs[ref];
					} else {
						const newVal = [refValue];
						if (canSetRef(ref, rawRef.k)) ref.value = newVal;
						if (rawRef.k) refs[rawRef.k] = newVal;
					}
					else if (!existing.includes(refValue)) existing.push(refValue);
				} else if (_isString) {
					refs[ref] = value;
					if (canSetSetupRef(ref)) setupState[ref] = value;
				} else if (_isRef) {
					if (canSetRef(ref, rawRef.k)) ref.value = value;
					if (rawRef.k) refs[rawRef.k] = value;
				}
			};
			if (value) {
				const job = () => {
					doSet();
					pendingSetRefMap.delete(rawRef);
				};
				job.id = -1;
				pendingSetRefMap.set(rawRef, job);
				queuePostRenderEffect(job, parentSuspense);
			} else {
				invalidatePendingSetRef(rawRef);
				doSet();
			}
		}
	}
}
function invalidatePendingSetRef(rawRef) {
	const pendingSetRef = pendingSetRefMap.get(rawRef);
	if (pendingSetRef) {
		pendingSetRef.flags |= 8;
		pendingSetRefMap.delete(rawRef);
	}
}
getGlobalThis().requestIdleCallback;
getGlobalThis().cancelIdleCallback;
var isAsyncWrapper = (i) => !!i.type.__asyncLoader;
var isKeepAlive = (vnode) => vnode.type.__isKeepAlive;
function onActivated(hook, target) {
	registerKeepAliveHook(hook, "a", target);
}
function onDeactivated(hook, target) {
	registerKeepAliveHook(hook, "da", target);
}
function registerKeepAliveHook(hook, type, target = currentInstance) {
	const wrappedHook = hook.__wdc || (hook.__wdc = () => {
		let current = target;
		while (current) {
			if (current.isDeactivated) return;
			current = current.parent;
		}
		return hook();
	});
	injectHook(type, wrappedHook, target);
	if (target) {
		let current = target.parent;
		while (current && current.parent) {
			if (isKeepAlive(current.parent.vnode)) injectToKeepAliveRoot(wrappedHook, type, target, current);
			current = current.parent;
		}
	}
}
function injectToKeepAliveRoot(hook, type, target, keepAliveRoot) {
	const injected = injectHook(type, hook, keepAliveRoot, true);
	onUnmounted(() => {
		remove(keepAliveRoot[type], injected);
	}, target);
}
function injectHook(type, hook, target = currentInstance, prepend = false) {
	if (target) {
		const hooks = target[type] || (target[type] = []);
		const wrappedHook = hook.__weh || (hook.__weh = (...args) => {
			pauseTracking();
			const reset = setCurrentInstance(target);
			const res = callWithAsyncErrorHandling(hook, target, type, args);
			reset();
			resetTracking();
			return res;
		});
		if (prepend) hooks.unshift(wrappedHook);
		else hooks.push(wrappedHook);
		return wrappedHook;
	}
}
var createHook = (lifecycle) => (hook, target = currentInstance) => {
	if (!isInSSRComponentSetup || lifecycle === "sp") injectHook(lifecycle, (...args) => hook(...args), target);
};
var onBeforeMount = createHook("bm");
var onMounted = createHook("m");
var onBeforeUpdate = createHook("bu");
var onUpdated = createHook("u");
var onBeforeUnmount = createHook("bum");
var onUnmounted = createHook("um");
var onServerPrefetch = createHook("sp");
var onRenderTriggered = createHook("rtg");
var onRenderTracked = createHook("rtc");
function onErrorCaptured(hook, target = currentInstance) {
	injectHook("ec", hook, target);
}
var COMPONENTS = "components";
var NULL_DYNAMIC_COMPONENT = /* @__PURE__ */ Symbol.for("v-ndc");
function resolveDynamicComponent(component) {
	if (isString(component)) return resolveAsset(COMPONENTS, component, false) || component;
	else return component || NULL_DYNAMIC_COMPONENT;
}
function resolveAsset(type, name, warnMissing = true, maybeSelfReference = false) {
	const instance = currentRenderingInstance || currentInstance;
	if (instance) {
		const Component = instance.type;
		if (type === COMPONENTS) {
			const selfName = getComponentName(Component, false);
			if (selfName && (selfName === name || selfName === camelize(name) || selfName === capitalize(camelize(name)))) return Component;
		}
		const res = resolve(instance[type] || Component[type], name) || resolve(instance.appContext[type], name);
		if (!res && maybeSelfReference) return Component;
		return res;
	}
}
function resolve(registry, name) {
	return registry && (registry[name] || registry[camelize(name)] || registry[capitalize(camelize(name))]);
}
function renderList(source, renderItem, cache, index) {
	let ret;
	const cached = cache && cache[index];
	const sourceIsArray = isArray(source);
	if (sourceIsArray || isString(source)) {
		const sourceIsReactiveArray = sourceIsArray && /* @__PURE__ */ isReactive(source);
		let needsWrap = false;
		let isReadonlySource = false;
		if (sourceIsReactiveArray) {
			needsWrap = !/* @__PURE__ */ isShallow(source);
			isReadonlySource = /* @__PURE__ */ isReadonly(source);
			source = shallowReadArray(source);
		}
		ret = new Array(source.length);
		for (let i = 0, l = source.length; i < l; i++) ret[i] = renderItem(needsWrap ? isReadonlySource ? toReadonly(toReactive(source[i])) : toReactive(source[i]) : source[i], i, void 0, cached && cached[i]);
	} else if (typeof source === "number") {
		ret = new Array(source);
		for (let i = 0; i < source; i++) ret[i] = renderItem(i + 1, i, void 0, cached && cached[i]);
	} else if (isObject(source)) if (source[Symbol.iterator]) ret = Array.from(source, (item, i) => renderItem(item, i, void 0, cached && cached[i]));
	else {
		const keys = Object.keys(source);
		ret = new Array(keys.length);
		for (let i = 0, l = keys.length; i < l; i++) {
			const key = keys[i];
			ret[i] = renderItem(source[key], key, i, cached && cached[i]);
		}
	}
	else ret = [];
	if (cache) cache[index] = ret;
	return ret;
}
function renderSlot(slots, name, props = {}, fallback, noSlotted) {
	if (currentRenderingInstance.ce || currentRenderingInstance.parent && isAsyncWrapper(currentRenderingInstance.parent) && currentRenderingInstance.parent.ce) {
		const hasProps = Object.keys(props).length > 0;
		if (name !== "default") props.name = name;
		return openBlock(), createBlock(Fragment, null, [createVNode("slot", props, fallback && fallback())], hasProps ? -2 : 64);
	}
	let slot = slots[name];
	if (slot && slot._c) slot._d = false;
	openBlock();
	const validSlotContent = slot && ensureValidVNode(slot(props));
	const slotKey = props.key || validSlotContent && validSlotContent.key;
	const rendered = createBlock(Fragment, { key: (slotKey && !isSymbol(slotKey) ? slotKey : `_${name}`) + (!validSlotContent && fallback ? "_fb" : "") }, validSlotContent || (fallback ? fallback() : []), validSlotContent && slots._ === 1 ? 64 : -2);
	if (!noSlotted && rendered.scopeId) rendered.slotScopeIds = [rendered.scopeId + "-s"];
	if (slot && slot._c) slot._d = true;
	return rendered;
}
function ensureValidVNode(vnodes) {
	return vnodes.some((child) => {
		if (!isVNode(child)) return true;
		if (child.type === Comment) return false;
		if (child.type === Fragment && !ensureValidVNode(child.children)) return false;
		return true;
	}) ? vnodes : null;
}
var getPublicInstance = (i) => {
	if (!i) return null;
	if (isStatefulComponent(i)) return getComponentPublicInstance(i);
	return getPublicInstance(i.parent);
};
var publicPropertiesMap = /* @__PURE__ */ extend(/* @__PURE__ */ Object.create(null), {
	$: (i) => i,
	$el: (i) => i.vnode.el,
	$data: (i) => i.data,
	$props: (i) => i.props,
	$attrs: (i) => i.attrs,
	$slots: (i) => i.slots,
	$refs: (i) => i.refs,
	$parent: (i) => getPublicInstance(i.parent),
	$root: (i) => getPublicInstance(i.root),
	$host: (i) => i.ce,
	$emit: (i) => i.emit,
	$options: (i) => resolveMergedOptions(i),
	$forceUpdate: (i) => i.f || (i.f = () => {
		queueJob(i.update);
	}),
	$nextTick: (i) => i.n || (i.n = nextTick.bind(i.proxy)),
	$watch: (i) => instanceWatch.bind(i)
});
var hasSetupBinding = (state, key) => state !== EMPTY_OBJ && !state.__isScriptSetup && hasOwn(state, key);
var PublicInstanceProxyHandlers = {
	get({ _: instance }, key) {
		if (key === "__v_skip") return true;
		const { ctx, setupState, data, props, accessCache, type, appContext } = instance;
		if (key[0] !== "$") {
			const n = accessCache[key];
			if (n !== void 0) switch (n) {
				case 1: return setupState[key];
				case 2: return data[key];
				case 4: return ctx[key];
				case 3: return props[key];
			}
			else if (hasSetupBinding(setupState, key)) {
				accessCache[key] = 1;
				return setupState[key];
			} else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
				accessCache[key] = 2;
				return data[key];
			} else if (hasOwn(props, key)) {
				accessCache[key] = 3;
				return props[key];
			} else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) {
				accessCache[key] = 4;
				return ctx[key];
			} else if (shouldCacheAccess) accessCache[key] = 0;
		}
		const publicGetter = publicPropertiesMap[key];
		let cssModule, globalProperties;
		if (publicGetter) {
			if (key === "$attrs") track(instance.attrs, "get", "");
			return publicGetter(instance);
		} else if ((cssModule = type.__cssModules) && (cssModule = cssModule[key])) return cssModule;
		else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) {
			accessCache[key] = 4;
			return ctx[key];
		} else if (globalProperties = appContext.config.globalProperties, hasOwn(globalProperties, key)) return globalProperties[key];
	},
	set({ _: instance }, key, value) {
		const { data, setupState, ctx } = instance;
		if (hasSetupBinding(setupState, key)) {
			setupState[key] = value;
			return true;
		} else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
			data[key] = value;
			return true;
		} else if (hasOwn(instance.props, key)) return false;
		if (key[0] === "$" && key.slice(1) in instance) return false;
		else ctx[key] = value;
		return true;
	},
	has({ _: { data, setupState, accessCache, ctx, appContext, props, type } }, key) {
		let cssModules;
		return !!(accessCache[key] || data !== EMPTY_OBJ && key[0] !== "$" && hasOwn(data, key) || hasSetupBinding(setupState, key) || hasOwn(props, key) || hasOwn(ctx, key) || hasOwn(publicPropertiesMap, key) || hasOwn(appContext.config.globalProperties, key) || (cssModules = type.__cssModules) && cssModules[key]);
	},
	defineProperty(target, key, descriptor) {
		if (descriptor.get != null) target._.accessCache[key] = 0;
		else if (hasOwn(descriptor, "value")) this.set(target, key, descriptor.value, null);
		return Reflect.defineProperty(target, key, descriptor);
	}
};
function normalizePropsOrEmits(props) {
	return isArray(props) ? props.reduce((normalized, p) => (normalized[p] = null, normalized), {}) : props;
}
var shouldCacheAccess = true;
function applyOptions(instance) {
	const options = resolveMergedOptions(instance);
	const publicThis = instance.proxy;
	const ctx = instance.ctx;
	shouldCacheAccess = false;
	if (options.beforeCreate) callHook(options.beforeCreate, instance, "bc");
	const { data: dataOptions, computed: computedOptions, methods, watch: watchOptions, provide: provideOptions, inject: injectOptions, created, beforeMount, mounted, beforeUpdate, updated, activated, deactivated, beforeDestroy, beforeUnmount, destroyed, unmounted, render, renderTracked, renderTriggered, errorCaptured, serverPrefetch, expose, inheritAttrs, components, directives, filters } = options;
	const checkDuplicateProperties = null;
	if (injectOptions) resolveInjections(injectOptions, ctx, checkDuplicateProperties);
	if (methods) for (const key in methods) {
		const methodHandler = methods[key];
		if (isFunction(methodHandler)) ctx[key] = methodHandler.bind(publicThis);
	}
	if (dataOptions) {
		const data = dataOptions.call(publicThis, publicThis);
		if (!isObject(data)) {} else instance.data = /* @__PURE__ */ reactive(data);
	}
	shouldCacheAccess = true;
	if (computedOptions) for (const key in computedOptions) {
		const opt = computedOptions[key];
		const c = computed({
			get: isFunction(opt) ? opt.bind(publicThis, publicThis) : isFunction(opt.get) ? opt.get.bind(publicThis, publicThis) : NOOP,
			set: !isFunction(opt) && isFunction(opt.set) ? opt.set.bind(publicThis) : NOOP
		});
		Object.defineProperty(ctx, key, {
			enumerable: true,
			configurable: true,
			get: () => c.value,
			set: (v) => c.value = v
		});
	}
	if (watchOptions) for (const key in watchOptions) createWatcher(watchOptions[key], ctx, publicThis, key);
	if (provideOptions) {
		const provides = isFunction(provideOptions) ? provideOptions.call(publicThis) : provideOptions;
		Reflect.ownKeys(provides).forEach((key) => {
			provide(key, provides[key]);
		});
	}
	if (created) callHook(created, instance, "c");
	function registerLifecycleHook(register, hook) {
		if (isArray(hook)) hook.forEach((_hook) => register(_hook.bind(publicThis)));
		else if (hook) register(hook.bind(publicThis));
	}
	registerLifecycleHook(onBeforeMount, beforeMount);
	registerLifecycleHook(onMounted, mounted);
	registerLifecycleHook(onBeforeUpdate, beforeUpdate);
	registerLifecycleHook(onUpdated, updated);
	registerLifecycleHook(onActivated, activated);
	registerLifecycleHook(onDeactivated, deactivated);
	registerLifecycleHook(onErrorCaptured, errorCaptured);
	registerLifecycleHook(onRenderTracked, renderTracked);
	registerLifecycleHook(onRenderTriggered, renderTriggered);
	registerLifecycleHook(onBeforeUnmount, beforeUnmount);
	registerLifecycleHook(onUnmounted, unmounted);
	registerLifecycleHook(onServerPrefetch, serverPrefetch);
	if (isArray(expose)) {
		if (expose.length) {
			const exposed = instance.exposed || (instance.exposed = {});
			expose.forEach((key) => {
				Object.defineProperty(exposed, key, {
					get: () => publicThis[key],
					set: (val) => publicThis[key] = val,
					enumerable: true
				});
			});
		} else if (!instance.exposed) instance.exposed = {};
	}
	if (render && instance.render === NOOP) instance.render = render;
	if (inheritAttrs != null) instance.inheritAttrs = inheritAttrs;
	if (components) instance.components = components;
	if (directives) instance.directives = directives;
	if (serverPrefetch) markAsyncBoundary(instance);
}
function resolveInjections(injectOptions, ctx, checkDuplicateProperties = NOOP) {
	if (isArray(injectOptions)) injectOptions = normalizeInject(injectOptions);
	for (const key in injectOptions) {
		const opt = injectOptions[key];
		let injected;
		if (isObject(opt)) if ("default" in opt) injected = inject(opt.from || key, opt.default, true);
		else injected = inject(opt.from || key);
		else injected = inject(opt);
		if (/* @__PURE__ */ isRef(injected)) Object.defineProperty(ctx, key, {
			enumerable: true,
			configurable: true,
			get: () => injected.value,
			set: (v) => injected.value = v
		});
		else ctx[key] = injected;
	}
}
function callHook(hook, instance, type) {
	callWithAsyncErrorHandling(isArray(hook) ? hook.map((h) => h.bind(instance.proxy)) : hook.bind(instance.proxy), instance, type);
}
function createWatcher(raw, ctx, publicThis, key) {
	let getter = key.includes(".") ? createPathGetter(publicThis, key) : () => publicThis[key];
	if (isString(raw)) {
		const handler = ctx[raw];
		if (isFunction(handler)) watch(getter, handler);
	} else if (isFunction(raw)) watch(getter, raw.bind(publicThis));
	else if (isObject(raw)) if (isArray(raw)) raw.forEach((r) => createWatcher(r, ctx, publicThis, key));
	else {
		const handler = isFunction(raw.handler) ? raw.handler.bind(publicThis) : ctx[raw.handler];
		if (isFunction(handler)) watch(getter, handler, raw);
	}
}
function resolveMergedOptions(instance) {
	const base = instance.type;
	const { mixins, extends: extendsOptions } = base;
	const { mixins: globalMixins, optionsCache: cache, config: { optionMergeStrategies } } = instance.appContext;
	const cached = cache.get(base);
	let resolved;
	if (cached) resolved = cached;
	else if (!globalMixins.length && !mixins && !extendsOptions) resolved = base;
	else {
		resolved = {};
		if (globalMixins.length) globalMixins.forEach((m) => mergeOptions(resolved, m, optionMergeStrategies, true));
		mergeOptions(resolved, base, optionMergeStrategies);
	}
	if (isObject(base)) cache.set(base, resolved);
	return resolved;
}
function mergeOptions(to, from, strats, asMixin = false) {
	const { mixins, extends: extendsOptions } = from;
	if (extendsOptions) mergeOptions(to, extendsOptions, strats, true);
	if (mixins) mixins.forEach((m) => mergeOptions(to, m, strats, true));
	for (const key in from) if (asMixin && key === "expose") {} else {
		const strat = internalOptionMergeStrats[key] || strats && strats[key];
		to[key] = strat ? strat(to[key], from[key]) : from[key];
	}
	return to;
}
var internalOptionMergeStrats = {
	data: mergeDataFn,
	props: mergeEmitsOrPropsOptions,
	emits: mergeEmitsOrPropsOptions,
	methods: mergeObjectOptions,
	computed: mergeObjectOptions,
	beforeCreate: mergeAsArray,
	created: mergeAsArray,
	beforeMount: mergeAsArray,
	mounted: mergeAsArray,
	beforeUpdate: mergeAsArray,
	updated: mergeAsArray,
	beforeDestroy: mergeAsArray,
	beforeUnmount: mergeAsArray,
	destroyed: mergeAsArray,
	unmounted: mergeAsArray,
	activated: mergeAsArray,
	deactivated: mergeAsArray,
	errorCaptured: mergeAsArray,
	serverPrefetch: mergeAsArray,
	components: mergeObjectOptions,
	directives: mergeObjectOptions,
	watch: mergeWatchOptions,
	provide: mergeDataFn,
	inject: mergeInject
};
function mergeDataFn(to, from) {
	if (!from) return to;
	if (!to) return from;
	return function mergedDataFn() {
		return extend(isFunction(to) ? to.call(this, this) : to, isFunction(from) ? from.call(this, this) : from);
	};
}
function mergeInject(to, from) {
	return mergeObjectOptions(normalizeInject(to), normalizeInject(from));
}
function normalizeInject(raw) {
	if (isArray(raw)) {
		const res = {};
		for (let i = 0; i < raw.length; i++) res[raw[i]] = raw[i];
		return res;
	}
	return raw;
}
function mergeAsArray(to, from) {
	return to ? [...new Set([].concat(to, from))] : from;
}
function mergeObjectOptions(to, from) {
	return to ? extend(/* @__PURE__ */ Object.create(null), to, from) : from;
}
function mergeEmitsOrPropsOptions(to, from) {
	if (to) {
		if (isArray(to) && isArray(from)) return [.../* @__PURE__ */ new Set([...to, ...from])];
		return extend(/* @__PURE__ */ Object.create(null), normalizePropsOrEmits(to), normalizePropsOrEmits(from != null ? from : {}));
	} else return from;
}
function mergeWatchOptions(to, from) {
	if (!to) return from;
	if (!from) return to;
	const merged = extend(/* @__PURE__ */ Object.create(null), to);
	for (const key in from) merged[key] = mergeAsArray(to[key], from[key]);
	return merged;
}
function createAppContext() {
	return {
		app: null,
		config: {
			isNativeTag: NO,
			performance: false,
			globalProperties: {},
			optionMergeStrategies: {},
			errorHandler: void 0,
			warnHandler: void 0,
			compilerOptions: {}
		},
		mixins: [],
		components: {},
		directives: {},
		provides: /* @__PURE__ */ Object.create(null),
		optionsCache: /* @__PURE__ */ new WeakMap(),
		propsCache: /* @__PURE__ */ new WeakMap(),
		emitsCache: /* @__PURE__ */ new WeakMap()
	};
}
var uid$1 = 0;
function createAppAPI(render, hydrate) {
	return function createApp(rootComponent, rootProps = null) {
		if (!isFunction(rootComponent)) rootComponent = extend({}, rootComponent);
		if (rootProps != null && !isObject(rootProps)) rootProps = null;
		const context = createAppContext();
		const installedPlugins = /* @__PURE__ */ new WeakSet();
		const pluginCleanupFns = [];
		let isMounted = false;
		const app = context.app = {
			_uid: uid$1++,
			_component: rootComponent,
			_props: rootProps,
			_container: null,
			_context: context,
			_instance: null,
			version,
			get config() {
				return context.config;
			},
			set config(v) {},
			use(plugin, ...options) {
				if (installedPlugins.has(plugin)) {} else if (plugin && isFunction(plugin.install)) {
					installedPlugins.add(plugin);
					plugin.install(app, ...options);
				} else if (isFunction(plugin)) {
					installedPlugins.add(plugin);
					plugin(app, ...options);
				}
				return app;
			},
			mixin(mixin) {
				if (!context.mixins.includes(mixin)) context.mixins.push(mixin);
				return app;
			},
			component(name, component) {
				if (!component) return context.components[name];
				context.components[name] = component;
				return app;
			},
			directive(name, directive) {
				if (!directive) return context.directives[name];
				context.directives[name] = directive;
				return app;
			},
			mount(rootContainer, isHydrate, namespace) {
				if (!isMounted) {
					const vnode = app._ceVNode || createVNode(rootComponent, rootProps);
					vnode.appContext = context;
					if (namespace === true) namespace = "svg";
					else if (namespace === false) namespace = void 0;
					if (isHydrate && hydrate) hydrate(vnode, rootContainer);
					else render(vnode, rootContainer, namespace);
					isMounted = true;
					app._container = rootContainer;
					rootContainer.__vue_app__ = app;
					return getComponentPublicInstance(vnode.component);
				}
			},
			onUnmount(cleanupFn) {
				pluginCleanupFns.push(cleanupFn);
			},
			unmount() {
				if (isMounted) {
					callWithAsyncErrorHandling(pluginCleanupFns, app._instance, 16);
					render(null, app._container);
					delete app._container.__vue_app__;
				}
			},
			provide(key, value) {
				context.provides[key] = value;
				return app;
			},
			runWithContext(fn) {
				const lastApp = currentApp;
				currentApp = app;
				try {
					return fn();
				} finally {
					currentApp = lastApp;
				}
			}
		};
		return app;
	};
}
var currentApp = null;
var getModelModifiers = (props, modelName) => {
	return modelName === "modelValue" || modelName === "model-value" ? props.modelModifiers : props[`${modelName}Modifiers`] || props[`${camelize(modelName)}Modifiers`] || props[`${hyphenate(modelName)}Modifiers`];
};
function emit(instance, event, ...rawArgs) {
	if (instance.isUnmounted) return;
	const props = instance.vnode.props || EMPTY_OBJ;
	let args = rawArgs;
	const isModelListener = event.startsWith("update:");
	const modifiers = isModelListener && getModelModifiers(props, event.slice(7));
	if (modifiers) {
		if (modifiers.trim) args = rawArgs.map((a) => isString(a) ? a.trim() : a);
		if (modifiers.number) args = rawArgs.map(looseToNumber);
	}
	let handlerName;
	let handler = props[handlerName = toHandlerKey(event)] || props[handlerName = toHandlerKey(camelize(event))];
	if (!handler && isModelListener) handler = props[handlerName = toHandlerKey(hyphenate(event))];
	if (handler) callWithAsyncErrorHandling(handler, instance, 6, args);
	const onceHandler = props[handlerName + `Once`];
	if (onceHandler) {
		if (!instance.emitted) instance.emitted = {};
		else if (instance.emitted[handlerName]) return;
		instance.emitted[handlerName] = true;
		callWithAsyncErrorHandling(onceHandler, instance, 6, args);
	}
}
var mixinEmitsCache = /* @__PURE__ */ new WeakMap();
function normalizeEmitsOptions(comp, appContext, asMixin = false) {
	const cache = asMixin ? mixinEmitsCache : appContext.emitsCache;
	const cached = cache.get(comp);
	if (cached !== void 0) return cached;
	const raw = comp.emits;
	let normalized = {};
	let hasExtends = false;
	if (!isFunction(comp)) {
		const extendEmits = (raw2) => {
			const normalizedFromExtend = normalizeEmitsOptions(raw2, appContext, true);
			if (normalizedFromExtend) {
				hasExtends = true;
				extend(normalized, normalizedFromExtend);
			}
		};
		if (!asMixin && appContext.mixins.length) appContext.mixins.forEach(extendEmits);
		if (comp.extends) extendEmits(comp.extends);
		if (comp.mixins) comp.mixins.forEach(extendEmits);
	}
	if (!raw && !hasExtends) {
		if (isObject(comp)) cache.set(comp, null);
		return null;
	}
	if (isArray(raw)) raw.forEach((key) => normalized[key] = null);
	else extend(normalized, raw);
	if (isObject(comp)) cache.set(comp, normalized);
	return normalized;
}
function isEmitListener(options, key) {
	if (!options || !isOn(key)) return false;
	key = key.slice(2).replace(/Once$/, "");
	return hasOwn(options, key[0].toLowerCase() + key.slice(1)) || hasOwn(options, hyphenate(key)) || hasOwn(options, key);
}
function renderComponentRoot(instance) {
	const { type: Component, vnode, proxy, withProxy, propsOptions: [propsOptions], slots, attrs, emit, render, renderCache, props, data, setupState, ctx, inheritAttrs } = instance;
	const prev = setCurrentRenderingInstance(instance);
	let result;
	let fallthroughAttrs;
	try {
		if (vnode.shapeFlag & 4) {
			const proxyToUse = withProxy || proxy;
			const thisProxy = proxyToUse;
			result = normalizeVNode(render.call(thisProxy, proxyToUse, renderCache, props, setupState, data, ctx));
			fallthroughAttrs = attrs;
		} else {
			const render2 = Component;
			result = normalizeVNode(render2.length > 1 ? render2(props, {
				attrs,
				slots,
				emit
			}) : render2(props, null));
			fallthroughAttrs = Component.props ? attrs : getFunctionalFallthrough(attrs);
		}
	} catch (err) {
		blockStack.length = 0;
		handleError(err, instance, 1);
		result = createVNode(Comment);
	}
	let root = result;
	if (fallthroughAttrs && inheritAttrs !== false) {
		const keys = Object.keys(fallthroughAttrs);
		const { shapeFlag } = root;
		if (keys.length) {
			if (shapeFlag & 7) {
				if (propsOptions && keys.some(isModelListener)) fallthroughAttrs = filterModelListeners(fallthroughAttrs, propsOptions);
				root = cloneVNode(root, fallthroughAttrs, false, true);
			}
		}
	}
	if (vnode.dirs) {
		root = cloneVNode(root, null, false, true);
		root.dirs = root.dirs ? root.dirs.concat(vnode.dirs) : vnode.dirs;
	}
	if (vnode.transition) setTransitionHooks(root, vnode.transition);
	result = root;
	setCurrentRenderingInstance(prev);
	return result;
}
var getFunctionalFallthrough = (attrs) => {
	let res;
	for (const key in attrs) if (key === "class" || key === "style" || isOn(key)) (res || (res = {}))[key] = attrs[key];
	return res;
};
var filterModelListeners = (attrs, props) => {
	const res = {};
	for (const key in attrs) if (!isModelListener(key) || !(key.slice(9) in props)) res[key] = attrs[key];
	return res;
};
function shouldUpdateComponent(prevVNode, nextVNode, optimized) {
	const { props: prevProps, children: prevChildren, component } = prevVNode;
	const { props: nextProps, children: nextChildren, patchFlag } = nextVNode;
	const emits = component.emitsOptions;
	if (nextVNode.dirs || nextVNode.transition) return true;
	if (optimized && patchFlag >= 0) {
		if (patchFlag & 1024) return true;
		if (patchFlag & 16) {
			if (!prevProps) return !!nextProps;
			return hasPropsChanged(prevProps, nextProps, emits);
		} else if (patchFlag & 8) {
			const dynamicProps = nextVNode.dynamicProps;
			for (let i = 0; i < dynamicProps.length; i++) {
				const key = dynamicProps[i];
				if (hasPropValueChanged(nextProps, prevProps, key) && !isEmitListener(emits, key)) return true;
			}
		}
	} else {
		if (prevChildren || nextChildren) {
			if (!nextChildren || !nextChildren.$stable) return true;
		}
		if (prevProps === nextProps) return false;
		if (!prevProps) return !!nextProps;
		if (!nextProps) return true;
		return hasPropsChanged(prevProps, nextProps, emits);
	}
	return false;
}
function hasPropsChanged(prevProps, nextProps, emitsOptions) {
	const nextKeys = Object.keys(nextProps);
	if (nextKeys.length !== Object.keys(prevProps).length) return true;
	for (let i = 0; i < nextKeys.length; i++) {
		const key = nextKeys[i];
		if (hasPropValueChanged(nextProps, prevProps, key) && !isEmitListener(emitsOptions, key)) return true;
	}
	return false;
}
function hasPropValueChanged(nextProps, prevProps, key) {
	const nextProp = nextProps[key];
	const prevProp = prevProps[key];
	if (key === "style" && isObject(nextProp) && isObject(prevProp)) return !looseEqual(nextProp, prevProp);
	return nextProp !== prevProp;
}
function updateHOCHostEl({ vnode, parent, suspense }, el) {
	while (parent) {
		const root = parent.subTree;
		if (root.suspense && root.suspense.activeBranch === vnode) {
			root.suspense.vnode.el = root.el = el;
			vnode = root;
		}
		if (root === vnode) {
			(vnode = parent.vnode).el = el;
			parent = parent.parent;
		} else break;
	}
	if (suspense && suspense.activeBranch === vnode) suspense.vnode.el = el;
}
var internalObjectProto = {};
var createInternalObject = () => Object.create(internalObjectProto);
var isInternalObject = (obj) => Object.getPrototypeOf(obj) === internalObjectProto;
function initProps(instance, rawProps, isStateful, isSSR = false) {
	const props = {};
	const attrs = createInternalObject();
	instance.propsDefaults = /* @__PURE__ */ Object.create(null);
	setFullProps(instance, rawProps, props, attrs);
	for (const key in instance.propsOptions[0]) if (!(key in props)) props[key] = void 0;
	if (isStateful) instance.props = isSSR ? props : /* @__PURE__ */ shallowReactive(props);
	else if (!instance.type.props) instance.props = attrs;
	else instance.props = props;
	instance.attrs = attrs;
}
function updateProps(instance, rawProps, rawPrevProps, optimized) {
	const { props, attrs, vnode: { patchFlag } } = instance;
	const rawCurrentProps = /* @__PURE__ */ toRaw(props);
	const [options] = instance.propsOptions;
	let hasAttrsChanged = false;
	if ((optimized || patchFlag > 0) && !(patchFlag & 16)) {
		if (patchFlag & 8) {
			const propsToUpdate = instance.vnode.dynamicProps;
			for (let i = 0; i < propsToUpdate.length; i++) {
				let key = propsToUpdate[i];
				if (isEmitListener(instance.emitsOptions, key)) continue;
				const value = rawProps[key];
				if (options) if (hasOwn(attrs, key)) {
					if (value !== attrs[key]) {
						attrs[key] = value;
						hasAttrsChanged = true;
					}
				} else {
					const camelizedKey = camelize(key);
					props[camelizedKey] = resolvePropValue(options, rawCurrentProps, camelizedKey, value, instance, false);
				}
				else if (value !== attrs[key]) {
					attrs[key] = value;
					hasAttrsChanged = true;
				}
			}
		}
	} else {
		if (setFullProps(instance, rawProps, props, attrs)) hasAttrsChanged = true;
		let kebabKey;
		for (const key in rawCurrentProps) if (!rawProps || !hasOwn(rawProps, key) && ((kebabKey = hyphenate(key)) === key || !hasOwn(rawProps, kebabKey))) if (options) {
			if (rawPrevProps && (rawPrevProps[key] !== void 0 || rawPrevProps[kebabKey] !== void 0)) props[key] = resolvePropValue(options, rawCurrentProps, key, void 0, instance, true);
		} else delete props[key];
		if (attrs !== rawCurrentProps) {
			for (const key in attrs) if (!rawProps || !hasOwn(rawProps, key) && true) {
				delete attrs[key];
				hasAttrsChanged = true;
			}
		}
	}
	if (hasAttrsChanged) trigger(instance.attrs, "set", "");
}
function setFullProps(instance, rawProps, props, attrs) {
	const [options, needCastKeys] = instance.propsOptions;
	let hasAttrsChanged = false;
	let rawCastValues;
	if (rawProps) for (let key in rawProps) {
		if (isReservedProp(key)) continue;
		const value = rawProps[key];
		let camelKey;
		if (options && hasOwn(options, camelKey = camelize(key))) if (!needCastKeys || !needCastKeys.includes(camelKey)) props[camelKey] = value;
		else (rawCastValues || (rawCastValues = {}))[camelKey] = value;
		else if (!isEmitListener(instance.emitsOptions, key)) {
			if (!(key in attrs) || value !== attrs[key]) {
				attrs[key] = value;
				hasAttrsChanged = true;
			}
		}
	}
	if (needCastKeys) {
		const rawCurrentProps = /* @__PURE__ */ toRaw(props);
		const castValues = rawCastValues || EMPTY_OBJ;
		for (let i = 0; i < needCastKeys.length; i++) {
			const key = needCastKeys[i];
			props[key] = resolvePropValue(options, rawCurrentProps, key, castValues[key], instance, !hasOwn(castValues, key));
		}
	}
	return hasAttrsChanged;
}
function resolvePropValue(options, props, key, value, instance, isAbsent) {
	const opt = options[key];
	if (opt != null) {
		const hasDefault = hasOwn(opt, "default");
		if (hasDefault && value === void 0) {
			const defaultValue = opt.default;
			if (opt.type !== Function && !opt.skipFactory && isFunction(defaultValue)) {
				const { propsDefaults } = instance;
				if (key in propsDefaults) value = propsDefaults[key];
				else {
					const reset = setCurrentInstance(instance);
					value = propsDefaults[key] = defaultValue.call(null, props);
					reset();
				}
			} else value = defaultValue;
			if (instance.ce) instance.ce._setProp(key, value);
		}
		if (opt[0]) {
			if (isAbsent && !hasDefault) value = false;
			else if (opt[1] && (value === "" || value === hyphenate(key))) value = true;
		}
	}
	return value;
}
var mixinPropsCache = /* @__PURE__ */ new WeakMap();
function normalizePropsOptions(comp, appContext, asMixin = false) {
	const cache = asMixin ? mixinPropsCache : appContext.propsCache;
	const cached = cache.get(comp);
	if (cached) return cached;
	const raw = comp.props;
	const normalized = {};
	const needCastKeys = [];
	let hasExtends = false;
	if (!isFunction(comp)) {
		const extendProps = (raw2) => {
			hasExtends = true;
			const [props, keys] = normalizePropsOptions(raw2, appContext, true);
			extend(normalized, props);
			if (keys) needCastKeys.push(...keys);
		};
		if (!asMixin && appContext.mixins.length) appContext.mixins.forEach(extendProps);
		if (comp.extends) extendProps(comp.extends);
		if (comp.mixins) comp.mixins.forEach(extendProps);
	}
	if (!raw && !hasExtends) {
		if (isObject(comp)) cache.set(comp, EMPTY_ARR);
		return EMPTY_ARR;
	}
	if (isArray(raw)) for (let i = 0; i < raw.length; i++) {
		const normalizedKey = camelize(raw[i]);
		if (validatePropName(normalizedKey)) normalized[normalizedKey] = EMPTY_OBJ;
	}
	else if (raw) for (const key in raw) {
		const normalizedKey = camelize(key);
		if (validatePropName(normalizedKey)) {
			const opt = raw[key];
			const prop = normalized[normalizedKey] = isArray(opt) || isFunction(opt) ? { type: opt } : extend({}, opt);
			const propType = prop.type;
			let shouldCast = false;
			let shouldCastTrue = true;
			if (isArray(propType)) for (let index = 0; index < propType.length; ++index) {
				const type = propType[index];
				const typeName = isFunction(type) && type.name;
				if (typeName === "Boolean") {
					shouldCast = true;
					break;
				} else if (typeName === "String") shouldCastTrue = false;
			}
			else shouldCast = isFunction(propType) && propType.name === "Boolean";
			prop[0] = shouldCast;
			prop[1] = shouldCastTrue;
			if (shouldCast || hasOwn(prop, "default")) needCastKeys.push(normalizedKey);
		}
	}
	const res = [normalized, needCastKeys];
	if (isObject(comp)) cache.set(comp, res);
	return res;
}
function validatePropName(key) {
	if (key[0] !== "$" && !isReservedProp(key)) return true;
	return false;
}
var isInternalKey = (key) => key === "_" || key === "_ctx" || key === "$stable";
var normalizeSlotValue = (value) => isArray(value) ? value.map(normalizeVNode) : [normalizeVNode(value)];
var normalizeSlot = (key, rawSlot, ctx) => {
	if (rawSlot._n) return rawSlot;
	const normalized = withCtx((...args) => {
		return normalizeSlotValue(rawSlot(...args));
	}, ctx);
	normalized._c = false;
	return normalized;
};
var normalizeObjectSlots = (rawSlots, slots, instance) => {
	const ctx = rawSlots._ctx;
	for (const key in rawSlots) {
		if (isInternalKey(key)) continue;
		const value = rawSlots[key];
		if (isFunction(value)) slots[key] = normalizeSlot(key, value, ctx);
		else if (value != null) {
			const normalized = normalizeSlotValue(value);
			slots[key] = () => normalized;
		}
	}
};
var normalizeVNodeSlots = (instance, children) => {
	const normalized = normalizeSlotValue(children);
	instance.slots.default = () => normalized;
};
var assignSlots = (slots, children, optimized) => {
	for (const key in children) if (optimized || !isInternalKey(key)) slots[key] = children[key];
};
var initSlots = (instance, children, optimized) => {
	const slots = instance.slots = createInternalObject();
	if (instance.vnode.shapeFlag & 32) {
		const type = children._;
		if (type) {
			assignSlots(slots, children, optimized);
			if (optimized) def(slots, "_", type, true);
		} else normalizeObjectSlots(children, slots);
	} else if (children) normalizeVNodeSlots(instance, children);
};
var updateSlots = (instance, children, optimized) => {
	const { vnode, slots } = instance;
	let needDeletionCheck = true;
	let deletionComparisonTarget = EMPTY_OBJ;
	if (vnode.shapeFlag & 32) {
		const type = children._;
		if (type) if (optimized && type === 1) needDeletionCheck = false;
		else assignSlots(slots, children, optimized);
		else {
			needDeletionCheck = !children.$stable;
			normalizeObjectSlots(children, slots);
		}
		deletionComparisonTarget = children;
	} else if (children) {
		normalizeVNodeSlots(instance, children);
		deletionComparisonTarget = { default: 1 };
	}
	if (needDeletionCheck) {
		for (const key in slots) if (!isInternalKey(key) && deletionComparisonTarget[key] == null) delete slots[key];
	}
};
function initFeatureFlags() {}
var queuePostRenderEffect = queueEffectWithSuspense;
function createRenderer(options) {
	return baseCreateRenderer(options);
}
function baseCreateRenderer(options, createHydrationFns) {
	initFeatureFlags();
	const target = getGlobalThis();
	target.__VUE__ = true;
	const { insert: hostInsert, remove: hostRemove, patchProp: hostPatchProp, createElement: hostCreateElement, createText: hostCreateText, createComment: hostCreateComment, setText: hostSetText, setElementText: hostSetElementText, parentNode: hostParentNode, nextSibling: hostNextSibling, setScopeId: hostSetScopeId = NOOP, insertStaticContent: hostInsertStaticContent } = options;
	const patch = (n1, n2, container, anchor = null, parentComponent = null, parentSuspense = null, namespace = void 0, slotScopeIds = null, optimized = !!n2.dynamicChildren) => {
		if (n1 === n2) return;
		if (n1 && !isSameVNodeType(n1, n2)) {
			anchor = getNextHostNode(n1);
			unmount(n1, parentComponent, parentSuspense, true);
			n1 = null;
		}
		if (n2.patchFlag === -2) {
			optimized = false;
			n2.dynamicChildren = null;
		}
		const { type, ref, shapeFlag } = n2;
		switch (type) {
			case Text:
				processText(n1, n2, container, anchor);
				break;
			case Comment:
				processCommentNode(n1, n2, container, anchor);
				break;
			case Static:
				if (n1 == null) mountStaticNode(n2, container, anchor, namespace);
				break;
			case Fragment:
				processFragment(n1, n2, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized);
				break;
			default: if (shapeFlag & 1) processElement(n1, n2, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized);
			else if (shapeFlag & 6) processComponent(n1, n2, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized);
			else if (shapeFlag & 64) type.process(n1, n2, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized, internals);
			else if (shapeFlag & 128) type.process(n1, n2, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized, internals);
		}
		if (ref != null && parentComponent) setRef(ref, n1 && n1.ref, parentSuspense, n2 || n1, !n2);
		else if (ref == null && n1 && n1.ref != null) setRef(n1.ref, null, parentSuspense, n1, true);
	};
	const processText = (n1, n2, container, anchor) => {
		if (n1 == null) hostInsert(n2.el = hostCreateText(n2.children), container, anchor);
		else {
			const el = n2.el = n1.el;
			if (n2.children !== n1.children) hostSetText(el, n2.children);
		}
	};
	const processCommentNode = (n1, n2, container, anchor) => {
		if (n1 == null) hostInsert(n2.el = hostCreateComment(n2.children || ""), container, anchor);
		else n2.el = n1.el;
	};
	const mountStaticNode = (n2, container, anchor, namespace) => {
		[n2.el, n2.anchor] = hostInsertStaticContent(n2.children, container, anchor, namespace, n2.el, n2.anchor);
	};
	const moveStaticNode = ({ el, anchor }, container, nextSibling) => {
		let next;
		while (el && el !== anchor) {
			next = hostNextSibling(el);
			hostInsert(el, container, nextSibling);
			el = next;
		}
		hostInsert(anchor, container, nextSibling);
	};
	const removeStaticNode = ({ el, anchor }) => {
		let next;
		while (el && el !== anchor) {
			next = hostNextSibling(el);
			hostRemove(el);
			el = next;
		}
		hostRemove(anchor);
	};
	const processElement = (n1, n2, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized) => {
		if (n2.type === "svg") namespace = "svg";
		else if (n2.type === "math") namespace = "mathml";
		if (n1 == null) mountElement(n2, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized);
		else {
			const customElement = n1.el && n1.el._isVueCE ? n1.el : null;
			try {
				if (customElement) customElement._beginPatch();
				patchElement(n1, n2, parentComponent, parentSuspense, namespace, slotScopeIds, optimized);
			} finally {
				if (customElement) customElement._endPatch();
			}
		}
	};
	const mountElement = (vnode, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized) => {
		let el;
		let vnodeHook;
		const { props, shapeFlag, transition, dirs } = vnode;
		el = vnode.el = hostCreateElement(vnode.type, namespace, props && props.is, props);
		if (shapeFlag & 8) hostSetElementText(el, vnode.children);
		else if (shapeFlag & 16) mountChildren(vnode.children, el, null, parentComponent, parentSuspense, resolveChildrenNamespace(vnode, namespace), slotScopeIds, optimized);
		if (dirs) invokeDirectiveHook(vnode, null, parentComponent, "created");
		setScopeId(el, vnode, vnode.scopeId, slotScopeIds, parentComponent);
		if (props) {
			for (const key in props) if (key !== "value" && !isReservedProp(key)) hostPatchProp(el, key, null, props[key], namespace, parentComponent);
			if ("value" in props) hostPatchProp(el, "value", null, props.value, namespace);
			if (vnodeHook = props.onVnodeBeforeMount) invokeVNodeHook(vnodeHook, parentComponent, vnode);
		}
		if (dirs) invokeDirectiveHook(vnode, null, parentComponent, "beforeMount");
		const needCallTransitionHooks = needTransition(parentSuspense, transition);
		if (needCallTransitionHooks) transition.beforeEnter(el);
		hostInsert(el, container, anchor);
		if ((vnodeHook = props && props.onVnodeMounted) || needCallTransitionHooks || dirs) queuePostRenderEffect(() => {
			try {
				vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, vnode);
				needCallTransitionHooks && transition.enter(el);
				dirs && invokeDirectiveHook(vnode, null, parentComponent, "mounted");
			} finally {}
		}, parentSuspense);
	};
	const setScopeId = (el, vnode, scopeId, slotScopeIds, parentComponent) => {
		if (scopeId) hostSetScopeId(el, scopeId);
		if (slotScopeIds) for (let i = 0; i < slotScopeIds.length; i++) hostSetScopeId(el, slotScopeIds[i]);
		if (parentComponent) {
			let subTree = parentComponent.subTree;
			if (vnode === subTree || isSuspense(subTree.type) && (subTree.ssContent === vnode || subTree.ssFallback === vnode)) {
				const parentVNode = parentComponent.vnode;
				setScopeId(el, parentVNode, parentVNode.scopeId, parentVNode.slotScopeIds, parentComponent.parent);
			}
		}
	};
	const mountChildren = (children, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized, start = 0) => {
		for (let i = start; i < children.length; i++) patch(null, children[i] = optimized ? cloneIfMounted(children[i]) : normalizeVNode(children[i]), container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized);
	};
	const patchElement = (n1, n2, parentComponent, parentSuspense, namespace, slotScopeIds, optimized) => {
		const el = n2.el = n1.el;
		let { patchFlag, dynamicChildren, dirs } = n2;
		patchFlag |= n1.patchFlag & 16;
		const oldProps = n1.props || EMPTY_OBJ;
		const newProps = n2.props || EMPTY_OBJ;
		let vnodeHook;
		parentComponent && toggleRecurse(parentComponent, false);
		if (vnodeHook = newProps.onVnodeBeforeUpdate) invokeVNodeHook(vnodeHook, parentComponent, n2, n1);
		if (dirs) invokeDirectiveHook(n2, n1, parentComponent, "beforeUpdate");
		parentComponent && toggleRecurse(parentComponent, true);
		if (oldProps.innerHTML && newProps.innerHTML == null || oldProps.textContent && newProps.textContent == null) hostSetElementText(el, "");
		if (dynamicChildren) patchBlockChildren(n1.dynamicChildren, dynamicChildren, el, parentComponent, parentSuspense, resolveChildrenNamespace(n2, namespace), slotScopeIds);
		else if (!optimized) patchChildren(n1, n2, el, null, parentComponent, parentSuspense, resolveChildrenNamespace(n2, namespace), slotScopeIds, false);
		if (patchFlag > 0) {
			if (patchFlag & 16) patchProps(el, oldProps, newProps, parentComponent, namespace);
			else {
				if (patchFlag & 2) {
					if (oldProps.class !== newProps.class) hostPatchProp(el, "class", null, newProps.class, namespace);
				}
				if (patchFlag & 4) hostPatchProp(el, "style", oldProps.style, newProps.style, namespace);
				if (patchFlag & 8) {
					const propsToUpdate = n2.dynamicProps;
					for (let i = 0; i < propsToUpdate.length; i++) {
						const key = propsToUpdate[i];
						const prev = oldProps[key];
						const next = newProps[key];
						if (next !== prev || key === "value") hostPatchProp(el, key, prev, next, namespace, parentComponent);
					}
				}
			}
			if (patchFlag & 1) {
				if (n1.children !== n2.children) hostSetElementText(el, n2.children);
			}
		} else if (!optimized && dynamicChildren == null) patchProps(el, oldProps, newProps, parentComponent, namespace);
		if ((vnodeHook = newProps.onVnodeUpdated) || dirs) queuePostRenderEffect(() => {
			vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, n2, n1);
			dirs && invokeDirectiveHook(n2, n1, parentComponent, "updated");
		}, parentSuspense);
	};
	const patchBlockChildren = (oldChildren, newChildren, fallbackContainer, parentComponent, parentSuspense, namespace, slotScopeIds) => {
		for (let i = 0; i < newChildren.length; i++) {
			const oldVNode = oldChildren[i];
			const newVNode = newChildren[i];
			patch(oldVNode, newVNode, oldVNode.el && (oldVNode.type === Fragment || !isSameVNodeType(oldVNode, newVNode) || oldVNode.shapeFlag & 198) ? hostParentNode(oldVNode.el) : fallbackContainer, null, parentComponent, parentSuspense, namespace, slotScopeIds, true);
		}
	};
	const patchProps = (el, oldProps, newProps, parentComponent, namespace) => {
		if (oldProps !== newProps) {
			if (oldProps !== EMPTY_OBJ) {
				for (const key in oldProps) if (!isReservedProp(key) && !(key in newProps)) hostPatchProp(el, key, oldProps[key], null, namespace, parentComponent);
			}
			for (const key in newProps) {
				if (isReservedProp(key)) continue;
				const next = newProps[key];
				const prev = oldProps[key];
				if (next !== prev && key !== "value") hostPatchProp(el, key, prev, next, namespace, parentComponent);
			}
			if ("value" in newProps) hostPatchProp(el, "value", oldProps.value, newProps.value, namespace);
		}
	};
	const processFragment = (n1, n2, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized) => {
		const fragmentStartAnchor = n2.el = n1 ? n1.el : hostCreateText("");
		const fragmentEndAnchor = n2.anchor = n1 ? n1.anchor : hostCreateText("");
		let { patchFlag, dynamicChildren, slotScopeIds: fragmentSlotScopeIds } = n2;
		if (fragmentSlotScopeIds) slotScopeIds = slotScopeIds ? slotScopeIds.concat(fragmentSlotScopeIds) : fragmentSlotScopeIds;
		if (n1 == null) {
			hostInsert(fragmentStartAnchor, container, anchor);
			hostInsert(fragmentEndAnchor, container, anchor);
			mountChildren(n2.children || [], container, fragmentEndAnchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized);
		} else if (patchFlag > 0 && patchFlag & 64 && dynamicChildren && n1.dynamicChildren && n1.dynamicChildren.length === dynamicChildren.length) {
			patchBlockChildren(n1.dynamicChildren, dynamicChildren, container, parentComponent, parentSuspense, namespace, slotScopeIds);
			if (n2.key != null || parentComponent && n2 === parentComponent.subTree) traverseStaticChildren(n1, n2, true);
		} else patchChildren(n1, n2, container, fragmentEndAnchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized);
	};
	const processComponent = (n1, n2, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized) => {
		n2.slotScopeIds = slotScopeIds;
		if (n1 == null) if (n2.shapeFlag & 512) parentComponent.ctx.activate(n2, container, anchor, namespace, optimized);
		else mountComponent(n2, container, anchor, parentComponent, parentSuspense, namespace, optimized);
		else updateComponent(n1, n2, optimized);
	};
	const mountComponent = (initialVNode, container, anchor, parentComponent, parentSuspense, namespace, optimized) => {
		const instance = initialVNode.component = createComponentInstance(initialVNode, parentComponent, parentSuspense);
		if (isKeepAlive(initialVNode)) instance.ctx.renderer = internals;
		setupComponent(instance, false, optimized);
		if (instance.asyncDep) {
			parentSuspense && parentSuspense.registerDep(instance, setupRenderEffect, optimized);
			if (!initialVNode.el) {
				const placeholder = instance.subTree = createVNode(Comment);
				processCommentNode(null, placeholder, container, anchor);
				initialVNode.placeholder = placeholder.el;
			}
		} else setupRenderEffect(instance, initialVNode, container, anchor, parentSuspense, namespace, optimized);
	};
	const updateComponent = (n1, n2, optimized) => {
		const instance = n2.component = n1.component;
		if (shouldUpdateComponent(n1, n2, optimized)) if (instance.asyncDep && !instance.asyncResolved) {
			updateComponentPreRender(instance, n2, optimized);
			return;
		} else {
			instance.next = n2;
			instance.update();
		}
		else {
			n2.el = n1.el;
			instance.vnode = n2;
		}
	};
	const setupRenderEffect = (instance, initialVNode, container, anchor, parentSuspense, namespace, optimized) => {
		const componentUpdateFn = () => {
			if (!instance.isMounted) {
				let vnodeHook;
				const { el, props } = initialVNode;
				const { bm, m, parent, root, type } = instance;
				const isAsyncWrapperVNode = isAsyncWrapper(initialVNode);
				toggleRecurse(instance, false);
				if (bm) invokeArrayFns(bm);
				if (!isAsyncWrapperVNode && (vnodeHook = props && props.onVnodeBeforeMount)) invokeVNodeHook(vnodeHook, parent, initialVNode);
				toggleRecurse(instance, true);
				if (el && hydrateNode) {
					const hydrateSubTree = () => {
						instance.subTree = renderComponentRoot(instance);
						hydrateNode(el, instance.subTree, instance, parentSuspense, null);
					};
					if (isAsyncWrapperVNode && type.__asyncHydrate) type.__asyncHydrate(el, instance, hydrateSubTree);
					else hydrateSubTree();
				} else {
					if (root.ce && root.ce._hasShadowRoot()) root.ce._injectChildStyle(type, instance.parent ? instance.parent.type : void 0);
					const subTree = instance.subTree = renderComponentRoot(instance);
					patch(null, subTree, container, anchor, instance, parentSuspense, namespace);
					initialVNode.el = subTree.el;
				}
				if (m) queuePostRenderEffect(m, parentSuspense);
				if (!isAsyncWrapperVNode && (vnodeHook = props && props.onVnodeMounted)) {
					const scopedInitialVNode = initialVNode;
					queuePostRenderEffect(() => invokeVNodeHook(vnodeHook, parent, scopedInitialVNode), parentSuspense);
				}
				if (initialVNode.shapeFlag & 256 || parent && isAsyncWrapper(parent.vnode) && parent.vnode.shapeFlag & 256) instance.a && queuePostRenderEffect(instance.a, parentSuspense);
				instance.isMounted = true;
				initialVNode = container = anchor = null;
			} else {
				let { next, bu, u, parent, vnode } = instance;
				{
					const nonHydratedAsyncRoot = locateNonHydratedAsyncRoot(instance);
					if (nonHydratedAsyncRoot) {
						if (next) {
							next.el = vnode.el;
							updateComponentPreRender(instance, next, optimized);
						}
						nonHydratedAsyncRoot.asyncDep.then(() => {
							queuePostRenderEffect(() => {
								if (!instance.isUnmounted) update();
							}, parentSuspense);
						});
						return;
					}
				}
				let originNext = next;
				let vnodeHook;
				toggleRecurse(instance, false);
				if (next) {
					next.el = vnode.el;
					updateComponentPreRender(instance, next, optimized);
				} else next = vnode;
				if (bu) invokeArrayFns(bu);
				if (vnodeHook = next.props && next.props.onVnodeBeforeUpdate) invokeVNodeHook(vnodeHook, parent, next, vnode);
				toggleRecurse(instance, true);
				const nextTree = renderComponentRoot(instance);
				const prevTree = instance.subTree;
				instance.subTree = nextTree;
				patch(prevTree, nextTree, hostParentNode(prevTree.el), getNextHostNode(prevTree), instance, parentSuspense, namespace);
				next.el = nextTree.el;
				if (originNext === null) updateHOCHostEl(instance, nextTree.el);
				if (u) queuePostRenderEffect(u, parentSuspense);
				if (vnodeHook = next.props && next.props.onVnodeUpdated) queuePostRenderEffect(() => invokeVNodeHook(vnodeHook, parent, next, vnode), parentSuspense);
			}
		};
		instance.scope.on();
		const effect = instance.effect = new ReactiveEffect(componentUpdateFn);
		instance.scope.off();
		const update = instance.update = effect.run.bind(effect);
		const job = instance.job = effect.runIfDirty.bind(effect);
		job.i = instance;
		job.id = instance.uid;
		effect.scheduler = () => queueJob(job);
		toggleRecurse(instance, true);
		update();
	};
	const updateComponentPreRender = (instance, nextVNode, optimized) => {
		nextVNode.component = instance;
		const prevProps = instance.vnode.props;
		instance.vnode = nextVNode;
		instance.next = null;
		updateProps(instance, nextVNode.props, prevProps, optimized);
		updateSlots(instance, nextVNode.children, optimized);
		pauseTracking();
		flushPreFlushCbs(instance);
		resetTracking();
	};
	const patchChildren = (n1, n2, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized = false) => {
		const c1 = n1 && n1.children;
		const prevShapeFlag = n1 ? n1.shapeFlag : 0;
		const c2 = n2.children;
		const { patchFlag, shapeFlag } = n2;
		if (patchFlag > 0) {
			if (patchFlag & 128) {
				patchKeyedChildren(c1, c2, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized);
				return;
			} else if (patchFlag & 256) {
				patchUnkeyedChildren(c1, c2, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized);
				return;
			}
		}
		if (shapeFlag & 8) {
			if (prevShapeFlag & 16) unmountChildren(c1, parentComponent, parentSuspense);
			if (c2 !== c1) hostSetElementText(container, c2);
		} else if (prevShapeFlag & 16) if (shapeFlag & 16) patchKeyedChildren(c1, c2, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized);
		else unmountChildren(c1, parentComponent, parentSuspense, true);
		else {
			if (prevShapeFlag & 8) hostSetElementText(container, "");
			if (shapeFlag & 16) mountChildren(c2, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized);
		}
	};
	const patchUnkeyedChildren = (c1, c2, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized) => {
		c1 = c1 || EMPTY_ARR;
		c2 = c2 || EMPTY_ARR;
		const oldLength = c1.length;
		const newLength = c2.length;
		const commonLength = Math.min(oldLength, newLength);
		let i;
		for (i = 0; i < commonLength; i++) {
			const nextChild = c2[i] = optimized ? cloneIfMounted(c2[i]) : normalizeVNode(c2[i]);
			patch(c1[i], nextChild, container, null, parentComponent, parentSuspense, namespace, slotScopeIds, optimized);
		}
		if (oldLength > newLength) unmountChildren(c1, parentComponent, parentSuspense, true, false, commonLength);
		else mountChildren(c2, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized, commonLength);
	};
	const patchKeyedChildren = (c1, c2, container, parentAnchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized) => {
		let i = 0;
		const l2 = c2.length;
		let e1 = c1.length - 1;
		let e2 = l2 - 1;
		while (i <= e1 && i <= e2) {
			const n1 = c1[i];
			const n2 = c2[i] = optimized ? cloneIfMounted(c2[i]) : normalizeVNode(c2[i]);
			if (isSameVNodeType(n1, n2)) patch(n1, n2, container, null, parentComponent, parentSuspense, namespace, slotScopeIds, optimized);
			else break;
			i++;
		}
		while (i <= e1 && i <= e2) {
			const n1 = c1[e1];
			const n2 = c2[e2] = optimized ? cloneIfMounted(c2[e2]) : normalizeVNode(c2[e2]);
			if (isSameVNodeType(n1, n2)) patch(n1, n2, container, null, parentComponent, parentSuspense, namespace, slotScopeIds, optimized);
			else break;
			e1--;
			e2--;
		}
		if (i > e1) {
			if (i <= e2) {
				const nextPos = e2 + 1;
				const anchor = nextPos < l2 ? c2[nextPos].el : parentAnchor;
				while (i <= e2) {
					patch(null, c2[i] = optimized ? cloneIfMounted(c2[i]) : normalizeVNode(c2[i]), container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized);
					i++;
				}
			}
		} else if (i > e2) while (i <= e1) {
			unmount(c1[i], parentComponent, parentSuspense, true);
			i++;
		}
		else {
			const s1 = i;
			const s2 = i;
			const keyToNewIndexMap = /* @__PURE__ */ new Map();
			for (i = s2; i <= e2; i++) {
				const nextChild = c2[i] = optimized ? cloneIfMounted(c2[i]) : normalizeVNode(c2[i]);
				if (nextChild.key != null) keyToNewIndexMap.set(nextChild.key, i);
			}
			let j;
			let patched = 0;
			const toBePatched = e2 - s2 + 1;
			let moved = false;
			let maxNewIndexSoFar = 0;
			const newIndexToOldIndexMap = new Array(toBePatched);
			for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;
			for (i = s1; i <= e1; i++) {
				const prevChild = c1[i];
				if (patched >= toBePatched) {
					unmount(prevChild, parentComponent, parentSuspense, true);
					continue;
				}
				let newIndex;
				if (prevChild.key != null) newIndex = keyToNewIndexMap.get(prevChild.key);
				else for (j = s2; j <= e2; j++) if (newIndexToOldIndexMap[j - s2] === 0 && isSameVNodeType(prevChild, c2[j])) {
					newIndex = j;
					break;
				}
				if (newIndex === void 0) unmount(prevChild, parentComponent, parentSuspense, true);
				else {
					newIndexToOldIndexMap[newIndex - s2] = i + 1;
					if (newIndex >= maxNewIndexSoFar) maxNewIndexSoFar = newIndex;
					else moved = true;
					patch(prevChild, c2[newIndex], container, null, parentComponent, parentSuspense, namespace, slotScopeIds, optimized);
					patched++;
				}
			}
			const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : EMPTY_ARR;
			j = increasingNewIndexSequence.length - 1;
			for (i = toBePatched - 1; i >= 0; i--) {
				const nextIndex = s2 + i;
				const nextChild = c2[nextIndex];
				const anchorVNode = c2[nextIndex + 1];
				const anchor = nextIndex + 1 < l2 ? anchorVNode.el || resolveAsyncComponentPlaceholder(anchorVNode) : parentAnchor;
				if (newIndexToOldIndexMap[i] === 0) patch(null, nextChild, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized);
				else if (moved) if (j < 0 || i !== increasingNewIndexSequence[j]) move(nextChild, container, anchor, 2);
				else j--;
			}
		}
	};
	const move = (vnode, container, anchor, moveType, parentSuspense = null) => {
		const { el, type, transition, children, shapeFlag } = vnode;
		if (shapeFlag & 6) {
			move(vnode.component.subTree, container, anchor, moveType);
			return;
		}
		if (shapeFlag & 128) {
			vnode.suspense.move(container, anchor, moveType);
			return;
		}
		if (shapeFlag & 64) {
			type.move(vnode, container, anchor, internals);
			return;
		}
		if (type === Fragment) {
			hostInsert(el, container, anchor);
			for (let i = 0; i < children.length; i++) move(children[i], container, anchor, moveType);
			hostInsert(vnode.anchor, container, anchor);
			return;
		}
		if (type === Static) {
			moveStaticNode(vnode, container, anchor);
			return;
		}
		if (moveType !== 2 && shapeFlag & 1 && transition) if (moveType === 0) {
			transition.beforeEnter(el);
			hostInsert(el, container, anchor);
			queuePostRenderEffect(() => transition.enter(el), parentSuspense);
		} else {
			const { leave, delayLeave, afterLeave } = transition;
			const remove2 = () => {
				if (vnode.ctx.isUnmounted) hostRemove(el);
				else hostInsert(el, container, anchor);
			};
			const performLeave = () => {
				if (el._isLeaving) el[leaveCbKey](true);
				leave(el, () => {
					remove2();
					afterLeave && afterLeave();
				});
			};
			if (delayLeave) delayLeave(el, remove2, performLeave);
			else performLeave();
		}
		else hostInsert(el, container, anchor);
	};
	const unmount = (vnode, parentComponent, parentSuspense, doRemove = false, optimized = false) => {
		const { type, props, ref, children, dynamicChildren, shapeFlag, patchFlag, dirs, cacheIndex, memo } = vnode;
		if (patchFlag === -2) optimized = false;
		if (ref != null) {
			pauseTracking();
			setRef(ref, null, parentSuspense, vnode, true);
			resetTracking();
		}
		if (cacheIndex != null) parentComponent.renderCache[cacheIndex] = void 0;
		if (shapeFlag & 256) {
			parentComponent.ctx.deactivate(vnode);
			return;
		}
		const shouldInvokeDirs = shapeFlag & 1 && dirs;
		const shouldInvokeVnodeHook = !isAsyncWrapper(vnode);
		let vnodeHook;
		if (shouldInvokeVnodeHook && (vnodeHook = props && props.onVnodeBeforeUnmount)) invokeVNodeHook(vnodeHook, parentComponent, vnode);
		if (shapeFlag & 6) unmountComponent(vnode.component, parentSuspense, doRemove);
		else {
			if (shapeFlag & 128) {
				vnode.suspense.unmount(parentSuspense, doRemove);
				return;
			}
			if (shouldInvokeDirs) invokeDirectiveHook(vnode, null, parentComponent, "beforeUnmount");
			if (shapeFlag & 64) vnode.type.remove(vnode, parentComponent, parentSuspense, internals, doRemove);
			else if (dynamicChildren && !dynamicChildren.hasOnce && (type !== Fragment || patchFlag > 0 && patchFlag & 64)) unmountChildren(dynamicChildren, parentComponent, parentSuspense, false, true);
			else if (type === Fragment && patchFlag & 384 || !optimized && shapeFlag & 16) unmountChildren(children, parentComponent, parentSuspense);
			if (doRemove) remove(vnode);
		}
		const shouldInvalidateMemo = memo != null && cacheIndex == null;
		if (shouldInvokeVnodeHook && (vnodeHook = props && props.onVnodeUnmounted) || shouldInvokeDirs || shouldInvalidateMemo) queuePostRenderEffect(() => {
			vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, vnode);
			shouldInvokeDirs && invokeDirectiveHook(vnode, null, parentComponent, "unmounted");
			if (shouldInvalidateMemo) vnode.el = null;
		}, parentSuspense);
	};
	const remove = (vnode) => {
		const { type, el, anchor, transition } = vnode;
		if (type === Fragment) {
			removeFragment(el, anchor);
			return;
		}
		if (type === Static) {
			removeStaticNode(vnode);
			return;
		}
		const performRemove = () => {
			hostRemove(el);
			if (transition && !transition.persisted && transition.afterLeave) transition.afterLeave();
		};
		if (vnode.shapeFlag & 1 && transition && !transition.persisted) {
			const { leave, delayLeave } = transition;
			const performLeave = () => leave(el, performRemove);
			if (delayLeave) delayLeave(vnode.el, performRemove, performLeave);
			else performLeave();
		} else performRemove();
	};
	const removeFragment = (cur, end) => {
		let next;
		while (cur !== end) {
			next = hostNextSibling(cur);
			hostRemove(cur);
			cur = next;
		}
		hostRemove(end);
	};
	const unmountComponent = (instance, parentSuspense, doRemove) => {
		const { bum, scope, job, subTree, um, m, a } = instance;
		invalidateMount(m);
		invalidateMount(a);
		if (bum) invokeArrayFns(bum);
		scope.stop();
		if (job) {
			job.flags |= 8;
			unmount(subTree, instance, parentSuspense, doRemove);
		}
		if (um) queuePostRenderEffect(um, parentSuspense);
		queuePostRenderEffect(() => {
			instance.isUnmounted = true;
		}, parentSuspense);
	};
	const unmountChildren = (children, parentComponent, parentSuspense, doRemove = false, optimized = false, start = 0) => {
		for (let i = start; i < children.length; i++) unmount(children[i], parentComponent, parentSuspense, doRemove, optimized);
	};
	const getNextHostNode = (vnode) => {
		if (vnode.shapeFlag & 6) return getNextHostNode(vnode.component.subTree);
		if (vnode.shapeFlag & 128) return vnode.suspense.next();
		const el = hostNextSibling(vnode.anchor || vnode.el);
		const teleportEnd = el && el[TeleportEndKey];
		return teleportEnd ? hostNextSibling(teleportEnd) : el;
	};
	let isFlushing = false;
	const render = (vnode, container, namespace) => {
		let instance;
		if (vnode == null) {
			if (container._vnode) {
				unmount(container._vnode, null, null, true);
				instance = container._vnode.component;
			}
		} else patch(container._vnode || null, vnode, container, null, null, null, namespace);
		container._vnode = vnode;
		if (!isFlushing) {
			isFlushing = true;
			flushPreFlushCbs(instance);
			flushPostFlushCbs();
			isFlushing = false;
		}
	};
	const internals = {
		p: patch,
		um: unmount,
		m: move,
		r: remove,
		mt: mountComponent,
		mc: mountChildren,
		pc: patchChildren,
		pbc: patchBlockChildren,
		n: getNextHostNode,
		o: options
	};
	let hydrate;
	let hydrateNode;
	if (createHydrationFns) [hydrate, hydrateNode] = createHydrationFns(internals);
	return {
		render,
		hydrate,
		createApp: createAppAPI(render, hydrate)
	};
}
function resolveChildrenNamespace({ type, props }, currentNamespace) {
	return currentNamespace === "svg" && type === "foreignObject" || currentNamespace === "mathml" && type === "annotation-xml" && props && props.encoding && props.encoding.includes("html") ? void 0 : currentNamespace;
}
function toggleRecurse({ effect, job }, allowed) {
	if (allowed) {
		effect.flags |= 32;
		job.flags |= 4;
	} else {
		effect.flags &= -33;
		job.flags &= -5;
	}
}
function needTransition(parentSuspense, transition) {
	return (!parentSuspense || parentSuspense && !parentSuspense.pendingBranch) && transition && !transition.persisted;
}
function traverseStaticChildren(n1, n2, shallow = false) {
	const ch1 = n1.children;
	const ch2 = n2.children;
	if (isArray(ch1) && isArray(ch2)) for (let i = 0; i < ch1.length; i++) {
		const c1 = ch1[i];
		let c2 = ch2[i];
		if (c2.shapeFlag & 1 && !c2.dynamicChildren) {
			if (c2.patchFlag <= 0 || c2.patchFlag === 32) {
				c2 = ch2[i] = cloneIfMounted(ch2[i]);
				c2.el = c1.el;
			}
			if (!shallow && c2.patchFlag !== -2) traverseStaticChildren(c1, c2);
		}
		if (c2.type === Text) {
			if (c2.patchFlag === -1) c2 = ch2[i] = cloneIfMounted(c2);
			c2.el = c1.el;
		}
		if (c2.type === Comment && !c2.el) c2.el = c1.el;
	}
}
function getSequence(arr) {
	const p = arr.slice();
	const result = [0];
	let i, j, u, v, c;
	const len = arr.length;
	for (i = 0; i < len; i++) {
		const arrI = arr[i];
		if (arrI !== 0) {
			j = result[result.length - 1];
			if (arr[j] < arrI) {
				p[i] = j;
				result.push(i);
				continue;
			}
			u = 0;
			v = result.length - 1;
			while (u < v) {
				c = u + v >> 1;
				if (arr[result[c]] < arrI) u = c + 1;
				else v = c;
			}
			if (arrI < arr[result[u]]) {
				if (u > 0) p[i] = result[u - 1];
				result[u] = i;
			}
		}
	}
	u = result.length;
	v = result[u - 1];
	while (u-- > 0) {
		result[u] = v;
		v = p[v];
	}
	return result;
}
function locateNonHydratedAsyncRoot(instance) {
	const subComponent = instance.subTree.component;
	if (subComponent) if (subComponent.asyncDep && !subComponent.asyncResolved) return subComponent;
	else return locateNonHydratedAsyncRoot(subComponent);
}
function invalidateMount(hooks) {
	if (hooks) for (let i = 0; i < hooks.length; i++) hooks[i].flags |= 8;
}
function resolveAsyncComponentPlaceholder(anchorVnode) {
	if (anchorVnode.placeholder) return anchorVnode.placeholder;
	const instance = anchorVnode.component;
	if (instance) return resolveAsyncComponentPlaceholder(instance.subTree);
	return null;
}
var isSuspense = (type) => type.__isSuspense;
function queueEffectWithSuspense(fn, suspense) {
	if (suspense && suspense.pendingBranch) if (isArray(fn)) suspense.effects.push(...fn);
	else suspense.effects.push(fn);
	else queuePostFlushCb(fn);
}
var Fragment = /* @__PURE__ */ Symbol.for("v-fgt");
var Text = /* @__PURE__ */ Symbol.for("v-txt");
var Comment = /* @__PURE__ */ Symbol.for("v-cmt");
var Static = /* @__PURE__ */ Symbol.for("v-stc");
var blockStack = [];
var currentBlock = null;
function openBlock(disableTracking = false) {
	blockStack.push(currentBlock = disableTracking ? null : []);
}
function closeBlock() {
	blockStack.pop();
	currentBlock = blockStack[blockStack.length - 1] || null;
}
var isBlockTreeEnabled = 1;
function setBlockTracking(value, inVOnce = false) {
	isBlockTreeEnabled += value;
	if (value < 0 && currentBlock && inVOnce) currentBlock.hasOnce = true;
}
function setupBlock(vnode) {
	vnode.dynamicChildren = isBlockTreeEnabled > 0 ? currentBlock || EMPTY_ARR : null;
	closeBlock();
	if (isBlockTreeEnabled > 0 && currentBlock) currentBlock.push(vnode);
	return vnode;
}
function createElementBlock(type, props, children, patchFlag, dynamicProps, shapeFlag) {
	return setupBlock(createBaseVNode(type, props, children, patchFlag, dynamicProps, shapeFlag, true));
}
function createBlock(type, props, children, patchFlag, dynamicProps) {
	return setupBlock(createVNode(type, props, children, patchFlag, dynamicProps, true));
}
function isVNode(value) {
	return value ? value.__v_isVNode === true : false;
}
function isSameVNodeType(n1, n2) {
	return n1.type === n2.type && n1.key === n2.key;
}
var normalizeKey = ({ key }) => key != null ? key : null;
var normalizeRef = ({ ref, ref_key, ref_for }) => {
	if (typeof ref === "number") ref = "" + ref;
	return ref != null ? isString(ref) || /* @__PURE__ */ isRef(ref) || isFunction(ref) ? {
		i: currentRenderingInstance,
		r: ref,
		k: ref_key,
		f: !!ref_for
	} : ref : null;
};
function createBaseVNode(type, props = null, children = null, patchFlag = 0, dynamicProps = null, shapeFlag = type === Fragment ? 0 : 1, isBlockNode = false, needFullChildrenNormalization = false) {
	const vnode = {
		__v_isVNode: true,
		__v_skip: true,
		type,
		props,
		key: props && normalizeKey(props),
		ref: props && normalizeRef(props),
		scopeId: currentScopeId,
		slotScopeIds: null,
		children,
		component: null,
		suspense: null,
		ssContent: null,
		ssFallback: null,
		dirs: null,
		transition: null,
		el: null,
		anchor: null,
		target: null,
		targetStart: null,
		targetAnchor: null,
		staticCount: 0,
		shapeFlag,
		patchFlag,
		dynamicProps,
		dynamicChildren: null,
		appContext: null,
		ctx: currentRenderingInstance
	};
	if (needFullChildrenNormalization) {
		normalizeChildren(vnode, children);
		if (shapeFlag & 128) type.normalize(vnode);
	} else if (children) vnode.shapeFlag |= isString(children) ? 8 : 16;
	if (isBlockTreeEnabled > 0 && !isBlockNode && currentBlock && (vnode.patchFlag > 0 || shapeFlag & 6) && vnode.patchFlag !== 32) currentBlock.push(vnode);
	return vnode;
}
var createVNode = _createVNode;
function _createVNode(type, props = null, children = null, patchFlag = 0, dynamicProps = null, isBlockNode = false) {
	if (!type || type === NULL_DYNAMIC_COMPONENT) type = Comment;
	if (isVNode(type)) {
		const cloned = cloneVNode(type, props, true);
		if (children) normalizeChildren(cloned, children);
		if (isBlockTreeEnabled > 0 && !isBlockNode && currentBlock) if (cloned.shapeFlag & 6) currentBlock[currentBlock.indexOf(type)] = cloned;
		else currentBlock.push(cloned);
		cloned.patchFlag = -2;
		return cloned;
	}
	if (isClassComponent(type)) type = type.__vccOpts;
	if (props) {
		props = guardReactiveProps(props);
		let { class: klass, style } = props;
		if (klass && !isString(klass)) props.class = normalizeClass(klass);
		if (isObject(style)) {
			if (/* @__PURE__ */ isProxy(style) && !isArray(style)) style = extend({}, style);
			props.style = normalizeStyle(style);
		}
	}
	const shapeFlag = isString(type) ? 1 : isSuspense(type) ? 128 : isTeleport(type) ? 64 : isObject(type) ? 4 : isFunction(type) ? 2 : 0;
	return createBaseVNode(type, props, children, patchFlag, dynamicProps, shapeFlag, isBlockNode, true);
}
function guardReactiveProps(props) {
	if (!props) return null;
	return /* @__PURE__ */ isProxy(props) || isInternalObject(props) ? extend({}, props) : props;
}
function cloneVNode(vnode, extraProps, mergeRef = false, cloneTransition = false) {
	const { props, ref, patchFlag, children, transition } = vnode;
	const mergedProps = extraProps ? mergeProps(props || {}, extraProps) : props;
	const cloned = {
		__v_isVNode: true,
		__v_skip: true,
		type: vnode.type,
		props: mergedProps,
		key: mergedProps && normalizeKey(mergedProps),
		ref: extraProps && extraProps.ref ? mergeRef && ref ? isArray(ref) ? ref.concat(normalizeRef(extraProps)) : [ref, normalizeRef(extraProps)] : normalizeRef(extraProps) : ref,
		scopeId: vnode.scopeId,
		slotScopeIds: vnode.slotScopeIds,
		children,
		target: vnode.target,
		targetStart: vnode.targetStart,
		targetAnchor: vnode.targetAnchor,
		staticCount: vnode.staticCount,
		shapeFlag: vnode.shapeFlag,
		patchFlag: extraProps && vnode.type !== Fragment ? patchFlag === -1 ? 16 : patchFlag | 16 : patchFlag,
		dynamicProps: vnode.dynamicProps,
		dynamicChildren: vnode.dynamicChildren,
		appContext: vnode.appContext,
		dirs: vnode.dirs,
		transition,
		component: vnode.component,
		suspense: vnode.suspense,
		ssContent: vnode.ssContent && cloneVNode(vnode.ssContent),
		ssFallback: vnode.ssFallback && cloneVNode(vnode.ssFallback),
		placeholder: vnode.placeholder,
		el: vnode.el,
		anchor: vnode.anchor,
		ctx: vnode.ctx,
		ce: vnode.ce
	};
	if (transition && cloneTransition) setTransitionHooks(cloned, transition.clone(cloned));
	return cloned;
}
function createTextVNode(text = " ", flag = 0) {
	return createVNode(Text, null, text, flag);
}
function createStaticVNode(content, numberOfNodes) {
	const vnode = createVNode(Static, null, content);
	vnode.staticCount = numberOfNodes;
	return vnode;
}
function createCommentVNode(text = "", asBlock = false) {
	return asBlock ? (openBlock(), createBlock(Comment, null, text)) : createVNode(Comment, null, text);
}
function normalizeVNode(child) {
	if (child == null || typeof child === "boolean") return createVNode(Comment);
	else if (isArray(child)) return createVNode(Fragment, null, child.slice());
	else if (isVNode(child)) return cloneIfMounted(child);
	else return createVNode(Text, null, String(child));
}
function cloneIfMounted(child) {
	return child.el === null && child.patchFlag !== -1 || child.memo ? child : cloneVNode(child);
}
function normalizeChildren(vnode, children) {
	let type = 0;
	const { shapeFlag } = vnode;
	if (children == null) children = null;
	else if (isArray(children)) type = 16;
	else if (typeof children === "object") if (shapeFlag & 65) {
		const slot = children.default;
		if (slot) {
			slot._c && (slot._d = false);
			normalizeChildren(vnode, slot());
			slot._c && (slot._d = true);
		}
		return;
	} else {
		type = 32;
		const slotFlag = children._;
		if (!slotFlag && !isInternalObject(children)) children._ctx = currentRenderingInstance;
		else if (slotFlag === 3 && currentRenderingInstance) if (currentRenderingInstance.slots._ === 1) children._ = 1;
		else {
			children._ = 2;
			vnode.patchFlag |= 1024;
		}
	}
	else if (isFunction(children)) {
		children = {
			default: children,
			_ctx: currentRenderingInstance
		};
		type = 32;
	} else {
		children = String(children);
		if (shapeFlag & 64) {
			type = 16;
			children = [createTextVNode(children)];
		} else type = 8;
	}
	vnode.children = children;
	vnode.shapeFlag |= type;
}
function mergeProps(...args) {
	const ret = {};
	for (let i = 0; i < args.length; i++) {
		const toMerge = args[i];
		for (const key in toMerge) if (key === "class") {
			if (ret.class !== toMerge.class) ret.class = normalizeClass([ret.class, toMerge.class]);
		} else if (key === "style") ret.style = normalizeStyle([ret.style, toMerge.style]);
		else if (isOn(key)) {
			const existing = ret[key];
			const incoming = toMerge[key];
			if (incoming && existing !== incoming && !(isArray(existing) && existing.includes(incoming))) ret[key] = existing ? [].concat(existing, incoming) : incoming;
			else if (incoming == null && existing == null && !isModelListener(key)) ret[key] = incoming;
		} else if (key !== "") ret[key] = toMerge[key];
	}
	return ret;
}
function invokeVNodeHook(hook, instance, vnode, prevVNode = null) {
	callWithAsyncErrorHandling(hook, instance, 7, [vnode, prevVNode]);
}
var emptyAppContext = createAppContext();
var uid = 0;
function createComponentInstance(vnode, parent, suspense) {
	const type = vnode.type;
	const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;
	const instance = {
		uid: uid++,
		vnode,
		type,
		parent,
		appContext,
		root: null,
		next: null,
		subTree: null,
		effect: null,
		update: null,
		job: null,
		scope: new EffectScope(true),
		render: null,
		proxy: null,
		exposed: null,
		exposeProxy: null,
		withProxy: null,
		provides: parent ? parent.provides : Object.create(appContext.provides),
		ids: parent ? parent.ids : [
			"",
			0,
			0
		],
		accessCache: null,
		renderCache: [],
		components: null,
		directives: null,
		propsOptions: normalizePropsOptions(type, appContext),
		emitsOptions: normalizeEmitsOptions(type, appContext),
		emit: null,
		emitted: null,
		propsDefaults: EMPTY_OBJ,
		inheritAttrs: type.inheritAttrs,
		ctx: EMPTY_OBJ,
		data: EMPTY_OBJ,
		props: EMPTY_OBJ,
		attrs: EMPTY_OBJ,
		slots: EMPTY_OBJ,
		refs: EMPTY_OBJ,
		setupState: EMPTY_OBJ,
		setupContext: null,
		suspense,
		suspenseId: suspense ? suspense.pendingId : 0,
		asyncDep: null,
		asyncResolved: false,
		isMounted: false,
		isUnmounted: false,
		isDeactivated: false,
		bc: null,
		c: null,
		bm: null,
		m: null,
		bu: null,
		u: null,
		um: null,
		bum: null,
		da: null,
		a: null,
		rtg: null,
		rtc: null,
		ec: null,
		sp: null
	};
	instance.ctx = { _: instance };
	instance.root = parent ? parent.root : instance;
	instance.emit = emit.bind(null, instance);
	if (vnode.ce) vnode.ce(instance);
	return instance;
}
var currentInstance = null;
var getCurrentInstance = () => currentInstance || currentRenderingInstance;
var internalSetCurrentInstance;
var setInSSRSetupState;
{
	const g = getGlobalThis();
	const registerGlobalSetter = (key, setter) => {
		let setters;
		if (!(setters = g[key])) setters = g[key] = [];
		setters.push(setter);
		return (v) => {
			if (setters.length > 1) setters.forEach((set) => set(v));
			else setters[0](v);
		};
	};
	internalSetCurrentInstance = registerGlobalSetter(`__VUE_INSTANCE_SETTERS__`, (v) => currentInstance = v);
	setInSSRSetupState = registerGlobalSetter(`__VUE_SSR_SETTERS__`, (v) => isInSSRComponentSetup = v);
}
var setCurrentInstance = (instance) => {
	const prev = currentInstance;
	internalSetCurrentInstance(instance);
	instance.scope.on();
	return () => {
		instance.scope.off();
		internalSetCurrentInstance(prev);
	};
};
var unsetCurrentInstance = () => {
	currentInstance && currentInstance.scope.off();
	internalSetCurrentInstance(null);
};
function isStatefulComponent(instance) {
	return instance.vnode.shapeFlag & 4;
}
var isInSSRComponentSetup = false;
function setupComponent(instance, isSSR = false, optimized = false) {
	isSSR && setInSSRSetupState(isSSR);
	const { props, children } = instance.vnode;
	const isStateful = isStatefulComponent(instance);
	initProps(instance, props, isStateful, isSSR);
	initSlots(instance, children, optimized || isSSR);
	const setupResult = isStateful ? setupStatefulComponent(instance, isSSR) : void 0;
	isSSR && setInSSRSetupState(false);
	return setupResult;
}
function setupStatefulComponent(instance, isSSR) {
	const Component = instance.type;
	instance.accessCache = /* @__PURE__ */ Object.create(null);
	instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers);
	const { setup } = Component;
	if (setup) {
		pauseTracking();
		const setupContext = instance.setupContext = setup.length > 1 ? createSetupContext(instance) : null;
		const reset = setCurrentInstance(instance);
		const setupResult = callWithErrorHandling(setup, instance, 0, [instance.props, setupContext]);
		const isAsyncSetup = isPromise(setupResult);
		resetTracking();
		reset();
		if ((isAsyncSetup || instance.sp) && !isAsyncWrapper(instance)) markAsyncBoundary(instance);
		if (isAsyncSetup) {
			setupResult.then(unsetCurrentInstance, unsetCurrentInstance);
			if (isSSR) return setupResult.then((resolvedResult) => {
				handleSetupResult(instance, resolvedResult, isSSR);
			}).catch((e) => {
				handleError(e, instance, 0);
			});
			else instance.asyncDep = setupResult;
		} else handleSetupResult(instance, setupResult, isSSR);
	} else finishComponentSetup(instance, isSSR);
}
function handleSetupResult(instance, setupResult, isSSR) {
	if (isFunction(setupResult)) if (instance.type.__ssrInlineRender) instance.ssrRender = setupResult;
	else instance.render = setupResult;
	else if (isObject(setupResult)) instance.setupState = proxyRefs(setupResult);
	finishComponentSetup(instance, isSSR);
}
var compile;
var installWithProxy;
function finishComponentSetup(instance, isSSR, skipOptions) {
	const Component = instance.type;
	if (!instance.render) {
		if (!isSSR && compile && !Component.render) {
			const template = Component.template || resolveMergedOptions(instance).template;
			if (template) {
				const { isCustomElement, compilerOptions } = instance.appContext.config;
				const { delimiters, compilerOptions: componentCompilerOptions } = Component;
				Component.render = compile(template, extend(extend({
					isCustomElement,
					delimiters
				}, compilerOptions), componentCompilerOptions));
			}
		}
		instance.render = Component.render || NOOP;
		if (installWithProxy) installWithProxy(instance);
	}
	{
		const reset = setCurrentInstance(instance);
		pauseTracking();
		try {
			applyOptions(instance);
		} finally {
			resetTracking();
			reset();
		}
	}
}
var attrsProxyHandlers = { get(target, key) {
	track(target, "get", "");
	return target[key];
} };
function createSetupContext(instance) {
	const expose = (exposed) => {
		instance.exposed = exposed || {};
	};
	return {
		attrs: new Proxy(instance.attrs, attrsProxyHandlers),
		slots: instance.slots,
		emit: instance.emit,
		expose
	};
}
function getComponentPublicInstance(instance) {
	if (instance.exposed) return instance.exposeProxy || (instance.exposeProxy = new Proxy(proxyRefs(markRaw(instance.exposed)), {
		get(target, key) {
			if (key in target) return target[key];
			else if (key in publicPropertiesMap) return publicPropertiesMap[key](instance);
		},
		has(target, key) {
			return key in target || key in publicPropertiesMap;
		}
	}));
	else return instance.proxy;
}
function getComponentName(Component, includeInferred = true) {
	return isFunction(Component) ? Component.displayName || Component.name : Component.name || includeInferred && Component.__name;
}
function isClassComponent(value) {
	return isFunction(value) && "__vccOpts" in value;
}
var computed = (getterOrOptions, debugOptions) => {
	return /* @__PURE__ */ computed$1(getterOrOptions, debugOptions, isInSSRComponentSetup);
};
var version = "3.5.31";
//#endregion
//#region node_modules/@vue/runtime-dom/dist/runtime-dom.esm-bundler.js
/**
* @vue/runtime-dom v3.5.31
* (c) 2018-present Yuxi (Evan) You and Vue contributors
* @license MIT
**/
var policy = void 0;
var tt = typeof window !== "undefined" && window.trustedTypes;
if (tt) try {
	policy = /* @__PURE__ */ tt.createPolicy("vue", { createHTML: (val) => val });
} catch (e) {}
var unsafeToTrustedHTML = policy ? (val) => policy.createHTML(val) : (val) => val;
var svgNS = "http://www.w3.org/2000/svg";
var mathmlNS = "http://www.w3.org/1998/Math/MathML";
var doc = typeof document !== "undefined" ? document : null;
var templateContainer = doc && /* @__PURE__ */ doc.createElement("template");
var nodeOps = {
	insert: (child, parent, anchor) => {
		parent.insertBefore(child, anchor || null);
	},
	remove: (child) => {
		const parent = child.parentNode;
		if (parent) parent.removeChild(child);
	},
	createElement: (tag, namespace, is, props) => {
		const el = namespace === "svg" ? doc.createElementNS(svgNS, tag) : namespace === "mathml" ? doc.createElementNS(mathmlNS, tag) : is ? doc.createElement(tag, { is }) : doc.createElement(tag);
		if (tag === "select" && props && props.multiple != null) el.setAttribute("multiple", props.multiple);
		return el;
	},
	createText: (text) => doc.createTextNode(text),
	createComment: (text) => doc.createComment(text),
	setText: (node, text) => {
		node.nodeValue = text;
	},
	setElementText: (el, text) => {
		el.textContent = text;
	},
	parentNode: (node) => node.parentNode,
	nextSibling: (node) => node.nextSibling,
	querySelector: (selector) => doc.querySelector(selector),
	setScopeId(el, id) {
		el.setAttribute(id, "");
	},
	insertStaticContent(content, parent, anchor, namespace, start, end) {
		const before = anchor ? anchor.previousSibling : parent.lastChild;
		if (start && (start === end || start.nextSibling)) while (true) {
			parent.insertBefore(start.cloneNode(true), anchor);
			if (start === end || !(start = start.nextSibling)) break;
		}
		else {
			templateContainer.innerHTML = unsafeToTrustedHTML(namespace === "svg" ? `<svg>${content}</svg>` : namespace === "mathml" ? `<math>${content}</math>` : content);
			const template = templateContainer.content;
			if (namespace === "svg" || namespace === "mathml") {
				const wrapper = template.firstChild;
				while (wrapper.firstChild) template.appendChild(wrapper.firstChild);
				template.removeChild(wrapper);
			}
			parent.insertBefore(template, anchor);
		}
		return [before ? before.nextSibling : parent.firstChild, anchor ? anchor.previousSibling : parent.lastChild];
	}
};
var vtcKey = /* @__PURE__ */ Symbol("_vtc");
function patchClass(el, value, isSVG) {
	const transitionClasses = el[vtcKey];
	if (transitionClasses) value = (value ? [value, ...transitionClasses] : [...transitionClasses]).join(" ");
	if (value == null) el.removeAttribute("class");
	else if (isSVG) el.setAttribute("class", value);
	else el.className = value;
}
var vShowOriginalDisplay = /* @__PURE__ */ Symbol("_vod");
var vShowHidden = /* @__PURE__ */ Symbol("_vsh");
var vShow = {
	name: "show",
	beforeMount(el, { value }, { transition }) {
		el[vShowOriginalDisplay] = el.style.display === "none" ? "" : el.style.display;
		if (transition && value) transition.beforeEnter(el);
		else setDisplay(el, value);
	},
	mounted(el, { value }, { transition }) {
		if (transition && value) transition.enter(el);
	},
	updated(el, { value, oldValue }, { transition }) {
		if (!value === !oldValue) return;
		if (transition) if (value) {
			transition.beforeEnter(el);
			setDisplay(el, true);
			transition.enter(el);
		} else transition.leave(el, () => {
			setDisplay(el, false);
		});
		else setDisplay(el, value);
	},
	beforeUnmount(el, { value }) {
		setDisplay(el, value);
	}
};
function setDisplay(el, value) {
	el.style.display = value ? el[vShowOriginalDisplay] : "none";
	el[vShowHidden] = !value;
}
var CSS_VAR_TEXT = /* @__PURE__ */ Symbol("");
var displayRE = /(?:^|;)\s*display\s*:/;
function patchStyle(el, prev, next) {
	const style = el.style;
	const isCssString = isString(next);
	let hasControlledDisplay = false;
	if (next && !isCssString) {
		if (prev) if (!isString(prev)) {
			for (const key in prev) if (next[key] == null) setStyle(style, key, "");
		} else for (const prevStyle of prev.split(";")) {
			const key = prevStyle.slice(0, prevStyle.indexOf(":")).trim();
			if (next[key] == null) setStyle(style, key, "");
		}
		for (const key in next) {
			if (key === "display") hasControlledDisplay = true;
			setStyle(style, key, next[key]);
		}
	} else if (isCssString) {
		if (prev !== next) {
			const cssVarText = style[CSS_VAR_TEXT];
			if (cssVarText) next += ";" + cssVarText;
			style.cssText = next;
			hasControlledDisplay = displayRE.test(next);
		}
	} else if (prev) el.removeAttribute("style");
	if (vShowOriginalDisplay in el) {
		el[vShowOriginalDisplay] = hasControlledDisplay ? style.display : "";
		if (el[vShowHidden]) style.display = "none";
	}
}
var importantRE = /\s*!important$/;
function setStyle(style, name, val) {
	if (isArray(val)) val.forEach((v) => setStyle(style, name, v));
	else {
		if (val == null) val = "";
		if (name.startsWith("--")) style.setProperty(name, val);
		else {
			const prefixed = autoPrefix(style, name);
			if (importantRE.test(val)) style.setProperty(hyphenate(prefixed), val.replace(importantRE, ""), "important");
			else style[prefixed] = val;
		}
	}
}
var prefixes = [
	"Webkit",
	"Moz",
	"ms"
];
var prefixCache = {};
function autoPrefix(style, rawName) {
	const cached = prefixCache[rawName];
	if (cached) return cached;
	let name = camelize(rawName);
	if (name !== "filter" && name in style) return prefixCache[rawName] = name;
	name = capitalize(name);
	for (let i = 0; i < prefixes.length; i++) {
		const prefixed = prefixes[i] + name;
		if (prefixed in style) return prefixCache[rawName] = prefixed;
	}
	return rawName;
}
var xlinkNS = "http://www.w3.org/1999/xlink";
function patchAttr(el, key, value, isSVG, instance, isBoolean = isSpecialBooleanAttr(key)) {
	if (isSVG && key.startsWith("xlink:")) if (value == null) el.removeAttributeNS(xlinkNS, key.slice(6, key.length));
	else el.setAttributeNS(xlinkNS, key, value);
	else if (value == null || isBoolean && !includeBooleanAttr(value)) el.removeAttribute(key);
	else el.setAttribute(key, isBoolean ? "" : isSymbol(value) ? String(value) : value);
}
function patchDOMProp(el, key, value, parentComponent, attrName) {
	if (key === "innerHTML" || key === "textContent") {
		if (value != null) el[key] = key === "innerHTML" ? unsafeToTrustedHTML(value) : value;
		return;
	}
	const tag = el.tagName;
	if (key === "value" && tag !== "PROGRESS" && !tag.includes("-")) {
		const oldValue = tag === "OPTION" ? el.getAttribute("value") || "" : el.value;
		const newValue = value == null ? el.type === "checkbox" ? "on" : "" : String(value);
		if (oldValue !== newValue || !("_value" in el)) el.value = newValue;
		if (value == null) el.removeAttribute(key);
		el._value = value;
		return;
	}
	let needRemove = false;
	if (value === "" || value == null) {
		const type = typeof el[key];
		if (type === "boolean") value = includeBooleanAttr(value);
		else if (value == null && type === "string") {
			value = "";
			needRemove = true;
		} else if (type === "number") {
			value = 0;
			needRemove = true;
		}
	}
	try {
		el[key] = value;
	} catch (e) {}
	needRemove && el.removeAttribute(attrName || key);
}
function addEventListener(el, event, handler, options) {
	el.addEventListener(event, handler, options);
}
function removeEventListener(el, event, handler, options) {
	el.removeEventListener(event, handler, options);
}
var veiKey = /* @__PURE__ */ Symbol("_vei");
function patchEvent(el, rawName, prevValue, nextValue, instance = null) {
	const invokers = el[veiKey] || (el[veiKey] = {});
	const existingInvoker = invokers[rawName];
	if (nextValue && existingInvoker) existingInvoker.value = nextValue;
	else {
		const [name, options] = parseName(rawName);
		if (nextValue) addEventListener(el, name, invokers[rawName] = createInvoker(nextValue, instance), options);
		else if (existingInvoker) {
			removeEventListener(el, name, existingInvoker, options);
			invokers[rawName] = void 0;
		}
	}
}
var optionsModifierRE = /(?:Once|Passive|Capture)$/;
function parseName(name) {
	let options;
	if (optionsModifierRE.test(name)) {
		options = {};
		let m;
		while (m = name.match(optionsModifierRE)) {
			name = name.slice(0, name.length - m[0].length);
			options[m[0].toLowerCase()] = true;
		}
	}
	return [name[2] === ":" ? name.slice(3) : hyphenate(name.slice(2)), options];
}
var cachedNow = 0;
var p = /* @__PURE__ */ Promise.resolve();
var getNow = () => cachedNow || (p.then(() => cachedNow = 0), cachedNow = Date.now());
function createInvoker(initialValue, instance) {
	const invoker = (e) => {
		if (!e._vts) e._vts = Date.now();
		else if (e._vts <= invoker.attached) return;
		callWithAsyncErrorHandling(patchStopImmediatePropagation(e, invoker.value), instance, 5, [e]);
	};
	invoker.value = initialValue;
	invoker.attached = getNow();
	return invoker;
}
function patchStopImmediatePropagation(e, value) {
	if (isArray(value)) {
		const originalStop = e.stopImmediatePropagation;
		e.stopImmediatePropagation = () => {
			originalStop.call(e);
			e._stopped = true;
		};
		return value.map((fn) => (e2) => !e2._stopped && fn && fn(e2));
	} else return value;
}
var isNativeOn = (key) => key.charCodeAt(0) === 111 && key.charCodeAt(1) === 110 && key.charCodeAt(2) > 96 && key.charCodeAt(2) < 123;
var patchProp = (el, key, prevValue, nextValue, namespace, parentComponent) => {
	const isSVG = namespace === "svg";
	if (key === "class") patchClass(el, nextValue, isSVG);
	else if (key === "style") patchStyle(el, prevValue, nextValue);
	else if (isOn(key)) {
		if (!isModelListener(key)) patchEvent(el, key, prevValue, nextValue, parentComponent);
	} else if (key[0] === "." ? (key = key.slice(1), true) : key[0] === "^" ? (key = key.slice(1), false) : shouldSetAsProp(el, key, nextValue, isSVG)) {
		patchDOMProp(el, key, nextValue);
		if (!el.tagName.includes("-") && (key === "value" || key === "checked" || key === "selected")) patchAttr(el, key, nextValue, isSVG, parentComponent, key !== "value");
	} else if (el._isVueCE && (shouldSetAsPropForVueCE(el, key) || el._def.__asyncLoader && (/[A-Z]/.test(key) || !isString(nextValue)))) patchDOMProp(el, camelize(key), nextValue, parentComponent, key);
	else {
		if (key === "true-value") el._trueValue = nextValue;
		else if (key === "false-value") el._falseValue = nextValue;
		patchAttr(el, key, nextValue, isSVG);
	}
};
function shouldSetAsProp(el, key, value, isSVG) {
	if (isSVG) {
		if (key === "innerHTML" || key === "textContent") return true;
		if (key in el && isNativeOn(key) && isFunction(value)) return true;
		return false;
	}
	if (key === "spellcheck" || key === "draggable" || key === "translate" || key === "autocorrect") return false;
	if (key === "sandbox" && el.tagName === "IFRAME") return false;
	if (key === "form") return false;
	if (key === "list" && el.tagName === "INPUT") return false;
	if (key === "type" && el.tagName === "TEXTAREA") return false;
	if (key === "width" || key === "height") {
		const tag = el.tagName;
		if (tag === "IMG" || tag === "VIDEO" || tag === "CANVAS" || tag === "SOURCE") return false;
	}
	if (isNativeOn(key) && isString(value)) return false;
	return key in el;
}
function shouldSetAsPropForVueCE(el, key) {
	const props = el._def.props;
	if (!props) return false;
	const camelKey = camelize(key);
	return Array.isArray(props) ? props.some((prop) => camelize(prop) === camelKey) : Object.keys(props).some((prop) => camelize(prop) === camelKey);
}
var getModelAssigner = (vnode) => {
	const fn = vnode.props["onUpdate:modelValue"] || false;
	return isArray(fn) ? (value) => invokeArrayFns(fn, value) : fn;
};
function onCompositionStart(e) {
	e.target.composing = true;
}
function onCompositionEnd(e) {
	const target = e.target;
	if (target.composing) {
		target.composing = false;
		target.dispatchEvent(new Event("input"));
	}
}
var assignKey = /* @__PURE__ */ Symbol("_assign");
function castValue(value, trim, number) {
	if (trim) value = value.trim();
	if (number) value = looseToNumber(value);
	return value;
}
var vModelText = {
	created(el, { modifiers: { lazy, trim, number } }, vnode) {
		el[assignKey] = getModelAssigner(vnode);
		const castToNumber = number || vnode.props && vnode.props.type === "number";
		addEventListener(el, lazy ? "change" : "input", (e) => {
			if (e.target.composing) return;
			el[assignKey](castValue(el.value, trim, castToNumber));
		});
		if (trim || castToNumber) addEventListener(el, "change", () => {
			el.value = castValue(el.value, trim, castToNumber);
		});
		if (!lazy) {
			addEventListener(el, "compositionstart", onCompositionStart);
			addEventListener(el, "compositionend", onCompositionEnd);
			addEventListener(el, "change", onCompositionEnd);
		}
	},
	mounted(el, { value }) {
		el.value = value == null ? "" : value;
	},
	beforeUpdate(el, { value, oldValue, modifiers: { lazy, trim, number } }, vnode) {
		el[assignKey] = getModelAssigner(vnode);
		if (el.composing) return;
		const elValue = (number || el.type === "number") && !/^0\d/.test(el.value) ? looseToNumber(el.value) : el.value;
		const newValue = value == null ? "" : value;
		if (elValue === newValue) return;
		const rootNode = el.getRootNode();
		if ((rootNode instanceof Document || rootNode instanceof ShadowRoot) && rootNode.activeElement === el && el.type !== "range") {
			if (lazy && value === oldValue) return;
			if (trim && el.value.trim() === newValue) return;
		}
		el.value = newValue;
	}
};
var vModelCheckbox = {
	deep: true,
	created(el, _, vnode) {
		el[assignKey] = getModelAssigner(vnode);
		addEventListener(el, "change", () => {
			const modelValue = el._modelValue;
			const elementValue = getValue(el);
			const checked = el.checked;
			const assign = el[assignKey];
			if (isArray(modelValue)) {
				const index = looseIndexOf(modelValue, elementValue);
				const found = index !== -1;
				if (checked && !found) assign(modelValue.concat(elementValue));
				else if (!checked && found) {
					const filtered = [...modelValue];
					filtered.splice(index, 1);
					assign(filtered);
				}
			} else if (isSet(modelValue)) {
				const cloned = new Set(modelValue);
				if (checked) cloned.add(elementValue);
				else cloned.delete(elementValue);
				assign(cloned);
			} else assign(getCheckboxValue(el, checked));
		});
	},
	mounted: setChecked,
	beforeUpdate(el, binding, vnode) {
		el[assignKey] = getModelAssigner(vnode);
		setChecked(el, binding, vnode);
	}
};
function setChecked(el, { value, oldValue }, vnode) {
	el._modelValue = value;
	let checked;
	if (isArray(value)) checked = looseIndexOf(value, vnode.props.value) > -1;
	else if (isSet(value)) checked = value.has(vnode.props.value);
	else {
		if (value === oldValue) return;
		checked = looseEqual(value, getCheckboxValue(el, true));
	}
	if (el.checked !== checked) el.checked = checked;
}
var vModelRadio = {
	created(el, { value }, vnode) {
		el.checked = looseEqual(value, vnode.props.value);
		el[assignKey] = getModelAssigner(vnode);
		addEventListener(el, "change", () => {
			el[assignKey](getValue(el));
		});
	},
	beforeUpdate(el, { value, oldValue }, vnode) {
		el[assignKey] = getModelAssigner(vnode);
		if (value !== oldValue) el.checked = looseEqual(value, vnode.props.value);
	}
};
var vModelSelect = {
	deep: true,
	created(el, { value, modifiers: { number } }, vnode) {
		const isSetModel = isSet(value);
		addEventListener(el, "change", () => {
			const selectedVal = Array.prototype.filter.call(el.options, (o) => o.selected).map((o) => number ? looseToNumber(getValue(o)) : getValue(o));
			el[assignKey](el.multiple ? isSetModel ? new Set(selectedVal) : selectedVal : selectedVal[0]);
			el._assigning = true;
			nextTick(() => {
				el._assigning = false;
			});
		});
		el[assignKey] = getModelAssigner(vnode);
	},
	mounted(el, { value }) {
		setSelected(el, value);
	},
	beforeUpdate(el, _binding, vnode) {
		el[assignKey] = getModelAssigner(vnode);
	},
	updated(el, { value }) {
		if (!el._assigning) setSelected(el, value);
	}
};
function setSelected(el, value) {
	const isMultiple = el.multiple;
	const isArrayValue = isArray(value);
	if (isMultiple && !isArrayValue && !isSet(value)) return;
	for (let i = 0, l = el.options.length; i < l; i++) {
		const option = el.options[i];
		const optionValue = getValue(option);
		if (isMultiple) if (isArrayValue) {
			const optionType = typeof optionValue;
			if (optionType === "string" || optionType === "number") option.selected = value.some((v) => String(v) === String(optionValue));
			else option.selected = looseIndexOf(value, optionValue) > -1;
		} else option.selected = value.has(optionValue);
		else if (looseEqual(getValue(option), value)) {
			if (el.selectedIndex !== i) el.selectedIndex = i;
			return;
		}
	}
	if (!isMultiple && el.selectedIndex !== -1) el.selectedIndex = -1;
}
function getValue(el) {
	return "_value" in el ? el._value : el.value;
}
function getCheckboxValue(el, checked) {
	const key = checked ? "_trueValue" : "_falseValue";
	return key in el ? el[key] : checked;
}
var systemModifiers = [
	"ctrl",
	"shift",
	"alt",
	"meta"
];
var modifierGuards = {
	stop: (e) => e.stopPropagation(),
	prevent: (e) => e.preventDefault(),
	self: (e) => e.target !== e.currentTarget,
	ctrl: (e) => !e.ctrlKey,
	shift: (e) => !e.shiftKey,
	alt: (e) => !e.altKey,
	meta: (e) => !e.metaKey,
	left: (e) => "button" in e && e.button !== 0,
	middle: (e) => "button" in e && e.button !== 1,
	right: (e) => "button" in e && e.button !== 2,
	exact: (e, modifiers) => systemModifiers.some((m) => e[`${m}Key`] && !modifiers.includes(m))
};
var withModifiers = (fn, modifiers) => {
	if (!fn) return fn;
	const cache = fn._withMods || (fn._withMods = {});
	const cacheKey = modifiers.join(".");
	return cache[cacheKey] || (cache[cacheKey] = ((event, ...args) => {
		for (let i = 0; i < modifiers.length; i++) {
			const guard = modifierGuards[modifiers[i]];
			if (guard && guard(event, modifiers)) return;
		}
		return fn(event, ...args);
	}));
};
var rendererOptions = /* @__PURE__ */ extend({ patchProp }, nodeOps);
var renderer;
function ensureRenderer() {
	return renderer || (renderer = createRenderer(rendererOptions));
}
var createApp = ((...args) => {
	const app = ensureRenderer().createApp(...args);
	const { mount } = app;
	app.mount = (containerOrSelector) => {
		const container = normalizeContainer(containerOrSelector);
		if (!container) return;
		const component = app._component;
		if (!isFunction(component) && !component.render && !component.template) component.template = container.innerHTML;
		if (container.nodeType === 1) container.textContent = "";
		const proxy = mount(container, false, resolveRootNamespace(container));
		if (container instanceof Element) {
			container.removeAttribute("v-cloak");
			container.setAttribute("data-v-app", "");
		}
		return proxy;
	};
	return app;
});
function resolveRootNamespace(container) {
	if (container instanceof SVGElement) return "svg";
	if (typeof MathMLElement === "function" && container instanceof MathMLElement) return "mathml";
}
function normalizeContainer(container) {
	if (isString(container)) return document.querySelector(container);
	return container;
}
//#endregion
//#region node_modules/pinia/dist/pinia.mjs
/*!
* pinia v3.0.4
* (c) 2025 Eduardo San Martin Morote
* @license MIT
*/
var IS_CLIENT = typeof window !== "undefined";
/**
* setActivePinia must be called to handle SSR at the top of functions like
* `fetch`, `setup`, `serverPrefetch` and others
*/
var activePinia;
/**
* Sets or unsets the active pinia. Used in SSR and internally when calling
* actions and getters
*
* @param pinia - Pinia instance
*/
var setActivePinia = (pinia) => activePinia = pinia;
var piniaSymbol = Symbol();
function isPlainObject(o) {
	return o && typeof o === "object" && Object.prototype.toString.call(o) === "[object Object]" && typeof o.toJSON !== "function";
}
/**
* Possible types for SubscriptionCallback
*/
var MutationType;
(function(MutationType) {
	/**
	* Direct mutation of the state:
	*
	* - `store.name = 'new name'`
	* - `store.$state.name = 'new name'`
	* - `store.list.push('new item')`
	*/
	MutationType["direct"] = "direct";
	/**
	* Mutated the state with `$patch` and an object
	*
	* - `store.$patch({ name: 'newName' })`
	*/
	MutationType["patchObject"] = "patch object";
	/**
	* Mutated the state with `$patch` and a function
	*
	* - `store.$patch(state => state.name = 'newName')`
	*/
	MutationType["patchFunction"] = "patch function";
})(MutationType || (MutationType = {}));
var _global = typeof window === "object" && window.window === window ? window : typeof self === "object" && self.self === self ? self : typeof global === "object" && global.global === global ? global : typeof globalThis === "object" ? globalThis : { HTMLElement: null };
function bom(blob, { autoBom = false } = {}) {
	if (autoBom && /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) return new Blob([String.fromCharCode(65279), blob], { type: blob.type });
	return blob;
}
function download(url, name, opts) {
	const xhr = new XMLHttpRequest();
	xhr.open("GET", url);
	xhr.responseType = "blob";
	xhr.onload = function() {
		saveAs(xhr.response, name, opts);
	};
	xhr.onerror = function() {
		console.error("could not download file");
	};
	xhr.send();
}
function corsEnabled(url) {
	const xhr = new XMLHttpRequest();
	xhr.open("HEAD", url, false);
	try {
		xhr.send();
	} catch (e) {}
	return xhr.status >= 200 && xhr.status <= 299;
}
function click(node) {
	try {
		node.dispatchEvent(new MouseEvent("click"));
	} catch (e) {
		const evt = new MouseEvent("click", {
			bubbles: true,
			cancelable: true,
			view: window,
			detail: 0,
			screenX: 80,
			screenY: 20,
			clientX: 80,
			clientY: 20,
			ctrlKey: false,
			altKey: false,
			shiftKey: false,
			metaKey: false,
			button: 0,
			relatedTarget: null
		});
		node.dispatchEvent(evt);
	}
}
var _navigator = typeof navigator === "object" ? navigator : { userAgent: "" };
var isMacOSWebView = /Macintosh/.test(_navigator.userAgent) && /AppleWebKit/.test(_navigator.userAgent) && !/Safari/.test(_navigator.userAgent);
var saveAs = !IS_CLIENT ? () => {} : typeof HTMLAnchorElement !== "undefined" && "download" in HTMLAnchorElement.prototype && !isMacOSWebView ? downloadSaveAs : "msSaveOrOpenBlob" in _navigator ? msSaveAs : fileSaverSaveAs;
function downloadSaveAs(blob, name = "download", opts) {
	const a = document.createElement("a");
	a.download = name;
	a.rel = "noopener";
	if (typeof blob === "string") {
		a.href = blob;
		if (a.origin !== location.origin) if (corsEnabled(a.href)) download(blob, name, opts);
		else {
			a.target = "_blank";
			click(a);
		}
		else click(a);
	} else {
		a.href = URL.createObjectURL(blob);
		setTimeout(function() {
			URL.revokeObjectURL(a.href);
		}, 4e4);
		setTimeout(function() {
			click(a);
		}, 0);
	}
}
function msSaveAs(blob, name = "download", opts) {
	if (typeof blob === "string") if (corsEnabled(blob)) download(blob, name, opts);
	else {
		const a = document.createElement("a");
		a.href = blob;
		a.target = "_blank";
		setTimeout(function() {
			click(a);
		});
	}
	else navigator.msSaveOrOpenBlob(bom(blob, opts), name);
}
function fileSaverSaveAs(blob, name, opts, popup) {
	popup = popup || open("", "_blank");
	if (popup) popup.document.title = popup.document.body.innerText = "downloading...";
	if (typeof blob === "string") return download(blob, name, opts);
	const force = blob.type === "application/octet-stream";
	const isSafari = /constructor/i.test(String(_global.HTMLElement)) || "safari" in _global;
	const isChromeIOS = /CriOS\/[\d]+/.test(navigator.userAgent);
	if ((isChromeIOS || force && isSafari || isMacOSWebView) && typeof FileReader !== "undefined") {
		const reader = new FileReader();
		reader.onloadend = function() {
			let url = reader.result;
			if (typeof url !== "string") {
				popup = null;
				throw new Error("Wrong reader.result type");
			}
			url = isChromeIOS ? url : url.replace(/^data:[^;]*;/, "data:attachment/file;");
			if (popup) popup.location.href = url;
			else location.assign(url);
			popup = null;
		};
		reader.readAsDataURL(blob);
	} else {
		const url = URL.createObjectURL(blob);
		if (popup) popup.location.assign(url);
		else location.href = url;
		popup = null;
		setTimeout(function() {
			URL.revokeObjectURL(url);
		}, 4e4);
	}
}
var { assign: assign$1 } = Object;
/**
* Creates a Pinia instance to be used by the application
*/
function createPinia() {
	const scope = effectScope(true);
	const state = scope.run(() => /* @__PURE__ */ ref({}));
	let _p = [];
	let toBeInstalled = [];
	const pinia = markRaw({
		install(app) {
			setActivePinia(pinia);
			pinia._a = app;
			app.provide(piniaSymbol, pinia);
			app.config.globalProperties.$pinia = pinia;
			toBeInstalled.forEach((plugin) => _p.push(plugin));
			toBeInstalled = [];
		},
		use(plugin) {
			if (!this._a) toBeInstalled.push(plugin);
			else _p.push(plugin);
			return this;
		},
		_p,
		_a: null,
		_e: scope,
		_s: /* @__PURE__ */ new Map(),
		state
	});
	return pinia;
}
var noop = () => {};
function addSubscription(subscriptions, callback, detached, onCleanup = noop) {
	subscriptions.add(callback);
	const removeSubscription = () => {
		subscriptions.delete(callback) && onCleanup();
	};
	if (!detached && getCurrentScope()) onScopeDispose(removeSubscription);
	return removeSubscription;
}
function triggerSubscriptions(subscriptions, ...args) {
	subscriptions.forEach((callback) => {
		callback(...args);
	});
}
var fallbackRunWithContext = (fn) => fn();
/**
* Marks a function as an action for `$onAction`
* @internal
*/
var ACTION_MARKER = Symbol();
/**
* Action name symbol. Allows to add a name to an action after defining it
* @internal
*/
var ACTION_NAME = Symbol();
function mergeReactiveObjects(target, patchToApply) {
	if (target instanceof Map && patchToApply instanceof Map) patchToApply.forEach((value, key) => target.set(key, value));
	else if (target instanceof Set && patchToApply instanceof Set) patchToApply.forEach(target.add, target);
	for (const key in patchToApply) {
		if (!patchToApply.hasOwnProperty(key)) continue;
		const subPatch = patchToApply[key];
		const targetValue = target[key];
		if (isPlainObject(targetValue) && isPlainObject(subPatch) && target.hasOwnProperty(key) && !/* @__PURE__ */ isRef(subPatch) && !/* @__PURE__ */ isReactive(subPatch)) target[key] = mergeReactiveObjects(targetValue, subPatch);
		else target[key] = subPatch;
	}
	return target;
}
var skipHydrateSymbol = Symbol();
/**
* Returns whether a value should be hydrated
*
* @param obj - target variable
* @returns true if `obj` should be hydrated
*/
function shouldHydrate(obj) {
	return !isPlainObject(obj) || !Object.prototype.hasOwnProperty.call(obj, skipHydrateSymbol);
}
var { assign } = Object;
function isComputed(o) {
	return !!(/* @__PURE__ */ isRef(o) && o.effect);
}
function createOptionsStore(id, options, pinia, hot) {
	const { state, actions, getters } = options;
	const initialState = pinia.state.value[id];
	let store;
	function setup() {
		if (!initialState && true)
 /* istanbul ignore if */
		pinia.state.value[id] = state ? state() : {};
		return assign(/* @__PURE__ */ toRefs(pinia.state.value[id]), actions, Object.keys(getters || {}).reduce((computedGetters, name) => {
			computedGetters[name] = markRaw(computed(() => {
				setActivePinia(pinia);
				const store = pinia._s.get(id);
				return getters[name].call(store, store);
			}));
			return computedGetters;
		}, {}));
	}
	store = createSetupStore(id, setup, options, pinia, hot, true);
	return store;
}
function createSetupStore($id, setup, options = {}, pinia, hot, isOptionsStore) {
	let scope;
	const optionsForPlugin = assign({ actions: {} }, options);
	const $subscribeOptions = { deep: true };
	let isListening;
	let isSyncListening;
	let subscriptions = /* @__PURE__ */ new Set();
	let actionSubscriptions = /* @__PURE__ */ new Set();
	let debuggerEvents;
	const initialState = pinia.state.value[$id];
	if (!isOptionsStore && !initialState && true)
 /* istanbul ignore if */
	pinia.state.value[$id] = {};
	let activeListener;
	function $patch(partialStateOrMutator) {
		let subscriptionMutation;
		isListening = isSyncListening = false;
		if (typeof partialStateOrMutator === "function") {
			partialStateOrMutator(pinia.state.value[$id]);
			subscriptionMutation = {
				type: MutationType.patchFunction,
				storeId: $id,
				events: debuggerEvents
			};
		} else {
			mergeReactiveObjects(pinia.state.value[$id], partialStateOrMutator);
			subscriptionMutation = {
				type: MutationType.patchObject,
				payload: partialStateOrMutator,
				storeId: $id,
				events: debuggerEvents
			};
		}
		const myListenerId = activeListener = Symbol();
		nextTick().then(() => {
			if (activeListener === myListenerId) isListening = true;
		});
		isSyncListening = true;
		triggerSubscriptions(subscriptions, subscriptionMutation, pinia.state.value[$id]);
	}
	const $reset = isOptionsStore ? function $reset() {
		const { state } = options;
		const newState = state ? state() : {};
		this.$patch(($state) => {
			assign($state, newState);
		});
	} : noop;
	function $dispose() {
		scope.stop();
		subscriptions.clear();
		actionSubscriptions.clear();
		pinia._s.delete($id);
	}
	/**
	* Helper that wraps function so it can be tracked with $onAction
	* @param fn - action to wrap
	* @param name - name of the action
	*/
	const action = (fn, name = "") => {
		if (ACTION_MARKER in fn) {
			fn[ACTION_NAME] = name;
			return fn;
		}
		const wrappedAction = function() {
			setActivePinia(pinia);
			const args = Array.from(arguments);
			const afterCallbackSet = /* @__PURE__ */ new Set();
			const onErrorCallbackSet = /* @__PURE__ */ new Set();
			function after(callback) {
				afterCallbackSet.add(callback);
			}
			function onError(callback) {
				onErrorCallbackSet.add(callback);
			}
			triggerSubscriptions(actionSubscriptions, {
				args,
				name: wrappedAction[ACTION_NAME],
				store,
				after,
				onError
			});
			let ret;
			try {
				ret = fn.apply(this && this.$id === $id ? this : store, args);
			} catch (error) {
				triggerSubscriptions(onErrorCallbackSet, error);
				throw error;
			}
			if (ret instanceof Promise) return ret.then((value) => {
				triggerSubscriptions(afterCallbackSet, value);
				return value;
			}).catch((error) => {
				triggerSubscriptions(onErrorCallbackSet, error);
				return Promise.reject(error);
			});
			triggerSubscriptions(afterCallbackSet, ret);
			return ret;
		};
		wrappedAction[ACTION_MARKER] = true;
		wrappedAction[ACTION_NAME] = name;
		return wrappedAction;
	};
	const store = /* @__PURE__ */ reactive({
		_p: pinia,
		$id,
		$onAction: addSubscription.bind(null, actionSubscriptions),
		$patch,
		$reset,
		$subscribe(callback, options = {}) {
			const removeSubscription = addSubscription(subscriptions, callback, options.detached, () => stopWatcher());
			const stopWatcher = scope.run(() => watch(() => pinia.state.value[$id], (state) => {
				if (options.flush === "sync" ? isSyncListening : isListening) callback({
					storeId: $id,
					type: MutationType.direct,
					events: debuggerEvents
				}, state);
			}, assign({}, $subscribeOptions, options)));
			return removeSubscription;
		},
		$dispose
	});
	pinia._s.set($id, store);
	const setupStore = (pinia._a && pinia._a.runWithContext || fallbackRunWithContext)(() => pinia._e.run(() => (scope = effectScope()).run(() => setup({ action }))));
	for (const key in setupStore) {
		const prop = setupStore[key];
		if (/* @__PURE__ */ isRef(prop) && !isComputed(prop) || /* @__PURE__ */ isReactive(prop)) {
			if (!isOptionsStore) {
				if (initialState && shouldHydrate(prop)) if (/* @__PURE__ */ isRef(prop)) prop.value = initialState[key];
				else mergeReactiveObjects(prop, initialState[key]);
				pinia.state.value[$id][key] = prop;
			}
		} else if (typeof prop === "function") {
			setupStore[key] = action(prop, key);
			optionsForPlugin.actions[key] = prop;
		}
	}
	/* istanbul ignore if */
	assign(store, setupStore);
	assign(/* @__PURE__ */ toRaw(store), setupStore);
	Object.defineProperty(store, "$state", {
		get: () => pinia.state.value[$id],
		set: (state) => {
			$patch(($state) => {
				assign($state, state);
			});
		}
	});
	pinia._p.forEach((extender) => {
		assign(store, scope.run(() => extender({
			store,
			app: pinia._a,
			pinia,
			options: optionsForPlugin
		})));
	});
	if (initialState && isOptionsStore && options.hydrate) options.hydrate(store.$state, initialState);
	isListening = true;
	isSyncListening = true;
	return store;
}
/*! #__NO_SIDE_EFFECTS__ */
function defineStore(id, setup, setupOptions) {
	let options;
	const isSetupStore = typeof setup === "function";
	options = isSetupStore ? setupOptions : setup;
	function useStore(pinia, hot) {
		const hasContext = hasInjectionContext();
		pinia = pinia || (hasContext ? inject(piniaSymbol, null) : null);
		if (pinia) setActivePinia(pinia);
		pinia = activePinia;
		if (!pinia._s.has(id)) if (isSetupStore) createSetupStore(id, setup, options, pinia);
		else createOptionsStore(id, options, pinia);
		return pinia._s.get(id);
	}
	useStore.$id = id;
	return useStore;
}
//#endregion
//#region src/renderer/dev-mock.ts
var eventCallback = null;
function emitEvent(event) {
	if (eventCallback) eventCallback(event);
}
function simulateUpdateFlow() {
	const flow = new URLSearchParams(window.location.search).get("update-flow") ?? "happy";
	if (flow === "none") return;
	if (flow === "uptodate") {
		setTimeout(() => emitEvent({
			type: "update-check-result",
			upToDate: true
		}), 200);
		return;
	}
	if (flow === "manual") {
		setTimeout(() => emitEvent({
			type: "update-available",
			version: "3.0.0",
			asar: {
				url: "mock://app.asar",
				size: 14e6,
				sha256: "abc123"
			},
			fullPackageUrl: "mock://app.zip",
			requiresManualUpdate: true,
			releasedAt: (/* @__PURE__ */ new Date()).toISOString()
		}), 200);
		return;
	}
	setTimeout(() => emitEvent({
		type: "update-available",
		version: "3.0.0",
		asar: {
			url: "mock://app.asar",
			size: 14e6,
			sha256: "abc123"
		},
		fullPackageUrl: "mock://app.zip",
		requiresManualUpdate: false,
		releasedAt: (/* @__PURE__ */ new Date()).toISOString()
	}), 200);
}
function simulateDownloadFlow() {
	const flow = new URLSearchParams(window.location.search).get("update-flow") ?? "happy";
	const total = 14e6;
	const steps = 14;
	const errorAt = flow === "error" ? 8 : -1;
	for (let i = 1; i <= steps; i++) setTimeout(() => {
		if (i === errorAt) {
			emitEvent({
				type: "update-download-error",
				error: "Hash mismatch (simulation)",
				phase: "verify"
			});
			return;
		}
		if (errorAt !== -1 && i > errorAt) return;
		emitEvent({
			type: "update-download-progress",
			received: Math.round(total * i / steps),
			total,
			speedBps: 2e6
		});
		if (i === steps && errorAt === -1) setTimeout(() => emitEvent({
			type: "update-download-complete",
			version: "3.0.0"
		}), 100);
	}, i * 200);
}
function simulateApplyFlow() {
	setTimeout(() => emitEvent({
		type: "update-applied",
		previousVersion: "2.6.1",
		currentVersion: "3.0.0"
	}), 500);
}
var MOCK_DEVICES = [{
	serial: "MOCK-ABC123",
	status: "device",
	model: "Pico_G3",
	product: "A8210"
}, {
	serial: "MOCK-DEF456",
	status: "device",
	model: "Quest_3",
	product: "Quest"
}];
var handlers = {
	"detect-devices": () => MOCK_DEVICES,
	"detect-model": () => "PICO G3",
	"match-profile": () => ({
		model: "PICO G3",
		displayName: "Pico G3",
		type: "PICOG3",
		aliases: ["pico", "pico g3"],
		repo: "SerenityVR_PicoG3",
		packageName: "com.IRIS.SerenityVR6",
		commandFile: "pico_commands.txt",
		targetDir: "/storage/emulated/0/Android/data/com.IRIS.SerenityVR6/files/Serenity",
		description: "Casque VR Pico G3"
	}),
	"list-profiles": () => [{
		model: "PICO G3",
		displayName: "Pico G3",
		type: "PICOG3",
		aliases: ["A8210"],
		repo: "SerenityVR_PicoG3",
		packageName: "com.IRIS.SerenityVR6",
		commandFile: "pico_commands.txt",
		targetDir: "/storage/emulated/0/Android/data/com.IRIS.SerenityVR6/files/Serenity",
		description: ""
	}],
	"save-custom-profile": () => true,
	"delete-custom-profile": () => true,
	"list-command-files": () => ["pico_commands.txt", "tablet_commands.txt"],
	"load-products": () => [
		"WellnessHQ",
		"AtmosphAirHQ2025",
		"HorizonSmartHQ2025"
	],
	"load-qualities": () => [
		"HQ_foveated",
		"mHQ",
		"DEV"
	],
	"calculate-assets": (_product, _quality, _type, _extra, _serial, policy, _sdcard) => ({
		requiredFiles: [
			"serenityapp",
			"serenityFileName.env",
			"video1.mp4",
			"video2.mp4",
			"video3.mp4"
		],
		missingFiles: [],
		serenityFiles: [{
			fileName: "serenityapp",
			sourcePath: "/mock/products/WellnessHQ/serenityapp",
			size: 4096
		}, {
			fileName: "serenityFileName.env",
			sourcePath: "/mock/products/WellnessHQ/serenityFileName.env",
			size: 256
		}],
		fileSizes: {
			"serenityapp": 4096,
			"serenityFileName.env": 256,
			"video1.mp4": 5e8,
			"video2.mp4": 5e8,
			"video3.mp4": 5e8
		},
		totalSize: 15e8,
		totalCount: 5,
		existingOnDeviceSize: 5e8,
		existingOnDeviceCount: 1,
		netTransferSize: policy === "overwrite-all" ? 15e8 : 1e9,
		duplicateFiles: _sdcard ? ["video1.mp4"] : [],
		sourceDuplicates: [{
			baseName: "video2.mp4",
			variants: ["1_video2.mp4", "2_video2.mp4"]
		}]
	}),
	"detect-storage": () => [{
		type: "internal",
		path: "/storage/emulated/0",
		displayName: "Stockage interne",
		totalBytes: 64e9,
		usedBytes: 2e10,
		availableBytes: 44e9
	}],
	"fetch-releases": () => [{
		tagName: "v8.1.0",
		name: "Release 8.1.0",
		publishedAt: "2026-03-15T00:00:00Z"
	}, {
		tagName: "v8.0.7",
		name: "Release 8.0.7",
		publishedAt: "2026-02-01T00:00:00Z"
	}],
	"find-incomplete-session": () => null,
	"start-execution": (plan) => {
		const tabId = plan?.tabId ?? "default";
		setTimeout(() => emitEvent({
			type: "progress",
			tabId,
			phase: "download",
			icon: "⬇",
			message: "Téléchargement de l'APK..."
		}), 300);
		setTimeout(() => emitEvent({
			type: "progress",
			tabId,
			phase: "install",
			icon: "📦",
			message: "Installation de l'APK..."
		}), 800);
		setTimeout(() => emitEvent({
			type: "progress",
			tabId,
			phase: "configure",
			icon: "⚙",
			message: "Configuration de l'appareil..."
		}), 1200);
		setTimeout(() => emitEvent({
			type: "progress",
			tabId,
			phase: "transfer",
			icon: "📁",
			message: "Transfert des fichiers..."
		}), 1600);
		setTimeout(() => emitEvent({
			type: "file-complete",
			tabId,
			fileName: "video1.mp4",
			size: 5e8,
			completed: 1,
			total: 3
		}), 2200);
		setTimeout(() => emitEvent({
			type: "file-complete",
			tabId,
			fileName: "video2.mp4",
			size: 5e8,
			completed: 2,
			total: 3
		}), 2800);
		setTimeout(() => emitEvent({
			type: "file-complete",
			tabId,
			fileName: "video3.mp4",
			size: 5e8,
			completed: 3,
			total: 3
		}), 3400);
		setTimeout(() => emitEvent({
			type: "execution-done",
			tabId,
			success: true,
			message: "Installation terminée avec succès",
			stats: {
				filesTransferred: 3,
				filesSkipped: 0,
				filesFailed: 0,
				filesMissing: 0,
				bytesTransferred: 15e8,
				durationSeconds: 4
			}
		}), 4e3);
	},
	"stop-execution": () => {},
	"select-local-apk": () => "/mock/path/to/SerenityVR.apk",
	"select-directory": () => "/mock/selected/directory",
	"select-file": () => "/mock/selected/file.mp4",
	"get-config": () => ({
		mediaTransfer: {
			parentSourceDirVR: "/Users/mock/ASSETS/Videos",
			parentSourceDirTAB: "/Users/mock/ASSETS/Viewers",
			sourceDirOldVR: "/Users/mock/ASSETS/Videos",
			sourceDirOldTablet: "/Users/mock/ASSETS/Viewers",
			parentFileListDir: "/Users/mock/ASSETS/Serenity",
			deviceTargets: {}
		},
		transferLogging: {
			logDirectory: "./logs",
			enableChecksum: true,
			maxRetries: 3,
			verifyAfterTransfer: true
		},
		timeouts: {
			adbDefaultMs: 3e4,
			md5TimeoutMs: 3e5,
			pushTimeoutMs: 6e5,
			installTimeoutMs: 12e4
		},
		performance: { maxConcurrentVerify: 2 },
		webhooks: { resultDigestUrl: "" },
		github: {
			owner: "SerenityIris",
			maxReleasesToShow: 10
		},
		productFilter: { excludePatterns: ["2024"] },
		packageNames: {}
	}),
	"save-config": () => true,
	"get-config-path": () => "/mock/config.json",
	"get-app-version": () => "2.4.0",
	"open-external": () => {},
	"show-item-in-folder": () => {},
	"transfer-single-file": () => true,
	"check-for-updates": () => {
		simulateUpdateFlow();
	},
	"updater:start-download": () => {
		simulateDownloadFlow();
	},
	"updater:cancel-download": () => {},
	"updater:apply-update": () => {
		simulateApplyFlow();
	},
	"updater:get-state": () => ({
		phase: "idle",
		received: 0,
		total: 0,
		speedBps: 0
	}),
	"launch-app": () => {},
	"force-stop-app": () => {},
	"read-device-file": () => "Rapport de vérification\n========================\nFichiers vérifiés: 42\nFichiers manquants: 0\nÉtat: OK\n",
	"get-device-report-path": () => "/sdcard/Android/data/com.IRIS.SerenityVR6/files/report.log",
	"delete-device-file": () => true,
	"file-exists-on-device": () => true,
	"clear-apk-cache": () => 3,
	"clear-completed-sessions": () => 5,
	"clear-incomplete-sessions": () => 1,
	"clear-md5-cache": () => true,
	"send-result-digest": () => ({
		success: true,
		status: 200
	}),
	"scan-device-duplicates": () => ({
		crossStorage: [{
			baseName: "1_video1.mp4",
			files: [{
				fileName: "1_video1.mp4",
				path: "/storage/emulated/0/data/1_video1.mp4",
				storage: "internal",
				size: 5e8
			}, {
				fileName: "1_video1.mp4",
				path: "/storage/ABCD-1234/data/1_video1.mp4",
				storage: "sdcard",
				size: 5e8
			}]
		}],
		prefixVariants: [{
			baseName: "video2.mp4",
			files: [{
				fileName: "1_video2.mp4",
				path: "/storage/emulated/0/data/1_video2.mp4",
				storage: "internal",
				size: 4e8
			}, {
				fileName: "2_video2.mp4",
				path: "/storage/emulated/0/data/2_video2.mp4",
				storage: "internal",
				size: 4e8
			}]
		}],
		totalFiles: 10,
		internalCount: 7,
		sdcardCount: 3
	})
};
function installDevMock() {
	if (window.api) return;
	console.log("[dev-mock] Installing window.api mock (not running in Electron)");
	window.api = {
		invoke: async (channel, ...args) => {
			console.log(`[dev-mock] invoke: ${channel}`, args);
			const handler = handlers[channel];
			if (handler) return handler(...args);
			console.warn(`[dev-mock] No handler for channel: ${channel}`);
		},
		onEvent: (callback) => {
			eventCallback = callback;
		},
		removeEventListeners: () => {
			eventCallback = null;
		}
	};
}
//#endregion
//#region src/renderer/styles/theme.ts
var STEP_LABELS = [
	"Appareil",
	"Configuration",
	"Contenu",
	"Version",
	"Résumé",
	"Installation",
	"Résultats"
];
//#endregion
//#region src/renderer/stores/installation.ts
var TAB_STORE_KEY = "tab-store";
function createInstallationStore(tabId) {
	return defineStore(`installation-${tabId}`, () => {
		return _createStoreBody(/* @__PURE__ */ ref(tabId));
	})();
}
var _defaultStore = null;
function useInstallationStore() {
	const injected = inject(TAB_STORE_KEY, null);
	if (injected) return injected;
	if (!_defaultStore) _defaultStore = createInstallationStore("default");
	return _defaultStore;
}
function _createStoreBody(_tabId) {
	const currentStep = /* @__PURE__ */ ref(0);
	const totalSteps = STEP_LABELS.length;
	const devices = /* @__PURE__ */ ref([]);
	const selectedDeviceId = /* @__PURE__ */ ref();
	const recentDevices = /* @__PURE__ */ ref(JSON.parse(localStorage.getItem("recentDevices") ?? "[]"));
	const showCancelConfirm = /* @__PURE__ */ ref(false);
	const detectedDeviceModel = /* @__PURE__ */ ref();
	const deviceProfile = /* @__PURE__ */ ref();
	const deviceName = /* @__PURE__ */ ref("");
	const networkName = /* @__PURE__ */ ref("");
	const storageLocations = /* @__PURE__ */ ref([]);
	const storageMode = /* @__PURE__ */ ref("internal");
	const storagePriority = /* @__PURE__ */ ref("internal-first");
	const hasSufficientStorage = /* @__PURE__ */ ref(true);
	const product = /* @__PURE__ */ ref();
	const quality = /* @__PURE__ */ ref();
	const transferPolicy = /* @__PURE__ */ ref("update-different");
	const enableVerification = /* @__PURE__ */ ref(true);
	const skipFiles = /* @__PURE__ */ ref(false);
	const releaseTag = /* @__PURE__ */ ref();
	const releaseDate = /* @__PURE__ */ ref();
	const releaseName = /* @__PURE__ */ ref();
	const skipApk = /* @__PURE__ */ ref(false);
	const localApkPath = /* @__PURE__ */ ref();
	const extraSearchDirs = /* @__PURE__ */ ref([]);
	const totalAssetSize = /* @__PURE__ */ ref(0);
	const netTransferSize = /* @__PURE__ */ ref(0);
	const existingOnDeviceSize = /* @__PURE__ */ ref(0);
	const existingOnDeviceCount = /* @__PURE__ */ ref(0);
	const totalFileCount = /* @__PURE__ */ ref(0);
	const requiredFiles = /* @__PURE__ */ ref([]);
	const missingFiles = /* @__PURE__ */ ref([]);
	const serenityFiles = /* @__PURE__ */ ref([]);
	const fileSizes = /* @__PURE__ */ ref({});
	const duplicateFiles = /* @__PURE__ */ ref([]);
	const sourceDuplicates = /* @__PURE__ */ ref([]);
	const isExecuting = /* @__PURE__ */ ref(false);
	const executionPhase = /* @__PURE__ */ ref("");
	const executionMessage = /* @__PURE__ */ ref("");
	const executionStats = /* @__PURE__ */ ref(null);
	const executionSuccess = /* @__PURE__ */ ref(null);
	const deviceDisconnected = /* @__PURE__ */ ref(false);
	const logEntries = /* @__PURE__ */ ref([]);
	const filesCompleted = /* @__PURE__ */ ref(0);
	const filesTotal = /* @__PURE__ */ ref(0);
	const filesSkippedCount = /* @__PURE__ */ ref(0);
	const fileStatuses = /* @__PURE__ */ ref(/* @__PURE__ */ new Map());
	const fileErrors = /* @__PURE__ */ ref(/* @__PURE__ */ new Map());
	const filesMissingCount = /* @__PURE__ */ ref(0);
	const filesVerifiedCount = /* @__PURE__ */ ref(0);
	const filesVerifyFailedCount = /* @__PURE__ */ ref(0);
	const bytesTransferred = /* @__PURE__ */ ref(0);
	const executionStartedAt = /* @__PURE__ */ ref(null);
	const selectedDevice = computed(() => devices.value.find((d) => d.serial === selectedDeviceId.value));
	const isVR = computed(() => deviceProfile.value?.type === "PICOG3" || deviceProfile.value?.type === "QUEST3");
	const canGoNext = computed(() => {
		switch (currentStep.value) {
			case 0: return !!selectedDeviceId.value;
			case 1: return !!deviceProfile.value && deviceName.value.length > 0;
			case 2: return skipFiles.value || !!product.value && !!quality.value;
			case 3: return skipApk.value || !!releaseTag.value;
			case 4: return true;
			default: return false;
		}
	});
	const canGoBack = computed(() => currentStep.value > 0 && currentStep.value < 5);
	function nextStep() {
		if (currentStep.value < totalSteps - 1 && canGoNext.value) currentStep.value++;
	}
	function prevStep() {
		if (canGoBack.value) currentStep.value--;
	}
	function goToStep(step) {
		if (step >= 0 && step < totalSteps) currentStep.value = step;
	}
	function addLogEntry(message, type = "info") {
		const time = (/* @__PURE__ */ new Date()).toLocaleTimeString("fr-FR");
		logEntries.value.push({
			time,
			message,
			type
		});
		if (logEntries.value.length > 2e3) logEntries.value = logEntries.value.slice(-2e3);
	}
	function saveRecentDevice(serial, model, profileName) {
		const existing = recentDevices.value.filter((d) => d.serial !== serial);
		recentDevices.value = [{
			serial,
			model,
			profileName
		}, ...existing].slice(0, 5);
		localStorage.setItem("recentDevices", JSON.stringify(recentDevices.value));
	}
	function handleIpcEvent(event) {
		switch (event.type) {
			case "devices-changed":
				devices.value = event.devices;
				if (selectedDeviceId.value && !event.devices.some((d) => d.serial === selectedDeviceId.value && d.status === "device")) deviceDisconnected.value = true;
				if (deviceDisconnected.value && selectedDeviceId.value && event.devices.some((d) => d.serial === selectedDeviceId.value && d.status === "device")) deviceDisconnected.value = false;
				break;
			case "progress":
				executionPhase.value = event.phase;
				executionMessage.value = event.message;
				if (!executionStartedAt.value) executionStartedAt.value = Date.now();
				addLogEntry(event.message, "info");
				break;
			case "file-status":
				fileStatuses.value = new Map(fileStatuses.value.set(event.fileName, event.status));
				break;
			case "file-complete":
				filesCompleted.value = event.completed;
				filesTotal.value = event.total;
				bytesTransferred.value += event.size;
				break;
			case "file-verified":
				if (event.match) {
					filesVerifiedCount.value++;
					addLogEntry(`Vérifié: ${event.fileName} (host=${event.hostMd5} device=${event.deviceMd5 ?? "n/a"})`, "success");
				} else {
					filesVerifyFailedCount.value++;
					fileErrors.value = new Map(fileErrors.value.set(event.fileName, `MD5 mismatch: host=${event.hostMd5} device=${event.deviceMd5 ?? "n/a"}`));
					addLogEntry(`Vérification échouée: ${event.fileName} (host=${event.hostMd5} device=${event.deviceMd5 ?? "n/a"})`, "error");
				}
				break;
			case "file-skip":
				if (event.reason.includes("manquant")) {
					filesMissingCount.value++;
					addLogEntry(`Manquant: ${event.fileName}`, "error");
				} else {
					filesSkippedCount.value++;
					addLogEntry(`Ignoré: ${event.fileName} — ${event.reason}`, "info");
				}
				break;
			case "file-error":
				fileErrors.value = new Map(fileErrors.value.set(event.fileName, event.error));
				addLogEntry(`Erreur: ${event.fileName} — ${event.error}`, "error");
				if (event.error.includes("déconnecté")) deviceDisconnected.value = true;
				break;
			case "execution-done":
				isExecuting.value = false;
				executionSuccess.value = event.success;
				executionMessage.value = event.message;
				executionStats.value = event.stats;
				addLogEntry(event.message, event.success ? "success" : "error");
				currentStep.value = 6;
				break;
			case "storage-full":
				addLogEntry(`Stockage plein: ${event.context.fileName}`, "error");
				break;
		}
	}
	function reset() {
		currentStep.value = 0;
		devices.value = [];
		selectedDeviceId.value = void 0;
		detectedDeviceModel.value = void 0;
		deviceProfile.value = void 0;
		deviceName.value = "";
		networkName.value = "";
		storageLocations.value = [];
		storageMode.value = "internal";
		storagePriority.value = "internal-first";
		hasSufficientStorage.value = true;
		product.value = void 0;
		quality.value = void 0;
		transferPolicy.value = "update-different";
		enableVerification.value = true;
		skipFiles.value = false;
		releaseTag.value = void 0;
		releaseDate.value = void 0;
		releaseName.value = void 0;
		skipApk.value = false;
		localApkPath.value = void 0;
		showCancelConfirm.value = false;
		totalAssetSize.value = 0;
		netTransferSize.value = 0;
		existingOnDeviceSize.value = 0;
		existingOnDeviceCount.value = 0;
		totalFileCount.value = 0;
		extraSearchDirs.value = [];
		requiredFiles.value = [];
		missingFiles.value = [];
		serenityFiles.value = [];
		fileSizes.value = {};
		duplicateFiles.value = [];
		sourceDuplicates.value = [];
		isExecuting.value = false;
		executionPhase.value = "";
		executionMessage.value = "";
		executionStats.value = null;
		executionSuccess.value = null;
		deviceDisconnected.value = false;
		logEntries.value = [];
		filesCompleted.value = 0;
		filesTotal.value = 0;
		filesSkippedCount.value = 0;
		filesMissingCount.value = 0;
		filesVerifiedCount.value = 0;
		filesVerifyFailedCount.value = 0;
		bytesTransferred.value = 0;
		executionStartedAt.value = null;
		fileStatuses.value = /* @__PURE__ */ new Map();
		fileErrors.value = /* @__PURE__ */ new Map();
	}
	return {
		tabId: _tabId,
		currentStep,
		totalSteps,
		devices,
		selectedDeviceId,
		recentDevices,
		showCancelConfirm,
		detectedDeviceModel,
		deviceProfile,
		deviceName,
		networkName,
		storageLocations,
		storageMode,
		storagePriority,
		hasSufficientStorage,
		product,
		quality,
		transferPolicy,
		enableVerification,
		skipFiles,
		releaseTag,
		releaseDate,
		releaseName,
		skipApk,
		localApkPath,
		extraSearchDirs,
		totalAssetSize,
		netTransferSize,
		existingOnDeviceSize,
		existingOnDeviceCount,
		totalFileCount,
		requiredFiles,
		missingFiles,
		serenityFiles,
		fileSizes,
		duplicateFiles,
		sourceDuplicates,
		isExecuting,
		executionPhase,
		executionMessage,
		executionStats,
		executionSuccess,
		deviceDisconnected,
		logEntries,
		filesCompleted,
		filesTotal,
		filesSkippedCount,
		filesMissingCount,
		filesVerifiedCount,
		filesVerifyFailedCount,
		bytesTransferred,
		executionStartedAt,
		fileStatuses,
		fileErrors,
		selectedDevice,
		isVR,
		canGoNext,
		canGoBack,
		nextStep,
		prevStep,
		goToStep,
		addLogEntry,
		handleIpcEvent,
		saveRecentDevice,
		reset
	};
}
//#endregion
//#region src/renderer/stores/tabs.ts
var tabCounter = 0;
function generateTabId() {
	return `tab-${Date.now()}-${++tabCounter}`;
}
var useTabsStore = defineStore("tabs", () => {
	const tabs = /* @__PURE__ */ ref([]);
	const activeTabId = /* @__PURE__ */ ref("");
	const storeInstances = /* @__PURE__ */ new Map();
	function createTab() {
		const id = generateTabId();
		tabs.value.push({
			id,
			createdAt: Date.now()
		});
		const store = createInstallationStore(id);
		storeInstances.set(id, store);
		activeTabId.value = id;
		return id;
	}
	function closeTab(tabId) {
		const store = storeInstances.get(tabId);
		if (store) {
			if (store.isExecuting) window.api.invoke("stop-execution", tabId);
			store.$dispose();
			storeInstances.delete(tabId);
		}
		tabs.value = tabs.value.filter((t) => t.id !== tabId);
		if (activeTabId.value === tabId) activeTabId.value = tabs.value.length > 0 ? tabs.value[tabs.value.length - 1].id : "";
		if (tabs.value.length === 0) createTab();
	}
	function switchTab(tabId) {
		if (storeInstances.has(tabId)) activeTabId.value = tabId;
	}
	function getTabStore(tabId) {
		return storeInstances.get(tabId);
	}
	function getAllStores() {
		return Array.from(storeInstances.values());
	}
	const activeStore = computed(() => {
		return storeInstances.get(activeTabId.value);
	});
	function devicesInUseExcept(tabId) {
		const inUse = /* @__PURE__ */ new Set();
		for (const [id, store] of storeInstances) if (id !== tabId && store.selectedDeviceId) inUse.add(store.selectedDeviceId);
		return inUse;
	}
	function getTabLabel(tabId) {
		const store = storeInstances.get(tabId);
		if (!store) return "Onglet";
		const type = store.deviceProfile?.displayName ?? "";
		const name = store.deviceName || "N/D";
		if (type) return `${type} — ${name}`;
		return STEP_LABELS[store.currentStep] ?? "Nouveau";
	}
	function isTabExecuting(tabId) {
		return storeInstances.get(tabId)?.isExecuting ?? false;
	}
	return {
		tabs,
		activeTabId,
		createTab,
		closeTab,
		switchTab,
		getTabStore,
		getAllStores,
		activeStore,
		devicesInUseExcept,
		getTabLabel,
		isTabExecuting
	};
});
//#endregion
//#region src/renderer/stores/update.ts
var useUpdateStore = defineStore("update", () => {
	const phase = /* @__PURE__ */ ref("idle");
	const version = /* @__PURE__ */ ref();
	const asar = /* @__PURE__ */ ref();
	const fullPackageUrl = /* @__PURE__ */ ref();
	const releasedAt = /* @__PURE__ */ ref();
	const notes = /* @__PURE__ */ ref();
	const received = /* @__PURE__ */ ref(0);
	const total = /* @__PURE__ */ ref(0);
	const speedBps = /* @__PURE__ */ ref(0);
	const errorMessage = /* @__PURE__ */ ref();
	const errorPhase = /* @__PURE__ */ ref();
	const upToDateFlash = /* @__PURE__ */ ref(false);
	const appliedToast = /* @__PURE__ */ ref(null);
	const progressPercent = computed(() => {
		if (!total.value) return 0;
		return Math.min(100, Math.round(received.value / total.value * 100));
	});
	const isBannerVisible = computed(() => phase.value === "available" || phase.value === "downloading" || phase.value === "verifying" || phase.value === "ready" || phase.value === "applying" || phase.value === "error" || phase.value === "manual-required");
	function handleIpcEvent(event) {
		switch (event.type) {
			case "update-available":
				phase.value = event.requiresManualUpdate ? "manual-required" : "available";
				version.value = event.version;
				asar.value = event.asar;
				fullPackageUrl.value = event.fullPackageUrl;
				releasedAt.value = event.releasedAt;
				notes.value = event.notes;
				received.value = 0;
				total.value = event.asar.size;
				speedBps.value = 0;
				errorMessage.value = void 0;
				errorPhase.value = void 0;
				return true;
			case "update-check-result":
				if (event.upToDate) {
					upToDateFlash.value = true;
					setTimeout(() => {
						upToDateFlash.value = false;
					}, 3e3);
				} else upToDateFlash.value = false;
				return true;
			case "update-download-progress":
				phase.value = "downloading";
				received.value = event.received;
				total.value = event.total;
				speedBps.value = event.speedBps;
				return true;
			case "update-download-complete":
				phase.value = "ready";
				received.value = total.value;
				speedBps.value = 0;
				return true;
			case "update-download-error":
				phase.value = "error";
				errorMessage.value = event.error;
				errorPhase.value = event.phase;
				return true;
			case "update-apply-error":
				phase.value = "error";
				errorMessage.value = event.error;
				errorPhase.value = "apply";
				return true;
			case "update-applied":
				appliedToast.value = {
					previous: event.previousVersion,
					current: event.currentVersion
				};
				setTimeout(() => {
					appliedToast.value = null;
				}, 6e3);
				return true;
			default: return false;
		}
	}
	async function startDownload() {
		if (phase.value !== "available" && phase.value !== "error") return;
		await window.api.invoke("updater:start-download");
	}
	async function cancelDownload() {
		await window.api.invoke("updater:cancel-download");
	}
	async function applyUpdate() {
		if (phase.value !== "ready") return;
		await window.api.invoke("updater:apply-update");
	}
	async function checkNow() {
		await window.api.invoke("check-for-updates");
	}
	async function openManualDownload() {
		if (fullPackageUrl.value) await window.api.invoke("open-external", fullPackageUrl.value);
	}
	function dismissError() {
		if (phase.value === "error") {
			phase.value = asar.value ? "available" : "idle";
			errorMessage.value = void 0;
			errorPhase.value = void 0;
		}
	}
	return {
		phase,
		version,
		asar,
		fullPackageUrl,
		releasedAt,
		notes,
		received,
		total,
		speedBps,
		errorMessage,
		errorPhase,
		upToDateFlash,
		appliedToast,
		progressPercent,
		isBannerVisible,
		handleIpcEvent,
		startDownload,
		cancelDownload,
		applyUpdate,
		checkNow,
		openManualDownload,
		dismissError
	};
});
//#endregion
//#region src/renderer/utils/format.ts
/**
* Format a byte count into a human-readable French string.
* Handles undefined/0 gracefully. Full range: o → Ko → Mo → Go.
*/
function formatSize(bytes) {
	if (bytes == null || bytes === 0) return "0 o";
	if (bytes < 1024) return bytes + " o";
	if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(0) + " Ko";
	if (bytes < 1024 * 1024 * 1024) return (bytes / (1024 * 1024)).toFixed(1) + " Mo";
	return (bytes / (1024 * 1024 * 1024)).toFixed(2) + " Go";
}
//#endregion
//#region src/renderer/components/StatusStrip.vue?vue&type=script&setup=true&lang.ts
var _hoisted_1$20 = {
	key: 0,
	class: "strip"
};
var _hoisted_2$20 = { class: "strip-row" };
var _hoisted_3$17 = {
	key: 0,
	class: "strip-item"
};
var _hoisted_4$15 = {
	key: 1,
	class: "strip-item"
};
var _hoisted_5$15 = { class: "strip-row" };
var _hoisted_6$13 = {
	key: 0,
	class: "strip-item"
};
var _hoisted_7$12 = {
	key: 1,
	class: "strip-item"
};
var _hoisted_8$11 = {
	key: 2,
	class: "strip-item"
};
var StatusStrip_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
	__name: "StatusStrip",
	setup(__props) {
		const store = useInstallationStore();
		const deviceLabel = computed(() => {
			if (!store.deviceProfile) return "";
			const serial = store.selectedDeviceId ?? "";
			return `${store.deviceProfile.displayName} (${serial})`;
		});
		const nameLabel = computed(() => {
			const parts = [];
			if (store.deviceName) parts.push(store.deviceName);
			if (store.networkName) parts.push(store.networkName);
			return parts.join("/");
		});
		const contentLabel = computed(() => {
			const parts = [];
			if (store.product) parts.push(store.product);
			if (store.quality) parts.push(store.quality);
			return parts.join("/");
		});
		const sizeLabel = computed(() => store.netTransferSize > 0 ? formatSize(store.netTransferSize) : "");
		return (_ctx, _cache) => {
			return unref(store).selectedDeviceId ? (openBlock(), createElementBlock("div", _hoisted_1$20, [createBaseVNode("div", _hoisted_2$20, [deviceLabel.value ? (openBlock(), createElementBlock("span", _hoisted_3$17, "📱 " + toDisplayString(deviceLabel.value), 1)) : createCommentVNode("", true), nameLabel.value ? (openBlock(), createElementBlock("span", _hoisted_4$15, "🏷 " + toDisplayString(nameLabel.value), 1)) : createCommentVNode("", true)]), createBaseVNode("div", _hoisted_5$15, [
				contentLabel.value ? (openBlock(), createElementBlock("span", _hoisted_6$13, "📁 " + toDisplayString(contentLabel.value), 1)) : createCommentVNode("", true),
				unref(store).releaseTag ? (openBlock(), createElementBlock("span", _hoisted_7$12, "📦 " + toDisplayString(unref(store).releaseTag), 1)) : createCommentVNode("", true),
				sizeLabel.value ? (openBlock(), createElementBlock("span", _hoisted_8$11, "💾 " + toDisplayString(sizeLabel.value), 1)) : createCommentVNode("", true)
			])])) : createCommentVNode("", true);
		};
	}
});
//#endregion
//#region \0plugin-vue:export-helper
var _plugin_vue_export_helper_default = (sfc, props) => {
	const target = sfc.__vccOpts || sfc;
	for (const [key, val] of props) target[key] = val;
	return target;
};
//#endregion
//#region src/renderer/components/StatusStrip.vue
var StatusStrip_default = /* @__PURE__ */ _plugin_vue_export_helper_default(StatusStrip_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-cc9807ea"]]);
//#endregion
//#region src/renderer/components/StepIndicator.vue?vue&type=script&setup=true&lang.ts
var _hoisted_1$19 = { class: "steps" };
var _hoisted_2$19 = [
	"disabled",
	"title",
	"onClick"
];
//#endregion
//#region src/renderer/components/StepIndicator.vue
var StepIndicator_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
	__name: "StepIndicator",
	props: { currentStep: {} },
	emits: ["jump"],
	setup(__props, { emit: __emit }) {
		const emit = __emit;
		const navSteps = STEP_LABELS.slice(0, 5);
		return (_ctx, _cache) => {
			return openBlock(), createElementBlock("div", _hoisted_1$19, [(openBlock(true), createElementBlock(Fragment, null, renderList(unref(navSteps), (label, i) => {
				return openBlock(), createElementBlock(Fragment, { key: i }, [createBaseVNode("button", {
					class: normalizeClass(["step-dot", {
						"step-active": i === __props.currentStep,
						"step-done": i < __props.currentStep,
						"step-future": i > __props.currentStep
					}]),
					disabled: i > __props.currentStep,
					title: label,
					onClick: ($event) => emit("jump", i)
				}, toDisplayString(i + 1), 11, _hoisted_2$19), i < unref(navSteps).length - 1 ? (openBlock(), createElementBlock("div", {
					key: 0,
					class: normalizeClass(["step-line", { "line-done": i < __props.currentStep }])
				}, null, 2)) : createCommentVNode("", true)], 64);
			}), 128))]);
		};
	}
}), [["__scopeId", "data-v-428a5599"]]);
//#endregion
//#region src/renderer/components/ConfirmDialog.vue?vue&type=script&setup=true&lang.ts
var _hoisted_1$18 = { class: "dialog" };
var _hoisted_2$18 = { class: "dialog-message" };
var _hoisted_3$16 = { class: "dialog-actions" };
//#endregion
//#region src/renderer/components/ConfirmDialog.vue
var ConfirmDialog_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
	__name: "ConfirmDialog",
	props: { message: {} },
	emits: ["confirm", "cancel"],
	setup(__props, { emit: __emit }) {
		const emit = __emit;
		return (_ctx, _cache) => {
			return openBlock(), createElementBlock("div", {
				class: "overlay",
				onClick: _cache[2] || (_cache[2] = withModifiers(($event) => emit("cancel"), ["self"]))
			}, [createBaseVNode("div", _hoisted_1$18, [createBaseVNode("p", _hoisted_2$18, toDisplayString(__props.message), 1), createBaseVNode("div", _hoisted_3$16, [createBaseVNode("button", {
				class: "btn btn-no",
				onClick: _cache[0] || (_cache[0] = ($event) => emit("cancel"))
			}, "Non"), createBaseVNode("button", {
				class: "btn btn-yes",
				onClick: _cache[1] || (_cache[1] = ($event) => emit("confirm"))
			}, "Oui")])])]);
		};
	}
}), [["__scopeId", "data-v-05d58638"]]);
//#endregion
//#region src/renderer/components/UpdateBanner.vue?vue&type=script&setup=true&lang.ts
var _hoisted_1$17 = { class: "msg" };
var _hoisted_2$17 = { class: "msg" };
var _hoisted_3$15 = { class: "msg" };
var _hoisted_4$14 = {
	key: 3,
	class: "msg"
};
var _hoisted_5$14 = { class: "msg error" };
var _hoisted_6$12 = { class: "msg" };
//#endregion
//#region src/renderer/components/UpdateBanner.vue
var UpdateBanner_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
	__name: "UpdateBanner",
	setup(__props) {
		const update = useUpdateStore();
		function formatMB(bytes) {
			return (bytes / 1024 / 1024).toFixed(1);
		}
		function formatSpeed(bps) {
			if (!bps) return "";
			if (bps > 1024 * 1024) return `${(bps / 1024 / 1024).toFixed(1)} Mo/s`;
			return `${Math.round(bps / 1024)} Ko/s`;
		}
		const downloadLabel = computed(() => {
			const rx = formatMB(update.received);
			const tx = formatMB(update.total);
			const speed = formatSpeed(update.speedBps);
			return `Téléchargement: ${rx} / ${tx} Mo${speed ? " — " + speed : ""}`;
		});
		return (_ctx, _cache) => {
			return unref(update).isBannerVisible ? (openBlock(), createElementBlock("div", {
				key: 0,
				class: normalizeClass(["update-banner", `phase-${unref(update).phase}`])
			}, [unref(update).phase === "available" ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createBaseVNode("span", _hoisted_1$17, "Nouvelle version " + toDisplayString(unref(update).version) + " disponible", 1), createBaseVNode("button", {
				class: "btn-update",
				onClick: _cache[0] || (_cache[0] = ($event) => unref(update).startDownload())
			}, "Mettre à jour")], 64)) : unref(update).phase === "downloading" || unref(update).phase === "verifying" ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [
				createBaseVNode("span", _hoisted_2$17, toDisplayString(unref(update).phase === "verifying" ? "Vérification…" : downloadLabel.value), 1),
				createBaseVNode("button", {
					class: "btn-cancel",
					onClick: _cache[1] || (_cache[1] = ($event) => unref(update).cancelDownload())
				}, "Annuler"),
				createBaseVNode("div", {
					class: "progress",
					style: normalizeStyle({ width: unref(update).progressPercent + "%" })
				}, null, 4)
			], 64)) : unref(update).phase === "ready" ? (openBlock(), createElementBlock(Fragment, { key: 2 }, [createBaseVNode("span", _hoisted_3$15, "Mise à jour " + toDisplayString(unref(update).version) + " prête", 1), createBaseVNode("button", {
				class: "btn-update",
				onClick: _cache[2] || (_cache[2] = ($event) => unref(update).applyUpdate())
			}, "Redémarrer maintenant")], 64)) : unref(update).phase === "applying" ? (openBlock(), createElementBlock("span", _hoisted_4$14, "Application de la mise à jour…")) : unref(update).phase === "error" ? (openBlock(), createElementBlock(Fragment, { key: 4 }, [
				createBaseVNode("span", _hoisted_5$14, "Échec : " + toDisplayString(unref(update).errorMessage), 1),
				createBaseVNode("button", {
					class: "btn-update",
					onClick: _cache[3] || (_cache[3] = ($event) => unref(update).dismissError())
				}, "Fermer"),
				unref(update).asar ? (openBlock(), createElementBlock("button", {
					key: 0,
					class: "btn-cancel",
					onClick: _cache[4] || (_cache[4] = ($event) => unref(update).startDownload())
				}, "Réessayer")) : createCommentVNode("", true)
			], 64)) : unref(update).phase === "manual-required" ? (openBlock(), createElementBlock(Fragment, { key: 5 }, [createBaseVNode("span", _hoisted_6$12, "Mise à jour majeure " + toDisplayString(unref(update).version) + " — téléchargement complet requis", 1), unref(update).fullPackageUrl ? (openBlock(), createElementBlock("button", {
				key: 0,
				class: "btn-update",
				onClick: _cache[5] || (_cache[5] = ($event) => unref(update).openManualDownload())
			}, "Télécharger")) : createCommentVNode("", true)], 64)) : createCommentVNode("", true)], 2)) : createCommentVNode("", true);
		};
	}
}), [["__scopeId", "data-v-711d1e06"]]);
//#endregion
//#region src/renderer/components/Layout.vue?vue&type=script&setup=true&lang.ts
var _hoisted_1$16 = { class: "layout" };
var _hoisted_2$16 = { class: "header" };
var _hoisted_3$14 = { class: "title" };
var _hoisted_4$13 = {
	key: 0,
	class: "up-to-date"
};
var _hoisted_5$13 = {
	key: 1,
	class: "applied-toast"
};
var _hoisted_6$11 = {
	key: 0,
	class: "step-bar",
	"data-testid": "step-indicator"
};
var _hoisted_7$11 = { class: "content" };
var _hoisted_8$10 = {
	key: 1,
	class: "footer"
};
var _hoisted_9$9 = { class: "footer-right" };
var _hoisted_10$9 = ["disabled"];
var _hoisted_11$9 = {
	key: 3,
	class: "overlay",
	"data-testid": "disconnect-dialog"
};
//#endregion
//#region src/renderer/components/Layout.vue
var Layout_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
	__name: "Layout",
	props: { storeOverride: {} },
	setup(__props) {
		const store = __props.storeOverride ?? useInstallationStore();
		provide(TAB_STORE_KEY, store);
		const updateStore = useUpdateStore();
		function handleCancelClick() {
			if (store.isExecuting || store.currentStep > 0) store.showCancelConfirm = true;
			else store.reset();
		}
		function confirmCancel() {
			store.showCancelConfirm = false;
			store.reset();
		}
		const version = /* @__PURE__ */ ref("");
		onMounted(async () => {
			try {
				version.value = await window.api.invoke("get-app-version");
			} catch {
				version.value = "";
			}
		});
		function triggerUpdateCheck() {
			updateStore.checkNow();
		}
		function handleKeydown(e) {
			if (e.key !== "Enter") return;
			const tag = e.target?.tagName;
			if (tag === "INPUT" || tag === "TEXTAREA") return;
			if (store.currentStep > 3) return;
			if (store.canGoNext) store.nextStep();
		}
		onMounted(() => window.addEventListener("keydown", handleKeydown));
		onUnmounted(() => window.removeEventListener("keydown", handleKeydown));
		const showDisconnectDialog = /* @__PURE__ */ ref(false);
		watch(() => store.deviceDisconnected, (disconnected) => {
			if (disconnected && store.currentStep >= 1 && store.currentStep <= 4) showDisconnectDialog.value = true;
			if (!disconnected && showDisconnectDialog.value) showDisconnectDialog.value = false;
		});
		function handleDisconnectCancel() {
			showDisconnectDialog.value = false;
			store.reset();
		}
		return (_ctx, _cache) => {
			return openBlock(), createElementBlock("div", _hoisted_1$16, [
				createBaseVNode("header", _hoisted_2$16, [createBaseVNode("h1", _hoisted_3$14, [
					_cache[3] || (_cache[3] = createTextVNode("SerenityInstaller ", -1)),
					createBaseVNode("span", {
						class: "version",
						onClick: triggerUpdateCheck,
						title: "Vérifier les mises à jour"
					}, "v" + toDisplayString(version.value), 1),
					unref(updateStore).upToDateFlash ? (openBlock(), createElementBlock("span", _hoisted_4$13, "✓ À jour")) : createCommentVNode("", true),
					unref(updateStore).appliedToast ? (openBlock(), createElementBlock("span", _hoisted_5$13, " ✓ Mise à jour vers " + toDisplayString(unref(updateStore).appliedToast.current) + " appliquée ", 1)) : createCommentVNode("", true)
				]), renderSlot(_ctx.$slots, "header-right", {}, void 0, true)]),
				createVNode(UpdateBanner_default),
				createVNode(StatusStrip_default),
				unref(store).currentStep < 5 ? (openBlock(), createElementBlock("div", _hoisted_6$11, [createVNode(StepIndicator_default, {
					"current-step": unref(store).currentStep,
					onJump: unref(store).goToStep
				}, null, 8, ["current-step", "onJump"])])) : createCommentVNode("", true),
				createBaseVNode("main", _hoisted_7$11, [renderSlot(_ctx.$slots, "default", {}, void 0, true)]),
				unref(store).currentStep < 4 ? (openBlock(), createElementBlock("footer", _hoisted_8$10, [createBaseVNode("button", {
					class: "btn btn-cancel",
					"data-testid": "cancel-btn",
					onClick: handleCancelClick
				}, " Annuler "), createBaseVNode("div", _hoisted_9$9, [unref(store).canGoBack ? (openBlock(), createElementBlock("button", {
					key: 0,
					class: "btn btn-secondary",
					"data-testid": "back-btn",
					onClick: _cache[0] || (_cache[0] = ($event) => unref(store).prevStep())
				}, " Retour ")) : createCommentVNode("", true), createBaseVNode("button", {
					class: "btn btn-primary",
					disabled: !unref(store).canGoNext,
					"data-testid": "next-btn",
					onClick: _cache[1] || (_cache[1] = ($event) => unref(store).nextStep())
				}, " Suivant ", 8, _hoisted_10$9)])])) : createCommentVNode("", true),
				unref(store).showCancelConfirm ? (openBlock(), createBlock(ConfirmDialog_default, {
					key: 2,
					message: "Êtes-vous sûr de vouloir annuler ? Toute progression sera perdue.",
					onConfirm: confirmCancel,
					onCancel: _cache[2] || (_cache[2] = ($event) => unref(store).showCancelConfirm = false)
				})) : createCommentVNode("", true),
				showDisconnectDialog.value ? (openBlock(), createElementBlock("div", _hoisted_11$9, [createBaseVNode("div", { class: "dialog" }, [_cache[4] || (_cache[4] = createBaseVNode("p", { class: "dialog-message" }, "L'appareil a été déconnecté. Veuillez reconnecter un appareil.", -1)), createBaseVNode("div", { class: "dialog-actions" }, [createBaseVNode("button", {
					class: "btn btn-cancel-dialog",
					onClick: handleDisconnectCancel
				}, "Annuler")])])])) : createCommentVNode("", true)
			]);
		};
	}
}), [["__scopeId", "data-v-8d50af5b"]]);
//#endregion
//#region src/renderer/components/TabBar.vue?vue&type=script&setup=true&lang.ts
var _hoisted_1$15 = {
	key: 0,
	class: "tab-bar",
	"data-testid": "tab-bar"
};
var _hoisted_2$15 = ["data-testid", "onClick"];
var _hoisted_3$13 = {
	key: 0,
	class: "tab-spinner"
};
var _hoisted_4$12 = { class: "tab-label" };
var _hoisted_5$12 = ["data-testid", "onClick"];
//#endregion
//#region src/renderer/components/TabBar.vue
var TabBar_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
	__name: "TabBar",
	setup(__props) {
		const tabsStore = useTabsStore();
		const confirmCloseTabId = /* @__PURE__ */ ref(null);
		function handleClose(tabId) {
			if (tabsStore.isTabExecuting(tabId)) confirmCloseTabId.value = tabId;
			else tabsStore.closeTab(tabId);
		}
		function confirmClose() {
			if (confirmCloseTabId.value) {
				window.api.invoke("stop-execution", confirmCloseTabId.value);
				tabsStore.closeTab(confirmCloseTabId.value);
				confirmCloseTabId.value = null;
			}
		}
		return (_ctx, _cache) => {
			return unref(tabsStore).tabs.length > 0 ? (openBlock(), createElementBlock("div", _hoisted_1$15, [
				(openBlock(true), createElementBlock(Fragment, null, renderList(unref(tabsStore).tabs, (tab) => {
					return openBlock(), createElementBlock("div", {
						key: tab.id,
						class: normalizeClass(["tab", { "tab-active": tab.id === unref(tabsStore).activeTabId }]),
						"data-testid": `tab-${tab.id}`,
						onClick: ($event) => unref(tabsStore).switchTab(tab.id)
					}, [
						unref(tabsStore).isTabExecuting(tab.id) ? (openBlock(), createElementBlock("span", _hoisted_3$13, "◐")) : createCommentVNode("", true),
						createBaseVNode("span", _hoisted_4$12, toDisplayString(unref(tabsStore).getTabLabel(tab.id)), 1),
						unref(tabsStore).tabs.length > 1 ? (openBlock(), createElementBlock("button", {
							key: 1,
							class: "tab-close",
							"data-testid": `close-tab-${tab.id}`,
							onClick: withModifiers(($event) => handleClose(tab.id), ["stop"]),
							title: "Fermer l'onglet"
						}, "×", 8, _hoisted_5$12)) : createCommentVNode("", true)
					], 10, _hoisted_2$15);
				}), 128)),
				createBaseVNode("button", {
					class: "tab-add",
					"data-testid": "new-tab-btn",
					onClick: _cache[0] || (_cache[0] = ($event) => unref(tabsStore).createTab()),
					title: "Nouvel onglet"
				}, "+"),
				confirmCloseTabId.value ? (openBlock(), createBlock(ConfirmDialog_default, {
					key: 0,
					message: "Une installation est en cours dans cet onglet. Voulez-vous l'arrêter et fermer l'onglet ?",
					onConfirm: confirmClose,
					onCancel: _cache[1] || (_cache[1] = ($event) => confirmCloseTabId.value = null)
				})) : createCommentVNode("", true)
			])) : createCommentVNode("", true);
		};
	}
}), [["__scopeId", "data-v-45ce84c2"]]);
//#endregion
//#region src/renderer/composables/useIpc.ts
function api() {
	return window.api;
}
function useIpc() {
	function detectDevices() {
		return api().invoke("detect-devices");
	}
	function detectModel(serial) {
		return api().invoke("detect-model", serial);
	}
	function matchProfile(model) {
		return api().invoke("match-profile", model);
	}
	function loadProducts(deviceType) {
		return api().invoke("load-products", deviceType);
	}
	function loadQualities(deviceType) {
		return api().invoke("load-qualities", deviceType);
	}
	function calculateAssets(product, quality, deviceType, extraDirs = [], serial, transferPolicy, sdcardTargetDir) {
		return api().invoke("calculate-assets", product, quality, deviceType, [...extraDirs], serial, transferPolicy, sdcardTargetDir);
	}
	function detectStorage(serial) {
		return api().invoke("detect-storage", serial);
	}
	function fetchReleases(repo) {
		return api().invoke("fetch-releases", repo);
	}
	function findIncompleteSession(deviceId, product, quality) {
		return api().invoke("find-incomplete-session", deviceId, product, quality);
	}
	function startExecution(plan) {
		return api().invoke("start-execution", plan);
	}
	function stopExecution() {
		return api().invoke("stop-execution");
	}
	function onIpcEvent(callback) {
		api().onEvent(callback);
	}
	function removeEventListeners() {
		api().removeEventListeners();
	}
	return {
		detectDevices,
		detectModel,
		matchProfile,
		loadProducts,
		loadQualities,
		calculateAssets,
		detectStorage,
		fetchReleases,
		findIncompleteSession,
		startExecution,
		stopExecution,
		onIpcEvent,
		removeEventListeners
	};
}
//#endregion
//#region src/renderer/composables/useDevice.ts
function useDevice() {
	const ipc = useIpc();
	const store = useInstallationStore();
	const loading = /* @__PURE__ */ ref(false);
	const error = /* @__PURE__ */ ref(null);
	let pollTimer = null;
	async function refresh() {
		loading.value = true;
		error.value = null;
		try {
			store.devices = await ipc.detectDevices();
		} catch (err) {
			error.value = err instanceof Error ? err.message : String(err);
		} finally {
			loading.value = false;
		}
	}
	async function selectDevice(serial) {
		store.selectedDeviceId = serial;
		store.deviceProfile = void 0;
		store.detectedDeviceModel = void 0;
		store.deviceDisconnected = false;
		try {
			const model = await ipc.detectModel(serial);
			store.detectedDeviceModel = model;
			const profile = await ipc.matchProfile(model);
			if (profile) {
				store.deviceProfile = profile;
				await prefillFromDeviceSettings(serial, profile.targetDir);
			}
			store.saveRecentDevice(serial, model, profile?.displayName);
		} catch {
			store.detectedDeviceModel = void 0;
		}
	}
	async function prefillFromDeviceSettings(serial, targetDir) {
		if (!targetDir) return;
		const path = `${targetDir.replace(/\/+$/, "")}/settings.csv`;
		let content = null;
		try {
			content = await window.api.invoke("read-device-file", serial, path);
		} catch {
			return;
		}
		if (!content) return;
		for (const rawLine of content.split("\n")) {
			const line = rawLine.trim();
			if (!line) continue;
			const idx = line.indexOf(";");
			if (idx < 0) continue;
			const key = line.slice(0, idx).trim();
			const value = line.slice(idx + 1).trim();
			if (!value) continue;
			if (key === "device_name" && !store.networkName) store.networkName = value;
			else if (key === "ref" && !store.deviceName) store.deviceName = value;
		}
	}
	function startPolling(intervalMs = 3e3) {
		refresh();
		pollTimer = setInterval(refresh, intervalMs);
	}
	function stopPolling() {
		if (pollTimer) {
			clearInterval(pollTimer);
			pollTimer = null;
		}
	}
	onUnmounted(() => stopPolling());
	return {
		loading,
		error,
		refresh,
		selectDevice,
		startPolling,
		stopPolling
	};
}
//#endregion
//#region src/renderer/pages/DeviceConnect.vue?vue&type=script&setup=true&lang.ts
var _hoisted_1$14 = { class: "page" };
var _hoisted_2$14 = {
	key: 0,
	class: "scan-state"
};
var _hoisted_3$12 = {
	key: 1,
	class: "error-box"
};
var _hoisted_4$11 = {
	key: 2,
	class: "section"
};
var _hoisted_5$11 = { class: "device-grid" };
var _hoisted_6$10 = { class: "device-info" };
var _hoisted_7$10 = { class: "device-serial" };
var _hoisted_8$9 = { class: "device-model" };
var _hoisted_9$8 = {
	key: 3,
	class: "section",
	"data-testid": "device-list"
};
var _hoisted_10$8 = { class: "device-grid" };
var _hoisted_11$8 = ["data-testid", "onClick"];
var _hoisted_12$8 = { class: "device-info" };
var _hoisted_13$8 = { class: "device-serial" };
var _hoisted_14$7 = { class: "device-model" };
var _hoisted_15$7 = {
	key: 0,
	class: "device-badge badge-claimed"
};
var _hoisted_16$7 = {
	key: 1,
	class: "device-badge badge-ok"
};
var _hoisted_17$7 = {
	key: 4,
	class: "section"
};
var _hoisted_18$7 = { class: "device-grid" };
var _hoisted_19$6 = { class: "device-info" };
var _hoisted_20$6 = { class: "device-serial" };
var _hoisted_21$6 = { class: "device-model" };
var _hoisted_22$6 = { class: "device-badge" };
//#endregion
//#region src/renderer/pages/DeviceConnect.vue
var DeviceConnect_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
	__name: "DeviceConnect",
	setup(__props) {
		const store = useInstallationStore();
		const tabsStore = useTabsStore();
		const { loading, error, selectDevice, startPolling, stopPolling } = useDevice();
		const devicesInUse = computed(() => tabsStore.devicesInUseExcept(store.tabId));
		function isClaimedByOtherTab(serial) {
			return devicesInUse.value.has(serial);
		}
		onMounted(() => {
			startPolling();
		});
		function onSelect(serial) {
			if (isClaimedByOtherTab(serial)) return;
			selectDevice(serial);
			stopPolling();
		}
		function statusIcon(status) {
			switch (status) {
				case "device": return "✓";
				case "unauthorized": return "⚠";
				case "offline": return "✗";
				default: return "?";
			}
		}
		function statusLabel(status) {
			switch (status) {
				case "device": return "Connecté";
				case "unauthorized": return "Non autorisé";
				case "offline": return "Hors ligne";
				default: return status;
			}
		}
		const connectedDevices = computed(() => store.devices.filter((d) => d.status === "device"));
		const otherDevices = computed(() => store.devices.filter((d) => d.status !== "device"));
		return (_ctx, _cache) => {
			return openBlock(), createElementBlock("div", _hoisted_1$14, [
				unref(store).devices.length === 0 && !unref(error) ? (openBlock(), createElementBlock("div", _hoisted_2$14, [..._cache[1] || (_cache[1] = [createStaticVNode("<div class=\"pulse-wrap\" data-v-96b899c5><div class=\"pulse-ring\" data-v-96b899c5></div><span class=\"pulse-icon\" data-v-96b899c5>📱</span></div><p class=\"scan-text\" data-v-96b899c5>Recherche d&#39;appareils...</p><p class=\"scan-hint\" data-v-96b899c5>Connectez un appareil Android via USB</p>", 3)])])) : createCommentVNode("", true),
				unref(error) ? (openBlock(), createElementBlock("div", _hoisted_3$12, [createBaseVNode("p", null, toDisplayString(unref(error)), 1), createBaseVNode("button", {
					class: "btn-retry",
					onClick: _cache[0] || (_cache[0] = ($event) => unref(startPolling)())
				}, "Réessayer")])) : createCommentVNode("", true),
				unref(store).devices.length === 0 && unref(store).recentDevices.length > 0 && !unref(error) ? (openBlock(), createElementBlock("div", _hoisted_4$11, [_cache[4] || (_cache[4] = createBaseVNode("h3", null, "Appareils récents", -1)), createBaseVNode("div", _hoisted_5$11, [(openBlock(true), createElementBlock(Fragment, null, renderList(unref(store).recentDevices, (rd) => {
					return openBlock(), createElementBlock("div", {
						key: rd.serial,
						class: "device-card device-offline"
					}, [
						_cache[2] || (_cache[2] = createBaseVNode("span", { class: "device-icon" }, "○", -1)),
						createBaseVNode("div", _hoisted_6$10, [createBaseVNode("span", _hoisted_7$10, toDisplayString(rd.serial), 1), createBaseVNode("span", _hoisted_8$9, toDisplayString(rd.profileName ?? rd.model ?? ""), 1)]),
						_cache[3] || (_cache[3] = createBaseVNode("span", { class: "device-badge" }, "Hors ligne", -1))
					]);
				}), 128))])])) : createCommentVNode("", true),
				connectedDevices.value.length > 0 ? (openBlock(), createElementBlock("div", _hoisted_9$8, [_cache[6] || (_cache[6] = createBaseVNode("h3", null, "Appareils disponibles", -1)), createBaseVNode("div", _hoisted_10$8, [(openBlock(true), createElementBlock(Fragment, null, renderList(connectedDevices.value, (device) => {
					return openBlock(), createElementBlock("div", {
						key: device.serial,
						class: normalizeClass(["device-card", {
							"device-selected": device.serial === unref(store).selectedDeviceId,
							"device-claimed": isClaimedByOtherTab(device.serial)
						}]),
						"data-testid": `device-row-${device.serial}`,
						onClick: ($event) => !isClaimedByOtherTab(device.serial) && onSelect(device.serial)
					}, [
						_cache[5] || (_cache[5] = createBaseVNode("span", { class: "device-icon icon-ok" }, "✓", -1)),
						createBaseVNode("div", _hoisted_12$8, [createBaseVNode("span", _hoisted_13$8, toDisplayString(device.serial), 1), createBaseVNode("span", _hoisted_14$7, toDisplayString(device.model ?? ""), 1)]),
						isClaimedByOtherTab(device.serial) ? (openBlock(), createElementBlock("span", _hoisted_15$7, "Autre onglet")) : (openBlock(), createElementBlock("span", _hoisted_16$7, "Connecté"))
					], 10, _hoisted_11$8);
				}), 128))])])) : createCommentVNode("", true),
				otherDevices.value.length > 0 ? (openBlock(), createElementBlock("div", _hoisted_17$7, [_cache[7] || (_cache[7] = createBaseVNode("h3", null, "Autres appareils", -1)), createBaseVNode("div", _hoisted_18$7, [(openBlock(true), createElementBlock(Fragment, null, renderList(otherDevices.value, (device) => {
					return openBlock(), createElementBlock("div", {
						key: device.serial,
						class: "device-card device-offline"
					}, [
						createBaseVNode("span", { class: normalizeClass(["device-icon", device.status === "unauthorized" ? "icon-warn" : "icon-off"]) }, toDisplayString(statusIcon(device.status)), 3),
						createBaseVNode("div", _hoisted_19$6, [createBaseVNode("span", _hoisted_20$6, toDisplayString(device.serial), 1), createBaseVNode("span", _hoisted_21$6, toDisplayString(device.model ?? ""), 1)]),
						createBaseVNode("span", _hoisted_22$6, toDisplayString(statusLabel(device.status)), 1)
					]);
				}), 128))])])) : createCommentVNode("", true)
			]);
		};
	}
}), [["__scopeId", "data-v-96b899c5"]]);
//#endregion
//#region src/renderer/pages/DeviceConfig.vue?vue&type=script&setup=true&lang.ts
var _hoisted_1$13 = { class: "page" };
var _hoisted_2$13 = { class: "device-header" };
var _hoisted_3$11 = { class: "device-type" };
var _hoisted_4$10 = { class: "device-serial" };
var _hoisted_5$10 = {
	key: 0,
	class: "storage-info"
};
var _hoisted_6$9 = { class: "storage-label" };
var _hoisted_7$9 = { class: "storage-bar-mini" };
var _hoisted_8$8 = { class: "storage-detail" };
var _hoisted_9$7 = {
	key: 1,
	class: "section",
	"data-testid": "custom-profile-form"
};
var _hoisted_10$7 = { class: "form-grid" };
var _hoisted_11$7 = { class: "field" };
var _hoisted_12$7 = { class: "field" };
var _hoisted_13$7 = { class: "field" };
var _hoisted_14$6 = ["value"];
var _hoisted_15$6 = { class: "field" };
var _hoisted_16$6 = { class: "field" };
var _hoisted_17$6 = ["value"];
var _hoisted_18$6 = { class: "field" };
var _hoisted_19$5 = { class: "field" };
var _hoisted_20$5 = ["value"];
var _hoisted_21$5 = { class: "field" };
var _hoisted_22$5 = { class: "field full-width" };
var _hoisted_23$5 = { class: "form-actions" };
var _hoisted_24$5 = ["disabled"];
var _hoisted_25$5 = {
	key: 2,
	class: "section"
};
var _hoisted_26$4 = { class: "profile-grid" };
var _hoisted_27$4 = ["data-testid", "onClick"];
var _hoisted_28$4 = { class: "profile-name" };
var _hoisted_29$4 = { class: "profile-desc" };
var _hoisted_30$4 = {
	key: 3,
	class: "form"
};
var _hoisted_31$4 = { class: "field" };
var _hoisted_32$4 = {
	key: 0,
	class: "field"
};
//#endregion
//#region src/renderer/pages/DeviceConfig.vue
var DeviceConfig_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
	__name: "DeviceConfig",
	setup(__props) {
		const store = useInstallationStore();
		const ipc = useIpc();
		const allProfiles = /* @__PURE__ */ ref([]);
		const commandFiles = /* @__PURE__ */ ref([]);
		const storageLocations = /* @__PURE__ */ ref([]);
		const showForm = /* @__PURE__ */ ref(false);
		const editing = /* @__PURE__ */ ref(false);
		const DEVICE_TYPES = [
			{
				value: "PICOG3",
				label: "VR — Pico G3"
			},
			{
				value: "QUEST3",
				label: "VR — Quest 3"
			},
			{
				value: "TAB",
				label: "Tablette"
			},
			{
				value: "TABS9",
				label: "Tablette — Tab S9"
			}
		];
		const REPO_BY_TYPE = {
			PICOG3: "SerenityVR_PicoG3",
			QUEST3: "SerenityVR_PicoG3",
			TAB: "SerenityManager",
			TABS9: "SerenityManager"
		};
		function isVRType(type) {
			return type === "PICOG3" || type === "QUEST3";
		}
		const form = /* @__PURE__ */ reactive({
			model: "",
			displayName: "",
			type: "PICOG3",
			aliases: "",
			repo: "",
			packageName: "",
			commandFile: "",
			targetDir: "",
			description: ""
		});
		onMounted(async () => {
			try {
				allProfiles.value = await window.api.invoke("list-profiles");
				commandFiles.value = await window.api.invoke("list-command-files");
			} catch {}
			if (store.selectedDeviceId) try {
				storageLocations.value = await ipc.detectStorage(store.selectedDeviceId);
				store.storageLocations = storageLocations.value;
			} catch {}
		});
		watch(() => store.isVR, (vr) => {
			if (vr && !store.networkName) store.networkName = "S1";
		}, { immediate: true });
		function selectProfile(profile) {
			store.deviceProfile = profile;
		}
		watch(() => form.type, (type) => {
			form.repo = REPO_BY_TYPE[type] ?? "";
		});
		function openNewForm() {
			const detected = store.detectedDeviceModel ?? "";
			form.model = detected;
			form.displayName = detected;
			form.type = "PICOG3";
			form.aliases = detected ? detected.toLowerCase() : "";
			form.repo = REPO_BY_TYPE["PICOG3"];
			form.packageName = isVRType(form.type) ? "com.IRIS.SerenityVR6" : "com.IRIS.SerenityManager";
			form.commandFile = commandFiles.value[0] ?? "";
			form.targetDir = isVRType(form.type) ? "/storage/emulated/0/Android/data/com.IRIS.SerenityVR6/files/Serenity" : "/storage/emulated/0/Android/data/com.IRIS.SerenityManager/files";
			form.description = "";
			editing.value = false;
			showForm.value = true;
		}
		function openEditForm() {
			const p = store.deviceProfile;
			if (!p) return;
			form.model = p.model;
			form.displayName = p.displayName;
			form.type = p.type;
			form.aliases = p.aliases.join(", ");
			form.repo = p.repo;
			form.packageName = p.packageName;
			form.commandFile = p.commandFile;
			form.targetDir = p.targetDir;
			form.description = p.description;
			editing.value = true;
			showForm.value = true;
		}
		async function saveProfile() {
			const profile = {
				model: form.model.trim(),
				displayName: form.displayName.trim(),
				type: form.type,
				aliases: form.aliases.split(",").map((a) => a.trim()).filter(Boolean),
				repo: form.repo.trim(),
				packageName: form.packageName.trim(),
				commandFile: form.commandFile,
				targetDir: form.targetDir.trim(),
				description: form.description.trim()
			};
			await window.api.invoke("save-custom-profile", JSON.parse(JSON.stringify(profile)));
			allProfiles.value = await window.api.invoke("list-profiles");
			store.deviceProfile = profile;
			showForm.value = false;
		}
		function cancelForm() {
			showForm.value = false;
		}
		const formValid = /* @__PURE__ */ ref(true);
		watch(form, () => {
			formValid.value = !!(form.model.trim() && form.displayName.trim() && form.packageName.trim() && form.targetDir.trim());
		}, {
			deep: true,
			immediate: true
		});
		return (_ctx, _cache) => {
			return openBlock(), createElementBlock("div", _hoisted_1$13, [
				createBaseVNode("div", _hoisted_2$13, [createBaseVNode("div", _hoisted_3$11, toDisplayString(unref(store).deviceProfile?.displayName ?? unref(store).detectedDeviceModel ?? "Inconnu"), 1), createBaseVNode("div", _hoisted_4$10, toDisplayString(unref(store).selectedDeviceId), 1)]),
				storageLocations.value.length > 0 ? (openBlock(), createElementBlock("div", _hoisted_5$10, [(openBlock(true), createElementBlock(Fragment, null, renderList(storageLocations.value, (loc) => {
					return openBlock(), createElementBlock("div", {
						key: loc.path,
						class: "storage-item"
					}, [
						createBaseVNode("span", _hoisted_6$9, toDisplayString(loc.displayName), 1),
						createBaseVNode("div", _hoisted_7$9, [createBaseVNode("div", {
							class: "storage-bar-fill",
							style: normalizeStyle({ width: (loc.totalBytes ? (loc.usedBytes ?? 0) / loc.totalBytes * 100 : 0) + "%" })
						}, null, 4)]),
						createBaseVNode("span", _hoisted_8$8, toDisplayString(unref(formatSize)((loc.totalBytes ?? 0) - (loc.usedBytes ?? 0))) + " libre / " + toDisplayString(unref(formatSize)(loc.totalBytes)), 1)
					]);
				}), 128))])) : createCommentVNode("", true),
				showForm.value ? (openBlock(), createElementBlock("div", _hoisted_9$7, [
					createBaseVNode("h3", null, toDisplayString(editing.value ? "Modifier le profil" : "Nouveau profil"), 1),
					createBaseVNode("div", _hoisted_10$7, [
						createBaseVNode("div", _hoisted_11$7, [_cache[10] || (_cache[10] = createBaseVNode("label", null, [createTextVNode("Nom affiché "), createBaseVNode("span", { class: "required" }, "*")], -1)), withDirectives(createBaseVNode("input", {
							"onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => form.displayName = $event),
							class: "input",
							"data-testid": "custom-profile-displayName",
							placeholder: "Ex: Mon Casque VR"
						}, null, 512), [[vModelText, form.displayName]])]),
						createBaseVNode("div", _hoisted_12$7, [_cache[11] || (_cache[11] = createBaseVNode("label", null, "Modèle (identifiant)", -1)), withDirectives(createBaseVNode("input", {
							"onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => form.model = $event),
							class: "input",
							"data-testid": "custom-profile-model",
							placeholder: "Ex: MY_DEVICE"
						}, null, 512), [[vModelText, form.model]])]),
						createBaseVNode("div", _hoisted_13$7, [_cache[12] || (_cache[12] = createBaseVNode("label", null, "Type d'appareil", -1)), withDirectives(createBaseVNode("select", {
							"onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => form.type = $event),
							class: "input",
							"data-testid": "custom-profile-type"
						}, [(openBlock(), createElementBlock(Fragment, null, renderList(DEVICE_TYPES, (t) => {
							return createBaseVNode("option", {
								key: t.value,
								value: t.value
							}, toDisplayString(t.label), 9, _hoisted_14$6);
						}), 64))], 512), [[vModelSelect, form.type]])]),
						createBaseVNode("div", _hoisted_15$6, [_cache[13] || (_cache[13] = createBaseVNode("label", null, "Aliases (séparés par virgule)", -1)), withDirectives(createBaseVNode("input", {
							"onUpdate:modelValue": _cache[3] || (_cache[3] = ($event) => form.aliases = $event),
							class: "input",
							"data-testid": "custom-profile-aliases",
							placeholder: "Ex: mon casque, custom vr"
						}, null, 512), [[vModelText, form.aliases]])]),
						createBaseVNode("div", _hoisted_16$6, [_cache[14] || (_cache[14] = createBaseVNode("label", null, "Dépôt GitHub", -1)), createBaseVNode("input", {
							value: form.repo,
							class: "input input-readonly",
							readonly: ""
						}, null, 8, _hoisted_17$6)]),
						createBaseVNode("div", _hoisted_18$6, [_cache[15] || (_cache[15] = createBaseVNode("label", null, [createTextVNode("Package APK "), createBaseVNode("span", { class: "required" }, "*")], -1)), withDirectives(createBaseVNode("input", {
							"onUpdate:modelValue": _cache[4] || (_cache[4] = ($event) => form.packageName = $event),
							class: "input",
							"data-testid": "custom-profile-packageName",
							placeholder: "Ex: com.IRIS.SerenityVR6"
						}, null, 512), [[vModelText, form.packageName]])]),
						createBaseVNode("div", _hoisted_19$5, [_cache[17] || (_cache[17] = createBaseVNode("label", null, "Fichier de commandes", -1)), withDirectives(createBaseVNode("select", {
							"onUpdate:modelValue": _cache[5] || (_cache[5] = ($event) => form.commandFile = $event),
							class: "input",
							"data-testid": "custom-profile-commandFile"
						}, [_cache[16] || (_cache[16] = createBaseVNode("option", { value: "" }, "(aucun)", -1)), (openBlock(true), createElementBlock(Fragment, null, renderList(commandFiles.value, (f) => {
							return openBlock(), createElementBlock("option", {
								key: f,
								value: f
							}, toDisplayString(f), 9, _hoisted_20$5);
						}), 128))], 512), [[vModelSelect, form.commandFile]])]),
						createBaseVNode("div", _hoisted_21$5, [_cache[18] || (_cache[18] = createBaseVNode("label", null, [createTextVNode("Dossier cible sur l'appareil "), createBaseVNode("span", { class: "required" }, "*")], -1)), withDirectives(createBaseVNode("input", {
							"onUpdate:modelValue": _cache[6] || (_cache[6] = ($event) => form.targetDir = $event),
							class: "input",
							"data-testid": "custom-profile-targetDir",
							placeholder: "Ex: /storage/emulated/0/Android/data/..."
						}, null, 512), [[vModelText, form.targetDir]])]),
						createBaseVNode("div", _hoisted_22$5, [_cache[19] || (_cache[19] = createBaseVNode("label", null, "Description", -1)), withDirectives(createBaseVNode("input", {
							"onUpdate:modelValue": _cache[7] || (_cache[7] = ($event) => form.description = $event),
							class: "input",
							"data-testid": "custom-profile-description",
							placeholder: "Ex: Casque VR personnalisé"
						}, null, 512), [[vModelText, form.description]])])
					]),
					createBaseVNode("div", _hoisted_23$5, [createBaseVNode("button", {
						class: "btn-cancel-form",
						onClick: cancelForm
					}, "Annuler"), createBaseVNode("button", {
						class: "btn-save-form",
						"data-testid": "save-custom-profile-btn",
						disabled: !formValid.value,
						onClick: saveProfile
					}, "Enregistrer", 8, _hoisted_24$5)])
				])) : !unref(store).deviceProfile ? (openBlock(), createElementBlock("div", _hoisted_25$5, [_cache[21] || (_cache[21] = createBaseVNode("p", { class: "warn-text" }, "Appareil non reconnu. Sélectionnez le type manuellement :", -1)), createBaseVNode("div", _hoisted_26$4, [(openBlock(true), createElementBlock(Fragment, null, renderList(allProfiles.value, (profile) => {
					return openBlock(), createElementBlock("button", {
						key: profile.model,
						class: "profile-btn",
						"data-testid": `profile-card-${profile.model}`,
						onClick: ($event) => selectProfile(profile)
					}, [createBaseVNode("span", _hoisted_28$4, toDisplayString(profile.displayName), 1), createBaseVNode("span", _hoisted_29$4, toDisplayString(profile.description), 1)], 8, _hoisted_27$4);
				}), 128)), createBaseVNode("button", {
					class: "profile-btn profile-btn-new",
					"data-testid": "create-custom-profile-btn",
					onClick: openNewForm
				}, [..._cache[20] || (_cache[20] = [createBaseVNode("span", { class: "profile-name" }, "+ Nouveau", -1), createBaseVNode("span", { class: "profile-desc" }, "Créer un profil personnalisé", -1)])])])])) : (openBlock(), createElementBlock("div", _hoisted_30$4, [createBaseVNode("div", { class: normalizeClass(["form-row", { "single-col": !unref(store).isVR }]) }, [createBaseVNode("div", _hoisted_31$4, [_cache[22] || (_cache[22] = createBaseVNode("label", { for: "deviceName" }, [createTextVNode("Nom de l'appareil "), createBaseVNode("span", { class: "required" }, "*")], -1)), withDirectives(createBaseVNode("input", {
					id: "deviceName",
					"onUpdate:modelValue": _cache[8] || (_cache[8] = ($event) => unref(store).deviceName = $event),
					type: "text",
					placeholder: "Ex: Dev42",
					class: "input",
					"data-testid": "device-name-input"
				}, null, 512), [[vModelText, unref(store).deviceName]])]), unref(store).isVR ? (openBlock(), createElementBlock("div", _hoisted_32$4, [_cache[23] || (_cache[23] = createBaseVNode("label", { for: "networkName" }, "Nom du réseau", -1)), withDirectives(createBaseVNode("input", {
					id: "networkName",
					"onUpdate:modelValue": _cache[9] || (_cache[9] = ($event) => unref(store).networkName = $event),
					type: "text",
					placeholder: "Ex: S1",
					class: "input",
					"data-testid": "network-name-input"
				}, null, 512), [[vModelText, unref(store).networkName]])])) : createCommentVNode("", true)], 2), createBaseVNode("button", {
					class: "btn-edit-profile",
					onClick: openEditForm
				}, "Modifier le profil")]))
			]);
		};
	}
}), [["__scopeId", "data-v-748880d8"]]);
//#endregion
//#region src/renderer/components/Card.vue?vue&type=script&setup=true&lang.ts
var _hoisted_1$12 = { class: "card" };
var _hoisted_2$12 = {
	key: 0,
	class: "card-title"
};
//#endregion
//#region src/renderer/components/Card.vue
var Card_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
	__name: "Card",
	props: { title: {} },
	setup(__props) {
		return (_ctx, _cache) => {
			return openBlock(), createElementBlock("div", _hoisted_1$12, [__props.title ? (openBlock(), createElementBlock("div", _hoisted_2$12, toDisplayString(__props.title), 1)) : createCommentVNode("", true), renderSlot(_ctx.$slots, "default", {}, void 0, true)]);
		};
	}
}), [["__scopeId", "data-v-ef30aa81"]]);
//#endregion
//#region src/renderer/components/StorageBar.vue?vue&type=script&setup=true&lang.ts
var _hoisted_1$11 = { class: "storage-bar-container" };
var _hoisted_2$11 = { class: "storage-track" };
var _hoisted_3$10 = { class: "storage-legend" };
var _hoisted_4$9 = { class: "legend-item" };
var _hoisted_5$9 = { class: "legend-item" };
var _hoisted_6$8 = { class: "legend-item" };
var _hoisted_7$8 = {
	key: 0,
	class: "storage-warning"
};
var MARGIN = .05;
//#endregion
//#region src/renderer/components/StorageBar.vue
var StorageBar_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
	__name: "StorageBar",
	props: {
		totalBytes: {},
		usedBytes: {},
		plannedBytes: {}
	},
	setup(__props) {
		const props = __props;
		function pct(value) {
			if (props.totalBytes === 0) return 0;
			return Math.min(100, value / props.totalBytes * 100);
		}
		const usedPct = computed(() => pct(props.usedBytes));
		const plannedPct = computed(() => pct(props.plannedBytes));
		const marginPct = computed(() => pct(props.plannedBytes * MARGIN));
		computed(() => Math.max(0, 100 - usedPct.value - plannedPct.value - marginPct.value));
		const sufficient = computed(() => {
			const needed = props.plannedBytes * (1 + MARGIN);
			return props.totalBytes - props.usedBytes >= needed;
		});
		return (_ctx, _cache) => {
			return openBlock(), createElementBlock("div", _hoisted_1$11, [
				createBaseVNode("div", _hoisted_2$11, [
					createBaseVNode("div", {
						class: "seg seg-used",
						style: normalizeStyle({ width: usedPct.value + "%" })
					}, null, 4),
					createBaseVNode("div", {
						class: "seg seg-planned",
						style: normalizeStyle({ width: plannedPct.value + "%" })
					}, null, 4),
					createBaseVNode("div", {
						class: "seg seg-margin",
						style: normalizeStyle({ width: marginPct.value + "%" })
					}, null, 4)
				]),
				createBaseVNode("div", _hoisted_3$10, [
					createBaseVNode("span", _hoisted_4$9, [_cache[0] || (_cache[0] = createBaseVNode("span", { class: "dot dot-used" }, null, -1)), createTextVNode(" Utilisé " + toDisplayString(unref(formatSize)(__props.usedBytes)), 1)]),
					createBaseVNode("span", _hoisted_5$9, [_cache[1] || (_cache[1] = createBaseVNode("span", { class: "dot dot-planned" }, null, -1)), createTextVNode(" Transfert " + toDisplayString(unref(formatSize)(__props.plannedBytes)), 1)]),
					_cache[3] || (_cache[3] = createBaseVNode("span", { class: "legend-item" }, [createBaseVNode("span", { class: "dot dot-margin" }), createTextVNode(" Marge 5%")], -1)),
					createBaseVNode("span", _hoisted_6$8, [_cache[2] || (_cache[2] = createBaseVNode("span", { class: "dot dot-free" }, null, -1)), createTextVNode(" Libre " + toDisplayString(unref(formatSize)(Math.max(0, __props.totalBytes - __props.usedBytes - __props.plannedBytes))), 1)])
				]),
				!sufficient.value ? (openBlock(), createElementBlock("div", _hoisted_7$8, " Espace insuffisant sur l'appareil ")) : createCommentVNode("", true)
			]);
		};
	}
}), [["__scopeId", "data-v-aa1c52ef"]]);
//#endregion
//#region src/renderer/pages/ContentSelect.vue?vue&type=script&setup=true&lang.ts
var _hoisted_1$10 = { class: "page" };
var _hoisted_2$10 = { class: "skip-row" };
var _hoisted_3$9 = { class: "checkbox-label" };
var _hoisted_4$8 = { class: "section" };
var _hoisted_5$8 = {
	key: 0,
	class: "loading"
};
var _hoisted_6$7 = {
	key: 1,
	class: "radio-list"
};
var _hoisted_7$7 = ["data-testid"];
var _hoisted_8$7 = ["value"];
var _hoisted_9$6 = {
	key: 0,
	class: "empty"
};
var _hoisted_10$6 = {
	key: 0,
	class: "section"
};
var _hoisted_11$6 = { class: "radio-list horizontal" };
var _hoisted_12$6 = ["data-testid"];
var _hoisted_13$6 = ["value"];
var _hoisted_14$5 = { class: "section" };
var _hoisted_15$5 = { class: "radio-list" };
var _hoisted_16$5 = ["data-testid"];
var _hoisted_17$5 = ["value"];
var _hoisted_18$5 = { class: "pol-label" };
var _hoisted_19$4 = { class: "pol-desc" };
var _hoisted_20$4 = { class: "verification-row" };
var _hoisted_21$4 = { class: "checkbox-label" };
var _hoisted_22$4 = {
	key: 1,
	class: "resume-banner"
};
var _hoisted_23$4 = {
	key: 0,
	class: "loading"
};
var _hoisted_24$4 = { class: "summary-row" };
var _hoisted_25$4 = {
	key: 0,
	class: "summary-row"
};
var _hoisted_26$3 = { class: "text-warn" };
var _hoisted_27$3 = { class: "summary-row" };
var _hoisted_28$3 = {
	key: 1,
	class: "summary-row"
};
var _hoisted_29$3 = { class: "summary-row" };
var _hoisted_30$3 = { class: "text-primary" };
var _hoisted_31$3 = {
	key: 2,
	class: "summary-hint"
};
var _hoisted_32$3 = {
	key: 3,
	class: "section"
};
var _hoisted_33$3 = { class: "extra-dir-path" };
var _hoisted_34$3 = ["onClick"];
var _hoisted_35$3 = {
	key: 0,
	class: "extra-dir-hint"
};
var _hoisted_36$3 = {
	key: 4,
	class: "source-dup-banner"
};
var _hoisted_37$3 = { class: "dup-details" };
var _hoisted_38$2 = { class: "dup-base" };
var _hoisted_39$1 = { class: "dup-variants" };
var _hoisted_40$1 = {
	key: 5,
	class: "section"
};
var _hoisted_41$1 = { class: "radio-list" };
var _hoisted_42$1 = ["data-testid"];
var _hoisted_43$1 = ["value"];
var _hoisted_44$1 = {
	key: 0,
	class: "priority-section"
};
var _hoisted_45$1 = { class: "radio-list horizontal" };
var _hoisted_46$1 = {
	key: 1,
	class: "duplicate-banner"
};
var _hoisted_47$1 = { class: "duplicate-details" };
//#endregion
//#region src/renderer/pages/ContentSelect.vue
var ContentSelect_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
	__name: "ContentSelect",
	setup(__props) {
		const store = useInstallationStore();
		console.log("[ContentSelect] Store tabId:", store.tabId, "deviceProfile:", store.deviceProfile?.type);
		const ipc = useIpc();
		const products = /* @__PURE__ */ ref([]);
		const qualities = /* @__PURE__ */ ref([]);
		const loadingProducts = /* @__PURE__ */ ref(false);
		const loadingAssets = /* @__PURE__ */ ref(false);
		const incompleteSession = /* @__PURE__ */ ref(null);
		let requestVersion = 0;
		const policies = [
			{
				value: "update-different",
				label: "Mettre à jour",
				desc: "Transfert uniquement les fichiers modifiés"
			},
			{
				value: "overwrite-all",
				label: "Tout écraser",
				desc: "Retransfère tous les fichiers"
			},
			{
				value: "skip-existing",
				label: "Ignorer existants",
				desc: "Ne transfère que les fichiers manquants"
			}
		];
		watch(() => store.deviceProfile?.type, async (deviceType) => {
			if (!deviceType) return;
			console.log("[ContentSelect] Device profile changed, loading products for:", deviceType);
			loadingProducts.value = true;
			try {
				products.value = await ipc.loadProducts(deviceType);
				qualities.value = await ipc.loadQualities(deviceType);
				console.log("[ContentSelect] Products:", products.value.length, "Qualities:", qualities.value.length);
			} catch (err) {
				console.error("[ContentSelect] Error loading products/qualities:", err);
			} finally {
				loadingProducts.value = false;
			}
		}, { immediate: true });
		watch(() => store.skipFiles, (skip) => {
			if (skip) {
				store.product = void 0;
				store.quality = void 0;
				store.transferPolicy = "update-different";
				store.totalAssetSize = 0;
				store.netTransferSize = 0;
				store.totalFileCount = 0;
				store.requiredFiles = [];
				store.missingFiles = [];
				store.serenityFiles = [];
			}
		});
		watch(() => [store.product, store.quality], async () => {
			if (!store.product || !store.quality || !store.deviceProfile) return;
			const version = ++requestVersion;
			incompleteSession.value = null;
			if (store.selectedDeviceId) {
				const existing = await ipc.findIncompleteSession(store.selectedDeviceId, store.product, store.quality);
				if (requestVersion !== version) return;
				if (existing) incompleteSession.value = {
					sessionId: existing.sessionId,
					lastUpdatedAt: existing.lastUpdatedAt
				};
			}
			if (store.selectedDeviceId) try {
				const locations = await ipc.detectStorage(store.selectedDeviceId);
				if (requestVersion !== version) return;
				store.storageLocations = locations;
			} catch {}
			if (requestVersion !== version) return;
			await recalculateAssets(version);
		});
		watch(() => [store.transferPolicy, store.storageMode], () => {
			if (store.product && store.quality) recalculateAssets();
		});
		async function recalculateAssets(version) {
			if (!store.product || !store.quality || !store.deviceProfile) return;
			const v = version ?? ++requestVersion;
			loadingAssets.value = true;
			try {
				let sdcardTargetDir;
				if (store.storageMode === "both") {
					const internalTarget = store.deviceProfile.targetDir ?? "";
					const sdLoc = store.storageLocations.find((l) => l.type === "sdcard");
					if (sdLoc && internalTarget) {
						const relativePath = internalTarget.replace(/^\/storage\/emulated\/0/, "");
						sdcardTargetDir = sdLoc.path + relativePath;
					}
				}
				const result = await ipc.calculateAssets(store.product, store.quality, store.deviceProfile.type, store.extraSearchDirs, store.selectedDeviceId, store.transferPolicy, sdcardTargetDir);
				if (requestVersion !== v) return;
				store.requiredFiles = result.requiredFiles;
				store.missingFiles = result.missingFiles;
				store.serenityFiles = result.serenityFiles ?? [];
				store.fileSizes = result.fileSizes ?? {};
				store.totalAssetSize = result.totalSize;
				store.totalFileCount = result.totalCount;
				store.existingOnDeviceSize = result.existingOnDeviceSize;
				store.existingOnDeviceCount = result.existingOnDeviceCount;
				store.netTransferSize = result.netTransferSize;
				store.duplicateFiles = result.duplicateFiles ?? [];
				store.sourceDuplicates = result.sourceDuplicates ?? [];
			} catch (err) {
				console.error("[ContentSelect] calculateAssets error:", err);
			} finally {
				if (requestVersion === v) loadingAssets.value = false;
			}
		}
		async function addSearchDir() {
			const dir = await window.api.invoke("select-directory");
			if (dir && !store.extraSearchDirs.includes(dir)) {
				store.extraSearchDirs.push(dir);
				await recalculateAssets();
			}
		}
		function removeSearchDir(dir) {
			store.extraSearchDirs = store.extraSearchDirs.filter((d) => d !== dir);
			recalculateAssets();
		}
		function storageTotal() {
			if (store.storageMode === "both") return store.storageLocations.reduce((sum, l) => sum + (l.totalBytes ?? 0), 0);
			return (store.storageLocations.find((l) => l.type === store.storageMode) ?? store.storageLocations[0])?.totalBytes ?? 0;
		}
		function storageUsed() {
			if (store.storageMode === "both") return store.storageLocations.reduce((sum, l) => sum + (l.usedBytes ?? 0), 0);
			return (store.storageLocations.find((l) => l.type === store.storageMode) ?? store.storageLocations[0])?.usedBytes ?? 0;
		}
		return (_ctx, _cache) => {
			return openBlock(), createElementBlock("div", _hoisted_1$10, [createBaseVNode("div", _hoisted_2$10, [createBaseVNode("label", _hoisted_3$9, [withDirectives(createBaseVNode("input", {
				type: "checkbox",
				"onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => unref(store).skipFiles = $event),
				"data-testid": "skip-files-toggle"
			}, null, 512), [[vModelCheckbox, unref(store).skipFiles]]), _cache[9] || (_cache[9] = createBaseVNode("span", null, "Ignorer le transfert de fichiers", -1))])]), !unref(store).skipFiles ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
				createBaseVNode("div", _hoisted_4$8, [_cache[10] || (_cache[10] = createBaseVNode("h3", null, "Produit", -1)), loadingProducts.value ? (openBlock(), createElementBlock("div", _hoisted_5$8, "Chargement...")) : (openBlock(), createElementBlock("div", _hoisted_6$7, [(openBlock(true), createElementBlock(Fragment, null, renderList(products.value, (p) => {
					return openBlock(), createElementBlock("label", {
						key: p,
						class: normalizeClass(["radio-item", { "radio-selected": unref(store).product === p }]),
						"data-testid": `product-${p}`
					}, [withDirectives(createBaseVNode("input", {
						type: "radio",
						value: p,
						"onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => unref(store).product = $event)
					}, null, 8, _hoisted_8$7), [[vModelRadio, unref(store).product]]), createBaseVNode("span", null, toDisplayString(p), 1)], 10, _hoisted_7$7);
				}), 128)), products.value.length === 0 ? (openBlock(), createElementBlock("p", _hoisted_9$6, "Aucun produit trouvé")) : createCommentVNode("", true)]))]),
				unref(store).product ? (openBlock(), createElementBlock("div", _hoisted_10$6, [_cache[11] || (_cache[11] = createBaseVNode("h3", null, "Qualité", -1)), createBaseVNode("div", _hoisted_11$6, [(openBlock(true), createElementBlock(Fragment, null, renderList(qualities.value, (q) => {
					return openBlock(), createElementBlock("label", {
						key: q,
						class: normalizeClass(["radio-item", { "radio-selected": unref(store).quality === q }]),
						"data-testid": `quality-${q}`
					}, [withDirectives(createBaseVNode("input", {
						type: "radio",
						value: q,
						"onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => unref(store).quality = $event)
					}, null, 8, _hoisted_13$6), [[vModelRadio, unref(store).quality]]), createBaseVNode("span", null, toDisplayString(q), 1)], 10, _hoisted_12$6);
				}), 128))])])) : createCommentVNode("", true),
				createBaseVNode("div", _hoisted_14$5, [_cache[12] || (_cache[12] = createBaseVNode("h3", null, "Politique de transfert", -1)), createBaseVNode("div", _hoisted_15$5, [(openBlock(), createElementBlock(Fragment, null, renderList(policies, (pol) => {
					return createBaseVNode("label", {
						key: pol.value,
						class: normalizeClass(["radio-item", { "radio-selected": unref(store).transferPolicy === pol.value }]),
						"data-testid": `policy-${pol.value}`
					}, [withDirectives(createBaseVNode("input", {
						type: "radio",
						value: pol.value,
						"onUpdate:modelValue": _cache[3] || (_cache[3] = ($event) => unref(store).transferPolicy = $event)
					}, null, 8, _hoisted_17$5), [[vModelRadio, unref(store).transferPolicy]]), createBaseVNode("div", null, [createBaseVNode("span", _hoisted_18$5, toDisplayString(pol.label), 1), createBaseVNode("span", _hoisted_19$4, toDisplayString(pol.desc), 1)])], 10, _hoisted_16$5);
				}), 64))])]),
				createBaseVNode("div", _hoisted_20$4, [createBaseVNode("label", _hoisted_21$4, [withDirectives(createBaseVNode("input", {
					type: "checkbox",
					"onUpdate:modelValue": _cache[4] || (_cache[4] = ($event) => unref(store).enableVerification = $event),
					"data-testid": "enable-verification-toggle"
				}, null, 512), [[vModelCheckbox, unref(store).enableVerification]]), _cache[13] || (_cache[13] = createBaseVNode("span", null, "Vérification MD5 après transfert", -1))])]),
				incompleteSession.value ? (openBlock(), createElementBlock("div", _hoisted_22$4, [createBaseVNode("span", null, "Une session incomplète a été trouvée (" + toDisplayString(new Date(incompleteSession.value.lastUpdatedAt).toLocaleString("fr-FR")) + ").", 1), _cache[14] || (_cache[14] = createBaseVNode("span", { class: "resume-hint" }, "Le transfert reprendra automatiquement les fichiers non terminés.", -1))])) : createCommentVNode("", true),
				unref(store).product && unref(store).quality ? (openBlock(), createBlock(Card_default, {
					key: 2,
					title: "Résumé des fichiers"
				}, {
					default: withCtx(() => [loadingAssets.value ? (openBlock(), createElementBlock("div", _hoisted_23$4, "Calcul en cours...")) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
						createBaseVNode("div", _hoisted_24$4, [_cache[15] || (_cache[15] = createBaseVNode("span", null, "Fichiers requis", -1)), createBaseVNode("span", null, toDisplayString(unref(store).totalFileCount), 1)]),
						unref(store).missingFiles.length > 0 ? (openBlock(), createElementBlock("div", _hoisted_25$4, [_cache[16] || (_cache[16] = createBaseVNode("span", null, "Fichiers manquants (source)", -1)), createBaseVNode("span", _hoisted_26$3, toDisplayString(unref(store).missingFiles.length), 1)])) : createCommentVNode("", true),
						createBaseVNode("div", _hoisted_27$3, [_cache[17] || (_cache[17] = createBaseVNode("span", null, "Taille totale", -1)), createBaseVNode("span", null, toDisplayString(unref(formatSize)(unref(store).totalAssetSize)), 1)]),
						unref(store).existingOnDeviceSize > 0 ? (openBlock(), createElementBlock("div", _hoisted_28$3, [createBaseVNode("span", null, toDisplayString(unref(store).transferPolicy === "overwrite-all" ? "À écraser sur l'appareil" : "Déjà sur l'appareil"), 1), createBaseVNode("span", { class: normalizeClass(unref(store).transferPolicy === "overwrite-all" ? "text-warn" : "text-success") }, toDisplayString(unref(formatSize)(unref(store).existingOnDeviceSize)), 3)])) : createCommentVNode("", true),
						createBaseVNode("div", _hoisted_29$3, [_cache[18] || (_cache[18] = createBaseVNode("span", null, "À transférer", -1)), createBaseVNode("span", _hoisted_30$3, toDisplayString(unref(formatSize)(unref(store).netTransferSize)), 1)]),
						unref(store).existingOnDeviceSize > 0 ? (openBlock(), createElementBlock("div", _hoisted_31$3, [unref(store).transferPolicy === "overwrite-all" ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createTextVNode(" Tous les fichiers seront retransférés, y compris les " + toDisplayString(unref(store).existingOnDeviceCount) + " fichier(s) existant(s) ", 1)], 64)) : unref(store).transferPolicy === "skip-existing" ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [createTextVNode(" Les fichiers existants seront ignorés ")], 64)) : (openBlock(), createElementBlock(Fragment, { key: 2 }, [createTextVNode(" Seuls les fichiers modifiés seront transférés ")], 64))])) : createCommentVNode("", true),
						storageTotal() > 0 ? (openBlock(), createBlock(StorageBar_default, {
							key: 3,
							"total-bytes": storageTotal(),
							"used-bytes": storageUsed(),
							"planned-bytes": unref(store).netTransferSize
						}, null, 8, [
							"total-bytes",
							"used-bytes",
							"planned-bytes"
						])) : createCommentVNode("", true)
					], 64))]),
					_: 1
				})) : createCommentVNode("", true),
				unref(store).product && unref(store).quality && (unref(store).missingFiles.length > 0 || unref(store).extraSearchDirs.length > 0) ? (openBlock(), createElementBlock("div", _hoisted_32$3, [
					_cache[19] || (_cache[19] = createBaseVNode("h3", null, "Dossiers de recherche supplémentaires", -1)),
					(openBlock(true), createElementBlock(Fragment, null, renderList(unref(store).extraSearchDirs, (dir) => {
						return openBlock(), createElementBlock("div", {
							key: dir,
							class: "extra-dir"
						}, [createBaseVNode("span", _hoisted_33$3, toDisplayString(dir), 1), createBaseVNode("button", {
							class: "extra-dir-remove",
							onClick: ($event) => removeSearchDir(dir)
						}, "×", 8, _hoisted_34$3)]);
					}), 128)),
					createBaseVNode("button", {
						class: "btn-add-dir",
						onClick: addSearchDir
					}, " + Ajouter un dossier source "),
					unref(store).missingFiles.length > 0 ? (openBlock(), createElementBlock("p", _hoisted_35$3, toDisplayString(unref(store).missingFiles.length) + " fichier(s) manquant(s). Ajoutez un dossier contenant les fichiers sources. ", 1)) : createCommentVNode("", true)
				])) : createCommentVNode("", true),
				unref(store).sourceDuplicates.length > 0 ? (openBlock(), createElementBlock("div", _hoisted_36$3, [_cache[22] || (_cache[22] = createBaseVNode("span", { class: "dup-icon" }, "⚠", -1)), createBaseVNode("div", null, [
					createBaseVNode("span", null, toDisplayString(unref(store).sourceDuplicates.length) + " fichier(s) avec plusieurs variantes de préfixe dans les sources", 1),
					_cache[21] || (_cache[21] = createBaseVNode("span", { class: "dup-hint" }, "Le fichier avec le préfixe prioritaire (1_ > 2_ > 0_ > sans) sera transféré.", -1)),
					createBaseVNode("details", _hoisted_37$3, [_cache[20] || (_cache[20] = createBaseVNode("summary", null, "Voir les doublons", -1)), (openBlock(true), createElementBlock(Fragment, null, renderList(unref(store).sourceDuplicates, (dup) => {
						return openBlock(), createElementBlock("div", {
							key: dup.baseName,
							class: "dup-entry"
						}, [createBaseVNode("span", _hoisted_38$2, toDisplayString(dup.baseName), 1), createBaseVNode("span", _hoisted_39$1, toDisplayString(dup.variants.join(", ")), 1)]);
					}), 128))])
				])])) : createCommentVNode("", true),
				unref(store).storageLocations.length > 1 && unref(store).product && unref(store).quality ? (openBlock(), createElementBlock("div", _hoisted_40$1, [
					_cache[28] || (_cache[28] = createBaseVNode("h3", null, "Emplacement de stockage", -1)),
					createBaseVNode("div", _hoisted_41$1, [(openBlock(true), createElementBlock(Fragment, null, renderList(unref(store).storageLocations, (loc) => {
						return openBlock(), createElementBlock("label", {
							key: loc.path,
							class: normalizeClass(["radio-item", { "radio-selected": unref(store).storageMode === loc.type }]),
							"data-testid": `storage-mode-${loc.type}`
						}, [withDirectives(createBaseVNode("input", {
							type: "radio",
							value: loc.type,
							"onUpdate:modelValue": _cache[5] || (_cache[5] = ($event) => unref(store).storageMode = $event)
						}, null, 8, _hoisted_43$1), [[vModelRadio, unref(store).storageMode]]), createBaseVNode("span", null, toDisplayString(loc.displayName) + " — " + toDisplayString(unref(formatSize)((loc.totalBytes ?? 0) - (loc.usedBytes ?? 0))) + " libre", 1)], 10, _hoisted_42$1);
					}), 128)), createBaseVNode("label", {
						class: normalizeClass(["radio-item", { "radio-selected": unref(store).storageMode === "both" }]),
						"data-testid": "storage-mode-both"
					}, [withDirectives(createBaseVNode("input", {
						type: "radio",
						value: "both",
						"onUpdate:modelValue": _cache[6] || (_cache[6] = ($event) => unref(store).storageMode = $event)
					}, null, 512), [[vModelRadio, unref(store).storageMode]]), createBaseVNode("span", null, "Les deux (bascule automatique) — " + toDisplayString(unref(formatSize)(unref(store).storageLocations.reduce((s, l) => s + ((l.totalBytes ?? 0) - (l.usedBytes ?? 0)), 0))) + " libre au total", 1)], 2)]),
					unref(store).storageMode === "both" ? (openBlock(), createElementBlock("div", _hoisted_44$1, [_cache[25] || (_cache[25] = createBaseVNode("h4", null, "Ordre de remplissage", -1)), createBaseVNode("div", _hoisted_45$1, [createBaseVNode("label", {
						class: normalizeClass(["radio-item small", { "radio-selected": unref(store).storagePriority === "internal-first" }]),
						"data-testid": "storage-priority-internal-first"
					}, [withDirectives(createBaseVNode("input", {
						type: "radio",
						value: "internal-first",
						"onUpdate:modelValue": _cache[7] || (_cache[7] = ($event) => unref(store).storagePriority = $event)
					}, null, 512), [[vModelRadio, unref(store).storagePriority]]), _cache[23] || (_cache[23] = createBaseVNode("span", null, "Interne d'abord", -1))], 2), createBaseVNode("label", {
						class: normalizeClass(["radio-item small", { "radio-selected": unref(store).storagePriority === "sdcard-first" }]),
						"data-testid": "storage-priority-sdcard-first"
					}, [withDirectives(createBaseVNode("input", {
						type: "radio",
						value: "sdcard-first",
						"onUpdate:modelValue": _cache[8] || (_cache[8] = ($event) => unref(store).storagePriority = $event)
					}, null, 512), [[vModelRadio, unref(store).storagePriority]]), _cache[24] || (_cache[24] = createBaseVNode("span", null, "Carte SD d'abord", -1))], 2)])])) : createCommentVNode("", true),
					unref(store).storageMode === "both" && unref(store).duplicateFiles.length > 0 ? (openBlock(), createElementBlock("div", _hoisted_46$1, [_cache[27] || (_cache[27] = createBaseVNode("span", { class: "duplicate-icon" }, "⚠", -1)), createBaseVNode("div", null, [createBaseVNode("span", null, toDisplayString(unref(store).duplicateFiles.length) + " fichier(s) présent(s) sur les deux stockages", 1), createBaseVNode("details", _hoisted_47$1, [_cache[26] || (_cache[26] = createBaseVNode("summary", null, "Voir la liste", -1)), createBaseVNode("ul", null, [(openBlock(true), createElementBlock(Fragment, null, renderList(unref(store).duplicateFiles, (f) => {
						return openBlock(), createElementBlock("li", { key: f }, toDisplayString(f), 1);
					}), 128))])])])])) : createCommentVNode("", true)
				])) : createCommentVNode("", true)
			], 64)) : createCommentVNode("", true)]);
		};
	}
}), [["__scopeId", "data-v-143c2a56"]]);
//#endregion
//#region src/renderer/pages/ReleaseSelect.vue?vue&type=script&setup=true&lang.ts
var _hoisted_1$9 = { class: "page" };
var _hoisted_2$9 = { class: "skip-row" };
var _hoisted_3$8 = { class: "checkbox-label" };
var _hoisted_4$7 = {
	key: 0,
	class: "local-apk-banner"
};
var _hoisted_5$7 = {
	key: 1,
	class: "loading"
};
var _hoisted_6$6 = {
	key: 2,
	class: "error"
};
var _hoisted_7$6 = {
	key: 3,
	class: "release-list"
};
var _hoisted_8$6 = { class: "release-row" };
var _hoisted_9$5 = { class: "release-info" };
var _hoisted_10$5 = { class: "release-tag" };
var _hoisted_11$5 = { class: "release-name" };
var _hoisted_12$5 = { class: "release-date" };
var _hoisted_13$5 = {
	key: 0,
	class: "empty"
};
//#endregion
//#region src/renderer/pages/ReleaseSelect.vue
var ReleaseSelect_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
	__name: "ReleaseSelect",
	setup(__props) {
		const store = useInstallationStore();
		const ipc = useIpc();
		const releases = /* @__PURE__ */ ref([]);
		const loading = /* @__PURE__ */ ref(false);
		const error = /* @__PURE__ */ ref(null);
		onMounted(async () => {
			if (!store.deviceProfile || store.skipApk) return;
			loading.value = true;
			error.value = null;
			try {
				releases.value = await ipc.fetchReleases(store.deviceProfile.repo);
			} catch (err) {
				error.value = err instanceof Error ? err.message : String(err);
			} finally {
				loading.value = false;
			}
		});
		function selectRelease(release) {
			store.releaseTag = release.tagName;
			store.releaseName = release.name;
			store.releaseDate = release.publishedAt;
			store.localApkPath = void 0;
		}
		async function selectLocalApk() {
			const path = await window.api.invoke("select-local-apk");
			if (path) {
				store.localApkPath = path;
				store.releaseTag = "local";
				store.releaseName = path.split("/").pop() ?? path;
				store.releaseDate = void 0;
				store.skipApk = false;
			}
		}
		function formatDate(iso) {
			return new Date(iso).toLocaleDateString("fr-FR", {
				day: "numeric",
				month: "long",
				year: "numeric"
			});
		}
		return (_ctx, _cache) => {
			return openBlock(), createElementBlock("div", _hoisted_1$9, [createBaseVNode("div", _hoisted_2$9, [createBaseVNode("label", _hoisted_3$8, [withDirectives(createBaseVNode("input", {
				type: "checkbox",
				"onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => unref(store).skipApk = $event),
				"data-testid": "skip-apk-toggle"
			}, null, 512), [[vModelCheckbox, unref(store).skipApk]]), _cache[1] || (_cache[1] = createBaseVNode("span", null, "Ignorer l'installation de l'APK", -1))])]), !unref(store).skipApk ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
				createBaseVNode("button", {
					class: "btn-local",
					"data-testid": "select-local-apk-btn",
					onClick: selectLocalApk
				}, " Utiliser un APK local "),
				unref(store).localApkPath ? (openBlock(), createElementBlock("div", _hoisted_4$7, [_cache[2] || (_cache[2] = createTextVNode(" APK local sélectionné : ", -1)), createBaseVNode("strong", null, toDisplayString(unref(store).localApkPath.split("/").pop()), 1)])) : createCommentVNode("", true),
				loading.value ? (openBlock(), createElementBlock("div", _hoisted_5$7, "Chargement des versions...")) : error.value ? (openBlock(), createElementBlock("div", _hoisted_6$6, toDisplayString(error.value), 1)) : (openBlock(), createElementBlock("div", _hoisted_7$6, [
					_cache[3] || (_cache[3] = createBaseVNode("h3", null, "Versions disponibles", -1)),
					(openBlock(true), createElementBlock(Fragment, null, renderList(releases.value, (r) => {
						return openBlock(), createBlock(Card_default, {
							key: r.tagName,
							class: normalizeClass(["release-card", { "release-selected": unref(store).releaseTag === r.tagName }]),
							"data-testid": `release-${r.tagName}`,
							onClick: ($event) => selectRelease(r)
						}, {
							default: withCtx(() => [createBaseVNode("div", _hoisted_8$6, [createBaseVNode("div", _hoisted_9$5, [createBaseVNode("span", _hoisted_10$5, toDisplayString(r.tagName), 1), createBaseVNode("span", _hoisted_11$5, toDisplayString(r.name), 1)]), createBaseVNode("span", _hoisted_12$5, toDisplayString(formatDate(r.publishedAt)), 1)])]),
							_: 2
						}, 1032, [
							"class",
							"data-testid",
							"onClick"
						]);
					}), 128)),
					releases.value.length === 0 ? (openBlock(), createElementBlock("p", _hoisted_13$5, "Aucune version trouvée")) : createCommentVNode("", true)
				]))
			], 64)) : createCommentVNode("", true)]);
		};
	}
}), [["__scopeId", "data-v-e9597d84"]]);
//#endregion
//#region src/renderer/composables/useTransfer.ts
function useTransfer() {
	const store = useInstallationStore();
	const launching = /* @__PURE__ */ ref(false);
	function buildPlan() {
		return {
			tabId: store.tabId,
			selectedDeviceId: store.selectedDeviceId,
			detectedDeviceModel: store.detectedDeviceModel,
			deviceProfile: store.deviceProfile,
			storageLocations: store.storageLocations,
			storageMode: store.storageMode,
			storagePriority: store.storagePriority,
			hasSufficientStorage: store.hasSufficientStorage,
			deviceName: store.deviceName,
			networkName: store.networkName,
			releaseTag: store.releaseTag,
			releaseDate: store.releaseDate,
			releaseName: store.releaseName,
			skipApk: store.skipApk,
			localApkPath: store.localApkPath,
			product: store.product,
			quality: store.quality,
			skipFiles: store.skipFiles,
			transferPolicy: store.transferPolicy,
			enableVerification: store.enableVerification,
			totalAssetSize: store.totalAssetSize,
			netTransferSize: store.netTransferSize,
			existingOnDeviceSize: store.existingOnDeviceSize,
			totalFileCount: store.totalFileCount,
			extraSearchDirs: store.extraSearchDirs,
			requiredFiles: store.requiredFiles,
			missingFiles: store.missingFiles,
			serenityFiles: store.serenityFiles,
			deviceCommandsPreview: []
		};
	}
	async function startExecution() {
		launching.value = true;
		store.executionSuccess = null;
		store.deviceDisconnected = false;
		store.logEntries = [];
		store.filesCompleted = 0;
		store.filesTotal = 0;
		const statusMap = /* @__PURE__ */ new Map();
		for (const file of store.missingFiles) statusMap.set(file, "missing");
		store.fileStatuses = statusMap;
		const dateStr = (/* @__PURE__ */ new Date()).toLocaleDateString("fr-FR", {
			weekday: "long",
			year: "numeric",
			month: "long",
			day: "numeric",
			hour: "2-digit",
			minute: "2-digit"
		});
		const policyLabels = {
			"update-different": "Mise à jour (différent)",
			"overwrite-all": "Écraser tout",
			"skip-existing": "Ignorer existants"
		};
		store.addLogEntry(`═══ Début de l'installation — ${dateStr} ═══`, "info");
		store.addLogEntry(`Appareil: ${store.deviceProfile?.displayName ?? store.detectedDeviceModel ?? "—"} (${store.selectedDeviceId ?? "—"})`, "info");
		store.addLogEntry(`Nom: ${store.deviceName || "—"}${store.networkName ? " | Réseau: " + store.networkName : ""}`, "info");
		store.addLogEntry(`Produit: ${store.product ?? "—"} | Qualité: ${store.quality ?? "—"}`, "info");
		store.addLogEntry(`Politique: ${policyLabels[store.transferPolicy] ?? store.transferPolicy} | Fichiers: ${store.requiredFiles.length} | APK: ${store.skipApk ? "ignoré" : store.releaseTag ?? "local"}`, "info");
		store.addLogEntry(`Stockage: ${{
			"internal": "Interne",
			"sdcard": "Carte SD",
			"both": "Les deux"
		}[store.storageMode] ?? store.storageMode}${store.storageMode === "both" ? " (" + ({
			"internal-first": "interne d'abord",
			"sdcard-first": "SD d'abord"
		}[store.storagePriority] ?? store.storagePriority) + ")" : ""} | Vérif. MD5: ${store.enableVerification ? "oui" : "non"} | Emplacements: ${store.storageLocations.length} (${store.storageLocations.map((l) => l.type).join(", ")})`, "info");
		store.addLogEntry(`───────────────────────────────────`, "info");
		try {
			const plan = JSON.parse(JSON.stringify(buildPlan()));
			console.log("[renderer] Starting execution with plan:", JSON.stringify({
				selectedDeviceId: plan.selectedDeviceId,
				product: plan.product,
				quality: plan.quality,
				skipApk: plan.skipApk,
				skipFiles: plan.skipFiles,
				releaseTag: plan.releaseTag,
				deviceProfile: plan.deviceProfile?.type,
				requiredFiles: plan.requiredFiles?.length,
				missingFiles: plan.missingFiles?.length,
				storageMode: plan.storageMode,
				storagePriority: plan.storagePriority,
				storageLocations: plan.storageLocations?.map((l) => ({
					type: l.type,
					path: l.path
				}))
			}));
			store.isExecuting = true;
			await window.api.invoke("start-execution", plan);
		} catch (err) {
			console.error("[renderer] startExecution error:", err);
			store.addLogEntry(`Erreur: ${err instanceof Error ? err.message : String(err)}`, "error");
			store.isExecuting = false;
		} finally {
			launching.value = false;
		}
	}
	async function stopExecution() {
		await window.api.invoke("stop-execution", store.tabId);
	}
	return {
		launching,
		buildPlan,
		startExecution,
		stopExecution
	};
}
//#endregion
//#region src/renderer/pages/Review.vue?vue&type=script&setup=true&lang.ts
var _hoisted_1$8 = { class: "page" };
var _hoisted_2$8 = { class: "cards-grid" };
var _hoisted_3$7 = { class: "row" };
var _hoisted_4$6 = { class: "row" };
var _hoisted_5$6 = { class: "mono" };
var _hoisted_6$5 = {
	key: 0,
	class: "row"
};
var _hoisted_7$5 = {
	key: 1,
	class: "row"
};
var _hoisted_8$5 = { class: "row" };
var _hoisted_9$4 = { class: "text-primary" };
var _hoisted_10$4 = {
	key: 0,
	class: "row"
};
var _hoisted_11$4 = { class: "content-grid" };
var _hoisted_12$4 = { class: "row" };
var _hoisted_13$4 = { class: "row" };
var _hoisted_14$4 = { class: "row" };
var _hoisted_15$4 = { class: "row" };
var _hoisted_16$4 = { class: "row" };
var _hoisted_17$4 = { class: "text-primary" };
var _hoisted_18$4 = {
	key: 0,
	class: "warning-box"
};
//#endregion
//#region src/renderer/pages/Review.vue
var Review_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
	__name: "Review",
	setup(__props) {
		const store = useInstallationStore();
		const { startExecution } = useTransfer();
		const policyLabels = {
			"update-different": "Mettre à jour",
			"overwrite-all": "Tout écraser",
			"skip-existing": "Ignorer existants"
		};
		function launch() {
			store.goToStep(5);
			startExecution();
		}
		return (_ctx, _cache) => {
			return openBlock(), createElementBlock("div", _hoisted_1$8, [
				createBaseVNode("div", _hoisted_2$8, [
					createVNode(Card_default, {
						title: "Appareil",
						class: "summary-card",
						onClick: _cache[0] || (_cache[0] = ($event) => unref(store).goToStep(0))
					}, {
						default: withCtx(() => [
							createBaseVNode("div", _hoisted_3$7, [_cache[3] || (_cache[3] = createBaseVNode("span", { class: "label" }, "Type", -1)), createBaseVNode("span", null, toDisplayString(unref(store).deviceProfile?.displayName ?? "Inconnu"), 1)]),
							createBaseVNode("div", _hoisted_4$6, [_cache[4] || (_cache[4] = createBaseVNode("span", { class: "label" }, "Série", -1)), createBaseVNode("span", _hoisted_5$6, toDisplayString(unref(store).selectedDeviceId), 1)]),
							unref(store).deviceName ? (openBlock(), createElementBlock("div", _hoisted_6$5, [_cache[5] || (_cache[5] = createBaseVNode("span", { class: "label" }, "Nom", -1)), createBaseVNode("span", null, toDisplayString(unref(store).deviceName), 1)])) : createCommentVNode("", true),
							unref(store).networkName ? (openBlock(), createElementBlock("div", _hoisted_7$5, [_cache[6] || (_cache[6] = createBaseVNode("span", { class: "label" }, "Réseau", -1)), createBaseVNode("span", null, toDisplayString(unref(store).networkName), 1)])) : createCommentVNode("", true)
						]),
						_: 1
					}),
					!unref(store).skipApk ? (openBlock(), createBlock(Card_default, {
						key: 0,
						title: "Version",
						class: "summary-card",
						onClick: _cache[1] || (_cache[1] = ($event) => unref(store).goToStep(3))
					}, {
						default: withCtx(() => [createBaseVNode("div", _hoisted_8$5, [_cache[7] || (_cache[7] = createBaseVNode("span", { class: "label" }, "Tag", -1)), createBaseVNode("span", _hoisted_9$4, toDisplayString(unref(store).releaseTag ?? "—"), 1)]), unref(store).releaseName ? (openBlock(), createElementBlock("div", _hoisted_10$4, [_cache[8] || (_cache[8] = createBaseVNode("span", { class: "label" }, "Nom", -1)), createBaseVNode("span", null, toDisplayString(unref(store).releaseName), 1)])) : createCommentVNode("", true)]),
						_: 1
					})) : (openBlock(), createBlock(Card_default, {
						key: 1,
						title: "Version",
						class: "summary-card muted"
					}, {
						default: withCtx(() => [..._cache[9] || (_cache[9] = [createBaseVNode("span", { class: "muted-text" }, "Installation APK ignorée", -1)])]),
						_: 1
					})),
					!unref(store).skipFiles ? (openBlock(), createBlock(Card_default, {
						key: 2,
						title: "Contenu",
						class: "summary-card card-wide",
						onClick: _cache[2] || (_cache[2] = ($event) => unref(store).goToStep(2))
					}, {
						default: withCtx(() => [createBaseVNode("div", _hoisted_11$4, [
							createBaseVNode("div", _hoisted_12$4, [_cache[10] || (_cache[10] = createBaseVNode("span", { class: "label" }, "Produit", -1)), createBaseVNode("span", null, toDisplayString(unref(store).product ?? "—"), 1)]),
							createBaseVNode("div", _hoisted_13$4, [_cache[11] || (_cache[11] = createBaseVNode("span", { class: "label" }, "Qualité", -1)), createBaseVNode("span", null, toDisplayString(unref(store).quality ?? "—"), 1)]),
							createBaseVNode("div", _hoisted_14$4, [_cache[12] || (_cache[12] = createBaseVNode("span", { class: "label" }, "Politique", -1)), createBaseVNode("span", null, toDisplayString(policyLabels[unref(store).transferPolicy] ?? unref(store).transferPolicy), 1)]),
							createBaseVNode("div", _hoisted_15$4, [_cache[13] || (_cache[13] = createBaseVNode("span", { class: "label" }, "Fichiers", -1)), createBaseVNode("span", null, toDisplayString(unref(store).totalFileCount), 1)]),
							createBaseVNode("div", _hoisted_16$4, [_cache[14] || (_cache[14] = createBaseVNode("span", { class: "label" }, "Taille", -1)), createBaseVNode("span", _hoisted_17$4, toDisplayString(unref(formatSize)(unref(store).netTransferSize)), 1)])
						])]),
						_: 1
					})) : (openBlock(), createBlock(Card_default, {
						key: 3,
						title: "Contenu",
						class: "summary-card card-wide muted"
					}, {
						default: withCtx(() => [..._cache[15] || (_cache[15] = [createBaseVNode("span", { class: "muted-text" }, "Transfert de fichiers ignoré", -1)])]),
						_: 1
					}))
				]),
				unref(store).missingFiles.length > 0 ? (openBlock(), createElementBlock("div", _hoisted_18$4, toDisplayString(unref(store).missingFiles.length) + " fichier(s) source introuvable(s) — l'installation continuera sans eux. ", 1)) : createCommentVNode("", true),
				createBaseVNode("button", {
					class: "btn-launch",
					"data-testid": "start-execution-btn",
					onClick: launch
				}, " Lancer l'installation ")
			]);
		};
	}
}), [["__scopeId", "data-v-1c979fea"]]);
//#endregion
//#region src/renderer/components/ProgressBar.vue?vue&type=script&setup=true&lang.ts
var _hoisted_1$7 = { class: "progress-container" };
var _hoisted_2$7 = {
	key: 0,
	class: "progress-header"
};
var _hoisted_3$6 = { class: "progress-label" };
var _hoisted_4$5 = { class: "progress-pct" };
var _hoisted_5$5 = { class: "progress-track" };
var _hoisted_6$4 = {
	key: 1,
	class: "progress-header secondary-header"
};
var _hoisted_7$4 = { class: "progress-label secondary-label" };
var _hoisted_8$4 = { class: "progress-pct secondary-pct" };
//#endregion
//#region src/renderer/components/ProgressBar.vue
var ProgressBar_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
	__name: "ProgressBar",
	props: {
		value: {},
		max: {},
		label: {},
		secondaryValue: {},
		secondaryMax: {},
		secondaryLabel: {}
	},
	setup(__props) {
		const props = __props;
		const percentage = computed(() => props.max > 0 ? Math.round(props.value / props.max * 100) : 0);
		const secondaryPercentage = computed(() => {
			if (!props.secondaryMax || props.secondaryMax <= 0) return 0;
			return Math.round((props.secondaryValue ?? 0) / props.secondaryMax * 100);
		});
		const secondaryTrackWidth = computed(() => {
			if (!props.secondaryMax || props.secondaryMax <= 0 || percentage.value <= 0) return 0;
			return Math.round(secondaryPercentage.value / 100 * percentage.value);
		});
		const showSecondary = computed(() => props.secondaryMax != null && props.secondaryMax > 0);
		return (_ctx, _cache) => {
			return openBlock(), createElementBlock("div", _hoisted_1$7, [
				__props.label ? (openBlock(), createElementBlock("div", _hoisted_2$7, [createBaseVNode("span", _hoisted_3$6, toDisplayString(__props.label), 1), createBaseVNode("span", _hoisted_4$5, toDisplayString(percentage.value) + "%", 1)])) : createCommentVNode("", true),
				createBaseVNode("div", _hoisted_5$5, [createBaseVNode("div", {
					class: "progress-fill",
					style: normalizeStyle({ width: percentage.value + "%" })
				}, null, 4), showSecondary.value ? (openBlock(), createElementBlock("div", {
					key: 0,
					class: "progress-fill-secondary",
					style: normalizeStyle({ width: secondaryTrackWidth.value + "%" })
				}, null, 4)) : createCommentVNode("", true)]),
				showSecondary.value && __props.secondaryLabel ? (openBlock(), createElementBlock("div", _hoisted_6$4, [createBaseVNode("span", _hoisted_7$4, toDisplayString(__props.secondaryLabel), 1), createBaseVNode("span", _hoisted_8$4, toDisplayString(secondaryPercentage.value) + "%", 1)])) : createCommentVNode("", true)
			]);
		};
	}
}), [["__scopeId", "data-v-36196173"]]);
//#endregion
//#region src/renderer/components/LogPanel.vue?vue&type=script&setup=true&lang.ts
var _hoisted_1$6 = { class: "log-time" };
var _hoisted_2$6 = { class: "log-msg" };
var _hoisted_3$5 = {
	key: 0,
	class: "log-empty"
};
var ROW_HEIGHT$1 = 18;
var OVERSCAN$1 = 5;
//#endregion
//#region src/renderer/components/LogPanel.vue
var LogPanel_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
	__name: "LogPanel",
	props: { entries: {} },
	setup(__props) {
		const props = __props;
		const container = /* @__PURE__ */ ref(null);
		const scrollTop = /* @__PURE__ */ ref(0);
		const containerHeight = /* @__PURE__ */ ref(300);
		const isAtBottom = /* @__PURE__ */ ref(true);
		function onScroll() {
			if (!container.value) return;
			scrollTop.value = container.value.scrollTop;
			isAtBottom.value = container.value.scrollHeight - container.value.clientHeight - container.value.scrollTop < ROW_HEIGHT$1 * 2;
		}
		let resizeObserver = null;
		onMounted(() => {
			if (container.value) {
				containerHeight.value = container.value.clientHeight;
				resizeObserver = new ResizeObserver(([entry]) => {
					containerHeight.value = entry.contentRect.height;
				});
				resizeObserver.observe(container.value);
			}
		});
		onUnmounted(() => {
			resizeObserver?.disconnect();
		});
		const totalHeight = computed(() => props.entries.length * ROW_HEIGHT$1);
		const visibleRange = computed(() => {
			return {
				start: Math.max(0, Math.floor(scrollTop.value / ROW_HEIGHT$1) - OVERSCAN$1),
				end: Math.min(props.entries.length, Math.ceil((scrollTop.value + containerHeight.value) / ROW_HEIGHT$1) + OVERSCAN$1)
			};
		});
		const visibleEntries = computed(() => props.entries.slice(visibleRange.value.start, visibleRange.value.end).map((entry, i) => ({
			entry,
			index: visibleRange.value.start + i
		})));
		const offsetY = computed(() => visibleRange.value.start * ROW_HEIGHT$1);
		watch(() => props.entries.length, () => {
			if (!isAtBottom.value || !container.value) return;
			const newTotal = props.entries.length * ROW_HEIGHT$1;
			const target = Math.max(0, newTotal - containerHeight.value);
			scrollTop.value = target;
			container.value.scrollTop = target;
		});
		function typeClass(type) {
			switch (type) {
				case "error": return "log-error";
				case "warn": return "log-warn";
				case "success": return "log-success";
				default: return "log-info";
			}
		}
		return (_ctx, _cache) => {
			return openBlock(), createElementBlock("div", {
				class: "log-panel",
				ref_key: "container",
				ref: container,
				onScroll
			}, [createBaseVNode("div", {
				class: "log-spacer",
				style: normalizeStyle({ height: totalHeight.value + "px" })
			}, [createBaseVNode("div", {
				class: "log-visible",
				style: normalizeStyle({ transform: `translateY(${offsetY.value}px)` })
			}, [(openBlock(true), createElementBlock(Fragment, null, renderList(visibleEntries.value, ({ entry, index }) => {
				return openBlock(), createElementBlock("div", {
					key: index,
					class: normalizeClass(["log-entry", typeClass(entry.type)])
				}, [createBaseVNode("span", _hoisted_1$6, toDisplayString(entry.time), 1), createBaseVNode("span", _hoisted_2$6, toDisplayString(entry.message), 1)], 2);
			}), 128))], 4)], 4), __props.entries.length === 0 ? (openBlock(), createElementBlock("div", _hoisted_3$5, "Aucun log")) : createCommentVNode("", true)], 544);
		};
	}
}), [["__scopeId", "data-v-370f839d"]]);
//#endregion
//#region src/renderer/components/FileList.vue?vue&type=script&setup=true&lang.ts
var _hoisted_1$5 = ["data-testid", "data-status"];
var _hoisted_2$5 = { class: "file-icon" };
var _hoisted_3$4 = { class: "file-name" };
var _hoisted_4$4 = {
	key: 0,
	class: "file-size"
};
var _hoisted_5$4 = { class: "file-badge" };
var ROW_HEIGHT = 22;
var OVERSCAN = 5;
//#endregion
//#region src/renderer/components/FileList.vue
var FileList_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
	__name: "FileList",
	props: {
		files: {},
		statuses: {},
		sizes: {}
	},
	setup(__props) {
		const props = __props;
		const containerRef = /* @__PURE__ */ ref(null);
		const scrollTop = /* @__PURE__ */ ref(0);
		const containerHeight = /* @__PURE__ */ ref(250);
		function onScroll() {
			if (containerRef.value) scrollTop.value = containerRef.value.scrollTop;
		}
		let resizeObserver = null;
		onMounted(() => {
			if (containerRef.value) {
				containerHeight.value = containerRef.value.clientHeight;
				resizeObserver = new ResizeObserver(([entry]) => {
					containerHeight.value = entry.contentRect.height;
				});
				resizeObserver.observe(containerRef.value);
			}
		});
		onUnmounted(() => {
			resizeObserver?.disconnect();
		});
		const totalHeight = computed(() => props.files.length * ROW_HEIGHT);
		const visibleRange = computed(() => {
			return {
				start: Math.max(0, Math.floor(scrollTop.value / ROW_HEIGHT) - OVERSCAN),
				end: Math.min(props.files.length, Math.ceil((scrollTop.value + containerHeight.value) / ROW_HEIGHT) + OVERSCAN)
			};
		});
		const visibleFiles = computed(() => props.files.slice(visibleRange.value.start, visibleRange.value.end).map((file, i) => ({
			file,
			index: visibleRange.value.start + i,
			status: props.statuses.get(file),
			size: props.sizes?.[file]
		})));
		const offsetY = computed(() => visibleRange.value.start * ROW_HEIGHT);
		function statusIcon(status) {
			switch (status) {
				case "pending": return "○";
				case "transferring": return "◐";
				case "completed": return "✓";
				case "verifying": return "🔍";
				case "verified": return "✓✓";
				case "verify-failed": return "✗✗";
				case "failed": return "✗";
				case "skipped": return "–";
				case "missing": return "?";
				default: return "○";
			}
		}
		function statusClass(status) {
			switch (status) {
				case "transferring": return "st-active";
				case "completed": return "st-done";
				case "verified": return "st-verified";
				case "verifying": return "st-verifying";
				case "verify-failed": return "st-verify-failed";
				case "failed": return "st-failed";
				case "skipped": return "st-skipped";
				case "missing": return "st-missing";
				default: return "st-pending";
			}
		}
		function statusLabel(status) {
			switch (status) {
				case "pending": return "En attente";
				case "transferring": return "Transfert...";
				case "completed": return "Transféré";
				case "verifying": return "Vérification...";
				case "verified": return "Vérifié";
				case "verify-failed": return "Vérif. échouée";
				case "failed": return "Erreur";
				case "skipped": return "Ignoré";
				case "missing": return "Manquant";
				default: return "En attente";
			}
		}
		return (_ctx, _cache) => {
			return openBlock(), createElementBlock("div", {
				ref_key: "containerRef",
				ref: containerRef,
				class: "file-list",
				onScroll
			}, [createBaseVNode("div", {
				class: "file-list-spacer",
				style: normalizeStyle({ height: totalHeight.value + "px" })
			}, [createBaseVNode("div", {
				class: "file-list-visible",
				style: normalizeStyle({ transform: `translateY(${offsetY.value}px)` })
			}, [(openBlock(true), createElementBlock(Fragment, null, renderList(visibleFiles.value, (item) => {
				return openBlock(), createElementBlock("div", {
					key: item.file,
					class: normalizeClass(["file-row", statusClass(item.status)]),
					"data-testid": `file-row-${item.file}`,
					"data-status": item.status ?? "pending"
				}, [
					createBaseVNode("span", _hoisted_2$5, toDisplayString(statusIcon(item.status)), 1),
					createBaseVNode("span", _hoisted_3$4, toDisplayString(item.file), 1),
					item.size ? (openBlock(), createElementBlock("span", _hoisted_4$4, toDisplayString(unref(formatSize)(item.size)), 1)) : createCommentVNode("", true),
					createBaseVNode("span", _hoisted_5$4, toDisplayString(statusLabel(item.status)), 1)
				], 10, _hoisted_1$5);
			}), 128))], 4)], 4)], 544);
		};
	}
}), [["__scopeId", "data-v-72115ca5"]]);
//#endregion
//#region src/renderer/pages/Execution.vue?vue&type=script&setup=true&lang.ts
var _hoisted_1$4 = ["data-phase"];
var _hoisted_2$4 = { class: "exec-top" };
var _hoisted_3$3 = {
	key: 0,
	class: "disconnect-banner",
	"data-testid": "exec-disconnect-banner"
};
var _hoisted_4$3 = { class: "stats-bar" };
var _hoisted_5$3 = { class: "stat" };
var _hoisted_6$3 = { class: "stat" };
var _hoisted_7$3 = { class: "stat" };
var _hoisted_8$3 = {
	key: 0,
	class: "stat-sep"
};
var _hoisted_9$3 = {
	key: 1,
	class: "stat stat-eta"
};
var _hoisted_10$3 = {
	key: 2,
	class: "stat-sep"
};
var _hoisted_11$3 = {
	key: 3,
	class: "stat stat-muted"
};
var _hoisted_12$3 = {
	key: 4,
	class: "stat-sep"
};
var _hoisted_13$3 = {
	key: 5,
	class: "stat stat-error"
};
var _hoisted_14$3 = {
	key: 6,
	class: "stat-sep"
};
var _hoisted_15$3 = {
	key: 7,
	class: "stat stat-error"
};
var _hoisted_16$3 = { class: "exec-body" };
var _hoisted_17$3 = { class: "exec-left" };
var _hoisted_18$3 = { class: "phases-row" };
var _hoisted_19$3 = { class: "phase-icon" };
var _hoisted_20$3 = { class: "phase-label" };
var _hoisted_21$3 = { class: "exec-right" };
var _hoisted_22$3 = { class: "exec-footer" };
var _hoisted_23$3 = { class: "footer-item" };
var _hoisted_24$3 = { class: "footer-item" };
var _hoisted_25$3 = { class: "footer-item" };
//#endregion
//#region src/renderer/pages/Execution.vue
var Execution_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
	__name: "Execution",
	setup(__props) {
		const store = useInstallationStore();
		const { stopExecution } = useTransfer();
		const now = /* @__PURE__ */ ref(Date.now());
		let tickTimer = null;
		onMounted(() => {
			tickTimer = setInterval(() => {
				now.value = Date.now();
			}, 1e3);
		});
		onUnmounted(() => {
			if (tickTimer) clearInterval(tickTimer);
		});
		const phases = [
			{
				key: "download",
				label: "Téléchargement APK"
			},
			{
				key: "install",
				label: "Installation APK"
			},
			{
				key: "configure",
				label: "Configuration"
			},
			{
				key: "transfer",
				label: "Transfert"
			}
		];
		function phaseIcon(phaseKey) {
			if (store.executionPhase === phaseKey) return "◐";
			if (phases.findIndex((p) => p.key === store.executionPhase) > phases.findIndex((p) => p.key === phaseKey)) return "✓";
			return "○";
		}
		function phaseClass(phaseKey) {
			if (store.executionPhase === phaseKey) return "phase-active";
			if (phases.findIndex((p) => p.key === store.executionPhase) > phases.findIndex((p) => p.key === phaseKey)) return "phase-done";
			return "phase-pending";
		}
		const transferStats = computed(() => {
			if (!store.executionStartedAt) return {
				elapsed: "—",
				speed: "—",
				eta: ""
			};
			const elapsedSec = Math.max(1, Math.round((now.value - store.executionStartedAt) / 1e3));
			const elapsed = elapsedSec < 60 ? `${elapsedSec}s` : `${Math.floor(elapsedSec / 60)}m ${elapsedSec % 60}s`;
			const speedBps = store.bytesTransferred === 0 ? 0 : store.bytesTransferred / elapsedSec;
			const speed = speedBps === 0 ? "—" : (speedBps / (1024 * 1024)).toFixed(1) + " Mo/s";
			let eta = "";
			if (speedBps > 0 && store.totalAssetSize > 0) {
				const remaining = Math.max(0, store.totalAssetSize - store.bytesTransferred);
				const secs = Math.round(remaining / speedBps);
				eta = secs < 60 ? `~${secs}s` : `~${Math.floor(secs / 60)}m ${secs % 60}s`;
			}
			return {
				elapsed,
				speed,
				eta
			};
		});
		return (_ctx, _cache) => {
			return openBlock(), createElementBlock("div", {
				class: "exec",
				"data-testid": `execution-phase`,
				"data-phase": unref(store).executionPhase
			}, [
				createBaseVNode("div", _hoisted_2$4, [
					unref(store).deviceDisconnected ? (openBlock(), createElementBlock("div", _hoisted_3$3, " Appareil déconnecté. Vérifiez le câble USB. ")) : createCommentVNode("", true),
					createVNode(ProgressBar_default, {
						value: unref(store).filesCompleted,
						max: unref(store).filesTotal || 1,
						label: `${unref(store).filesCompleted}/${unref(store).filesTotal} fichiers`,
						"secondary-value": unref(store).enableVerification ? unref(store).filesVerifiedCount + unref(store).filesVerifyFailedCount : void 0,
						"secondary-max": unref(store).enableVerification ? unref(store).filesCompleted : void 0,
						"secondary-label": unref(store).enableVerification && unref(store).filesCompleted > 0 ? `${unref(store).filesVerifiedCount + unref(store).filesVerifyFailedCount}/${unref(store).filesCompleted} vérifiés` : void 0
					}, null, 8, [
						"value",
						"max",
						"label",
						"secondary-value",
						"secondary-max",
						"secondary-label"
					]),
					createBaseVNode("div", _hoisted_4$3, [
						createBaseVNode("span", _hoisted_5$3, toDisplayString(unref(formatSize)(unref(store).bytesTransferred)), 1),
						_cache[2] || (_cache[2] = createBaseVNode("span", { class: "stat-sep" }, "·", -1)),
						createBaseVNode("span", _hoisted_6$3, toDisplayString(transferStats.value.speed), 1),
						_cache[3] || (_cache[3] = createBaseVNode("span", { class: "stat-sep" }, "·", -1)),
						createBaseVNode("span", _hoisted_7$3, toDisplayString(transferStats.value.elapsed), 1),
						transferStats.value.eta ? (openBlock(), createElementBlock("span", _hoisted_8$3, "·")) : createCommentVNode("", true),
						transferStats.value.eta ? (openBlock(), createElementBlock("span", _hoisted_9$3, toDisplayString(transferStats.value.eta), 1)) : createCommentVNode("", true),
						unref(store).filesSkippedCount > 0 ? (openBlock(), createElementBlock("span", _hoisted_10$3, "·")) : createCommentVNode("", true),
						unref(store).filesSkippedCount > 0 ? (openBlock(), createElementBlock("span", _hoisted_11$3, toDisplayString(unref(store).filesSkippedCount) + " ignoré(s)", 1)) : createCommentVNode("", true),
						unref(store).filesMissingCount > 0 ? (openBlock(), createElementBlock("span", _hoisted_12$3, "·")) : createCommentVNode("", true),
						unref(store).filesMissingCount > 0 ? (openBlock(), createElementBlock("span", _hoisted_13$3, toDisplayString(unref(store).filesMissingCount) + " manquant(s)", 1)) : createCommentVNode("", true),
						unref(store).filesVerifyFailedCount > 0 ? (openBlock(), createElementBlock("span", _hoisted_14$3, "·")) : createCommentVNode("", true),
						unref(store).filesVerifyFailedCount > 0 ? (openBlock(), createElementBlock("span", _hoisted_15$3, toDisplayString(unref(store).filesVerifyFailedCount) + " vérif. échouée(s)", 1)) : createCommentVNode("", true),
						_cache[4] || (_cache[4] = createBaseVNode("div", { class: "stats-spacer" }, null, -1)),
						unref(store).isExecuting ? (openBlock(), createElementBlock("button", {
							key: 8,
							class: "btn-stop",
							"data-testid": "stop-execution-btn",
							onClick: _cache[0] || (_cache[0] = (...args) => unref(stopExecution) && unref(stopExecution)(...args))
						}, " Arrêter ")) : createCommentVNode("", true),
						!unref(store).isExecuting && unref(store).executionSuccess !== null ? (openBlock(), createElementBlock("button", {
							key: 9,
							class: "btn-results",
							"data-testid": "go-to-results-btn",
							onClick: _cache[1] || (_cache[1] = ($event) => unref(store).goToStep(6))
						}, " Résultats → ")) : createCommentVNode("", true)
					])
				]),
				createBaseVNode("div", _hoisted_16$3, [createBaseVNode("div", _hoisted_17$3, [createBaseVNode("div", _hoisted_18$3, [(openBlock(), createElementBlock(Fragment, null, renderList(phases, (phase) => {
					return createBaseVNode("div", {
						key: phase.key,
						class: normalizeClass(["phase-chip", phaseClass(phase.key)])
					}, [createBaseVNode("span", _hoisted_19$3, toDisplayString(phaseIcon(phase.key)), 1), createBaseVNode("span", _hoisted_20$3, toDisplayString(phase.label), 1)], 2);
				}), 64))]), unref(store).requiredFiles.length > 0 ? (openBlock(), createBlock(Card_default, {
					key: 0,
					title: "Fichiers",
					class: "files-card"
				}, {
					default: withCtx(() => [createVNode(FileList_default, {
						files: unref(store).requiredFiles,
						statuses: unref(store).fileStatuses,
						sizes: unref(store).fileSizes
					}, null, 8, [
						"files",
						"statuses",
						"sizes"
					])]),
					_: 1
				})) : createCommentVNode("", true)]), createBaseVNode("div", _hoisted_21$3, [createVNode(Card_default, {
					title: "Journal",
					class: "journal-card"
				}, {
					default: withCtx(() => [createVNode(LogPanel_default, { entries: unref(store).logEntries }, null, 8, ["entries"])]),
					_: 1
				})])]),
				createBaseVNode("div", _hoisted_22$3, [
					createBaseVNode("span", _hoisted_23$3, toDisplayString(unref(store).deviceProfile?.displayName ?? "—"), 1),
					_cache[5] || (_cache[5] = createBaseVNode("span", { class: "footer-sep" }, "·", -1)),
					createBaseVNode("span", _hoisted_24$3, toDisplayString(unref(store).product ?? "—"), 1),
					_cache[6] || (_cache[6] = createBaseVNode("span", { class: "footer-sep" }, "·", -1)),
					createBaseVNode("span", _hoisted_25$3, toDisplayString(unref(store).releaseTag ?? "—"), 1)
				])
			], 8, _hoisted_1$4);
		};
	}
}), [["__scopeId", "data-v-e338f3f7"]]);
//#endregion
//#region src/renderer/components/DuplicateManager.vue?vue&type=script&setup=true&lang.ts
var _hoisted_1$3 = {
	class: "dup-manager",
	"data-testid": "duplicate-manager"
};
var _hoisted_2$3 = { class: "dup-header" };
var _hoisted_3$2 = { class: "dup-header-actions" };
var _hoisted_4$2 = ["disabled"];
var _hoisted_5$2 = { class: "dup-body" };
var _hoisted_6$2 = {
	key: 0,
	class: "dup-loading"
};
var _hoisted_7$2 = {
	key: 1,
	class: "dup-error"
};
var _hoisted_8$2 = { class: "dup-stats" };
var _hoisted_9$2 = {
	key: 0,
	class: "dup-empty"
};
var _hoisted_10$2 = {
	key: 1,
	class: "dup-section"
};
var _hoisted_11$2 = { class: "dup-section-header" };
var _hoisted_12$2 = { class: "dup-section-toolbar" };
var _hoisted_13$2 = { class: "dup-group-header" };
var _hoisted_14$2 = { class: "dup-group-name" };
var _hoisted_15$2 = { class: "dup-group-count" };
var _hoisted_16$2 = ["onClick"];
var _hoisted_17$2 = ["data-testid"];
var _hoisted_18$2 = [
	"checked",
	"data-testid",
	"onChange"
];
var _hoisted_19$2 = {
	key: 1,
	class: "dup-del-icon"
};
var _hoisted_20$2 = { class: "dup-file-name" };
var _hoisted_21$2 = { class: "dup-file-size" };
var _hoisted_22$2 = [
	"data-testid",
	"disabled",
	"onClick"
];
var _hoisted_23$2 = {
	key: 3,
	class: "dup-deleted-label"
};
var _hoisted_24$2 = {
	key: 2,
	class: "dup-section"
};
var _hoisted_25$2 = { class: "dup-section-header" };
var _hoisted_26$2 = { class: "dup-section-toolbar" };
var _hoisted_27$2 = { class: "dup-group-header" };
var _hoisted_28$2 = { class: "dup-group-name" };
var _hoisted_29$2 = { class: "dup-group-count" };
var _hoisted_30$2 = ["onClick"];
var _hoisted_31$2 = ["data-testid"];
var _hoisted_32$2 = [
	"checked",
	"data-testid",
	"onChange"
];
var _hoisted_33$2 = {
	key: 1,
	class: "dup-del-icon"
};
var _hoisted_34$2 = { class: "dup-file-name" };
var _hoisted_35$2 = { class: "dup-file-size" };
var _hoisted_36$2 = [
	"data-testid",
	"disabled",
	"onClick"
];
var _hoisted_37$2 = {
	key: 3,
	class: "dup-deleted-label"
};
var _hoisted_38$1 = {
	key: 0,
	class: "dup-batch"
};
//#endregion
//#region src/renderer/components/DuplicateManager.vue
var DuplicateManager_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
	__name: "DuplicateManager",
	emits: ["close", "deleted"],
	setup(__props, { emit: __emit }) {
		const emit = __emit;
		const store = useInstallationStore();
		const scanning = /* @__PURE__ */ ref(false);
		const scanResult = /* @__PURE__ */ ref(null);
		const scanError = /* @__PURE__ */ ref("");
		const selected = /* @__PURE__ */ ref(/* @__PURE__ */ new Set());
		const deleted = /* @__PURE__ */ ref(/* @__PURE__ */ new Set());
		const deleting = /* @__PURE__ */ ref(/* @__PURE__ */ new Set());
		const hasSelection = computed(() => selected.value.size > 0);
		async function scan() {
			if (!store.selectedDeviceId || !store.deviceProfile) return;
			scanning.value = true;
			scanError.value = "";
			scanResult.value = null;
			const internalDir = store.deviceProfile.targetDir;
			let sdcardDir;
			const sdLoc = store.storageLocations.find((l) => l.type === "sdcard");
			if (sdLoc) {
				const relativePath = internalDir.replace(/^\/storage\/emulated\/0/, "");
				sdcardDir = sdLoc.path + relativePath;
			}
			try {
				scanResult.value = await window.api.invoke("scan-device-duplicates", store.selectedDeviceId, internalDir, sdcardDir);
			} catch (err) {
				scanError.value = err instanceof Error ? err.message : String(err);
			} finally {
				scanning.value = false;
			}
		}
		onMounted(scan);
		function toggleSelect(path) {
			const next = new Set(selected.value);
			if (next.has(path)) next.delete(path);
			else next.add(path);
			selected.value = next;
		}
		function selectAllInGroup(group, keepFirst) {
			const next = new Set(selected.value);
			const files = group.files.filter((f) => !deleted.value.has(f.path));
			const toSelect = keepFirst ? files.slice(1) : files;
			for (const f of toSelect) next.add(f.path);
			selected.value = next;
		}
		async function deleteFile(path) {
			if (!store.selectedDeviceId) return;
			deleting.value = new Set([...deleting.value, path]);
			try {
				await window.api.invoke("delete-device-file", store.selectedDeviceId, path);
				deleted.value = new Set([...deleted.value, path]);
				selected.value = new Set([...selected.value].filter((p) => p !== path));
				emit("deleted");
			} finally {
				const next = new Set(deleting.value);
				next.delete(path);
				deleting.value = next;
			}
		}
		async function deleteSelected() {
			const paths = [...selected.value].filter((p) => !deleted.value.has(p));
			for (const path of paths) await deleteFile(path);
		}
		function selectAllByStorage(storage) {
			if (!scanResult.value) return;
			const next = new Set(selected.value);
			for (const group of scanResult.value.crossStorage) for (const f of group.files) if (f.storage === storage && !deleted.value.has(f.path)) next.add(f.path);
			selected.value = next;
		}
		function selectAllNonPrefixed() {
			if (!scanResult.value) return;
			const next = new Set(selected.value);
			for (const group of scanResult.value.prefixVariants) for (const f of group.files) if (!deleted.value.has(f.path) && !/^[012]_/.test(f.fileName)) next.add(f.path);
			selected.value = next;
		}
		function selectAllPrefixed() {
			if (!scanResult.value) return;
			const next = new Set(selected.value);
			for (const group of scanResult.value.prefixVariants) for (const f of group.files) if (!deleted.value.has(f.path) && /^[012]_/.test(f.fileName)) next.add(f.path);
			selected.value = next;
		}
		function clearSelection() {
			selected.value = /* @__PURE__ */ new Set();
		}
		function storageBadge(storage) {
			return storage === "internal" ? "Interne" : "SD";
		}
		return (_ctx, _cache) => {
			return openBlock(), createElementBlock("div", _hoisted_1$3, [
				createBaseVNode("div", _hoisted_2$3, [_cache[5] || (_cache[5] = createBaseVNode("h2", null, "Gestion des doublons", -1)), createBaseVNode("div", _hoisted_3$2, [createBaseVNode("button", {
					class: "btn-refresh",
					"data-testid": "duplicates-refresh",
					onClick: scan,
					disabled: scanning.value
				}, "Actualiser", 8, _hoisted_4$2), createBaseVNode("button", {
					class: "btn-close",
					"data-testid": "duplicates-close",
					onClick: _cache[0] || (_cache[0] = ($event) => emit("close"))
				}, "Fermer")])]),
				createBaseVNode("div", _hoisted_5$2, [scanning.value ? (openBlock(), createElementBlock("div", _hoisted_6$2, "Scan en cours...")) : scanError.value ? (openBlock(), createElementBlock("div", _hoisted_7$2, toDisplayString(scanError.value), 1)) : scanResult.value ? (openBlock(), createElementBlock(Fragment, { key: 2 }, [
					createBaseVNode("div", _hoisted_8$2, [
						createTextVNode(toDisplayString(scanResult.value.totalFiles) + " fichier(s) scannés · " + toDisplayString(scanResult.value.internalCount) + " interne · " + toDisplayString(scanResult.value.sdcardCount) + " SD · ", 1),
						createBaseVNode("strong", null, toDisplayString(scanResult.value.crossStorage.length), 1),
						_cache[6] || (_cache[6] = createTextVNode(" doublon(s) cross-stockage · ", -1)),
						createBaseVNode("strong", null, toDisplayString(scanResult.value.prefixVariants.length), 1),
						_cache[7] || (_cache[7] = createTextVNode(" variante(s) de préfixe ", -1))
					]),
					scanResult.value.crossStorage.length === 0 && scanResult.value.prefixVariants.length === 0 ? (openBlock(), createElementBlock("div", _hoisted_9$2, " Aucun doublon détecté. ")) : createCommentVNode("", true),
					scanResult.value.crossStorage.length > 0 ? (openBlock(), createElementBlock("div", _hoisted_10$2, [createBaseVNode("div", _hoisted_11$2, [_cache[8] || (_cache[8] = createBaseVNode("div", null, [createBaseVNode("h3", null, "Doublons cross-stockage"), createBaseVNode("p", { class: "dup-section-desc" }, "Même fichier présent sur le stockage interne et la carte SD.")], -1)), createBaseVNode("div", _hoisted_12$2, [createBaseVNode("button", {
						class: "btn-toolbar",
						onClick: _cache[1] || (_cache[1] = ($event) => selectAllByStorage("internal"))
					}, "Sélectionner tous Interne"), createBaseVNode("button", {
						class: "btn-toolbar",
						onClick: _cache[2] || (_cache[2] = ($event) => selectAllByStorage("sdcard"))
					}, "Sélectionner tous SD")])]), (openBlock(true), createElementBlock(Fragment, null, renderList(scanResult.value.crossStorage, (group) => {
						return openBlock(), createElementBlock("div", {
							key: "cs-" + group.baseName,
							class: "dup-group"
						}, [createBaseVNode("div", _hoisted_13$2, [
							createBaseVNode("span", _hoisted_14$2, toDisplayString(group.baseName), 1),
							createBaseVNode("span", _hoisted_15$2, toDisplayString(group.files.filter((f) => !deleted.value.has(f.path)).length) + " copie(s)", 1),
							createBaseVNode("button", {
								class: "btn-select-others",
								onClick: ($event) => selectAllInGroup(group, true)
							}, "Sélectionner les doublons", 8, _hoisted_16$2)
						]), (openBlock(true), createElementBlock(Fragment, null, renderList(group.files, (file) => {
							return openBlock(), createElementBlock("div", {
								key: file.path,
								class: normalizeClass(["dup-file", { "dup-deleted": deleted.value.has(file.path) }]),
								"data-testid": `duplicate-row-${file.path}`
							}, [
								!deleted.value.has(file.path) ? (openBlock(), createElementBlock("input", {
									key: 0,
									type: "checkbox",
									checked: selected.value.has(file.path),
									"data-testid": `duplicate-checkbox-${file.path}`,
									onChange: ($event) => toggleSelect(file.path)
								}, null, 40, _hoisted_18$2)) : (openBlock(), createElementBlock("span", _hoisted_19$2, "✗")),
								createBaseVNode("span", _hoisted_20$2, toDisplayString(file.fileName), 1),
								createBaseVNode("span", { class: normalizeClass(["dup-badge", "badge-" + file.storage]) }, toDisplayString(storageBadge(file.storage)), 3),
								createBaseVNode("span", _hoisted_21$2, toDisplayString(unref(formatSize)(file.size)), 1),
								!deleted.value.has(file.path) ? (openBlock(), createElementBlock("button", {
									key: 2,
									class: "btn-delete-one",
									"data-testid": `duplicate-delete-${file.path}`,
									disabled: deleting.value.has(file.path),
									onClick: ($event) => deleteFile(file.path)
								}, toDisplayString(deleting.value.has(file.path) ? "..." : "Supprimer"), 9, _hoisted_22$2)) : (openBlock(), createElementBlock("span", _hoisted_23$2, "supprimé"))
							], 10, _hoisted_17$2);
						}), 128))]);
					}), 128))])) : createCommentVNode("", true),
					scanResult.value.prefixVariants.length > 0 ? (openBlock(), createElementBlock("div", _hoisted_24$2, [createBaseVNode("div", _hoisted_25$2, [_cache[9] || (_cache[9] = createBaseVNode("div", null, [createBaseVNode("h3", null, "Variantes de préfixe"), createBaseVNode("p", { class: "dup-section-desc" }, "Même fichier de base avec différents préfixes (1_, 2_, 0_, sans).")], -1)), createBaseVNode("div", _hoisted_26$2, [createBaseVNode("button", {
						class: "btn-toolbar",
						onClick: _cache[3] || (_cache[3] = ($event) => selectAllNonPrefixed())
					}, "Sélectionner sans préfixe"), createBaseVNode("button", {
						class: "btn-toolbar",
						onClick: _cache[4] || (_cache[4] = ($event) => selectAllPrefixed())
					}, "Sélectionner avec préfixe")])]), (openBlock(true), createElementBlock(Fragment, null, renderList(scanResult.value.prefixVariants, (group) => {
						return openBlock(), createElementBlock("div", {
							key: "pv-" + group.baseName,
							class: "dup-group"
						}, [createBaseVNode("div", _hoisted_27$2, [
							createBaseVNode("span", _hoisted_28$2, toDisplayString(group.baseName), 1),
							createBaseVNode("span", _hoisted_29$2, toDisplayString(group.files.filter((f) => !deleted.value.has(f.path)).length) + " variante(s)", 1),
							createBaseVNode("button", {
								class: "btn-select-others",
								onClick: ($event) => selectAllInGroup(group, true)
							}, "Sélectionner les doublons", 8, _hoisted_30$2)
						]), (openBlock(true), createElementBlock(Fragment, null, renderList(group.files, (file) => {
							return openBlock(), createElementBlock("div", {
								key: file.path,
								class: normalizeClass(["dup-file", { "dup-deleted": deleted.value.has(file.path) }]),
								"data-testid": `duplicate-row-${file.path}`
							}, [
								!deleted.value.has(file.path) ? (openBlock(), createElementBlock("input", {
									key: 0,
									type: "checkbox",
									checked: selected.value.has(file.path),
									"data-testid": `duplicate-checkbox-${file.path}`,
									onChange: ($event) => toggleSelect(file.path)
								}, null, 40, _hoisted_32$2)) : (openBlock(), createElementBlock("span", _hoisted_33$2, "✗")),
								createBaseVNode("span", _hoisted_34$2, toDisplayString(file.fileName), 1),
								createBaseVNode("span", { class: normalizeClass(["dup-badge", "badge-" + file.storage]) }, toDisplayString(storageBadge(file.storage)), 3),
								createBaseVNode("span", _hoisted_35$2, toDisplayString(unref(formatSize)(file.size)), 1),
								!deleted.value.has(file.path) ? (openBlock(), createElementBlock("button", {
									key: 2,
									class: "btn-delete-one",
									"data-testid": `duplicate-delete-${file.path}`,
									disabled: deleting.value.has(file.path),
									onClick: ($event) => deleteFile(file.path)
								}, toDisplayString(deleting.value.has(file.path) ? "..." : "Supprimer"), 9, _hoisted_36$2)) : (openBlock(), createElementBlock("span", _hoisted_37$2, "supprimé"))
							], 10, _hoisted_31$2);
						}), 128))]);
					}), 128))])) : createCommentVNode("", true)
				], 64)) : createCommentVNode("", true)]),
				hasSelection.value ? (openBlock(), createElementBlock("div", _hoisted_38$1, [createBaseVNode("button", {
					class: "btn-batch-clear",
					onClick: clearSelection
				}, "Tout désélectionner"), createBaseVNode("button", {
					class: "btn-batch-delete",
					"data-testid": "delete-selected-btn",
					onClick: deleteSelected
				}, " Supprimer la sélection (" + toDisplayString(selected.value.size) + ") ", 1)])) : createCommentVNode("", true)
			]);
		};
	}
}), [["__scopeId", "data-v-80353fd1"]]);
//#endregion
//#region src/renderer/pages/Results.vue?vue&type=script&setup=true&lang.ts
var _hoisted_1$2 = { class: "page" };
var _hoisted_2$2 = ["data-state"];
var _hoisted_3$1 = { class: "banner-icon" };
var _hoisted_4$1 = { class: "banner-text" };
var _hoisted_5$1 = {
	key: 0,
	class: "banner-sub"
};
var _hoisted_6$1 = { class: "summary-card" };
var _hoisted_7$1 = { class: "summary-grid" };
var _hoisted_8$1 = { class: "summary-item" };
var _hoisted_9$1 = { class: "summary-value" };
var _hoisted_10$1 = { class: "summary-item" };
var _hoisted_11$1 = { class: "summary-value mono" };
var _hoisted_12$1 = { class: "summary-item" };
var _hoisted_13$1 = { class: "summary-value" };
var _hoisted_14$1 = {
	key: 0,
	class: "summary-item"
};
var _hoisted_15$1 = { class: "summary-value" };
var _hoisted_16$1 = {
	key: 1,
	class: "summary-item"
};
var _hoisted_17$1 = { class: "summary-value" };
var _hoisted_18$1 = {
	key: 2,
	class: "summary-item"
};
var _hoisted_19$1 = { class: "summary-value" };
var _hoisted_20$1 = {
	key: 3,
	class: "summary-item"
};
var _hoisted_21$1 = { class: "summary-value" };
var _hoisted_22$1 = { class: "summary-item" };
var _hoisted_23$1 = { class: "summary-value" };
var _hoisted_24$1 = {
	key: 4,
	class: "summary-item"
};
var _hoisted_25$1 = { class: "summary-value" };
var _hoisted_26$1 = {
	key: 5,
	class: "summary-item"
};
var _hoisted_27$1 = {
	key: 6,
	class: "summary-item"
};
var _hoisted_28$1 = {
	key: 7,
	class: "summary-item"
};
var _hoisted_29$1 = { class: "summary-value" };
var _hoisted_30$1 = {
	key: 8,
	class: "summary-item"
};
var _hoisted_31$1 = {
	key: 0,
	class: "stats-grid"
};
var _hoisted_32$1 = { class: "stat-tile" };
var _hoisted_33$1 = { class: "stat-value text-success" };
var _hoisted_34$1 = {
	key: 0,
	class: "stat-tile"
};
var _hoisted_35$1 = { class: "stat-value" };
var _hoisted_36$1 = {
	key: 1,
	class: "stat-tile"
};
var _hoisted_37$1 = { class: "stat-value text-warn" };
var _hoisted_38 = {
	key: 2,
	class: "stat-tile"
};
var _hoisted_39 = { class: "stat-value text-error" };
var _hoisted_40 = {
	key: 3,
	class: "stat-tile"
};
var _hoisted_41 = { class: "stat-value text-success" };
var _hoisted_42 = {
	key: 4,
	class: "stat-tile"
};
var _hoisted_43 = { class: "stat-value text-error" };
var _hoisted_44 = { class: "stat-tile" };
var _hoisted_45 = { class: "stat-value" };
var _hoisted_46 = { class: "stat-tile" };
var _hoisted_47 = { class: "stat-value" };
var _hoisted_48 = { class: "error-list" };
var _hoisted_49 = { class: "error-file-name" };
var _hoisted_50 = { class: "error-file-reason" };
var _hoisted_51 = {
	key: 2,
	class: "no-verify-banner"
};
var _hoisted_52 = { class: "missing-list" };
var _hoisted_53 = { class: "missing-icon" };
var _hoisted_54 = { class: "missing-name" };
var _hoisted_55 = ["onClick"];
var _hoisted_56 = {
	key: 1,
	class: "missing-done"
};
var _hoisted_57 = {
	key: 2,
	class: "missing-progress"
};
var _hoisted_58 = { class: "app-verify" };
var _hoisted_59 = { class: "app-verify-actions" };
var _hoisted_60 = ["disabled"];
var _hoisted_61 = ["disabled"];
var _hoisted_62 = {
	key: 0,
	class: "report-status"
};
var _hoisted_63 = {
	key: 1,
	class: "report-error"
};
var _hoisted_64 = {
	key: 2,
	class: "report-content",
	"data-testid": "device-report-output"
};
var _hoisted_65 = {
	key: 5,
	class: "report-hint"
};
var _hoisted_66 = { class: "asset-verify" };
var _hoisted_67 = ["disabled"];
var _hoisted_68 = {
	key: 0,
	class: "asset-verify-result",
	"data-testid": "asset-verify-result"
};
var _hoisted_69 = { class: "av-present" };
var _hoisted_70 = {
	key: 1,
	class: "av-missing-list"
};
var _hoisted_71 = { class: "actions" };
var _hoisted_72 = ["disabled", "title"];
var _hoisted_73 = { class: "modal-panel" };
//#endregion
//#region src/renderer/pages/Results.vue
var Results_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
	__name: "Results",
	setup(__props) {
		const showDuplicates = /* @__PURE__ */ ref(false);
		const duplicateCount = /* @__PURE__ */ ref(0);
		const store = useInstallationStore();
		const webhookUrl = /* @__PURE__ */ ref("");
		const sendingDigest = /* @__PURE__ */ ref(false);
		const digestStatus = /* @__PURE__ */ ref("");
		const appVersion = /* @__PURE__ */ ref("");
		async function loadWebhookConfig() {
			try {
				webhookUrl.value = (await window.api.invoke("get-config"))?.webhooks?.resultDigestUrl ?? "";
			} catch {
				webhookUrl.value = "";
			}
		}
		onMounted(async () => {
			loadWebhookConfig();
			try {
				appVersion.value = await window.api.invoke("get-app-version");
			} catch {}
		});
		onMounted(() => {
			window.addEventListener("focus", loadWebhookConfig);
		});
		function buildDigest() {
			return {
				timestamp: (/* @__PURE__ */ new Date()).toISOString(),
				installationId: store.tabId,
				appVersion: appVersion.value,
				device: {
					serial: store.selectedDeviceId,
					model: store.detectedDeviceModel,
					profile: store.deviceProfile ? {
						model: store.deviceProfile.model,
						displayName: store.deviceProfile.displayName,
						type: store.deviceProfile.type,
						packageName: store.deviceProfile.packageName,
						targetDir: store.deviceProfile.targetDir
					} : null,
					name: store.deviceName,
					network: store.networkName
				},
				content: {
					product: store.product,
					quality: store.quality,
					release: {
						tag: store.releaseTag,
						date: store.releaseDate,
						name: store.releaseName,
						skipApk: store.skipApk,
						localPath: store.localApkPath
					}
				},
				storage: {
					mode: store.storageMode,
					priority: store.storagePriority,
					locations: store.storageLocations
				},
				execution: {
					success: store.executionSuccess,
					message: store.executionMessage,
					startedAt: store.executionStartedAt,
					finishedAt: Date.now(),
					policy: store.transferPolicy,
					enableVerification: store.enableVerification,
					skipFiles: store.skipFiles,
					stats: store.executionStats,
					verification: {
						enabled: store.enableVerification,
						verifiedCount: store.filesVerifiedCount,
						verifyFailedCount: store.filesVerifyFailedCount
					},
					assets: {
						totalAssetSize: store.totalAssetSize,
						netTransferSize: store.netTransferSize,
						existingOnDeviceSize: store.existingOnDeviceSize,
						existingOnDeviceCount: store.existingOnDeviceCount,
						totalFileCount: store.totalFileCount
					}
				},
				files: {
					required: store.requiredFiles,
					missing: store.missingFiles,
					statuses: Object.fromEntries(store.fileStatuses),
					errors: Object.fromEntries(store.fileErrors),
					duplicatesOnDevice: store.duplicateFiles,
					sourceDuplicates: store.sourceDuplicates,
					serenityFiles: store.serenityFiles.map((f) => ({
						fileName: f.fileName,
						size: f.size
					}))
				},
				appReport: reportContent.value,
				logs: store.logEntries
			};
		}
		async function sendDigest() {
			if (!webhookUrl.value) return;
			sendingDigest.value = true;
			digestStatus.value = "";
			try {
				if (!reportContent.value && reportPath.value && !appLaunched.value) {
					digestStatus.value = "Lancement de l'application...";
					await launchAndWaitForReport();
				}
				digestStatus.value = "Envoi en cours...";
				const digest = buildDigest();
				const result = await window.api.invoke("send-result-digest", webhookUrl.value, JSON.parse(JSON.stringify(digest)));
				if (result.success) {
					digestStatus.value = `Envoyé ✓ (${result.status ?? 200})`;
					store.addLogEntry(`Rapport envoyé au webhook (HTTP ${result.status ?? 200})`, "success");
				} else {
					digestStatus.value = `Erreur: ${result.error ?? "inconnue"}`;
					store.addLogEntry(`Erreur envoi webhook: ${result.error ?? "inconnue"}`, "error");
				}
			} catch (err) {
				digestStatus.value = `Erreur: ${err instanceof Error ? err.message : String(err)}`;
			} finally {
				sendingDigest.value = false;
				setTimeout(() => {
					digestStatus.value = "";
				}, 5e3);
			}
		}
		async function refreshDuplicateCount() {
			if (!store.selectedDeviceId || !store.deviceProfile) return;
			try {
				const internalDir = store.deviceProfile.targetDir;
				let sdcardDir;
				const sdLoc = store.storageLocations.find((l) => l.type === "sdcard");
				if (sdLoc) sdcardDir = sdLoc.path + internalDir.replace(/^\/storage\/emulated\/0/, "");
				const result = await window.api.invoke("scan-device-duplicates", store.selectedDeviceId, internalDir, sdcardDir);
				duplicateCount.value = result.crossStorage.length + result.prefixVariants.length;
			} catch {}
		}
		onMounted(refreshDuplicateCount);
		const reportPath = /* @__PURE__ */ ref("");
		const appLaunching = /* @__PURE__ */ ref(false);
		const appLaunched = /* @__PURE__ */ ref(false);
		const reportLoading = /* @__PURE__ */ ref(false);
		const reportContent = /* @__PURE__ */ ref(null);
		const reportError = /* @__PURE__ */ ref("");
		async function fetchReportPath() {
			if (!store.deviceProfile?.type) return;
			try {
				reportPath.value = await window.api.invoke("get-device-report-path", store.deviceProfile.type);
			} catch {
				reportPath.value = "";
			}
		}
		onMounted(fetchReportPath);
		function onWindowFocus() {
			fetchReportPath();
		}
		onMounted(() => window.addEventListener("focus", onWindowFocus));
		async function launchAndWaitForReport() {
			if (!store.selectedDeviceId || !store.deviceProfile?.packageName || !reportPath.value) return;
			appLaunching.value = true;
			appLaunched.value = false;
			reportContent.value = null;
			reportError.value = "";
			try {
				await window.api.invoke("force-stop-app", store.selectedDeviceId, store.deviceProfile.packageName);
				await window.api.invoke("launch-app", store.selectedDeviceId, store.deviceProfile.packageName);
				appLaunched.value = true;
				appLaunching.value = false;
				reportLoading.value = true;
				const maxAttempts = 20;
				for (let i = 0; i < maxAttempts; i++) {
					await new Promise((r) => setTimeout(r, 3e3));
					const content = await window.api.invoke("read-device-file", store.selectedDeviceId, reportPath.value);
					if (content && content.trim().length > 0) {
						reportContent.value = content.trim();
						reportLoading.value = false;
						return;
					}
				}
				reportLoading.value = false;
				reportError.value = "Le rapport n'a pas été généré dans le délai imparti (60s).";
			} catch (err) {
				appLaunching.value = false;
				reportLoading.value = false;
				reportError.value = `Erreur: ${err instanceof Error ? err.message : String(err)}`;
			}
		}
		async function refreshReport() {
			if (!store.selectedDeviceId || !reportPath.value) return;
			reportLoading.value = true;
			reportError.value = "";
			try {
				reportContent.value = (await window.api.invoke("read-device-file", store.selectedDeviceId, reportPath.value))?.trim() || null;
				if (!reportContent.value) reportError.value = "Rapport introuvable sur l'appareil.";
			} catch (err) {
				reportError.value = `Erreur: ${err instanceof Error ? err.message : String(err)}`;
			} finally {
				reportLoading.value = false;
			}
		}
		const verifyingAssets = /* @__PURE__ */ ref(false);
		const assetVerifyResult = /* @__PURE__ */ ref(null);
		async function reverifyAssets() {
			if (!store.selectedDeviceId || !store.deviceProfile) return;
			verifyingAssets.value = true;
			assetVerifyResult.value = null;
			try {
				const internalDir = store.deviceProfile.targetDir;
				let sdcardDir;
				const sdLoc = store.storageLocations.find((l) => l.type === "sdcard");
				if (sdLoc) sdcardDir = sdLoc.path + internalDir.replace(/^\/storage\/emulated\/0/, "");
				const present = [];
				const missing = [];
				for (const fileName of store.requiredFiles) {
					let found = await window.api.invoke("file-exists-on-device", store.selectedDeviceId, internalDir + "/" + fileName);
					if (!found && sdcardDir) found = await window.api.invoke("file-exists-on-device", store.selectedDeviceId, sdcardDir + "/" + fileName);
					if (found) present.push(fileName);
					else missing.push(fileName);
				}
				assetVerifyResult.value = {
					present,
					missing
				};
			} catch (err) {
				store.addLogEntry(`Erreur vérification: ${err}`, "error");
			} finally {
				verifyingAssets.value = false;
			}
		}
		computed(() => !store.executionSuccess || (store.executionStats?.filesFailed ?? 0) > 0 || store.filesMissingCount > 0 || store.filesVerifyFailedCount > 0);
		const resultStatus = computed(() => {
			if (!store.executionSuccess) return "error";
			if (store.filesVerifyFailedCount > 0 || (store.executionStats?.filesFailed ?? 0) > 0) return "error";
			if (store.filesMissingCount > 0) return "warning";
			return "success";
		});
		const resultLabel = computed(() => {
			if (resultStatus.value === "success") return "Installation réussie";
			if (resultStatus.value === "warning") return "Installation terminée avec avertissements";
			return "Installation échouée";
		});
		const failureReasons = computed(() => {
			const reasons = [];
			const msg = store.executionMessage;
			if (!store.executionSuccess) {
				if (msg.includes("insuffisant") || msg.includes("Stockage plein")) reasons.push("Espace de stockage insuffisant");
				else if (msg.includes("déconnecté")) reasons.push("Appareil déconnecté");
				else if (msg.includes("APK")) reasons.push("Échec installation APK");
				else if (msg) reasons.push(msg);
			}
			if ((store.executionStats?.filesFailed ?? 0) > 0) reasons.push(`${store.executionStats.filesFailed} fichier(s) en erreur`);
			if (store.filesMissingCount > 0) reasons.push(`${store.filesMissingCount} fichier(s) manquant(s)`);
			if (store.filesVerifyFailedCount > 0) reasons.push(`${store.filesVerifyFailedCount} vérification(s) MD5 échouée(s)`);
			return reasons;
		});
		const failedFiles = computed(() => [...store.fileErrors.entries()].map(([name, error]) => ({
			name,
			error
		})));
		const policyLabels = {
			"update-different": "Mise à jour (différent)",
			"overwrite-all": "Écraser tout",
			"skip-existing": "Ignorer existants"
		};
		const storageModeLabels = {
			"internal": "Stockage interne",
			"sdcard": "Carte SD",
			"both": "Interne + Carte SD"
		};
		const executionDate = computed(() => {
			if (!store.executionStartedAt) return "";
			return new Date(store.executionStartedAt).toLocaleString("fr-FR", {
				weekday: "long",
				day: "numeric",
				month: "long",
				year: "numeric",
				hour: "2-digit",
				minute: "2-digit"
			});
		});
		function formatDuration(seconds) {
			if (seconds < 60) return `${seconds}s`;
			return `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
		}
		function newInstallation() {
			store.reset();
		}
		const exportCopied = /* @__PURE__ */ ref(false);
		const resolvedFiles = /* @__PURE__ */ ref(/* @__PURE__ */ new Set());
		const resolvingFile = /* @__PURE__ */ ref(null);
		async function resolveFile(fileName) {
			const sourcePath = await window.api.invoke("select-file");
			if (!sourcePath || !store.selectedDeviceId || !store.deviceProfile) return;
			resolvingFile.value = fileName;
			const targetPath = store.deviceProfile.targetDir + "/" + fileName;
			try {
				await window.api.invoke("transfer-single-file", store.selectedDeviceId, sourcePath, targetPath);
				resolvedFiles.value = new Set([...resolvedFiles.value, fileName]);
				store.addLogEntry(`Résolu: ${fileName}`, "success");
			} catch (err) {
				store.addLogEntry(`Erreur: ${fileName} — ${err}`, "error");
			} finally {
				resolvingFile.value = null;
			}
		}
		function exportLogs() {
			const text = store.logEntries.map((e) => `[${e.time}] [${e.type}] ${e.message}`).join("\n");
			navigator.clipboard.writeText(text).then(() => {
				exportCopied.value = true;
				setTimeout(() => {
					exportCopied.value = false;
				}, 2e3);
			});
		}
		return (_ctx, _cache) => {
			return openBlock(), createElementBlock("div", _hoisted_1$2, [
				createBaseVNode("div", {
					class: normalizeClass(["result-banner", "banner-" + resultStatus.value]),
					"data-testid": "results-banner",
					"data-state": resultStatus.value
				}, [createBaseVNode("span", _hoisted_3$1, toDisplayString(resultStatus.value === "success" ? "✓" : resultStatus.value === "warning" ? "⚠" : "✗"), 1), createBaseVNode("div", _hoisted_4$1, [
					createBaseVNode("h2", null, toDisplayString(resultLabel.value), 1),
					unref(store).executionStats ? (openBlock(), createElementBlock("span", _hoisted_5$1, toDisplayString(unref(store).executionStats.filesTransferred) + " fichier(s) · " + toDisplayString(unref(formatSize)(unref(store).executionStats.bytesTransferred)) + " · " + toDisplayString(formatDuration(unref(store).executionStats.durationSeconds)), 1)) : createCommentVNode("", true),
					(openBlock(true), createElementBlock(Fragment, null, renderList(failureReasons.value, (reason) => {
						return openBlock(), createElementBlock("span", {
							key: reason,
							class: "banner-sub banner-reason"
						}, toDisplayString(reason), 1);
					}), 128))
				])], 10, _hoisted_2$2),
				createBaseVNode("div", _hoisted_6$1, [createBaseVNode("div", _hoisted_7$1, [
					createBaseVNode("div", _hoisted_8$1, [_cache[3] || (_cache[3] = createBaseVNode("span", { class: "summary-label" }, "Appareil", -1)), createBaseVNode("span", _hoisted_9$1, toDisplayString(unref(store).deviceProfile?.displayName ?? unref(store).detectedDeviceModel ?? "—"), 1)]),
					createBaseVNode("div", _hoisted_10$1, [_cache[4] || (_cache[4] = createBaseVNode("span", { class: "summary-label" }, "Série", -1)), createBaseVNode("span", _hoisted_11$1, toDisplayString(unref(store).selectedDeviceId ?? "—"), 1)]),
					createBaseVNode("div", _hoisted_12$1, [_cache[5] || (_cache[5] = createBaseVNode("span", { class: "summary-label" }, "Nom", -1)), createBaseVNode("span", _hoisted_13$1, toDisplayString(unref(store).deviceName || "—"), 1)]),
					unref(store).networkName ? (openBlock(), createElementBlock("div", _hoisted_14$1, [_cache[6] || (_cache[6] = createBaseVNode("span", { class: "summary-label" }, "Réseau", -1)), createBaseVNode("span", _hoisted_15$1, toDisplayString(unref(store).networkName), 1)])) : createCommentVNode("", true),
					unref(store).product ? (openBlock(), createElementBlock("div", _hoisted_16$1, [_cache[7] || (_cache[7] = createBaseVNode("span", { class: "summary-label" }, "Produit", -1)), createBaseVNode("span", _hoisted_17$1, toDisplayString(unref(store).product), 1)])) : createCommentVNode("", true),
					unref(store).quality ? (openBlock(), createElementBlock("div", _hoisted_18$1, [_cache[8] || (_cache[8] = createBaseVNode("span", { class: "summary-label" }, "Qualité", -1)), createBaseVNode("span", _hoisted_19$1, toDisplayString(unref(store).quality), 1)])) : createCommentVNode("", true),
					!unref(store).skipFiles ? (openBlock(), createElementBlock("div", _hoisted_20$1, [_cache[9] || (_cache[9] = createBaseVNode("span", { class: "summary-label" }, "Politique", -1)), createBaseVNode("span", _hoisted_21$1, toDisplayString(policyLabels[unref(store).transferPolicy] ?? unref(store).transferPolicy), 1)])) : createCommentVNode("", true),
					createBaseVNode("div", _hoisted_22$1, [_cache[10] || (_cache[10] = createBaseVNode("span", { class: "summary-label" }, "Stockage", -1)), createBaseVNode("span", _hoisted_23$1, toDisplayString(storageModeLabels[unref(store).storageMode] ?? unref(store).storageMode), 1)]),
					!unref(store).skipApk ? (openBlock(), createElementBlock("div", _hoisted_24$1, [_cache[11] || (_cache[11] = createBaseVNode("span", { class: "summary-label" }, "APK", -1)), createBaseVNode("span", _hoisted_25$1, toDisplayString(unref(store).releaseTag === "local" ? unref(store).localApkPath?.split("/").pop() ?? "Local" : unref(store).releaseTag ?? "—"), 1)])) : createCommentVNode("", true),
					unref(store).skipApk ? (openBlock(), createElementBlock("div", _hoisted_26$1, [..._cache[12] || (_cache[12] = [createBaseVNode("span", { class: "summary-label" }, "APK", -1), createBaseVNode("span", { class: "summary-value dimmed" }, "Ignoré", -1)])])) : createCommentVNode("", true),
					unref(store).skipFiles ? (openBlock(), createElementBlock("div", _hoisted_27$1, [..._cache[13] || (_cache[13] = [createBaseVNode("span", { class: "summary-label" }, "Fichiers", -1), createBaseVNode("span", { class: "summary-value dimmed" }, "Ignorés", -1)])])) : createCommentVNode("", true),
					executionDate.value ? (openBlock(), createElementBlock("div", _hoisted_28$1, [_cache[14] || (_cache[14] = createBaseVNode("span", { class: "summary-label" }, "Date", -1)), createBaseVNode("span", _hoisted_29$1, toDisplayString(executionDate.value), 1)])) : createCommentVNode("", true),
					!unref(store).enableVerification ? (openBlock(), createElementBlock("div", _hoisted_30$1, [..._cache[15] || (_cache[15] = [createBaseVNode("span", { class: "summary-label" }, "Vérification", -1), createBaseVNode("span", { class: "summary-value dimmed" }, "Désactivée", -1)])])) : createCommentVNode("", true)
				])]),
				unref(store).executionStats ? (openBlock(), createElementBlock("div", _hoisted_31$1, [
					createBaseVNode("div", _hoisted_32$1, [createBaseVNode("span", _hoisted_33$1, toDisplayString(unref(store).executionStats.filesTransferred), 1), _cache[16] || (_cache[16] = createBaseVNode("span", { class: "stat-label" }, "Transférés", -1))]),
					unref(store).filesSkippedCount > 0 ? (openBlock(), createElementBlock("div", _hoisted_34$1, [createBaseVNode("span", _hoisted_35$1, toDisplayString(unref(store).filesSkippedCount), 1), _cache[17] || (_cache[17] = createBaseVNode("span", { class: "stat-label" }, "Ignorés", -1))])) : createCommentVNode("", true),
					unref(store).filesMissingCount > 0 ? (openBlock(), createElementBlock("div", _hoisted_36$1, [createBaseVNode("span", _hoisted_37$1, toDisplayString(unref(store).filesMissingCount), 1), _cache[18] || (_cache[18] = createBaseVNode("span", { class: "stat-label" }, "Manquants", -1))])) : createCommentVNode("", true),
					unref(store).executionStats.filesFailed > 0 ? (openBlock(), createElementBlock("div", _hoisted_38, [createBaseVNode("span", _hoisted_39, toDisplayString(unref(store).executionStats.filesFailed), 1), _cache[19] || (_cache[19] = createBaseVNode("span", { class: "stat-label" }, "Erreurs", -1))])) : createCommentVNode("", true),
					unref(store).filesVerifiedCount > 0 ? (openBlock(), createElementBlock("div", _hoisted_40, [createBaseVNode("span", _hoisted_41, toDisplayString(unref(store).filesVerifiedCount), 1), _cache[20] || (_cache[20] = createBaseVNode("span", { class: "stat-label" }, "Vérifiés", -1))])) : createCommentVNode("", true),
					unref(store).filesVerifyFailedCount > 0 ? (openBlock(), createElementBlock("div", _hoisted_42, [createBaseVNode("span", _hoisted_43, toDisplayString(unref(store).filesVerifyFailedCount), 1), _cache[21] || (_cache[21] = createBaseVNode("span", { class: "stat-label" }, "Vérif. échouées", -1))])) : createCommentVNode("", true),
					createBaseVNode("div", _hoisted_44, [createBaseVNode("span", _hoisted_45, toDisplayString(unref(formatSize)(unref(store).executionStats.bytesTransferred)), 1), _cache[22] || (_cache[22] = createBaseVNode("span", { class: "stat-label" }, "Volume", -1))]),
					createBaseVNode("div", _hoisted_46, [createBaseVNode("span", _hoisted_47, toDisplayString(formatDuration(unref(store).executionStats.durationSeconds)), 1), _cache[23] || (_cache[23] = createBaseVNode("span", { class: "stat-label" }, "Durée", -1))])
				])) : createCommentVNode("", true),
				failedFiles.value.length > 0 ? (openBlock(), createBlock(Card_default, {
					key: 1,
					title: `Fichiers en erreur (${failedFiles.value.length})`
				}, {
					default: withCtx(() => [createBaseVNode("div", _hoisted_48, [(openBlock(true), createElementBlock(Fragment, null, renderList(failedFiles.value, (f) => {
						return openBlock(), createElementBlock("div", {
							key: f.name,
							class: "error-item"
						}, [
							_cache[24] || (_cache[24] = createBaseVNode("span", { class: "error-file-icon" }, "✗", -1)),
							createBaseVNode("span", _hoisted_49, toDisplayString(f.name), 1),
							createBaseVNode("span", _hoisted_50, toDisplayString(f.error), 1)
						]);
					}), 128))])]),
					_: 1
				}, 8, ["title"])) : createCommentVNode("", true),
				!unref(store).enableVerification && !unref(store).skipFiles ? (openBlock(), createElementBlock("div", _hoisted_51, " Vérification MD5 désactivée — aucun fichier n'a été vérifié après le transfert. ")) : createCommentVNode("", true),
				unref(store).missingFiles.length > 0 ? (openBlock(), createBlock(Card_default, {
					key: 3,
					title: `Fichiers manquants (${unref(store).missingFiles.length - resolvedFiles.value.size} restants)`
				}, {
					default: withCtx(() => [createBaseVNode("div", _hoisted_52, [(openBlock(true), createElementBlock(Fragment, null, renderList(unref(store).missingFiles, (f) => {
						return openBlock(), createElementBlock("div", {
							key: f,
							class: normalizeClass(["missing-item", {
								"missing-resolved": resolvedFiles.value.has(f),
								"missing-resolving": resolvingFile.value === f
							}])
						}, [
							createBaseVNode("span", _hoisted_53, toDisplayString(resolvedFiles.value.has(f) ? "✓" : resolvingFile.value === f ? "◐" : "?"), 1),
							createBaseVNode("span", _hoisted_54, toDisplayString(f), 1),
							!resolvedFiles.value.has(f) && resolvingFile.value !== f ? (openBlock(), createElementBlock("button", {
								key: 0,
								class: "btn-resolve",
								onClick: ($event) => resolveFile(f)
							}, " Parcourir ", 8, _hoisted_55)) : createCommentVNode("", true),
							resolvedFiles.value.has(f) ? (openBlock(), createElementBlock("span", _hoisted_56, "OK")) : createCommentVNode("", true),
							resolvingFile.value === f ? (openBlock(), createElementBlock("span", _hoisted_57, "...")) : createCommentVNode("", true)
						], 2);
					}), 128))])]),
					_: 1
				}, 8, ["title"])) : createCommentVNode("", true),
				reportPath.value && unref(store).selectedDeviceId ? (openBlock(), createBlock(Card_default, {
					key: 4,
					title: "Vérification de l'application"
				}, {
					default: withCtx(() => [createBaseVNode("div", _hoisted_58, [
						createBaseVNode("div", _hoisted_59, [createBaseVNode("button", {
							class: "btn-launch",
							"data-testid": "launch-app-btn",
							disabled: appLaunching.value || reportLoading.value,
							onClick: launchAndWaitForReport
						}, toDisplayString(appLaunching.value ? "Lancement..." : appLaunched.value ? "Relancer l'application" : "Lancer l'application"), 9, _hoisted_60), appLaunched.value ? (openBlock(), createElementBlock("button", {
							key: 0,
							class: "btn-refresh-report",
							"data-testid": "refresh-report-btn",
							disabled: reportLoading.value,
							onClick: refreshReport
						}, " Relire le rapport ", 8, _hoisted_61)) : createCommentVNode("", true)]),
						appLaunched.value && !reportContent.value && !reportError.value ? (openBlock(), createElementBlock("div", _hoisted_62, [..._cache[25] || (_cache[25] = [createBaseVNode("span", { class: "report-spinner" }, "◐", -1), createBaseVNode("span", null, "En attente du rapport de l'application...", -1)])])) : createCommentVNode("", true),
						reportError.value ? (openBlock(), createElementBlock("div", _hoisted_63, toDisplayString(reportError.value), 1)) : createCommentVNode("", true),
						reportContent.value ? (openBlock(), createElementBlock("div", _hoisted_64, [createBaseVNode("pre", null, toDisplayString(reportContent.value), 1)])) : createCommentVNode("", true)
					])]),
					_: 1
				})) : createCommentVNode("", true),
				!reportPath.value && unref(store).deviceProfile ? (openBlock(), createElementBlock("div", _hoisted_65, " Configurez le chemin du rapport appareil dans les Paramètres pour activer la vérification post-installation. ")) : createCommentVNode("", true),
				unref(store).selectedDeviceId && unref(store).requiredFiles.length > 0 ? (openBlock(), createBlock(Card_default, {
					key: 6,
					title: "Vérification de présence des fichiers"
				}, {
					default: withCtx(() => [createBaseVNode("div", _hoisted_66, [
						createBaseVNode("button", {
							class: "btn-reverify",
							"data-testid": "reverify-assets-btn",
							disabled: verifyingAssets.value,
							onClick: reverifyAssets
						}, toDisplayString(verifyingAssets.value ? "Vérification en cours..." : "Vérifier la présence des fichiers"), 9, _hoisted_67),
						assetVerifyResult.value ? (openBlock(), createElementBlock("div", _hoisted_68, [
							createBaseVNode("span", _hoisted_69, toDisplayString(assetVerifyResult.value.present.length) + " présent(s)", 1),
							_cache[26] || (_cache[26] = createBaseVNode("span", { class: "av-sep" }, "·", -1)),
							createBaseVNode("span", { class: normalizeClass(assetVerifyResult.value.missing.length > 0 ? "av-missing" : "av-ok") }, toDisplayString(assetVerifyResult.value.missing.length > 0 ? assetVerifyResult.value.missing.length + " manquant(s)" : "Tous les fichiers sont présents"), 3)
						])) : createCommentVNode("", true),
						assetVerifyResult.value && assetVerifyResult.value.missing.length > 0 ? (openBlock(), createElementBlock("div", _hoisted_70, [(openBlock(true), createElementBlock(Fragment, null, renderList(assetVerifyResult.value.missing, (f) => {
							return openBlock(), createElementBlock("div", {
								key: f,
								class: "av-missing-item"
							}, [_cache[27] || (_cache[27] = createBaseVNode("span", { class: "av-missing-icon" }, "?", -1)), createBaseVNode("span", null, toDisplayString(f), 1)]);
						}), 128))])) : createCommentVNode("", true)
					])]),
					_: 1
				})) : createCommentVNode("", true),
				createBaseVNode("div", _hoisted_71, [
					unref(store).selectedDeviceId ? (openBlock(), createElementBlock("button", {
						key: 0,
						class: "btn-secondary",
						"data-testid": "open-duplicates-btn",
						onClick: _cache[0] || (_cache[0] = ($event) => showDuplicates.value = true)
					}, " Gérer les doublons" + toDisplayString(duplicateCount.value > 0 ? ` (${duplicateCount.value})` : ""), 1)) : createCommentVNode("", true),
					webhookUrl.value ? (openBlock(), createElementBlock("button", {
						key: 1,
						class: "btn-secondary",
						disabled: sendingDigest.value,
						onClick: sendDigest,
						title: digestStatus.value
					}, toDisplayString(sendingDigest.value ? "Envoi..." : digestStatus.value || "Envoyer le rapport"), 9, _hoisted_72)) : createCommentVNode("", true),
					createBaseVNode("button", {
						class: "btn-secondary",
						"data-testid": "export-log-btn",
						onClick: exportLogs
					}, toDisplayString(exportCopied.value ? "Copié !" : "Exporter le journal"), 1),
					createBaseVNode("button", {
						class: "btn-primary",
						"data-testid": "new-installation-btn",
						onClick: newInstallation
					}, " Nouvelle installation ")
				]),
				showDuplicates.value ? (openBlock(), createElementBlock("div", {
					key: 7,
					class: "modal-overlay",
					onClick: _cache[2] || (_cache[2] = withModifiers(($event) => showDuplicates.value = false, ["self"]))
				}, [createBaseVNode("div", _hoisted_73, [createVNode(DuplicateManager_default, {
					onClose: _cache[1] || (_cache[1] = ($event) => showDuplicates.value = false),
					onDeleted: refreshDuplicateCount
				})])])) : createCommentVNode("", true)
			]);
		};
	}
}), [["__scopeId", "data-v-0ce378f8"]]);
//#endregion
//#region src/renderer/pages/Settings.vue?vue&type=script&setup=true&lang.ts
var _hoisted_1$1 = {
	class: "settings",
	"data-testid": "settings-modal"
};
var _hoisted_2$1 = {
	key: 0,
	class: "loading"
};
var _hoisted_3 = { class: "section" };
var _hoisted_4 = { class: "path-row" };
var _hoisted_5 = [
	"data-testid",
	"value",
	"onInput"
];
var _hoisted_6 = ["onClick"];
var _hoisted_7 = { class: "section" };
var _hoisted_8 = ["value", "onInput"];
var _hoisted_9 = {
	key: 0,
	class: "section"
};
var _hoisted_10 = { class: "number-fields" };
var _hoisted_11 = { class: "number-field" };
var _hoisted_12 = ["value"];
var _hoisted_13 = { class: "number-field" };
var _hoisted_14 = ["value"];
var _hoisted_15 = { class: "number-field" };
var _hoisted_16 = ["value"];
var _hoisted_17 = { class: "number-field" };
var _hoisted_18 = ["value"];
var _hoisted_19 = {
	key: 1,
	class: "section"
};
var _hoisted_20 = { class: "number-fields" };
var _hoisted_21 = { class: "number-field" };
var _hoisted_22 = {
	key: 2,
	class: "section"
};
var _hoisted_23 = { class: "path-field" };
var _hoisted_24 = ["value"];
var _hoisted_25 = { class: "section" };
var _hoisted_26 = { class: "cache-actions" };
var _hoisted_27 = { class: "cache-row" };
var _hoisted_28 = { class: "cache-status" };
var _hoisted_29 = { class: "cache-row" };
var _hoisted_30 = { class: "cache-status" };
var _hoisted_31 = { class: "cache-row" };
var _hoisted_32 = { class: "cache-status" };
var _hoisted_33 = { class: "cache-row" };
var _hoisted_34 = { class: "cache-status" };
var _hoisted_35 = { class: "actions" };
var _hoisted_36 = ["disabled"];
var _hoisted_37 = {
	key: 3,
	class: "config-path"
};
//#endregion
//#region src/renderer/pages/Settings.vue
var Settings_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
	__name: "Settings",
	emits: ["close"],
	setup(__props, { emit: __emit }) {
		const emit = __emit;
		const config = /* @__PURE__ */ ref(null);
		const configPath = /* @__PURE__ */ ref("");
		const saving = /* @__PURE__ */ ref(false);
		const clearStatus = /* @__PURE__ */ ref({});
		async function clearCache(channel, key) {
			clearStatus.value = {
				...clearStatus.value,
				[key]: "..."
			};
			try {
				const result = await window.api.invoke(channel);
				clearStatus.value = {
					...clearStatus.value,
					[key]: typeof result === "number" ? `${result} supprimé(s)` : "Vidé"
				};
				setTimeout(() => {
					clearStatus.value = {
						...clearStatus.value,
						[key]: ""
					};
				}, 3e3);
			} catch {
				clearStatus.value = {
					...clearStatus.value,
					[key]: "Erreur"
				};
			}
		}
		const pathFields = [
			{
				key: "parentSourceDirVR",
				label: "Source vidéos VR"
			},
			{
				key: "parentSourceDirTAB",
				label: "Source vidéos Tablette"
			},
			{
				key: "sourceDirOldVR",
				label: "Ancien dossier VR"
			},
			{
				key: "sourceDirOldTablet",
				label: "Ancien dossier Tablette"
			},
			{
				key: "parentFileListDir",
				label: "Dossier des produits (listes)"
			}
		];
		const devicePathFields = [{
			key: "deviceReportPathVR",
			label: "Chemin rapport VR (sur appareil)"
		}, {
			key: "deviceReportPathTAB",
			label: "Chemin rapport Tablette (sur appareil)"
		}];
		onMounted(async () => {
			config.value = await window.api.invoke("get-config");
			configPath.value = await window.api.invoke("get-config-path");
		});
		async function browse(key) {
			const dir = await window.api.invoke("select-directory");
			if (dir && config.value) config.value.mediaTransfer[key] = dir;
		}
		async function openConfigFolder() {
			if (configPath.value) await window.api.invoke("show-item-in-folder", configPath.value);
		}
		async function save() {
			if (!config.value) return;
			saving.value = true;
			try {
				const toSave = {
					MediaTransfer: {
						ParentSourceDirVR: config.value.mediaTransfer.parentSourceDirVR,
						ParentSourceDirTAB: config.value.mediaTransfer.parentSourceDirTAB,
						SourceDirOldVR: config.value.mediaTransfer.sourceDirOldVR,
						SourceDirOldTablet: config.value.mediaTransfer.sourceDirOldTablet,
						ParentFileListDir: config.value.mediaTransfer.parentFileListDir,
						DeviceTargets: config.value.mediaTransfer.deviceTargets,
						deviceReportPathVR: config.value.mediaTransfer.deviceReportPathVR,
						deviceReportPathTAB: config.value.mediaTransfer.deviceReportPathTAB
					},
					transferLogging: config.value.transferLogging,
					github: config.value.github,
					productFilter: config.value.productFilter,
					packageNames: config.value.packageNames,
					timeouts: config.value.timeouts,
					performance: config.value.performance,
					webhooks: config.value.webhooks
				};
				await window.api.invoke("save-config", JSON.parse(JSON.stringify(toSave)));
			} finally {
				saving.value = false;
				emit("close");
			}
		}
		return (_ctx, _cache) => {
			return openBlock(), createElementBlock("div", _hoisted_1$1, [_cache[29] || (_cache[29] = createBaseVNode("div", { class: "settings-header" }, [createBaseVNode("h2", null, "Paramètres")], -1)), !config.value ? (openBlock(), createElementBlock("div", _hoisted_2$1, "Chargement...")) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
				createBaseVNode("div", _hoisted_3, [_cache[11] || (_cache[11] = createBaseVNode("h3", null, "Dossiers sources", -1)), (openBlock(), createElementBlock(Fragment, null, renderList(pathFields, (field) => {
					return createBaseVNode("div", {
						key: field.key,
						class: "path-field"
					}, [createBaseVNode("label", null, toDisplayString(field.label), 1), createBaseVNode("div", _hoisted_4, [createBaseVNode("input", {
						type: "text",
						class: "input",
						"data-testid": `settings-${field.key}`,
						value: config.value.mediaTransfer[field.key],
						onInput: (e) => config.value.mediaTransfer[field.key] = e.target.value
					}, null, 40, _hoisted_5), createBaseVNode("button", {
						class: "btn-browse",
						onClick: ($event) => browse(field.key)
					}, "Parcourir", 8, _hoisted_6)])]);
				}), 64))]),
				createBaseVNode("div", _hoisted_7, [_cache[12] || (_cache[12] = createBaseVNode("h3", null, "Rapports appareil", -1)), (openBlock(), createElementBlock(Fragment, null, renderList(devicePathFields, (field) => {
					return createBaseVNode("div", {
						key: field.key,
						class: "path-field"
					}, [createBaseVNode("label", null, toDisplayString(field.label), 1), createBaseVNode("input", {
						type: "text",
						class: "input",
						placeholder: "/storage/emulated/0/...",
						value: config.value.mediaTransfer[field.key],
						onInput: (e) => config.value.mediaTransfer[field.key] = e.target.value
					}, null, 40, _hoisted_8)]);
				}), 64))]),
				config.value.timeouts ? (openBlock(), createElementBlock("div", _hoisted_9, [_cache[17] || (_cache[17] = createBaseVNode("h3", null, "Délais d'attente", -1)), createBaseVNode("div", _hoisted_10, [
					createBaseVNode("div", _hoisted_11, [_cache[13] || (_cache[13] = createBaseVNode("label", null, "Commande ADB (s)", -1)), createBaseVNode("input", {
						type: "number",
						class: "input-num",
						value: Math.round(config.value.timeouts.adbDefaultMs / 1e3),
						onInput: _cache[0] || (_cache[0] = (e) => config.value.timeouts.adbDefaultMs = Number(e.target.value) * 1e3),
						min: "5"
					}, null, 40, _hoisted_12)]),
					createBaseVNode("div", _hoisted_13, [_cache[14] || (_cache[14] = createBaseVNode("label", null, "Vérification MD5 (s)", -1)), createBaseVNode("input", {
						type: "number",
						class: "input-num",
						value: Math.round(config.value.timeouts.md5TimeoutMs / 1e3),
						onInput: _cache[1] || (_cache[1] = (e) => config.value.timeouts.md5TimeoutMs = Number(e.target.value) * 1e3),
						min: "30"
					}, null, 40, _hoisted_14)]),
					createBaseVNode("div", _hoisted_15, [_cache[15] || (_cache[15] = createBaseVNode("label", null, "Transfert fichier (s)", -1)), createBaseVNode("input", {
						type: "number",
						class: "input-num",
						value: Math.round(config.value.timeouts.pushTimeoutMs / 1e3),
						onInput: _cache[2] || (_cache[2] = (e) => config.value.timeouts.pushTimeoutMs = Number(e.target.value) * 1e3),
						min: "60"
					}, null, 40, _hoisted_16)]),
					createBaseVNode("div", _hoisted_17, [_cache[16] || (_cache[16] = createBaseVNode("label", null, "Installation APK (s)", -1)), createBaseVNode("input", {
						type: "number",
						class: "input-num",
						value: Math.round(config.value.timeouts.installTimeoutMs / 1e3),
						onInput: _cache[3] || (_cache[3] = (e) => config.value.timeouts.installTimeoutMs = Number(e.target.value) * 1e3),
						min: "30"
					}, null, 40, _hoisted_18)])
				])])) : createCommentVNode("", true),
				config.value.performance ? (openBlock(), createElementBlock("div", _hoisted_19, [_cache[19] || (_cache[19] = createBaseVNode("h3", null, "Performance", -1)), createBaseVNode("div", _hoisted_20, [createBaseVNode("div", _hoisted_21, [_cache[18] || (_cache[18] = createBaseVNode("label", null, "Vérifications MD5 simultanées", -1)), withDirectives(createBaseVNode("input", {
					type: "number",
					class: "input-num",
					"onUpdate:modelValue": _cache[4] || (_cache[4] = ($event) => config.value.performance.maxConcurrentVerify = $event),
					min: "1",
					max: "8"
				}, null, 512), [[
					vModelText,
					config.value.performance.maxConcurrentVerify,
					void 0,
					{ number: true }
				]])])])])) : createCommentVNode("", true),
				config.value.webhooks ? (openBlock(), createElementBlock("div", _hoisted_22, [_cache[22] || (_cache[22] = createBaseVNode("h3", null, "Webhooks", -1)), createBaseVNode("div", _hoisted_23, [
					_cache[20] || (_cache[20] = createBaseVNode("label", null, "URL du webhook de rapport", -1)),
					createBaseVNode("input", {
						type: "text",
						class: "input",
						placeholder: "https://...",
						value: config.value.webhooks.resultDigestUrl,
						onInput: _cache[5] || (_cache[5] = (e) => config.value.webhooks.resultDigestUrl = e.target.value)
					}, null, 40, _hoisted_24),
					_cache[21] || (_cache[21] = createBaseVNode("p", { class: "field-hint" }, "Laissez vide pour désactiver. Un digest JSON est envoyé en POST lors du clic sur « Envoyer le rapport » dans les résultats.", -1))
				])])) : createCommentVNode("", true),
				createBaseVNode("div", _hoisted_25, [_cache[27] || (_cache[27] = createBaseVNode("h3", null, "Cache et sessions", -1)), createBaseVNode("div", _hoisted_26, [
					createBaseVNode("div", _hoisted_27, [
						_cache[23] || (_cache[23] = createBaseVNode("span", { class: "cache-label" }, "Cache APK", -1)),
						createBaseVNode("button", {
							class: "btn-cache",
							onClick: _cache[6] || (_cache[6] = ($event) => clearCache("clear-apk-cache", "apk"))
						}, "Vider"),
						createBaseVNode("span", _hoisted_28, toDisplayString(clearStatus.value.apk ?? ""), 1)
					]),
					createBaseVNode("div", _hoisted_29, [
						_cache[24] || (_cache[24] = createBaseVNode("span", { class: "cache-label" }, "Cache MD5 source", -1)),
						createBaseVNode("button", {
							class: "btn-cache",
							onClick: _cache[7] || (_cache[7] = ($event) => clearCache("clear-md5-cache", "md5"))
						}, "Vider"),
						createBaseVNode("span", _hoisted_30, toDisplayString(clearStatus.value.md5 ?? ""), 1)
					]),
					createBaseVNode("div", _hoisted_31, [
						_cache[25] || (_cache[25] = createBaseVNode("span", { class: "cache-label" }, "Sessions terminées", -1)),
						createBaseVNode("button", {
							class: "btn-cache",
							onClick: _cache[8] || (_cache[8] = ($event) => clearCache("clear-completed-sessions", "completed"))
						}, "Supprimer"),
						createBaseVNode("span", _hoisted_32, toDisplayString(clearStatus.value.completed ?? ""), 1)
					]),
					createBaseVNode("div", _hoisted_33, [
						_cache[26] || (_cache[26] = createBaseVNode("span", { class: "cache-label" }, "Sessions incomplètes", -1)),
						createBaseVNode("button", {
							class: "btn-cache",
							onClick: _cache[9] || (_cache[9] = ($event) => clearCache("clear-incomplete-sessions", "incomplete"))
						}, "Supprimer"),
						createBaseVNode("span", _hoisted_34, toDisplayString(clearStatus.value.incomplete ?? ""), 1)
					])
				])]),
				createBaseVNode("div", _hoisted_35, [createBaseVNode("button", {
					class: "btn-cancel",
					onClick: _cache[10] || (_cache[10] = ($event) => emit("close"))
				}, "Annuler"), createBaseVNode("button", {
					class: "btn-save",
					"data-testid": "settings-save-btn",
					onClick: save,
					disabled: saving.value
				}, toDisplayString(saving.value ? "Enregistrement..." : "Enregistrer"), 9, _hoisted_36)]),
				configPath.value ? (openBlock(), createElementBlock("p", _hoisted_37, [_cache[28] || (_cache[28] = createTextVNode(" Fichier de configuration : ", -1)), createBaseVNode("a", {
					href: "#",
					class: "config-link",
					onClick: withModifiers(openConfigFolder, ["prevent"])
				}, toDisplayString(configPath.value), 1)])) : createCommentVNode("", true)
			], 64))]);
		};
	}
}), [["__scopeId", "data-v-a4cc71f4"]]);
//#endregion
//#region src/renderer/App.vue?vue&type=script&setup=true&lang.ts
var _hoisted_1 = { class: "app-root" };
var _hoisted_2 = { class: "modal-panel" };
//#endregion
//#region src/renderer/App.vue
var App_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
	__name: "App",
	setup(__props) {
		const tabsStore = useTabsStore();
		const updateStore = useUpdateStore();
		const showSettings = /* @__PURE__ */ ref(false);
		const pages = [
			DeviceConnect_default,
			DeviceConfig_default,
			ContentSelect_default,
			ReleaseSelect_default,
			Review_default,
			Execution_default,
			Results_default
		];
		const UPDATE_EVENT_TYPES = new Set([
			"update-available",
			"update-check-result",
			"update-download-progress",
			"update-download-complete",
			"update-download-error",
			"update-apply-error",
			"update-applied"
		]);
		onMounted(() => {
			tabsStore.createTab();
			window.api.onEvent((data) => {
				const event = data;
				if (UPDATE_EVENT_TYPES.has(event.type)) updateStore.handleIpcEvent(event);
				else if (event.type === "devices-changed") for (const store of tabsStore.getAllStores()) store.handleIpcEvent(event);
				else if ("tabId" in event && event.tabId) {
					const store = tabsStore.getTabStore(event.tabId);
					if (store) store.handleIpcEvent(event);
				}
			});
		});
		onUnmounted(() => {
			window.api.removeEventListeners();
		});
		return (_ctx, _cache) => {
			return openBlock(), createElementBlock("div", _hoisted_1, [
				createVNode(TabBar_default),
				(openBlock(true), createElementBlock(Fragment, null, renderList(unref(tabsStore).tabs, (tab) => {
					return withDirectives((openBlock(), createElementBlock("div", {
						key: tab.id,
						class: "tab-content"
					}, [createVNode(Layout_default, {
						"store-override": unref(tabsStore).getTabStore(tab.id),
						onCancel: ($event) => unref(tabsStore).getTabStore(tab.id)?.reset()
					}, {
						"header-right": withCtx(() => [createBaseVNode("button", {
							class: "btn-settings",
							"data-testid": "settings-gear",
							onClick: _cache[0] || (_cache[0] = ($event) => showSettings.value = !showSettings.value),
							title: "Paramètres"
						}, " ⚙ ")]),
						default: withCtx(() => [(openBlock(), createBlock(resolveDynamicComponent(pages[unref(tabsStore).getTabStore(tab.id)?.currentStep ?? 0])))]),
						_: 2
					}, 1032, ["store-override", "onCancel"])])), [[vShow, tab.id === unref(tabsStore).activeTabId]]);
				}), 128)),
				showSettings.value ? (openBlock(), createElementBlock("div", {
					key: 0,
					class: "modal-overlay",
					onClick: _cache[2] || (_cache[2] = withModifiers(($event) => showSettings.value = false, ["self"]))
				}, [createBaseVNode("div", _hoisted_2, [createVNode(Settings_default, { onClose: _cache[1] || (_cache[1] = ($event) => showSettings.value = false) })])])) : createCommentVNode("", true)
			]);
		};
	}
}), [["__scopeId", "data-v-8f84d028"]]);
//#endregion
//#region src/renderer/main.ts
installDevMock();
var app = createApp(App_default);
app.use(createPinia());
app.mount("#app");
//#endregion
<!DOCTYPE html>
<html lang="fr">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self'" />
    <title>SerenityInstaller</title>
    <style>
      *, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }

      body {
        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
        background: #1A1A1A;
        color: #EEEEEE;
        font-size: 14px;
        line-height: 1.5;
        overflow: hidden;
        -webkit-font-smoothing: antialiased;
        user-select: none;
      }

      ::-webkit-scrollbar { width: 6px; }
      ::-webkit-scrollbar-track { background: transparent; }
      ::-webkit-scrollbar-thumb { background: #3A3A3A; border-radius: 3px; }
      ::-webkit-scrollbar-thumb:hover { background: #555; }

      input, button, select, textarea {
        font-family: inherit;
        font-size: inherit;
      }

      input[type="checkbox"] {
        accent-color: #00D7FF;
        width: 16px;
        height: 16px;
        cursor: pointer;
      }

      input[type="radio"] {
        accent-color: #00D7FF;
        cursor: pointer;
      }

      /* Page transition */
      .page-enter-active { animation: fadeIn 0.15s ease; }
      @keyframes fadeIn {
        from { opacity: 0; transform: translateY(6px); }
        to { opacity: 1; transform: translateY(0); }
      }
    </style>
    <script type="module" crossorigin src="./assets/index-UR0P3Lhq.js"></script>
    <link rel="stylesheet" crossorigin href="./assets/index-BgkclzTg.css">
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>
{
  "name": "serenity-installer",
  "version": "2.7.4",
  "description": "SerenityInstaller — Android device deployment tool",
  "main": "./out/main/index.js",
  "author": "",
  "license": "ISC",
  "dependencies": {}
}