diff --git a/icons/icon.png b/icons/icon.png
index acb2a81..637bdbd 100644
Binary files a/icons/icon.png and b/icons/icon.png differ
diff --git a/icons/icon.svg b/icons/icon.svg
new file mode 100644
index 0000000..b9193b6
--- /dev/null
+++ b/icons/icon.svg
@@ -0,0 +1,7 @@
+
diff --git a/package.json b/package.json
index df46e52..5dddfe0 100644
--- a/package.json
+++ b/package.json
@@ -15,6 +15,7 @@
"lint": "eslint \"src/**/*.{ts,tsx}\"",
"typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.node.json",
"inspect:electron": "node scripts/playwright-inspect-electron.cjs",
+ "build:icon": "electron scripts/build-icon.mjs",
"format": "prettier --write \"src/**/*.{ts,tsx,css}\" \"scripts/release-notes/**/*.mjs\"",
"format:check": "prettier --check \"src/**/*.{ts,tsx,css}\" \"scripts/release-notes/**/*.mjs\"",
"release:patch": "npm version patch -m \"chore(release): v%s\" && git push --follow-tags",
diff --git a/scripts/build-icon.mjs b/scripts/build-icon.mjs
new file mode 100644
index 0000000..df354db
--- /dev/null
+++ b/scripts/build-icon.mjs
@@ -0,0 +1,73 @@
+// Renders icons/icon.svg -> icons/icon.png (1024x1024, RGBA) using the Electron
+// binary that already ships with this project — no extra native dependency and
+// no network access. electron-builder generates the installer .ico from this
+// PNG, and the tray reuses it (see src/main/index.ts resolveTrayIcon).
+//
+// Run: npm run build:icon (after editing icons/icon.svg)
+import { app, BrowserWindow } from "electron";
+import { readFileSync, writeFileSync } from "node:fs";
+import { dirname, join } from "node:path";
+import { fileURLToPath } from "node:url";
+
+const root = join(dirname(fileURLToPath(import.meta.url)), "..");
+const SIZE = 1024;
+const svgPath = join(root, "icons", "icon.svg");
+const outPath = join(root, "icons", "icon.png");
+
+function fail(msg) {
+ console.error("[build-icon] " + msg);
+ process.exit(1);
+}
+
+app.disableHardwareAcceleration();
+const giveUp = setTimeout(() => fail("timed out waiting for a rendered frame"), 20000);
+
+app.whenReady().then(async () => {
+ const svg = readFileSync(svgPath, "utf8");
+ const html =
+ "" +
+ "" +
+ svg;
+
+ const win = new BrowserWindow({
+ width: SIZE,
+ height: SIZE,
+ show: false,
+ frame: false,
+ transparent: true,
+ backgroundColor: "#00000000",
+ useContentSize: true,
+ webPreferences: { offscreen: true },
+ });
+
+ let captured = null;
+ win.webContents.on("paint", (_e, _dirty, image) => {
+ if (image && !image.isEmpty()) captured = image;
+ });
+
+ await win.loadURL("data:text/html;charset=utf-8," + encodeURIComponent(html));
+ await new Promise((r) => setTimeout(r, 800)); // let the compositor settle
+
+ let image = captured;
+ if (!image || image.isEmpty()) {
+ try {
+ image = await win.webContents.capturePage();
+ } catch (e) {
+ return fail("offscreen paint empty and capturePage failed: " + e.message);
+ }
+ }
+ if (!image || image.isEmpty()) return fail("renderer produced an empty image");
+
+ const size = image.getSize();
+ if (size.width !== SIZE || size.height !== SIZE) {
+ image = image.resize({ width: SIZE, height: SIZE, quality: "best" });
+ }
+
+ writeFileSync(outPath, image.toPNG());
+ clearTimeout(giveUp);
+ console.log("[build-icon] wrote " + outPath + " (" + SIZE + "x" + SIZE + ")");
+ win.destroy();
+ app.quit();
+ process.exit(0);
+});