diff --git a/client/scripts/generate-icons.mjs b/client/scripts/generate-icons.mjs index 71faed966..0c1b808e2 100644 --- a/client/scripts/generate-icons.mjs +++ b/client/scripts/generate-icons.mjs @@ -26,4 +26,25 @@ for (const { name, size } of sizes) { console.log(` \u2713 ${name} (${size}x${size})`); } +// Maskable variants: Android lays a maskable icon edge to edge under the round +// launcher mask, so the glyph has to sit inside the safe zone (a circle of 80% +// of the canvas). The master SVG draws the glyph nearly full bleed; shrink and +// recenter it, keeping the brand gradient as the full background. +const maskableSvg = svgBuffer + .toString() + .replace('translate(56,51) scale(0.267)', 'translate(81,81) scale(0.234)'); + +const maskableSizes = [ + { name: 'icon-maskable-192x192.png', size: 192 }, + { name: 'icon-maskable-512x512.png', size: 512 }, +]; + +for (const { name, size } of maskableSizes) { + await sharp(Buffer.from(maskableSvg), { density: 300 }) + .resize(size, size) + .png({ compressionLevel: 9 }) + .toFile(join(iconsDir, name)); + console.log(` \u2713 ${name} (${size}x${size}, maskable)`); +} + console.log('PWA icons generated.'); diff --git a/client/vite.config.js b/client/vite.config.js index a9db8a963..985d070d2 100644 --- a/client/vite.config.js +++ b/client/vite.config.js @@ -218,7 +218,10 @@ export default defineConfig(({ mode }) => ({ { src: 'icons/apple-touch-icon-180x180.png', sizes: '180x180', type: 'image/png' }, { src: 'icons/icon-192x192.png', sizes: '192x192', type: 'image/png' }, { src: 'icons/icon-512x512.png', sizes: '512x512', type: 'image/png' }, - { src: 'icons/icon-512x512.png', sizes: '512x512', type: 'image/png', purpose: 'maskable' }, + // Dedicated safe-zone renders: the full-bleed icon under a maskable + // purpose filled the whole Android launcher tile without any padding. + { src: 'icons/icon-maskable-192x192.png', sizes: '192x192', type: 'image/png', purpose: 'maskable' }, + { src: 'icons/icon-maskable-512x512.png', sizes: '512x512', type: 'image/png', purpose: 'maskable' }, { src: 'icons/icon.svg', sizes: 'any', type: 'image/svg+xml' }, ], },