// Generates the PWA and browser icons (Step 11.1) from the Courier sprite on the // app's space background. Placeholder art until the user supplies a logo. // // node scripts/icons.mjs writes public/icons/*.png import { mkdirSync, readFileSync } from "node:fs"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import sharp from "sharp"; const webRoot = join(dirname(fileURLToPath(import.meta.url)), ".."); const outDir = join(webRoot, "public/icons"); /** --color-space-950 in src/index.css: the theme and background color. */ export const BACKGROUND = "#050919"; /** * Each icon: size in px and how much of it the ship fills. Maskable icons keep * the ship inside the central 80 % safe zone, so they fill less. */ export const ICONS = [ { file: "icon-192.png", size: 192, fill: 0.78 }, { file: "icon-512.png", size: 512, fill: 0.78 }, { file: "maskable-512.png", size: 512, fill: 0.56 }, { file: "apple-touch-icon.png", size: 180, fill: 0.7 }, { file: "favicon-32.png", size: 32, fill: 0.9 }, ]; sharp.cache(false); mkdirSync(outDir, { recursive: true }); const source = readFileSync(join(webRoot, "public/assets/map/ship-courier.png")); const trimmed = await sharp(source).trim().toBuffer(); for (const { file, size, fill } of ICONS) { const box = Math.round(size * fill); const ship = await sharp(trimmed) .resize(box, box, { fit: "inside" }) .toBuffer({ resolveWithObject: true }); await sharp({ create: { width: size, height: size, channels: 4, background: BACKGROUND }, }) .composite([ { input: ship.data, left: Math.round((size - ship.info.width) / 2), top: Math.round((size - ship.info.height) / 2), }, ]) .png({ compressionLevel: 9 }) .toFile(join(outDir, file)); } console.log(`icons: wrote ${ICONS.length} files to ${outDir}`);