Skip to content

Commit f814eba

Browse files
committed
feat: generate favicon.ico from logo using png-to-ico
- Add png-to-ico dependency for ICO generation - Update generate-icons.ts to create favicon.ico with 16/32/48px sizes - Update index.html to use /favicon.ico - Add favicon.ico to .gitignore (generated file)
1 parent 822599f commit f814eba

File tree

5 files changed

+24
-2
lines changed

5 files changed

+24
-2
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
public/fonts/
77
# Generated icons (from scripts/generate-icons.ts)
88
public/icon-*.png
9+
public/favicon.ico
910

1011
# OSX
1112
.DS_Store

bun.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107
"mermaid": "^11.12.0",
108108
"nodemon": "^3.1.10",
109109
"playwright": "^1.56.0",
110+
"png-to-ico": "^3.0.1",
110111
"postcss": "^8.5.6",
111112
"posthog-js": "^1.276.0",
112113
"prettier": "^3.6.2",
@@ -2761,6 +2762,10 @@
27612762

27622763
"plist": ["plist@3.1.0", "", { "dependencies": { "@xmldom/xmldom": "^0.8.8", "base64-js": "^1.5.1", "xmlbuilder": "^15.1.1" } }, "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ=="],
27632764

2765+
"png-to-ico": ["png-to-ico@3.0.1", "", { "dependencies": { "@types/node": "^22.10.3", "minimist": "^1.2.8", "pngjs": "^7.0.0" }, "bin": { "png-to-ico": "bin/cli.js" } }, "sha512-S8BOAoaGd9gT5uaemQ62arIY3Jzco7Uc7LwUTqRyqJDTsKqOAiyfyN4dSdT0D+Zf8XvgztgpRbM5wnQd7EgYwg=="],
2766+
2767+
"pngjs": ["pngjs@7.0.0", "", {}, "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow=="],
2768+
27642769
"points-on-curve": ["points-on-curve@0.2.0", "", {}, "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A=="],
27652770

27662771
"points-on-path": ["points-on-path@0.2.1", "", { "dependencies": { "path-data-parser": "0.1.0", "points-on-curve": "0.2.0" } }, "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g=="],

index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<meta name="description" content="Parallel agentic development with Electron + React" />
1010
<meta name="theme-color" content="#1e1e1e" />
1111
<link rel="manifest" href="/manifest.json" />
12-
<link rel="icon" type="image/png" href="/icon-192.png" />
12+
<link rel="icon" href="/favicon.ico" />
1313
<link rel="apple-touch-icon" href="/icon-192.png" />
1414
<title>mux - coder multiplexer</title>
1515
<style>

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@
148148
"mermaid": "^11.12.0",
149149
"nodemon": "^3.1.10",
150150
"playwright": "^1.56.0",
151+
"png-to-ico": "^3.0.1",
151152
"postcss": "^8.5.6",
152153
"posthog-js": "^1.276.0",
153154
"prettier": "^3.6.2",

scripts/generate-icons.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
#!/usr/bin/env bun
2-
import { mkdir, rm } from "node:fs/promises";
2+
import { mkdir, rm, writeFile } from "node:fs/promises";
33
import path from "node:path";
44
import { fileURLToPath } from "node:url";
5+
import pngToIco from "png-to-ico";
56
import sharp from "sharp";
67

78
const SIZES = [16, 32, 64, 128, 256, 512];
89
const PWA_SIZES = [192, 512];
10+
const FAVICON_SIZES = [16, 32, 48];
911

1012
const __filename = fileURLToPath(import.meta.url);
1113
const __dirname = path.dirname(__filename);
@@ -16,6 +18,7 @@ const PUBLIC_DIR = path.join(ROOT, "public");
1618
const ICONSET_DIR = path.join(BUILD_DIR, "icon.iconset");
1719
const PNG_OUTPUT = path.join(BUILD_DIR, "icon.png");
1820
const ICNS_OUTPUT = path.join(BUILD_DIR, "icon.icns");
21+
const FAVICON_OUTPUT = path.join(PUBLIC_DIR, "favicon.ico");
1922

2023
const args = new Set(process.argv.slice(2));
2124
if (args.size === 0) {
@@ -38,6 +41,17 @@ async function generatePwaIcons() {
3841
await Promise.all(tasks);
3942
}
4043

44+
async function generateFavicon() {
45+
// Generate PNGs at multiple sizes for the ICO
46+
const pngBuffers = await Promise.all(
47+
FAVICON_SIZES.map((size) =>
48+
sharp(SOURCE).resize(size, size, { fit: "cover" }).png().toBuffer()
49+
)
50+
);
51+
const icoBuffer = await pngToIco(pngBuffers);
52+
await writeFile(FAVICON_OUTPUT, icoBuffer);
53+
}
54+
4155
async function generateIconsetPngs() {
4256
await mkdir(ICONSET_DIR, { recursive: true });
4357

@@ -91,6 +105,7 @@ await mkdir(BUILD_DIR, { recursive: true });
91105
if (needsPng) {
92106
await generateIconPng();
93107
await generatePwaIcons();
108+
await generateFavicon();
94109
}
95110

96111
if (needsIcns) {

0 commit comments

Comments
 (0)