diff --git a/CLAUDE.md b/CLAUDE.md
index 4ebd1f9..7f43886 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -1,6 +1,26 @@
-# SP-404MK2 layers
+# 404 Layers
-A combo trainer for the Roland SP-404MK2, wrapped as a sideloaded Android APK.
+A combo trainer for the Roland SP-404MK2, sideloaded to an Android phone.
+
+**The name is set in five places** and `cap sync` regenerates one of them:
+`capacitor.config.json` (`appName`, which Capacitor writes into `strings.xml`),
+`android/.../values/strings.xml` (`app_name`, `title_activity_main`),
+`index.html` `
`, `package.json`, and this file. `reference/` keeps the old
+name on purpose — it is frozen.
+
+**`appId` must stay `dev.ncode.sp404layers`.** Changing it makes a different app
+as far as Android is concerned: a fresh install, and the drill progress on the
+phone orphaned.
+
+**The launcher icon is generated, not hand-drawn:** `npm run icon` runs
+`scripts/make-icon.mjs`, which draws a 3×3 pad grid with the centre pad lit using
+ImageMagick primitives — there is no SVG rasteriser on this machine. It writes
+the five adaptive foregrounds (art inside the inner 66%, because the launcher
+masks the rest), the five legacy square/round pairs, and previews to
+`design/icon/` since a PNG on disk is the only way to actually look at it. The
+convert calls pass `-strip`: without it the pixels are identical between runs but
+the bytes are not, so the committed PNGs churn and the script cannot be shown to
+be their source.
**Phase 1 (current):** the original single-file web app, wrapped in Capacitor as-is.
**Phase 2 (next):** port to React + TypeScript via Vite — data and pure logic with
diff --git a/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
deleted file mode 100644
index c7bd21d..0000000
--- a/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/android/app/src/main/res/drawable/ic_launcher_background.xml b/android/app/src/main/res/drawable/ic_launcher_background.xml
deleted file mode 100644
index d5fccc5..0000000
--- a/android/app/src/main/res/drawable/ic_launcher_background.xml
+++ /dev/null
@@ -1,170 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
index c023e50..31bdaa6 100644
Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
index 2127973..6384b4b 100644
Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
index b441f37..7c17eae 100644
Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
index 72905b8..6fbee68 100644
Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
index 8ed0605..4bb2b37 100644
Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
index 9502e47..b00a0f4 100644
Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
index 4d1e077..7e2e75d 100644
Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
index df0f158..4eff8f7 100644
Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
index 853db04..0bc4ef6 100644
Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
index 6cdf97c..7201136 100644
Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
index 2960cbb..32537d6 100644
Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
index 8e3093a..9c0dbb9 100644
Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
index 46de6e2..34f0d7e 100644
Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
index d2ea9ab..2830aa6 100644
Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
index a40d73e..2bec95d 100644
Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/android/app/src/main/res/values/ic_launcher_background.xml b/android/app/src/main/res/values/ic_launcher_background.xml
index c5d5899..fc35a53 100644
--- a/android/app/src/main/res/values/ic_launcher_background.xml
+++ b/android/app/src/main/res/values/ic_launcher_background.xml
@@ -1,4 +1,4 @@
- #FFFFFF
-
\ No newline at end of file
+ #e9e9e6
+
diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml
index 32173ea..e868774 100644
--- a/android/app/src/main/res/values/strings.xml
+++ b/android/app/src/main/res/values/strings.xml
@@ -1,7 +1,7 @@
- SP-404MK2 layers
- SP-404MK2 layers
+ 404 Layers
+ 404 Layers
dev.ncode.sp404layers
dev.ncode.sp404layers
diff --git a/capacitor.config.json b/capacitor.config.json
index 7735185..ab3dee3 100644
--- a/capacitor.config.json
+++ b/capacitor.config.json
@@ -1,6 +1,6 @@
{
"appId": "dev.ncode.sp404layers",
- "appName": "SP-404MK2 layers",
+ "appName": "404 Layers",
"webDir": "dist",
"android": {
"backgroundColor": "#151719",
diff --git a/design/icon/preview-as-launcher-shows-it.png b/design/icon/preview-as-launcher-shows-it.png
new file mode 100644
index 0000000..0b141d8
Binary files /dev/null and b/design/icon/preview-as-launcher-shows-it.png differ
diff --git a/design/icon/preview-round.png b/design/icon/preview-round.png
new file mode 100644
index 0000000..23668b3
Binary files /dev/null and b/design/icon/preview-round.png differ
diff --git a/design/icon/preview-square.png b/design/icon/preview-square.png
new file mode 100644
index 0000000..cc38281
Binary files /dev/null and b/design/icon/preview-square.png differ
diff --git a/index.html b/index.html
index cbac5ee..52a1f0e 100644
--- a/index.html
+++ b/index.html
@@ -14,7 +14,7 @@
-->
- SP-404MK2 layers
+ 404 Layers
diff --git a/package.json b/package.json
index 3ad17a0..a4f4fe9 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,7 @@
"version": "1.0.0",
"private": true,
"type": "module",
- "description": "SP-404MK2 combo trainer, wrapped as an Android app",
+ "description": "404 Layers — an SP-404MK2 combo trainer for Android",
"scripts": {
"sync:www": "bash scripts/sync-www.sh",
"sync": "npm run build && npx cap sync android",
@@ -20,6 +20,7 @@
"test:watch": "vitest",
"typecheck": "tsc --noEmit",
"extract:data": "node scripts/extract-reference-data.mjs",
+ "icon": "node scripts/make-icon.mjs",
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
diff --git a/scripts/make-icon.mjs b/scripts/make-icon.mjs
new file mode 100644
index 0000000..02b8759
--- /dev/null
+++ b/scripts/make-icon.mjs
@@ -0,0 +1,114 @@
+/**
+ * Draws the launcher icon: a 3x3 pad grid with the centre pad lit.
+ *
+ * Kept in the repo because the icon is otherwise only 15 binary PNGs with no
+ * record of how they were made — changing a colour or a gap would mean redrawing
+ * it from scratch. Re-run with: npm run icon
+ *
+ * There is no SVG rasteriser on this machine (no Inkscape, no rsvg-convert, and
+ * ImageMagick 6 here has no SVG delegate), so the artwork is ImageMagick
+ * primitives. The command is built as a real array: three attempts at this in
+ * bash collapsed the nine draw commands into one argument, which ImageMagick
+ * then read as a filename.
+ */
+import { execFileSync } from "node:child_process";
+import { mkdirSync, rmSync } from "node:fs";
+
+const BODY = "#e9e9e6";
+const PAD = "#1b1b1b";
+const ORANGE = "#ff4a00";
+const INK = "#111111";
+
+const RES = "android/app/src/main/res";
+
+/** Adaptive foregrounds: 108dp canvas, art confined to the inner 66%. */
+const FOREGROUND = [
+ ["mdpi", 108],
+ ["hdpi", 162],
+ ["xhdpi", 216],
+ ["xxhdpi", 324],
+ ["xxxhdpi", 432],
+];
+
+/** Legacy icons, for launchers older than adaptive support. */
+const LEGACY = [
+ ["mdpi", 48],
+ ["hdpi", 72],
+ ["xhdpi", 96],
+ ["xxhdpi", 144],
+ ["xxxhdpi", 192],
+];
+
+/**
+ * @param pct how much of the canvas the grid may use. 66 for adaptive
+ * foregrounds, because the launcher masks everything outside that; 82 for
+ * legacy icons, which are shown whole.
+ */
+function grid(size, out, pct, bg) {
+ const inner = Math.floor((size * pct) / 100);
+ const off = Math.floor((size - inner) / 2);
+ const cell = Math.floor((inner * 26) / 100); // 3 cells + 2 gaps of 11%
+ const gap = Math.floor((inner * 11) / 100);
+ const rad = Math.max(1, Math.floor((cell * 18) / 100)); // as on the pads
+ const stroke = Math.max(1, Math.round(size / 110));
+
+ const args = ["-size", `${size}x${size}`, `xc:${bg}`];
+ for (let row = 0; row < 3; row++) {
+ for (let col = 0; col < 3; col++) {
+ const x = off + col * (cell + gap);
+ const y = off + row * (cell + gap);
+ const lit = row === 1 && col === 1;
+ args.push(
+ "-fill", lit ? ORANGE : PAD,
+ "-stroke", INK,
+ "-strokewidth", String(stroke),
+ "-draw", `roundrectangle ${x},${y} ${x + cell},${y + cell} ${rad},${rad}`,
+ );
+ }
+ }
+ // -strip removes the tIME chunk. Without it the pixels are identical between
+ // runs but the bytes are not, so the committed PNGs churn on every re-run and
+ // this script cannot be shown to be their source.
+ args.push("-strip", out);
+ execFileSync("convert", args);
+}
+
+function circleMask(src, size, out) {
+ const half = Math.floor(size / 2);
+ execFileSync("convert", [
+ src,
+ "(", "-size", `${size}x${size}`, "xc:none",
+ "-fill", "white", "-draw", `circle ${half},${half} ${half},0`, ")",
+ "-alpha", "set", "-compose", "DstIn", "-composite", "-strip", out,
+ ]);
+}
+
+for (const [density, px] of FOREGROUND) {
+ grid(px, `${RES}/mipmap-${density}/ic_launcher_foreground.png`, 66, "none");
+}
+
+for (const [density, px] of LEGACY) {
+ const square = `${RES}/mipmap-${density}/ic_launcher.png`;
+ grid(px, square, 82, BODY);
+ circleMask(square, px, `${RES}/mipmap-${density}/ic_launcher_round.png`);
+}
+
+// Previews, since a PNG on disk is the only way to actually look at this.
+mkdirSync("design/icon", { recursive: true });
+grid(512, "design/icon/preview-square.png", 82, BODY);
+circleMask("design/icon/preview-square.png", 512, "design/icon/preview-round.png");
+
+grid(432, "design/icon/.fg.png", 66, "none");
+execFileSync("convert", [
+ "-size", "432x432", `xc:${BODY}`, "design/icon/.fg.png", "-composite",
+ "(", "-size", "432x432", "xc:none", "-fill", "white",
+ "-draw", "roundrectangle 0,0 431,431 108,108", ")",
+ "-alpha", "set", "-compose", "DstIn", "-composite",
+ "design/icon/preview-as-launcher-shows-it.png",
+]);
+rmSync("design/icon/.fg.png", { force: true });
+
+console.log(
+ `icon drawn: ${FOREGROUND.length} adaptive foregrounds, ` +
+ `${LEGACY.length} legacy pairs, 3 previews in design/icon/`,
+);