fuzzyworld

nuts / files / scripts / generate-icons.ts

generate-icons.ts

2.4 KB · history · blame

1/**
2 * Generates all icon / favicon assets from acorn.png into public/.
3 * Run with: pnpm generate-icons
4 *
5 * Icon by Freepik – Flaticon
6 * https://www.flaticon.com/search?author_id=296&style_id=1001&type=standard
7 */
8
9import sharp from 'sharp'
10import { mkdir } from 'node:fs/promises'
11import { resolve, dirname } from 'node:path'
12import { fileURLToPath } from 'node:url'
13
14const __dirname = dirname(fileURLToPath(import.meta.url))
15const root = resolve(__dirname, '..')
16const src = resolve(root, 'acorn.png')
17const out = resolve(root, 'public')
18
19await mkdir(out, { recursive: true })
20
21// ── Size definitions ───────────────────────────────────────────────────────
22interface Color {
23 r: number
24 g: number
25 b: number
26 alpha: number
27}
28
29interface Task {
30 file: string
31 size: number
32 bg: Color
33 padFraction?: number
34}
35
36const DARK_BG: Color = { r: 17, g: 17, b: 17, alpha: 1 }
37const WHITE_BG: Color = { r: 255, g: 255, b: 255, alpha: 1 }
38
39const tasks: Task[] = [
40 // Standard favicons (white background – legible at tiny sizes on all UIs)
41 { file: 'favicon-16x16.png', size: 16, bg: WHITE_BG },
42 { file: 'favicon-32x32.png', size: 32, bg: WHITE_BG },
43 // Apple Touch Icon (dark background to match the app)
44 { file: 'apple-touch-icon.png', size: 180, bg: DARK_BG },
45 // PWA / Android icons
46 { file: 'icon-192.png', size: 192, bg: DARK_BG },
47 { file: 'icon-512.png', size: 512, bg: DARK_BG },
48 // OG / share image (larger, dark bg, the icon centred with padding)
49 { file: 'og-image.png', size: 512, bg: DARK_BG, padFraction: 0.15 },
50]
51
52for (const { file, size, bg, padFraction = 0 } of tasks) {
53 const iconSize = Math.round(size * (1 - padFraction * 2))
54 const dest = resolve(out, file)
55 const transparent = { r: 0, g: 0, b: 0, alpha: 0 } as const
56
57 let pipeline = sharp(src).resize(iconSize, iconSize, {
58 fit: 'contain',
59 background: transparent,
60 })
61
62 if (padFraction > 0) {
63 const pad = Math.round(size * padFraction)
64 pipeline = pipeline.extend({
65 top: pad,
66 bottom: pad,
67 left: pad,
68 right: pad,
69 background: transparent,
70 })
71 }
72
73 await pipeline.flatten({ background: bg }).png().toFile(dest)
74 console.log(` ✓ ${file} (${size}×${size})`)
75}
76
77console.log('\nAll icons written to public/')