mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
fix(branding): regenerate raster favicons — white mark was shipped without its gradient tile (#7390)
* fix(branding): regenerate raster favicons — white mark was shipped without its gradient tile favicon.ico, icon-512.png and apple-touch-icon.png contained only the white network mark on a transparent background: every opaque pixel was pure #FFFFFF, with no trace of the red gradient tile that favicon.svg and apple-touch-icon.svg define. On light browser tab strips and bookmark bars the icon therefore rendered as a blank white square. Regenerate all three assets from the canonical SVG sources: - favicon.ico: same 7 frames as before (16–256), each rasterized from the vector at native size, stored as PNG frames — 141 KB → 16 KB - icon-512.png: rendered from favicon.svg with the gradient tile - apple-touch-icon.png: rendered from apple-touch-icon.svg at the 180×180 size layout.tsx declares (the shipped file was 512×512) Add scripts/ad-hoc/generate-brand-icons.mjs so the raster assets can be reproduced from the SVGs (byte-identical) instead of drifting again. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(branding): keep standalone PNGs truecolor; palette-quantize only ICO frames Review feedback (gemini-code-assist): 8-bit palette quantization measurably clips the antialiased gradient on large assets — 1242 → 253 distinct colors at 512px. Make palette opt-in per render: ICO frames keep it (16 KB container), icon-512.png and apple-touch-icon.png are now truecolor (+9 KB total). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 6.1 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 138 KiB After Width: | Height: | Size: 16 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 19 KiB |
77
scripts/ad-hoc/generate-brand-icons.mjs
Normal file
77
scripts/ad-hoc/generate-brand-icons.mjs
Normal file
@@ -0,0 +1,77 @@
|
||||
// Regenerates the raster brand icons in public/ from their SVG sources, so the
|
||||
// PNG/ICO assets can never drift from the canonical vector artwork again.
|
||||
//
|
||||
// public/favicon.svg → public/favicon.ico (16/32/48/64/96/128/256, PNG frames)
|
||||
// public/favicon.svg → public/icon-512.png
|
||||
// public/apple-touch-icon.svg → public/apple-touch-icon.png (180×180, as declared in layout.tsx)
|
||||
//
|
||||
// Usage:
|
||||
// npm i --no-save sharp
|
||||
// node scripts/ad-hoc/generate-brand-icons.mjs
|
||||
|
||||
import { readFileSync, writeFileSync } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { dirname, join } from "node:path";
|
||||
|
||||
let sharp;
|
||||
try {
|
||||
sharp = (await import("sharp")).default;
|
||||
} catch {
|
||||
console.error("sharp is required: npm i --no-save sharp");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const publicDir = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "public");
|
||||
const faviconSvg = readFileSync(join(publicDir, "favicon.svg"));
|
||||
const appleSvg = readFileSync(join(publicDir, "apple-touch-icon.svg"));
|
||||
|
||||
// favicon.svg is authored on a 32×32 viewBox; scale density so each target
|
||||
// size is rendered from the vector instead of upscaling a small raster.
|
||||
// Palette quantization (8-bit, ≤256 colors) is only applied where asked:
|
||||
// it keeps the ICO frames tiny, but measurably clips the antialiased
|
||||
// gradient on large standalone PNGs (1242 → 253 distinct colors at 512px),
|
||||
// so those stay truecolor.
|
||||
const renderPng = (svg, viewBox, size, usePalette = false) =>
|
||||
sharp(svg, { density: 72 * (size / viewBox) })
|
||||
.resize(size, size)
|
||||
.png({ compressionLevel: 9, palette: usePalette, effort: 10 })
|
||||
.toBuffer();
|
||||
|
||||
// ICO container with PNG-compressed frames (identical layout to the icon the
|
||||
// package previously shipped, but ~16 KB instead of ~141 KB).
|
||||
function buildIco(frames) {
|
||||
const header = Buffer.alloc(6);
|
||||
header.writeUInt16LE(0, 0); // reserved
|
||||
header.writeUInt16LE(1, 2); // type: icon
|
||||
header.writeUInt16LE(frames.length, 4);
|
||||
|
||||
let offset = 6 + 16 * frames.length;
|
||||
const entries = frames.map(({ size, png }) => {
|
||||
const e = Buffer.alloc(16);
|
||||
e.writeUInt8(size === 256 ? 0 : size, 0); // width (0 means 256)
|
||||
e.writeUInt8(size === 256 ? 0 : size, 1); // height
|
||||
e.writeUInt16LE(1, 4); // color planes
|
||||
e.writeUInt16LE(32, 6); // bits per pixel
|
||||
e.writeUInt32LE(png.length, 8);
|
||||
e.writeUInt32LE(offset, 12);
|
||||
offset += png.length;
|
||||
return e;
|
||||
});
|
||||
|
||||
return Buffer.concat([header, ...entries, ...frames.map((f) => f.png)]);
|
||||
}
|
||||
|
||||
const icoSizes = [16, 32, 48, 64, 96, 128, 256];
|
||||
const frames = [];
|
||||
for (const size of icoSizes) {
|
||||
frames.push({ size, png: await renderPng(faviconSvg, 32, size, true) });
|
||||
}
|
||||
writeFileSync(join(publicDir, "favicon.ico"), buildIco(frames));
|
||||
|
||||
writeFileSync(join(publicDir, "icon-512.png"), await renderPng(faviconSvg, 32, 512));
|
||||
|
||||
// layout.tsx declares apple-touch-icon.png as 180×180; its SVG source is
|
||||
// authored on a 180×180 viewBox.
|
||||
writeFileSync(join(publicDir, "apple-touch-icon.png"), await renderPng(appleSvg, 180, 180));
|
||||
|
||||
console.log(`favicon.ico (${icoSizes.join("/")}), icon-512.png, apple-touch-icon.png regenerated`);
|
||||
Reference in New Issue
Block a user