1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
|
import { deflateSync } from "zlib";
import { writeFileSync, mkdirSync } from "fs";
const CRC_TABLE = (() => {
const t = new Uint32Array(256);
for (let n = 0; n < 256; n++) {
let c = n;
for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
t[n] = c;
}
return t;
})();
function crc32(buf) {
let crc = 0xffffffff;
for (let i = 0; i < buf.length; i++) crc = CRC_TABLE[(crc ^ buf[i]) & 0xff] ^ (crc >>> 8);
return (crc ^ 0xffffffff) >>> 0;
}
function chunk(type, data) {
const typeBytes = Buffer.from(type, "ascii");
const crcVal = crc32(Buffer.concat([typeBytes, data]));
const out = Buffer.allocUnsafe(4 + 4 + data.length + 4);
out.writeUInt32BE(data.length, 0);
typeBytes.copy(out, 4);
data.copy(out, 8);
out.writeUInt32BE(crcVal, 8 + data.length);
return out;
}
// Purple #6c63ff = rgb(108, 99, 255)
// With a white checkmark drawn on it
function createIcon(size) {
const sig = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
const ihdr = Buffer.allocUnsafe(13);
ihdr.writeUInt32BE(size, 0);
ihdr.writeUInt32BE(size, 4);
ihdr[8] = 8; // bit depth
ihdr[9] = 2; // color type RGB
ihdr[10] = ihdr[11] = ihdr[12] = 0;
// Draw pixels: purple bg with white checkmark
const pixels = Buffer.alloc(size * size * 3);
const scale = size / 64;
// Fill purple
for (let i = 0; i < size * size; i++) {
pixels[i * 3] = 108;
pixels[i * 3 + 1] = 99;
pixels[i * 3 + 2] = 255;
}
// Draw white checkmark: ✓
// Checkmark path: (14,34)→(26,46)→(50,18) scaled to icon size
function setPixel(x, y) {
const xi = Math.round(x * scale);
const yi = Math.round(y * scale);
const thick = Math.max(2, Math.round(4 * scale));
for (let dy = -thick; dy <= thick; dy++) {
for (let dx = -thick; dx <= thick; dx++) {
const px = xi + dx;
const py = yi + dy;
if (px >= 0 && px < size && py >= 0 && py < size) {
pixels[(py * size + px) * 3] = 255;
pixels[(py * size + px) * 3 + 1] = 255;
pixels[(py * size + px) * 3 + 2] = 255;
}
}
}
}
// Left leg: (14,34) → (26,46)
const steps = 40;
for (let i = 0; i <= steps; i++) {
setPixel(14 + (26 - 14) * (i / steps), 34 + (46 - 34) * (i / steps));
}
// Right leg: (26,46) → (50,18)
for (let i = 0; i <= steps; i++) {
setPixel(26 + (50 - 26) * (i / steps), 46 + (18 - 46) * (i / steps));
}
// Build scanlines with filter=0 prefix
const raw = Buffer.allocUnsafe(size * (1 + size * 3));
for (let y = 0; y < size; y++) {
raw[y * (1 + size * 3)] = 0;
pixels.copy(raw, y * (1 + size * 3) + 1, y * size * 3, (y + 1) * size * 3);
}
const compressed = deflateSync(raw);
return Buffer.concat([sig, chunk("IHDR", ihdr), chunk("IDAT", compressed), chunk("IEND", Buffer.alloc(0))]);
}
mkdirSync("public/icons", { recursive: true });
writeFileSync("public/icons/icon-192.png", createIcon(192));
writeFileSync("public/icons/icon-512.png", createIcon(512));
console.log("Icons generated: public/icons/icon-{192,512}.png");
|