diff --git a/README.md b/README.md
index 4bee9c2..ca73743 100644
--- a/README.md
+++ b/README.md
@@ -112,10 +112,11 @@ export const BRANDING = {
} as const;
```
-To change the icon: replace `assets/icon.png` (1024×1024) and run
-`./scripts/gen-iconset.sh` (regenerates the `.iconset` with `sips`), or point
-`icon` at your own `.iconset`. `scripts/make-icon.py` regenerates the current
-placeholder.
+To change the icon: edit `assets/logo.svg` (app icon) and
+`assets/tray/tray-glyph.svg` (menu-bar glyph), then run
+`bun scripts/gen-icons.ts` — it regenerates the macOS iconset, `icon.png`, the
+tray template image, and the tray progress frames from the SVGs
+(`@resvg/resvg-js`, dev-only dependency).
> Renaming `appName` changes the app-data directory, so existing saved
> connections won't be found under the new name — copy `connections.json` across
diff --git a/assets/icon.iconset/icon_128x128.png b/assets/icon.iconset/icon_128x128.png
index ef0f8d4..adc8f9f 100644
Binary files a/assets/icon.iconset/icon_128x128.png and b/assets/icon.iconset/icon_128x128.png differ
diff --git a/assets/icon.iconset/icon_128x128@2x.png b/assets/icon.iconset/icon_128x128@2x.png
index 9adf35d..3592dda 100644
Binary files a/assets/icon.iconset/icon_128x128@2x.png and b/assets/icon.iconset/icon_128x128@2x.png differ
diff --git a/assets/icon.iconset/icon_16x16.png b/assets/icon.iconset/icon_16x16.png
index ea82638..64a7ea6 100644
Binary files a/assets/icon.iconset/icon_16x16.png and b/assets/icon.iconset/icon_16x16.png differ
diff --git a/assets/icon.iconset/icon_16x16@2x.png b/assets/icon.iconset/icon_16x16@2x.png
index 624d692..9d87236 100644
Binary files a/assets/icon.iconset/icon_16x16@2x.png and b/assets/icon.iconset/icon_16x16@2x.png differ
diff --git a/assets/icon.iconset/icon_256x256.png b/assets/icon.iconset/icon_256x256.png
index 9adf35d..3592dda 100644
Binary files a/assets/icon.iconset/icon_256x256.png and b/assets/icon.iconset/icon_256x256.png differ
diff --git a/assets/icon.iconset/icon_256x256@2x.png b/assets/icon.iconset/icon_256x256@2x.png
index 8ccdeff..aced476 100644
Binary files a/assets/icon.iconset/icon_256x256@2x.png and b/assets/icon.iconset/icon_256x256@2x.png differ
diff --git a/assets/icon.iconset/icon_32x32.png b/assets/icon.iconset/icon_32x32.png
index 624d692..9d87236 100644
Binary files a/assets/icon.iconset/icon_32x32.png and b/assets/icon.iconset/icon_32x32.png differ
diff --git a/assets/icon.iconset/icon_32x32@2x.png b/assets/icon.iconset/icon_32x32@2x.png
index 024d4c6..52a0ddf 100644
Binary files a/assets/icon.iconset/icon_32x32@2x.png and b/assets/icon.iconset/icon_32x32@2x.png differ
diff --git a/assets/icon.iconset/icon_512x512.png b/assets/icon.iconset/icon_512x512.png
index 8ccdeff..aced476 100644
Binary files a/assets/icon.iconset/icon_512x512.png and b/assets/icon.iconset/icon_512x512.png differ
diff --git a/assets/icon.iconset/icon_512x512@2x.png b/assets/icon.iconset/icon_512x512@2x.png
index 3bb2789..fff316e 100644
Binary files a/assets/icon.iconset/icon_512x512@2x.png and b/assets/icon.iconset/icon_512x512@2x.png differ
diff --git a/assets/icon.png b/assets/icon.png
index 3bb2789..fff316e 100644
Binary files a/assets/icon.png and b/assets/icon.png differ
diff --git a/assets/logo.svg b/assets/logo.svg
new file mode 100644
index 0000000..bae4864
--- /dev/null
+++ b/assets/logo.svg
@@ -0,0 +1,29 @@
+
diff --git a/assets/tray/progress/p0.png b/assets/tray/progress/p0.png
new file mode 100644
index 0000000..7b79961
Binary files /dev/null and b/assets/tray/progress/p0.png differ
diff --git a/assets/tray/progress/p10.png b/assets/tray/progress/p10.png
new file mode 100644
index 0000000..18c4dfc
Binary files /dev/null and b/assets/tray/progress/p10.png differ
diff --git a/assets/tray/progress/p100.png b/assets/tray/progress/p100.png
new file mode 100644
index 0000000..b9fd386
Binary files /dev/null and b/assets/tray/progress/p100.png differ
diff --git a/assets/tray/progress/p15.png b/assets/tray/progress/p15.png
new file mode 100644
index 0000000..982ce19
Binary files /dev/null and b/assets/tray/progress/p15.png differ
diff --git a/assets/tray/progress/p20.png b/assets/tray/progress/p20.png
new file mode 100644
index 0000000..2fb9287
Binary files /dev/null and b/assets/tray/progress/p20.png differ
diff --git a/assets/tray/progress/p25.png b/assets/tray/progress/p25.png
new file mode 100644
index 0000000..4b0e5f1
Binary files /dev/null and b/assets/tray/progress/p25.png differ
diff --git a/assets/tray/progress/p30.png b/assets/tray/progress/p30.png
new file mode 100644
index 0000000..b4fbd46
Binary files /dev/null and b/assets/tray/progress/p30.png differ
diff --git a/assets/tray/progress/p35.png b/assets/tray/progress/p35.png
new file mode 100644
index 0000000..1b6cb88
Binary files /dev/null and b/assets/tray/progress/p35.png differ
diff --git a/assets/tray/progress/p40.png b/assets/tray/progress/p40.png
new file mode 100644
index 0000000..18b3307
Binary files /dev/null and b/assets/tray/progress/p40.png differ
diff --git a/assets/tray/progress/p45.png b/assets/tray/progress/p45.png
new file mode 100644
index 0000000..1cba41b
Binary files /dev/null and b/assets/tray/progress/p45.png differ
diff --git a/assets/tray/progress/p5.png b/assets/tray/progress/p5.png
new file mode 100644
index 0000000..1cf4c40
Binary files /dev/null and b/assets/tray/progress/p5.png differ
diff --git a/assets/tray/progress/p50.png b/assets/tray/progress/p50.png
new file mode 100644
index 0000000..f193657
Binary files /dev/null and b/assets/tray/progress/p50.png differ
diff --git a/assets/tray/progress/p55.png b/assets/tray/progress/p55.png
new file mode 100644
index 0000000..2d14b62
Binary files /dev/null and b/assets/tray/progress/p55.png differ
diff --git a/assets/tray/progress/p60.png b/assets/tray/progress/p60.png
new file mode 100644
index 0000000..c66d77b
Binary files /dev/null and b/assets/tray/progress/p60.png differ
diff --git a/assets/tray/progress/p65.png b/assets/tray/progress/p65.png
new file mode 100644
index 0000000..182e845
Binary files /dev/null and b/assets/tray/progress/p65.png differ
diff --git a/assets/tray/progress/p70.png b/assets/tray/progress/p70.png
new file mode 100644
index 0000000..a5767d3
Binary files /dev/null and b/assets/tray/progress/p70.png differ
diff --git a/assets/tray/progress/p75.png b/assets/tray/progress/p75.png
new file mode 100644
index 0000000..7b380af
Binary files /dev/null and b/assets/tray/progress/p75.png differ
diff --git a/assets/tray/progress/p80.png b/assets/tray/progress/p80.png
new file mode 100644
index 0000000..f53be36
Binary files /dev/null and b/assets/tray/progress/p80.png differ
diff --git a/assets/tray/progress/p85.png b/assets/tray/progress/p85.png
new file mode 100644
index 0000000..26b85d8
Binary files /dev/null and b/assets/tray/progress/p85.png differ
diff --git a/assets/tray/progress/p90.png b/assets/tray/progress/p90.png
new file mode 100644
index 0000000..b5f51de
Binary files /dev/null and b/assets/tray/progress/p90.png differ
diff --git a/assets/tray/progress/p95.png b/assets/tray/progress/p95.png
new file mode 100644
index 0000000..eabadd7
Binary files /dev/null and b/assets/tray/progress/p95.png differ
diff --git a/assets/tray/tray-44-template.png b/assets/tray/tray-44-template.png
new file mode 100644
index 0000000..dde3f7d
Binary files /dev/null and b/assets/tray/tray-44-template.png differ
diff --git a/assets/tray/tray-glyph.svg b/assets/tray/tray-glyph.svg
new file mode 100644
index 0000000..d6f3eb4
--- /dev/null
+++ b/assets/tray/tray-glyph.svg
@@ -0,0 +1,11 @@
+
diff --git a/bun.lock b/bun.lock
index 2922712..a1e38a6 100644
--- a/bun.lock
+++ b/bun.lock
@@ -9,9 +9,10 @@
"ssh2": "^1.17.0",
},
"devDependencies": {
+ "@resvg/resvg-js": "^2.6.2",
"@types/bun": "latest",
"@types/ssh2": "^1.15.5",
- "typescript": "~5.9.0",
+ "typescript": "~5.9.3",
},
},
"ui": {
@@ -352,6 +353,32 @@
"@parcel/watcher-win32-x64": ["@parcel/watcher-win32-x64@2.5.6", "", { "os": "win32", "cpu": "x64" }, "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw=="],
+ "@resvg/resvg-js": ["@resvg/resvg-js@2.6.2", "", { "optionalDependencies": { "@resvg/resvg-js-android-arm-eabi": "2.6.2", "@resvg/resvg-js-android-arm64": "2.6.2", "@resvg/resvg-js-darwin-arm64": "2.6.2", "@resvg/resvg-js-darwin-x64": "2.6.2", "@resvg/resvg-js-linux-arm-gnueabihf": "2.6.2", "@resvg/resvg-js-linux-arm64-gnu": "2.6.2", "@resvg/resvg-js-linux-arm64-musl": "2.6.2", "@resvg/resvg-js-linux-x64-gnu": "2.6.2", "@resvg/resvg-js-linux-x64-musl": "2.6.2", "@resvg/resvg-js-win32-arm64-msvc": "2.6.2", "@resvg/resvg-js-win32-ia32-msvc": "2.6.2", "@resvg/resvg-js-win32-x64-msvc": "2.6.2" } }, "sha512-xBaJish5OeGmniDj9cW5PRa/PtmuVU3ziqrbr5xJj901ZDN4TosrVaNZpEiLZAxdfnhAe7uQ7QFWfjPe9d9K2Q=="],
+
+ "@resvg/resvg-js-android-arm-eabi": ["@resvg/resvg-js-android-arm-eabi@2.6.2", "", { "os": "android", "cpu": "arm" }, "sha512-FrJibrAk6v29eabIPgcTUMPXiEz8ssrAk7TXxsiZzww9UTQ1Z5KAbFJs+Z0Ez+VZTYgnE5IQJqBcoSiMebtPHA=="],
+
+ "@resvg/resvg-js-android-arm64": ["@resvg/resvg-js-android-arm64@2.6.2", "", { "os": "android", "cpu": "arm64" }, "sha512-VcOKezEhm2VqzXpcIJoITuvUS/fcjIw5NA/w3tjzWyzmvoCdd+QXIqy3FBGulWdClvp4g+IfUemigrkLThSjAQ=="],
+
+ "@resvg/resvg-js-darwin-arm64": ["@resvg/resvg-js-darwin-arm64@2.6.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-nmok2LnAd6nLUKI16aEB9ydMC6Lidiiq2m1nEBDR1LaaP7FGs4AJ90qDraxX+CWlVuRlvNjyYJTNv8qFjtL9+A=="],
+
+ "@resvg/resvg-js-darwin-x64": ["@resvg/resvg-js-darwin-x64@2.6.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-GInyZLjgWDfsVT6+SHxQVRwNzV0AuA1uqGsOAW+0th56J7Nh6bHHKXHBWzUrihxMetcFDmQMAX1tZ1fZDYSRsw=="],
+
+ "@resvg/resvg-js-linux-arm-gnueabihf": ["@resvg/resvg-js-linux-arm-gnueabihf@2.6.2", "", { "os": "linux", "cpu": "arm" }, "sha512-YIV3u/R9zJbpqTTNwTZM5/ocWetDKGsro0SWp70eGEM9eV2MerWyBRZnQIgzU3YBnSBQ1RcxRZvY/UxwESfZIw=="],
+
+ "@resvg/resvg-js-linux-arm64-gnu": ["@resvg/resvg-js-linux-arm64-gnu@2.6.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-zc2BlJSim7YR4FZDQ8OUoJg5holYzdiYMeobb9pJuGDidGL9KZUv7SbiD4E8oZogtYY42UZEap7dqkkYuA91pg=="],
+
+ "@resvg/resvg-js-linux-arm64-musl": ["@resvg/resvg-js-linux-arm64-musl@2.6.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-3h3dLPWNgSsD4lQBJPb4f+kvdOSJHa5PjTYVsWHxLUzH4IFTJUAnmuWpw4KqyQ3NA5QCyhw4TWgxk3jRkQxEKg=="],
+
+ "@resvg/resvg-js-linux-x64-gnu": ["@resvg/resvg-js-linux-x64-gnu@2.6.2", "", { "os": "linux", "cpu": "x64" }, "sha512-IVUe+ckIerA7xMZ50duAZzwf1U7khQe2E0QpUxu5MBJNao5RqC0zwV/Zm965vw6D3gGFUl7j4m+oJjubBVoftw=="],
+
+ "@resvg/resvg-js-linux-x64-musl": ["@resvg/resvg-js-linux-x64-musl@2.6.2", "", { "os": "linux", "cpu": "x64" }, "sha512-UOf83vqTzoYQO9SZ0fPl2ZIFtNIz/Rr/y+7X8XRX1ZnBYsQ/tTb+cj9TE+KHOdmlTFBxhYzVkP2lRByCzqi4jQ=="],
+
+ "@resvg/resvg-js-win32-arm64-msvc": ["@resvg/resvg-js-win32-arm64-msvc@2.6.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-7C/RSgCa+7vqZ7qAbItfiaAWhyRSoD4l4BQAbVDqRRsRgY+S+hgS3in0Rxr7IorKUpGE69X48q6/nOAuTJQxeQ=="],
+
+ "@resvg/resvg-js-win32-ia32-msvc": ["@resvg/resvg-js-win32-ia32-msvc@2.6.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-har4aPAlvjnLcil40AC77YDIk6loMawuJwFINEM7n0pZviwMkMvjb2W5ZirsNOZY4aDbo5tLx0wNMREp5Brk+w=="],
+
+ "@resvg/resvg-js-win32-x64-msvc": ["@resvg/resvg-js-win32-x64-msvc@2.6.2", "", { "os": "win32", "cpu": "x64" }, "sha512-ZXtYhtUr5SSaBrUDq7DiyjOFJqBVL/dOBN7N/qmi/pO0IgiWW/f/ue3nbvu9joWE5aAKDoIzy/CxsY0suwGosQ=="],
+
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.60.2", "", { "os": "android", "cpu": "arm" }, "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw=="],
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.60.2", "", { "os": "android", "cpu": "arm64" }, "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg=="],
diff --git a/package.json b/package.json
index e6601b5..6926b1b 100644
--- a/package.json
+++ b/package.json
@@ -16,6 +16,7 @@
"ssh2": "^1.17.0"
},
"devDependencies": {
+ "@resvg/resvg-js": "^2.6.2",
"@types/bun": "latest",
"@types/ssh2": "^1.15.5",
"typescript": "~5.9.3"
diff --git a/scripts/gen-icons.ts b/scripts/gen-icons.ts
new file mode 100644
index 0000000..cb73429
--- /dev/null
+++ b/scripts/gen-icons.ts
@@ -0,0 +1,53 @@
+// Regenerate every raster icon asset from the SVG sources (dev-time only).
+// bun scripts/gen-icons.ts
+// Produces: assets/icon.png (1024), assets/icon.iconset/* (macOS app icon),
+// assets/tray/tray-44-template.png, assets/tray/progress/p{0..100}.png.
+import { mkdir } from "node:fs/promises";
+import path from "node:path";
+import { Resvg } from "@resvg/resvg-js";
+
+const root = path.join(import.meta.dir, "..");
+const assets = path.join(root, "assets");
+
+function renderPng(svg: string, sizePx: number): Uint8Array {
+ const resvg = new Resvg(svg, { fitTo: { mode: "width", value: sizePx } });
+ return resvg.render().asPng();
+}
+
+async function writePng(file: string, svg: string, sizePx: number): Promise {
+ await mkdir(path.dirname(file), { recursive: true });
+ await Bun.write(file, renderPng(svg, sizePx));
+}
+
+// ---- app icon: iconset rendered per size (crisper than downscaling) ----
+const logo = await Bun.file(path.join(assets, "logo.svg")).text();
+await writePng(path.join(assets, "icon.png"), logo, 1024);
+for (const size of [16, 32, 128, 256, 512]) {
+ await writePng(path.join(assets, "icon.iconset", `icon_${size}x${size}.png`), logo, size);
+ await writePng(path.join(assets, "icon.iconset", `icon_${size}x${size}@2x.png`), logo, size * 2);
+}
+
+// ---- tray base (22pt status item @2x) ----
+const trayGlyph = await Bun.file(path.join(assets, "tray", "tray-glyph.svg")).text();
+await writePng(path.join(assets, "tray", "tray-44-template.png"), trayGlyph, 44);
+
+// ---- tray progress frames: faded arrows + centered bar filled to pct ----
+function progressSvg(pct: number): string {
+ const trackX = 4;
+ const trackW = 16;
+ const fillW = (trackW * pct) / 100;
+ return ``;
+}
+
+for (let pct = 0; pct <= 100; pct += 5) {
+ await writePng(path.join(assets, "tray", "progress", `p${pct}.png`), progressSvg(pct), 44);
+}
+
+console.log("icons regenerated: app iconset, tray template, 21 progress frames");
diff --git a/scripts/gen-iconset.sh b/scripts/gen-iconset.sh
deleted file mode 100755
index 44b67a9..0000000
--- a/scripts/gen-iconset.sh
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/usr/bin/env bash
-# Regenerate assets/icon.iconset from assets/icon.png (macOS: sips + iconutil).
-# Electrobun compiles the .iconset folder into AppIcon.icns at build time.
-set -euo pipefail
-cd "$(dirname "$0")/.."
-
-SRC="assets/icon.png"
-SET="assets/icon.iconset"
-rm -rf "$SET"
-mkdir -p "$SET"
-
-for size in 16 32 128 256 512; do
- sips -z "$size" "$size" "$SRC" --out "$SET/icon_${size}x${size}.png" >/dev/null
- sips -z $((size * 2)) $((size * 2)) "$SRC" --out "$SET/icon_${size}x${size}@2x.png" >/dev/null
-done
-
-echo "generated $SET"
diff --git a/scripts/make-icon.py b/scripts/make-icon.py
deleted file mode 100644
index ebbf0a3..0000000
--- a/scripts/make-icon.py
+++ /dev/null
@@ -1,103 +0,0 @@
-#!/usr/bin/env python3
-"""Generate a placeholder app icon (1024x1024 PNG) with a dual-panel + transfer
-motif. No third-party deps — raw RGBA encoded via zlib. Replace assets/icon.png
-(and re-run scripts/gen-iconset.sh) to rebrand."""
-import struct
-import zlib
-import sys
-
-N = 1024
-
-
-def lerp(a, b, t):
- return tuple(round(a[i] + (b[i] - a[i]) * t) for i in range(3))
-
-
-def rounded_rect_alpha(x, y, x0, y0, x1, y1, r):
- """Coverage (0..1) for a rounded rect with ~1px antialiasing."""
- # distance outside the rect's rounded boundary
- dx = max(x0 + r - x, 0, x - (x1 - r))
- dy = max(y0 + r - y, 0, y - (y1 - r))
- if x < x0 or x > x1 or y < y0 or y > y1:
- return 0.0
- # corner regions
- cx = None
- if x < x0 + r and y < y0 + r:
- cx, cy = x0 + r, y0 + r
- elif x > x1 - r and y < y0 + r:
- cx, cy = x1 - r, y0 + r
- elif x < x0 + r and y > y1 - r:
- cx, cy = x0 + r, y1 - r
- elif x > x1 - r and y > y1 - r:
- cx, cy = x1 - r, y1 - r
- if cx is not None:
- d = ((x - cx) ** 2 + (y - cy) ** 2) ** 0.5
- return max(0.0, min(1.0, r - d + 0.5))
- return 1.0
-
-
-BG_TOP = (0x4F, 0x46, 0xE5) # indigo-600
-BG_BOT = (0x18, 0x18, 0x1B) # zinc-900
-PANEL = (0xFA, 0xFA, 0xFA) # near white
-ROW = (0xC7, 0xCC, 0xD6)
-ARROW = (0xFB, 0xBF, 0x24) # amber-400
-
-buf = bytearray()
-margin = 96
-radius = 205
-# panels
-pgap = 70
-ptop = 340
-pbot = 760
-pw = (N - 2 * 300 - pgap) / 2
-lx0 = 300
-lx1 = lx0 + pw
-rx0 = lx1 + pgap
-rx1 = rx0 + pw
-
-for y in range(N):
- buf.append(0) # PNG filter byte per scanline
- for x in range(N):
- outer = rounded_rect_alpha(x + 0.5, y + 0.5, margin, margin, N - margin, N - margin, radius)
- t = y / N
- col = lerp(BG_TOP, BG_BOT, t)
- r, g, b = col
- a = outer
- # panels
- for (x0, x1) in ((lx0, lx1), (rx0, rx1)):
- pa = rounded_rect_alpha(x + 0.5, y + 0.5, x0, ptop, x1, pbot, 34)
- if pa > 0:
- r = round(r * (1 - pa) + PANEL[0] * pa)
- g = round(g * (1 - pa) + PANEL[1] * pa)
- b = round(b * (1 - pa) + PANEL[2] * pa)
- # file rows
- for row in range(4):
- ry = ptop + 70 + row * 74
- if ry <= y <= ry + 26 and x0 + 34 <= x <= x1 - 34:
- r, g, b = ROW
- # transfer arrows in the gap
- cx0, cx1 = lx1 + 6, rx0 - 6
- midy = (ptop + pbot) / 2
- if cx0 <= x <= cx1 and abs(y - midy) < 90:
- # two chevrons pointing right and left, forming <-> feel
- rel = (x - cx0) / (cx1 - cx0)
- band = abs(y - midy)
- if band < 46 - abs(rel - 0.5) * 40:
- r, g, b = ARROW
- a = max(a, outer)
- buf.extend((r, g, b, round(255 * a)))
-
-
-def chunk(tag, data):
- return struct.pack(">I", len(data)) + tag + data + struct.pack(">I", zlib.crc32(tag + data) & 0xFFFFFFFF)
-
-
-png = b"\x89PNG\r\n\x1a\n"
-png += chunk(b"IHDR", struct.pack(">IIBBBBB", N, N, 8, 6, 0, 0, 0))
-png += chunk(b"IDAT", zlib.compress(bytes(buf), 9))
-png += chunk(b"IEND", b"")
-
-out = sys.argv[1] if len(sys.argv) > 1 else "assets/icon.png"
-with open(out, "wb") as f:
- f.write(png)
-print(f"wrote {out} ({N}x{N})")