mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
Adding the app to a phone home screen produced a blank tile: index.html linked only an SVG favicon, and Safari ignores SVG for apple-touch-icon, substituting a screenshot of the page. There was no manifest at all, so Android had nothing to install against either. Generate the rasters from public/logo.svg with sharp, which the repo already carries. The output is committed so a plain `vite build` needs no image toolchain; re-run scripts/generate-icons.mjs after changing the logo. The logo's viewBox is wider than it is tall, so each icon centres the mark on a square #0a0a0f canvas rather than stretching it, inset to 78% normally and 54% for the maskable variant to survive Android's circular crop. The SVG favicon stays as the browser tab icon.
71 lines
2.5 KiB
JavaScript
71 lines
2.5 KiB
JavaScript
#!/usr/bin/env node
|
|
// Renders the home-screen and favicon rasters from `public/logo.svg`.
|
|
//
|
|
// Re-run after changing the logo: `node scripts/generate-icons.mjs`. The output
|
|
// is committed, because the install icons have to exist in a plain `vite build`
|
|
// and contributors should not need an image toolchain to ship the app.
|
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import sharp from 'sharp';
|
|
|
|
const REPO_ROOT = path.resolve(
|
|
fileURLToPath(new URL('.', import.meta.url)),
|
|
'..',
|
|
);
|
|
const LOGO = path.join(REPO_ROOT, 'public', 'logo.svg');
|
|
const OUT_DIR = path.join(REPO_ROOT, 'public', 'icons');
|
|
|
|
/** `--bg-dark` from src/ui/styles/foundation.css, so the tile matches the app. */
|
|
const BACKGROUND = '#0a0a0f';
|
|
|
|
/**
|
|
* Fraction of the canvas the mark is allowed to occupy.
|
|
*
|
|
* A maskable icon may be cropped to a circle inscribed in the middle 80% of the
|
|
* square, so its mark stays well inside that circle; the other icons are shown
|
|
* as drawn and only need optical breathing room.
|
|
*/
|
|
const INSET = { normal: 0.78, maskable: 0.54 };
|
|
|
|
const TARGETS = [
|
|
{ file: 'icon-32.png', size: 32, inset: INSET.normal },
|
|
{ file: 'icon-180.png', size: 180, inset: INSET.normal },
|
|
{ file: 'icon-192.png', size: 192, inset: INSET.normal },
|
|
{ file: 'icon-512.png', size: 512, inset: INSET.normal },
|
|
{ file: 'icon-512-maskable.png', size: 512, inset: INSET.maskable },
|
|
];
|
|
|
|
/** Fit the logo inside `size * inset` and centre it on an opaque square. */
|
|
async function render(svg, { size, inset }) {
|
|
const box = Math.round(size * inset);
|
|
// The logo's viewBox is wider than it is tall, so `contain` bounds it by
|
|
// width and leaves the vertical padding uneven — centring is done by the
|
|
// composite below rather than by the resize.
|
|
const mark = await sharp(svg, { density: 384 })
|
|
.resize(box, box, {
|
|
fit: 'contain',
|
|
background: { r: 0, g: 0, b: 0, alpha: 0 },
|
|
})
|
|
.png()
|
|
.toBuffer();
|
|
return sharp({
|
|
create: {
|
|
width: size,
|
|
height: size,
|
|
channels: 4,
|
|
background: BACKGROUND,
|
|
},
|
|
})
|
|
.composite([{ input: mark, gravity: 'centre' }])
|
|
.png({ compressionLevel: 9 })
|
|
.toBuffer();
|
|
}
|
|
|
|
const svg = await readFile(LOGO);
|
|
await mkdir(OUT_DIR, { recursive: true });
|
|
for (const target of TARGETS) {
|
|
await writeFile(path.join(OUT_DIR, target.file), await render(svg, target));
|
|
console.log(`icons: wrote ${target.file} (${target.size}px)`);
|
|
}
|