fix(pwa): use alpha mask for monochrome icon
Per W3C spec, purpose: "monochrome" icons use only the alpha channel as the silhouette; RGB is ignored and replaced with the platform theme color. The previous monochrome-icon.png was a black-on-white RGB image with no alpha, so Safari (macOS PWAs) and Chrome (Android themed icons) treated every pixel as opaque and painted the whole 1024x1024 canvas with theme_color (#ffa4c0) - a solid pink tile. Regenerate as RGBA with the silhouette in alpha (derived from the favicon's alpha channel via a sharp-based helper script). Rename to monochromeicon.png to bust Safari's stuck PWA icon cache from prior broken installs.
This commit is contained in:
40
_scripts/build-monochrome-icon.ts
Normal file
40
_scripts/build-monochrome-icon.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import path from "path"
|
||||
import sharp from "sharp"
|
||||
|
||||
// PWA spec: `purpose: "monochrome"` icons are *masks*. The user agent ignores
|
||||
// RGB and uses only the alpha channel as the silhouette, then paints it with
|
||||
// the platform theme color. So the source PNG must be RGBA with the silhouette
|
||||
// in alpha, NOT a black-on-white RGB image.
|
||||
|
||||
const SRC = path.resolve(__dirname, "../public/favicon.png")
|
||||
const OUT = path.resolve(__dirname, "../public/monochromeicon.png")
|
||||
const SIZE = 1024
|
||||
|
||||
async function main() {
|
||||
const { data, info } = await sharp(SRC)
|
||||
.resize(SIZE, SIZE, { fit: "contain", background: { r: 0, g: 0, b: 0, alpha: 0 } })
|
||||
.ensureAlpha()
|
||||
.raw()
|
||||
.toBuffer({ resolveWithObject: true })
|
||||
|
||||
if (info.channels !== 4) throw new Error(`expected RGBA, got ${info.channels} channels`)
|
||||
|
||||
const out = Buffer.alloc(data.length)
|
||||
for (let i = 0; i < data.length; i += 4) {
|
||||
out[i] = 0
|
||||
out[i + 1] = 0
|
||||
out[i + 2] = 0
|
||||
out[i + 3] = data[i + 3]
|
||||
}
|
||||
|
||||
await sharp(out, { raw: { width: SIZE, height: SIZE, channels: 4 } })
|
||||
.png({ compressionLevel: 9 })
|
||||
.toFile(OUT)
|
||||
|
||||
console.log(`Wrote ${OUT} (${SIZE}x${SIZE} RGBA)`)
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
console.error(e)
|
||||
process.exit(1)
|
||||
})
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 181 KiB |
BIN
public/monochromeicon.png
Normal file
BIN
public/monochromeicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 101 KiB |
@@ -22,7 +22,7 @@ export default defineConfig(({ command }) => {
|
||||
"pwa-512x512.png",
|
||||
"masked-icon.png",
|
||||
"maskable-icon-512x512.png",
|
||||
"monochrome-icon.png",
|
||||
"monochromeicon.png",
|
||||
"assets/*.svg"
|
||||
],
|
||||
manifest: {
|
||||
@@ -54,7 +54,7 @@ export default defineConfig(({ command }) => {
|
||||
purpose: "maskable"
|
||||
},
|
||||
{
|
||||
src: "monochrome-icon.png",
|
||||
src: "monochromeicon.png",
|
||||
sizes: "1024x1024",
|
||||
type: "image/png",
|
||||
purpose: "monochrome"
|
||||
|
||||
Reference in New Issue
Block a user