diff --git a/Bugger.xcodeproj/xcshareddata/xcschemes/Bugger.xcscheme b/Bugger.xcodeproj/xcshareddata/xcschemes/Bugger.xcscheme
index 7d035da..ebc307b 100644
--- a/Bugger.xcodeproj/xcshareddata/xcschemes/Bugger.xcscheme
+++ b/Bugger.xcodeproj/xcshareddata/xcschemes/Bugger.xcscheme
@@ -22,6 +22,13 @@
+
+
+
+
+
+
+
+
diff --git a/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json b/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json
index 3f00db4..c8bb1f2 100644
--- a/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json
+++ b/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -1,58 +1,68 @@
{
- "images" : [
+ "images": [
{
- "idiom" : "mac",
- "scale" : "1x",
- "size" : "16x16"
+ "size": "16x16",
+ "idiom": "mac",
+ "filename": "icon_16x16@x1.png",
+ "scale": "1x"
},
{
- "idiom" : "mac",
- "scale" : "2x",
- "size" : "16x16"
+ "size": "16x16",
+ "idiom": "mac",
+ "filename": "icon_16x16@x2.png",
+ "scale": "2x"
},
{
- "idiom" : "mac",
- "scale" : "1x",
- "size" : "32x32"
+ "size": "32x32",
+ "idiom": "mac",
+ "filename": "icon_32x32@x1.png",
+ "scale": "1x"
},
{
- "idiom" : "mac",
- "scale" : "2x",
- "size" : "32x32"
+ "size": "32x32",
+ "idiom": "mac",
+ "filename": "icon_32x32@x2.png",
+ "scale": "2x"
},
{
- "idiom" : "mac",
- "scale" : "1x",
- "size" : "128x128"
+ "size": "128x128",
+ "idiom": "mac",
+ "filename": "icon_128x128@x1.png",
+ "scale": "1x"
},
{
- "idiom" : "mac",
- "scale" : "2x",
- "size" : "128x128"
+ "size": "128x128",
+ "idiom": "mac",
+ "filename": "icon_128x128@x2.png",
+ "scale": "2x"
},
{
- "idiom" : "mac",
- "scale" : "1x",
- "size" : "256x256"
+ "size": "256x256",
+ "idiom": "mac",
+ "filename": "icon_256x256@x1.png",
+ "scale": "1x"
},
{
- "idiom" : "mac",
- "scale" : "2x",
- "size" : "256x256"
+ "size": "256x256",
+ "idiom": "mac",
+ "filename": "icon_256x256@x2.png",
+ "scale": "2x"
},
{
- "idiom" : "mac",
- "scale" : "1x",
- "size" : "512x512"
+ "size": "512x512",
+ "idiom": "mac",
+ "filename": "icon_512x512@x1.png",
+ "scale": "1x"
},
{
- "idiom" : "mac",
- "scale" : "2x",
- "size" : "512x512"
+ "size": "512x512",
+ "idiom": "mac",
+ "filename": "icon_512x512@x2.png",
+ "scale": "2x"
}
],
- "info" : {
- "author" : "xcode",
- "version" : 1
+ "info": {
+ "author": "xcode",
+ "version": 1
}
-}
+}
\ No newline at end of file
diff --git a/Resources/Assets.xcassets/AppIcon.appiconset/icon_128x128@x1.png b/Resources/Assets.xcassets/AppIcon.appiconset/icon_128x128@x1.png
new file mode 100644
index 0000000..549305c
Binary files /dev/null and b/Resources/Assets.xcassets/AppIcon.appiconset/icon_128x128@x1.png differ
diff --git a/Resources/Assets.xcassets/AppIcon.appiconset/icon_128x128@x2.png b/Resources/Assets.xcassets/AppIcon.appiconset/icon_128x128@x2.png
new file mode 100644
index 0000000..3386b06
Binary files /dev/null and b/Resources/Assets.xcassets/AppIcon.appiconset/icon_128x128@x2.png differ
diff --git a/Resources/Assets.xcassets/AppIcon.appiconset/icon_16x16@x1.png b/Resources/Assets.xcassets/AppIcon.appiconset/icon_16x16@x1.png
new file mode 100644
index 0000000..03019d2
Binary files /dev/null and b/Resources/Assets.xcassets/AppIcon.appiconset/icon_16x16@x1.png differ
diff --git a/Resources/Assets.xcassets/AppIcon.appiconset/icon_16x16@x2.png b/Resources/Assets.xcassets/AppIcon.appiconset/icon_16x16@x2.png
new file mode 100644
index 0000000..724af4f
Binary files /dev/null and b/Resources/Assets.xcassets/AppIcon.appiconset/icon_16x16@x2.png differ
diff --git a/Resources/Assets.xcassets/AppIcon.appiconset/icon_256x256@x1.png b/Resources/Assets.xcassets/AppIcon.appiconset/icon_256x256@x1.png
new file mode 100644
index 0000000..3386b06
Binary files /dev/null and b/Resources/Assets.xcassets/AppIcon.appiconset/icon_256x256@x1.png differ
diff --git a/Resources/Assets.xcassets/AppIcon.appiconset/icon_256x256@x2.png b/Resources/Assets.xcassets/AppIcon.appiconset/icon_256x256@x2.png
new file mode 100644
index 0000000..235334e
Binary files /dev/null and b/Resources/Assets.xcassets/AppIcon.appiconset/icon_256x256@x2.png differ
diff --git a/Resources/Assets.xcassets/AppIcon.appiconset/icon_32x32@x1.png b/Resources/Assets.xcassets/AppIcon.appiconset/icon_32x32@x1.png
new file mode 100644
index 0000000..724af4f
Binary files /dev/null and b/Resources/Assets.xcassets/AppIcon.appiconset/icon_32x32@x1.png differ
diff --git a/Resources/Assets.xcassets/AppIcon.appiconset/icon_32x32@x2.png b/Resources/Assets.xcassets/AppIcon.appiconset/icon_32x32@x2.png
new file mode 100644
index 0000000..ca61186
Binary files /dev/null and b/Resources/Assets.xcassets/AppIcon.appiconset/icon_32x32@x2.png differ
diff --git a/Resources/Assets.xcassets/AppIcon.appiconset/icon_512x512@x1.png b/Resources/Assets.xcassets/AppIcon.appiconset/icon_512x512@x1.png
new file mode 100644
index 0000000..235334e
Binary files /dev/null and b/Resources/Assets.xcassets/AppIcon.appiconset/icon_512x512@x1.png differ
diff --git a/Resources/Assets.xcassets/AppIcon.appiconset/icon_512x512@x2.png b/Resources/Assets.xcassets/AppIcon.appiconset/icon_512x512@x2.png
new file mode 100644
index 0000000..c08879f
Binary files /dev/null and b/Resources/Assets.xcassets/AppIcon.appiconset/icon_512x512@x2.png differ
diff --git a/scripts/generate_app_icon.py b/scripts/generate_app_icon.py
new file mode 100644
index 0000000..1cf1b75
--- /dev/null
+++ b/scripts/generate_app_icon.py
@@ -0,0 +1,173 @@
+#!/usr/bin/env python3
+"""Generate Bugger app icon (bug beetle) in all required sizes for macOS."""
+import math, os, shutil
+from pathlib import Path
+from PIL import Image, ImageDraw
+
+ROOT = Path(__file__).resolve().parent.parent
+ICONSET = ROOT / "Resources" / "Assets.xcassets" / "AppIcon.appiconset"
+
+# Required macOS icon sizes: (logical_size, scale_factor, actual_pixels)
+SIZES = [
+ (16, 1, 16), (16, 2, 32),
+ (32, 1, 32), (32, 2, 64),
+ (128, 1, 128), (128, 2, 256),
+ (256, 1, 256), (256, 2, 512),
+ (512, 1, 512), (512, 2, 1024),
+]
+
+
+def draw_bug(canvas_size: int) -> Image.Image:
+ """Draw a bug beetle icon at given canvas size. Returns RGBA image."""
+ img = Image.new("RGBA", (canvas_size, canvas_size), (0, 0, 0, 0))
+ d = ImageDraw.Draw(img)
+ s = canvas_size # shorthand
+
+ # Colors
+ body_fill = (45, 50, 55, 255) # dark charcoal
+ body_highlight = (65, 72, 78, 255) # lighter shell
+ accent_green = (76, 188, 128, 255) # green accent
+ antenna = (45, 50, 55, 255)
+ leg = (45, 50, 55, 255)
+ eye = (255, 255, 255, 255)
+
+ # Margins (proportional)
+ margin = s * 0.12
+
+ # ---- Body (rounded oval) ----
+ body_rect = (
+ margin + s * 0.08, # left
+ margin + s * 0.22, # top
+ s - margin - s * 0.08, # right
+ s - margin + s * 0.05, # bottom
+ )
+ d.rounded_rectangle(body_rect, radius=s * 0.28, fill=body_fill)
+
+ # ---- Shell highlight (lighter arc on top half) ----
+ shell_rect = (
+ margin + s * 0.06,
+ margin + s * 0.22,
+ s - margin - s * 0.06,
+ s * 0.62,
+ )
+ d.pieslice(shell_rect, start=180, end=360, fill=body_highlight)
+
+ # ---- Green accent stripe ----
+ stripe_rect = (
+ s * 0.32,
+ s * 0.38,
+ s * 0.68,
+ s * 0.62,
+ )
+ d.rounded_rectangle(stripe_rect, radius=s * 0.06, fill=accent_green)
+
+ # ---- Head (small semi-circle at top) ----
+ head_center = (s * 0.50, s * 0.30)
+ head_r = s * 0.095
+ d.ellipse(
+ (head_center[0] - head_r, head_center[1] - head_r,
+ head_center[0] + head_r, head_center[1] + head_r),
+ fill=body_fill
+ )
+
+ # ---- Eyes ----
+ eye_r = s * 0.025
+ eye_offset = s * 0.04
+ for ex in (head_center[0] - eye_offset, head_center[0] + eye_offset):
+ d.ellipse(
+ (ex - eye_r, head_center[1] - eye_r - s * 0.01,
+ ex + eye_r, head_center[1] + eye_r - s * 0.01),
+ fill=eye
+ )
+
+ # ---- Antennae (two curved lines) ----
+ ant_line_w = max(2, int(s * 0.015))
+ ant_start_y = head_center[1] - head_r + s * 0.01
+ for side, curve_dir in [(-1, -0.08), (1, 0.08)]:
+ ant_start_x = head_center[0] + side * s * 0.04
+ points = [
+ (ant_start_x, ant_start_y),
+ (ant_start_x + side * s * 0.06, ant_start_y - s * 0.08),
+ (ant_start_x + side * s * 0.14, ant_start_y - s * 0.18 + curve_dir * s),
+ (ant_start_x + side * s * 0.18 + curve_dir * s * 0.5, ant_start_y - s * 0.17),
+ ]
+ for i in range(len(points) - 1):
+ d.line([points[i], points[i + 1]], fill=antenna, width=ant_line_w)
+ # dot at tip
+ tip_r = max(1, int(s * 0.012))
+ tip = points[-1]
+ d.ellipse((tip[0] - tip_r, tip[1] - tip_r, tip[0] + tip_r, tip[1] + tip_r),
+ fill=antenna)
+
+ # ---- Legs (3 per side) ----
+ leg_w = max(2, int(s * 0.018))
+ leg_positions = [0.45, 0.56, 0.67] # y-positions as fraction of body height
+ body_left = margin + s * 0.02
+ body_right = s - margin - s * 0.02
+
+ for frac in leg_positions:
+ leg_y = s * (0.22 + frac * 0.57)
+ # Left legs
+ d.line(
+ [(body_left, leg_y),
+ (body_left - s * 0.16, leg_y - s * 0.08),
+ (body_left - s * 0.22, leg_y + s * 0.04)],
+ fill=leg, width=leg_w
+ )
+ # Right legs
+ d.line(
+ [(body_right, leg_y),
+ (body_right + s * 0.16, leg_y - s * 0.08),
+ (body_right + s * 0.22, leg_y + s * 0.04)],
+ fill=leg, width=leg_w
+ )
+
+ return img
+
+
+def main():
+ ICONSET.mkdir(parents=True, exist_ok=True)
+
+ # Generate the largest size and downscale
+ master = draw_bug(1024)
+
+ images = []
+ for logical, scale, pixels in SIZES:
+ if pixels == 1024:
+ img = master
+ else:
+ img = master.resize((pixels, pixels), Image.LANCZOS)
+
+ filename = f"icon_{logical}x{logical}@x{scale}.png"
+ filepath = ICONSET / filename
+ img.save(filepath, "PNG")
+ images.append({
+ "size": f"{logical}x{logical}",
+ "scale": f"{scale}x",
+ "filename": filename,
+ })
+ print(f" {filename} ({pixels}×{pixels})")
+
+ # Update Contents.json
+ contents = {
+ "images": [],
+ "info": {"author": "xcode", "version": 1},
+ }
+ for img in images:
+ contents["images"].append({
+ "size": img["size"],
+ "idiom": "mac",
+ "filename": img["filename"],
+ "scale": img["scale"],
+ })
+
+ import json
+ contents_path = ICONSET / "Contents.json"
+ with open(contents_path, "w") as f:
+ json.dump(contents, f, indent=2)
+ print(f"\nWrote {contents_path}")
+ print(f"Generated {len(images)} icon sizes in {ICONSET}")
+
+
+if __name__ == "__main__":
+ main()