diff --git a/packages/core/scripts/gen-favicons.mjs b/packages/core/scripts/gen-favicons.mjs new file mode 100644 index 0000000..6ef1118 --- /dev/null +++ b/packages/core/scripts/gen-favicons.mjs @@ -0,0 +1,70 @@ +/** + * # 脚本 gen-favicons.mjs + * 说明:以各站点 public/icon.svg(已有差异化品牌设计)为唯一设计源,生成: + * 1) sites//src/app/favicon.ico —— 16/32/48 三档 PNG-in-ICO(Next App Router 约定,自动注入 ) + * 2) sites//public/icon-192.png / icon-512.png —— 覆盖原五站同图的通用 PWA 图标(manifest.webmanifest 引用) + * 用法:bun packages/core/scripts/gen-favicons.mjs + */ +import { readFileSync, writeFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import sharp from "sharp"; + +const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "..", "sites"); +const SITES = ["ai-stack", "launchai", "the-ai-ledger", "devai-tools", "findai"]; +const ICO_SIZES = [16, 32, 48]; +const PNG_SIZES = [192, 512]; + +/** # 函数 renderSvg + * 说明:SVG 按目标尺寸矢量栅格化为 PNG(按 viewBox 64 推导 density 保证清晰度)。 + * 参数:svgBuffer 源 SVG;size 目标像素 + * 返回:PNG Buffer + */ +async function renderSvg(svgBuffer, size) { + const density = Math.round((72 * size) / 64); + return sharp(svgBuffer, { density }).resize(size, size).png().toBuffer(); +} + +/** # 函数 packIco + * 说明:把多档 PNG 打包为 ICO 容器(Vista+ 支持 PNG 压缩条目)。 + * 参数:pngs [{ size, buf }] + * 返回:ICO Buffer + */ +function packIco(pngs) { + const header = Buffer.alloc(6); + header.writeUInt16LE(0, 0); // 保留位 + header.writeUInt16LE(1, 2); // 类型:图标 + header.writeUInt16LE(pngs.length, 4); + const entries = Buffer.alloc(16 * pngs.length); + let offset = 6 + entries.length; + const blobs = []; + pngs.forEach((p, i) => { + const e = entries.subarray(i * 16, (i + 1) * 16); + e[0] = p.size >= 256 ? 0 : p.size; // 宽(0 表示 256) + e[1] = p.size >= 256 ? 0 : p.size; // 高 + e[2] = 0; // 调色板色数 + e[3] = 0; // 保留位 + e.writeUInt16LE(1, 4); // 色彩平面 + e.writeUInt16LE(32, 6); // 位深 + e.writeUInt32LE(p.buf.length, 8); + e.writeUInt32LE(offset, 12); + offset += p.buf.length; + blobs.push(p.buf); + }); + return Buffer.concat([header, entries, ...blobs]); +} + +for (const site of SITES) { + const svg = readFileSync(join(ROOT, site, "public", "icon.svg")); + const ico = packIco( + await Promise.all( + ICO_SIZES.map(async (size) => ({ size, buf: await renderSvg(svg, size) })) + ) + ); + writeFileSync(join(ROOT, site, "src", "app", "favicon.ico"), ico); + for (const size of PNG_SIZES) { + const buf = await renderSvg(svg, size); + writeFileSync(join(ROOT, site, "public", `icon-${size}.png`), buf); + } + console.log(`✓ ${site}: favicon.ico(16/32/48) + icon-192.png + icon-512.png`); +} diff --git a/sites/ai-stack/public/icon-192.png b/sites/ai-stack/public/icon-192.png index 7c35646..f89e8aa 100644 Binary files a/sites/ai-stack/public/icon-192.png and b/sites/ai-stack/public/icon-192.png differ diff --git a/sites/ai-stack/public/icon-512.png b/sites/ai-stack/public/icon-512.png index 9dcae12..541d968 100644 Binary files a/sites/ai-stack/public/icon-512.png and b/sites/ai-stack/public/icon-512.png differ diff --git a/sites/ai-stack/src/app/favicon.ico b/sites/ai-stack/src/app/favicon.ico new file mode 100644 index 0000000..b51777b Binary files /dev/null and b/sites/ai-stack/src/app/favicon.ico differ diff --git a/sites/devai-tools/public/icon-192.png b/sites/devai-tools/public/icon-192.png index 7c35646..3b4da01 100644 Binary files a/sites/devai-tools/public/icon-192.png and b/sites/devai-tools/public/icon-192.png differ diff --git a/sites/devai-tools/public/icon-512.png b/sites/devai-tools/public/icon-512.png index 9dcae12..8ae24db 100644 Binary files a/sites/devai-tools/public/icon-512.png and b/sites/devai-tools/public/icon-512.png differ diff --git a/sites/devai-tools/src/app/favicon.ico b/sites/devai-tools/src/app/favicon.ico new file mode 100644 index 0000000..3eb300c Binary files /dev/null and b/sites/devai-tools/src/app/favicon.ico differ diff --git a/sites/findai/public/icon-192.png b/sites/findai/public/icon-192.png index 7c35646..9f66e06 100644 Binary files a/sites/findai/public/icon-192.png and b/sites/findai/public/icon-192.png differ diff --git a/sites/findai/public/icon-512.png b/sites/findai/public/icon-512.png index 9dcae12..15932ae 100644 Binary files a/sites/findai/public/icon-512.png and b/sites/findai/public/icon-512.png differ diff --git a/sites/findai/src/app/favicon.ico b/sites/findai/src/app/favicon.ico new file mode 100644 index 0000000..af4db42 Binary files /dev/null and b/sites/findai/src/app/favicon.ico differ diff --git a/sites/launchai/public/icon-192.png b/sites/launchai/public/icon-192.png index 7c35646..ebb8d1f 100644 Binary files a/sites/launchai/public/icon-192.png and b/sites/launchai/public/icon-192.png differ diff --git a/sites/launchai/public/icon-512.png b/sites/launchai/public/icon-512.png index 9dcae12..fb14d89 100644 Binary files a/sites/launchai/public/icon-512.png and b/sites/launchai/public/icon-512.png differ diff --git a/sites/launchai/src/app/favicon.ico b/sites/launchai/src/app/favicon.ico new file mode 100644 index 0000000..bf9c7c8 Binary files /dev/null and b/sites/launchai/src/app/favicon.ico differ diff --git a/sites/the-ai-ledger/public/icon-192.png b/sites/the-ai-ledger/public/icon-192.png index 7c35646..0de9a57 100644 Binary files a/sites/the-ai-ledger/public/icon-192.png and b/sites/the-ai-ledger/public/icon-192.png differ diff --git a/sites/the-ai-ledger/public/icon-512.png b/sites/the-ai-ledger/public/icon-512.png index 9dcae12..36ee7f7 100644 Binary files a/sites/the-ai-ledger/public/icon-512.png and b/sites/the-ai-ledger/public/icon-512.png differ diff --git a/sites/the-ai-ledger/src/app/favicon.ico b/sites/the-ai-ledger/src/app/favicon.ico new file mode 100644 index 0000000..0fed9f7 Binary files /dev/null and b/sites/the-ai-ledger/src/app/favicon.ico differ