Skip to content

Commit 372ec55

Browse files
committed
feat: update 8 files
1 parent 6cc60ef commit 372ec55

8 files changed

Lines changed: 162 additions & 906 deletions

File tree

build/icon-blue-original.png

65.9 KB
Loading

build/icon.icns

-160 KB
Binary file not shown.

build/icon.ico

341 KB
Binary file not shown.

build/icon.png

-39.2 KB
Loading

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@
105105
"react-zoom-pan-pinch": "^3.7.0",
106106
"remark-breaks": "^4.0.0",
107107
"remark-gfm": "^4.0.1",
108+
"sharp": "^0.34.5",
108109
"shiki": "^1.24.4",
109110
"simple-git": "^3.28.0",
110111
"sonner": "^1.7.1",

scripts/generate-icns-magick.mjs

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* Generate macOS .icns using ImageMagick + iconutil (no sharp dependency).
5+
* Applies Apple-style rounded squircle with proper padding.
6+
*/
7+
8+
import { execSync } from 'child_process';
9+
import { existsSync, mkdirSync, rmSync } from 'fs';
10+
import { join, dirname } from 'path';
11+
import { fileURLToPath } from 'url';
12+
13+
const __dirname = dirname(fileURLToPath(import.meta.url));
14+
const BUILD_DIR = join(__dirname, '../build');
15+
const INPUT = join(BUILD_DIR, 'icon.png');
16+
const ICONSET_DIR = join(BUILD_DIR, 'icon.iconset');
17+
const OUTPUT_ICNS = join(BUILD_DIR, 'icon.icns');
18+
19+
const ICON_SIZES = [
20+
{ size: 16, scale: 1 },
21+
{ size: 16, scale: 2 },
22+
{ size: 32, scale: 1 },
23+
{ size: 32, scale: 2 },
24+
{ size: 128, scale: 1 },
25+
{ size: 128, scale: 2 },
26+
{ size: 256, scale: 1 },
27+
{ size: 256, scale: 2 },
28+
{ size: 512, scale: 1 },
29+
{ size: 512, scale: 2 },
30+
];
31+
32+
function run(cmd) {
33+
execSync(cmd, { stdio: 'pipe' });
34+
}
35+
36+
function main() {
37+
console.log('Generating macOS .icns with ImageMagick...\n');
38+
39+
if (!existsSync(INPUT)) {
40+
console.error(`Error: ${INPUT} not found`);
41+
process.exit(1);
42+
}
43+
44+
// Clean & create iconset dir
45+
if (existsSync(ICONSET_DIR)) rmSync(ICONSET_DIR, { recursive: true });
46+
mkdirSync(ICONSET_DIR, { recursive: true });
47+
48+
// Step 1: Create a 1024x1024 rounded squircle version
49+
// Content area: 824x824 with 100px padding, ~22% corner radius
50+
const contentSize = 824;
51+
const canvasSize = 1024;
52+
const padding = 100;
53+
const cornerRadius = Math.round(contentSize * 0.22); // ~181px
54+
55+
const roundedSource = join(ICONSET_DIR, 'source-rounded.png');
56+
57+
// Create rounded rectangle mask, composite content, place on transparent canvas
58+
const maskCmd = [
59+
'magick',
60+
'-size', `${contentSize}x${contentSize}`,
61+
'xc:none',
62+
'-draw', `"roundrectangle 0,0,${contentSize - 1},${contentSize - 1},${cornerRadius},${cornerRadius}"`,
63+
`"${join(ICONSET_DIR, 'mask.png')}"`
64+
].join(' ');
65+
66+
const compositeCmd = [
67+
'magick',
68+
`"${INPUT}"`,
69+
'-resize', `${contentSize}x${contentSize}!`,
70+
`"${join(ICONSET_DIR, 'mask.png')}"`,
71+
'-compose', 'DstIn',
72+
'-composite',
73+
`"${join(ICONSET_DIR, 'content.png')}"`
74+
].join(' ');
75+
76+
const canvasCmd = [
77+
'magick',
78+
'-size', `${canvasSize}x${canvasSize}`,
79+
'xc:none',
80+
`"${join(ICONSET_DIR, 'content.png')}"`,
81+
'-geometry', `+${padding}+${padding}`,
82+
'-composite',
83+
`"${roundedSource}"`
84+
].join(' ');
85+
86+
console.log('1. Creating rounded squircle...');
87+
run(maskCmd);
88+
run(compositeCmd);
89+
run(canvasCmd);
90+
console.log(' Done\n');
91+
92+
// Step 2: Generate all icon sizes
93+
console.log('2. Generating icon sizes...');
94+
for (const { size, scale } of ICON_SIZES) {
95+
const actual = size * scale;
96+
const filename = scale === 1
97+
? `icon_${size}x${size}.png`
98+
: `icon_${size}x${size}@${scale}x.png`;
99+
const outPath = join(ICONSET_DIR, filename);
100+
101+
run(`magick "${roundedSource}" -resize ${actual}x${actual} "${outPath}"`);
102+
console.log(` ${filename} (${actual}x${actual})`);
103+
}
104+
105+
// Clean temp files
106+
for (const f of ['mask.png', 'content.png', 'source-rounded.png']) {
107+
const p = join(ICONSET_DIR, f);
108+
if (existsSync(p)) rmSync(p);
109+
}
110+
111+
// Step 3: Create .icns
112+
console.log('\n3. Creating .icns...');
113+
run(`iconutil -c icns "${ICONSET_DIR}" -o "${OUTPUT_ICNS}"`);
114+
rmSync(ICONSET_DIR, { recursive: true });
115+
116+
console.log(` Created ${OUTPUT_ICNS}\n`);
117+
console.log('Done! Icon ready for packaging.');
118+
}
119+
120+
main();

scripts/make-black-icon.mjs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* Convert the blue icon to a black version using ImageMagick.
5+
* - Blue tones → black/near-black
6+
* - White elements → stay white
7+
*/
8+
9+
import { execSync } from 'child_process';
10+
import { join, dirname } from 'path';
11+
import { fileURLToPath } from 'url';
12+
13+
const __dirname = dirname(fileURLToPath(import.meta.url));
14+
const BUILD_DIR = join(__dirname, '../build');
15+
const INPUT = join(BUILD_DIR, 'icon-blue-original.png');
16+
const OUTPUT = join(BUILD_DIR, 'icon.png');
17+
18+
// Use ImageMagick to:
19+
// 1. Desaturate (remove blue)
20+
// 2. Apply a sigmoidal contrast to push darks to black, keep whites white
21+
// 3. Level adjust to deepen blacks
22+
const cmd = [
23+
'magick',
24+
`"${INPUT}"`,
25+
'-modulate', '100,0,100', // Remove all saturation (grayscale)
26+
'-brightness-contrast', '-40x60', // Darken midtones, boost contrast
27+
'-level', '0%,70%', // Crush darks harder, keep highlights
28+
`"${OUTPUT}"`
29+
].join(' ');
30+
31+
console.log('Converting icon to black...');
32+
console.log(`Command: ${cmd}\n`);
33+
34+
try {
35+
execSync(cmd, { stdio: 'inherit' });
36+
console.log(`\nDone! Black icon saved to ${OUTPUT}`);
37+
} catch (err) {
38+
console.error('Error:', err.message);
39+
process.exit(1);
40+
}

0 commit comments

Comments
 (0)