diff --git a/apps/desktop-client/build/icon.ico b/apps/desktop-client/build/icon.ico index 3628c79..1f1bec9 100644 Binary files a/apps/desktop-client/build/icon.ico and b/apps/desktop-client/build/icon.ico differ diff --git a/apps/desktop-client/build/icon.png b/apps/desktop-client/build/icon.png index 9315a39..dbb1868 100644 Binary files a/apps/desktop-client/build/icon.png and b/apps/desktop-client/build/icon.png differ diff --git a/apps/desktop-client/scripts/generate-brand-icons.cjs b/apps/desktop-client/scripts/generate-brand-icons.cjs index 792f9c8..9d1bd79 100644 --- a/apps/desktop-client/scripts/generate-brand-icons.cjs +++ b/apps/desktop-client/scripts/generate-brand-icons.cjs @@ -32,6 +32,12 @@ const OUT_TS = path.join(APP_DIR, 'src', 'main', 'brand-icons.ts') // Brand palette — keep in sync with the renderer (#1677ff is Ant Design blue). const BRAND_BLUE = '#1677ff' const DANGER_RED = '#ff4d4f' +const MAC_APP_ICON_PLATE = 824 +const MAC_APP_ICON_LOGO_RATIO = 0.62 +const APP_PNG_PLATE = 896 +const APP_PNG_LOGO_RATIO = 0.72 +const WINDOWS_ICO_PLATE = 1024 +const WINDOWS_ICO_LOGO_RATIO = 1 const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'brand-icons-')) const cleanup = () => fs.rmSync(tmpDir, { recursive: true, force: true }) @@ -212,19 +218,36 @@ function forceLogoColor(source, fill) { } /** A square app icon: logo centered on a rounded-rect (squircle-ish) bg. */ -function squareIconSvg(bg, logoSource = logo, boundsSource = logoBounds) { +function squareIconSvg(bg, logoSource = logo, boundsSource = logoBounds, options = {}) { const CANVAS = 1024 - // macOS icon grid: 824x824 art area inside a 1024 canvas (100px margin). - const PLATE = 824 + const PLATE = options.plate ?? MAC_APP_ICON_PLATE + const LOGO_RATIO = options.logoRatio ?? MAC_APP_ICON_LOGO_RATIO + const radius = options.radius ?? Math.round(PLATE * 0.225) const MARGIN = (CANVAS - PLATE) / 2 - const target = PLATE * 0.62 + const target = PLATE * LOGO_RATIO const { scale, tx, ty } = fitLogo(boundsSource, target, target, MARGIN + PLATE / 2, MARGIN + PLATE / 2) return ` - + ${logoSource.inner} ` } +function windowsIcoIconSvg(bg, logoSource = windowsTrayLogo, boundsSource = windowsTrayLogoBounds) { + // Windows .ico taskbar layers are tiny in practice; this denser composition + // keeps the foreground mark legible at 24-32px without changing icon.png. + return squareIconSvg(bg, logoSource, boundsSource, { + plate: WINDOWS_ICO_PLATE, + logoRatio: WINDOWS_ICO_LOGO_RATIO, + }) +} + +function appPngIconSvg(bg, logoSource = windowsTrayLogo, boundsSource = windowsTrayLogoBounds) { + return squareIconSvg(bg, logoSource, boundsSource, { + plate: APP_PNG_PLATE, + logoRatio: APP_PNG_LOGO_RATIO, + }) +} + /** * Tray glyph: the logo centered (with padding) inside a 32px square canvas. * tray.ts loads it with scaleFactor 2, so it shows at ~16pt on retina menu @@ -284,12 +307,19 @@ function pngsToIco(pngBuffers) { } function buildAppIcons() { - // 1024 master for the normal app icon. - const masterPng = svgToPng(squareIconSvg('#ffffff', windowsTrayLogo, windowsTrayLogoBounds), 'app-normal', ['-w', '1024', '-h', '1024']) fs.mkdirSync(BUILD_DIR, { recursive: true }) - fs.copyFileSync(masterPng, path.join(BUILD_DIR, 'icon.png')) + + // 1024 master for the normal app icon. + const appPng = svgToPng(appPngIconSvg('#ffffff'), 'app-normal', ['-w', '1024', '-h', '1024']) + fs.copyFileSync(appPng, path.join(BUILD_DIR, 'icon.png')) // .icns via an .iconset downscaled with sips. + const macMasterPng = svgToPng(squareIconSvg('#ffffff', windowsTrayLogo, windowsTrayLogoBounds), 'app-normal-mac', [ + '-w', + '1024', + '-h', + '1024', + ]) const iconset = path.join(tmpDir, 'icon.iconset') fs.mkdirSync(iconset, { recursive: true }) const variants = [ @@ -306,7 +336,7 @@ function buildAppIcons() { ] for (const [size, file] of variants) { const dest = path.join(iconset, file) - execFileSync('sips', ['-z', String(size), String(size), masterPng, '--out', dest], { + execFileSync('sips', ['-z', String(size), String(size), macMasterPng, '--out', dest], { stdio: 'ignore', }) } @@ -315,7 +345,7 @@ function buildAppIcons() { // .ico with every size Windows asks for (taskbar/Explorer/Alt-Tab/tiles). const icoSizes = [16, 24, 32, 48, 64, 128, 256] const icoPngs = icoSizes.map((s) => - fs.readFileSync(svgToPng(squareIconSvg('#ffffff', windowsTrayLogo, windowsTrayLogoBounds), `app-ico-${s}`, ['-w', String(s), '-h', String(s)])), + fs.readFileSync(svgToPng(windowsIcoIconSvg('#ffffff'), `app-ico-${s}`, ['-w', String(s), '-h', String(s)])), ) fs.writeFileSync(path.join(BUILD_DIR, 'icon.ico'), pngsToIco(icoPngs)) } @@ -356,7 +386,7 @@ function buildMainProcessIcons() { '// Tray icon shown when accounts have health issues (rendered @2x, not a template).', `export const TRAY_DANGER_ICON_BASE64 =\n '${trayDanger}'`, '', - '// Dock / window icon — logo on the brand-blue plate.', + '// Dock / window icon — logo on the white plate, tuned for Windows taskbar visibility.', `export const APP_ICON_NORMAL_BASE64 =\n '${appNormal}'`, '', '// Dock / window icon — logo on a danger plate when issues exist.',