chore(branding): replace application logo with approved icon pack

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
xiaohan.yuanandClaude Code committed 2026-10-21 20:31:18 +08:00
1 parent ce8bc58d5d
commit e3eeb1cafe
67 files changed
+310 -111

No files matched your search

+20 -103
View File
@@ -1,115 +1,32 @@
/**
* 生成 YCode 应用图标资产。
*
* 输入:build/icon.svg(唯一矢量源)
* 输出:build/icon.ico、build/icon.png、resources/icon.png、build/icon-preview.png
*
* 图案是深红旗面 + 按国旗制式排布的五颗金星(一大四小,3:1),
* 星群坐标由 scripts/star-layout.mjs 算出,这里只负责栅格化与打包。
*
* 从已确认的图标包同步 YCode 应用图标,不重新绘制或缩放原图。
* 输入:resources/branding/(原图、各尺寸 PNG、Windows ICO 和预览)
* 用法:node scripts/gen-icon.mjs
*/
import { Resvg } from '@resvg/resvg-js'
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs'
import { copyFileSync, mkdirSync, readdirSync } from 'node:fs'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
const root = join(dirname(fileURLToPath(import.meta.url)), '..')
const buildDir = join(root, 'build')
const resourcesDir = join(root, 'resources')
const packDir = join(root, 'resources', 'branding')
const masterSvg = readFileSync(join(buildDir, 'icon.svg'), 'utf8')
const ICO_SIZES = [16, 24, 32, 48, 64, 128, 256]
function render(svg, size) {
return new Resvg(svg, { fitTo: { mode: 'width', value: size } }).render().asPng()
function copy(source, destination) {
const target = join(root, destination)
mkdirSync(dirname(target), { recursive: true })
copyFileSync(join(packDir, source), target)
}
function pick() {
return masterSvg
copy('windows/ycode.ico', 'build/icon.ico')
copy('png/ycode-1024.png', 'build/icon.png')
copy('png/ycode-512.png', 'resources/icon.png')
copy('png/ycode-32.png', 'resources/tray.png')
copy('Ycode-Icon-Preview.png', 'build/icon-preview.png')
copy('png/ycode-128.png', 'src/renderer/src/assets/ycode.png')
copy('web/favicon.ico', 'src/renderer/public/favicon.ico')
for (const filename of readdirSync(join(packDir, 'png'))) {
const match = /^ycode-(\d+)\.png$/.exec(filename)
if (match) copy(`png/${filename}`, `build/sizes/icon-${match[1]}.png`)
}
/** 组装 Windows ICO:每个尺寸内嵌一张 PNG(Vista 及以后支持)。 */
function buildIco(entries) {
const header = Buffer.alloc(6)
header.writeUInt16LE(0, 0)
header.writeUInt16LE(1, 2)
header.writeUInt16LE(entries.length, 4)
const dirEntries = []
let offset = 6 + 16 * entries.length
for (const { size, png } of entries) {
const entry = Buffer.alloc(16)
entry.writeUInt8(size >= 256 ? 0 : size, 0)
entry.writeUInt8(size >= 256 ? 0 : size, 1)
entry.writeUInt8(0, 2)
entry.writeUInt8(0, 3)
entry.writeUInt16LE(1, 4)
entry.writeUInt16LE(32, 6)
entry.writeUInt32LE(png.length, 8)
entry.writeUInt32LE(offset, 12)
offset += png.length
dirEntries.push(entry)
}
return Buffer.concat([header, ...dirEntries, ...entries.map((e) => e.png)])
}
mkdirSync(resourcesDir, { recursive: true })
const icoEntries = ICO_SIZES.map((size) => ({ size, png: render(pick(), size) }))
writeFileSync(join(buildDir, 'icon.ico'), buildIco(icoEntries))
writeFileSync(join(buildDir, 'icon.png'), render(masterSvg, 1024))
writeFileSync(join(resourcesDir, 'icon.png'), render(masterSvg, 512))
// 托盘:Windows 通知区域按 16/20/24/32 取用,给 32px 让系统自行缩放
writeFileSync(join(resourcesDir, 'tray.png'), render(masterSvg, 32))
// 备用图:常用尺寸各出一张 PNG,供托盘、商店页、文档等场景直接取用
const PNG_SIZES = [16, 20, 24, 32, 40, 48, 64, 96, 128, 180, 256, 512, 1024]
const sizesDir = join(buildDir, 'sizes')
mkdirSync(sizesDir, { recursive: true })
for (const size of PNG_SIZES) {
writeFileSync(join(sizesDir, `icon-${size}.png`), render(pick(), size))
}
// 对照表:浅色与深色背景下,各尺寸的真实观感
const sampleSizes = [128, 64, 48, 32, 16]
function tile(size) {
return render(pick(), size).toString('base64')
}
const rows = [
{ label: '浅色任务栏', bg: '#FFFFFF', fg: '#475467' },
{ label: '深色任务栏', bg: '#202124', fg: '#D0D5DD' }
]
let y = 78
const rowSvg = rows
.map((row) => {
let x = 150
const imgs = sampleSizes
.map((size) => {
const markup = `<image x="${x}" y="${y + (128 - size) / 2}" width="${size}" height="${size}" href="data:image/png;base64,${tile(size)}"/>`
x += size + 28
return markup
})
.join('')
const label = `<text x="40" y="${y + 68}" font-size="13" fill="${row.fg}" font-family="system-ui, 'Microsoft YaHei', sans-serif">${row.label}</text>`
const block = `<rect x="20" y="${y}" width="640" height="128" rx="10" fill="${row.bg}"/>${label}${imgs}`
y += 152
return block
})
.join('')
const previewSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 680 ${y + 10}" width="680" height="${y + 10}">
<rect width="680" height="${y + 10}" fill="#F7F8FA"/>
<text x="20" y="34" font-size="15" font-weight="500" fill="#1F2328" font-family="system-ui, 'Microsoft YaHei', sans-serif">YCode 图标:128 / 64 / 48 / 32 / 16 px 实际尺寸对照</text>
<text x="20" y="56" font-size="12" fill="#667085" font-family="system-ui, 'Microsoft YaHei', sans-serif">五颗星按国旗制式:一大四小(3:1),四颗小星各有一角尖对准大星中心;旗面为深红渐变+柔和光泽</text>
${rowSvg}
</svg>`
writeFileSync(join(buildDir, 'icon-preview.png'), new Resvg(previewSvg, { fitTo: { mode: 'width', value: 1360 } }).render().asPng())
console.log('icon.ico', icoEntries.length, 'sizes')
console.log('icon.png / resources/icon.png / icon-preview.png done')
console.log('build/sizes/icon-{', PNG_SIZES.join(','), '}.png done')
console.log('YCode 图标已同步:Windows ICO、窗口、托盘、界面、favicon 和各尺寸 PNG')