feat(client): refresh brand to Heicode H logo + align macOS build script
- Replace logo source with new glassy gradient H (heicode-logo.jpg)
- Regen all icon sizes via scripts/rebuild-icons.py (PIL + ICO + ICNS):
32/64/128/128@2x/icon.png + Square{30,44,71,89,107,142,150,284,310}+StoreLogo
+ multi-res icon.ico + icon.icns
- Update public/app-icon.png to 1024×1024 derived
- Align cc-haha/desktop/scripts/build-macos-arm64.sh to Heicode branding:
APP_BUNDLE_NAME=HeiCode.app, APP_BUNDLE_ID=com.heicode.desktop,
DMG volume=Heicode, default DMG=Heicode_0.1.0_aarch64.dmg,
cleanup paths use heicode-desktop crate name (mirrors Cargo.toml rename)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Before Width: | Height: | Size: 343 KiB After Width: | Height: | Size: 414 KiB |
@@ -9,12 +9,14 @@ REPO_ROOT="$(cd "${DESKTOP_DIR}/.." && pwd)"
|
||||
TARGET_TRIPLE="aarch64-apple-darwin"
|
||||
TAURI_TARGET_DIR="${DESKTOP_DIR}/src-tauri/target"
|
||||
CANONICAL_OUTPUT_DIR="${DESKTOP_DIR}/build-artifacts/macos-arm64"
|
||||
APP_BUNDLE_NAME="Claude Code Haha.app"
|
||||
APP_BUNDLE_ID="com.claude-code-haha.desktop"
|
||||
APP_BUNDLE_NAME="HeiCode.app"
|
||||
APP_BUNDLE_ID="com.heicode.desktop"
|
||||
DMG_VOLUME_NAME="Heicode"
|
||||
DEFAULT_DMG_BASENAME="Heicode_0.1.0_aarch64.dmg"
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Build Claude Code Haha desktop for macOS Apple Silicon and output a DMG.
|
||||
Build Heicode desktop for macOS Apple Silicon and output a DMG.
|
||||
|
||||
Usage:
|
||||
./desktop/scripts/build-macos-arm64.sh [extra tauri build args...]
|
||||
@@ -79,10 +81,10 @@ rm -rf "${DESKTOP_DIR}/src-tauri/target/${TARGET_TRIPLE}/release/bundle"
|
||||
rm -rf "${DESKTOP_DIR}/src-tauri/target/release/bundle"
|
||||
rm -rf "${DESKTOP_DIR}/dist"
|
||||
rm -f "${DESKTOP_DIR}/tsconfig.tsbuildinfo"
|
||||
rm -rf "${DESKTOP_DIR}/src-tauri/target/${TARGET_TRIPLE}/release/build/claude-code-desktop-"*
|
||||
rm -rf "${DESKTOP_DIR}/src-tauri/target/${TARGET_TRIPLE}/release/.fingerprint/claude-code-desktop-"*
|
||||
rm -f "${DESKTOP_DIR}/src-tauri/target/${TARGET_TRIPLE}/release/deps/claude_code_desktop-"*
|
||||
rm -f "${DESKTOP_DIR}/src-tauri/target/${TARGET_TRIPLE}/release/deps/libclaude_code_desktop-"*
|
||||
rm -rf "${DESKTOP_DIR}/src-tauri/target/${TARGET_TRIPLE}/release/build/heicode-desktop-"*
|
||||
rm -rf "${DESKTOP_DIR}/src-tauri/target/${TARGET_TRIPLE}/release/.fingerprint/heicode-desktop-"*
|
||||
rm -f "${DESKTOP_DIR}/src-tauri/target/${TARGET_TRIPLE}/release/deps/heicode_desktop-"*
|
||||
rm -f "${DESKTOP_DIR}/src-tauri/target/${TARGET_TRIPLE}/release/deps/libheicode_desktop-"*
|
||||
|
||||
echo "[build-macos-arm64] Rebuilding frontend (tsc + vite)..."
|
||||
(cd "${DESKTOP_DIR}" && bun run build)
|
||||
@@ -159,15 +161,15 @@ build_canonical_dmg() {
|
||||
local staging_dir
|
||||
local rw_dmg
|
||||
|
||||
staging_dir="$(mktemp -d "${TMPDIR:-/tmp}/cc-haha-dmg.XXXXXX")"
|
||||
rw_dmg="$(mktemp "${TMPDIR:-/tmp}/cc-haha-rw.XXXXXX").dmg"
|
||||
staging_dir="$(mktemp -d "${TMPDIR:-/tmp}/heicode-dmg.XXXXXX")"
|
||||
rw_dmg="$(mktemp "${TMPDIR:-/tmp}/heicode-rw.XXXXXX").dmg"
|
||||
|
||||
cp -R "${app_bundle}" "${staging_dir}/"
|
||||
ln -s /Applications "${staging_dir}/Applications"
|
||||
|
||||
# Create a read-write DMG first so we can customize the Finder layout
|
||||
hdiutil create \
|
||||
-volname "Claude Code Haha" \
|
||||
-volname "${DMG_VOLUME_NAME}" \
|
||||
-srcfolder "${staging_dir}" \
|
||||
-ov \
|
||||
-format UDRW \
|
||||
@@ -188,7 +190,7 @@ build_canonical_dmg() {
|
||||
# 所以这里允许 osascript 非零退出,只 warn,不让 set -e 炸掉整个脚本。
|
||||
if ! osascript <<APPLESCRIPT
|
||||
tell application "Finder"
|
||||
tell disk "Claude Code Haha"
|
||||
tell disk "${DMG_VOLUME_NAME}"
|
||||
open
|
||||
set current view of container window to icon view
|
||||
set toolbar visible of container window to false
|
||||
@@ -274,7 +276,7 @@ if [[ -n "${LATEST_APP}" ]]; then
|
||||
rm -f "${CANONICAL_OUTPUT_DIR}/"*.dmg
|
||||
build_canonical_dmg \
|
||||
"${CANONICAL_OUTPUT_DIR}/${APP_BUNDLE_NAME}" \
|
||||
"${CANONICAL_OUTPUT_DIR}/$(basename "${LATEST_DMG:-Claude Code Haha_0.1.0_aarch64.dmg}")"
|
||||
"${CANONICAL_OUTPUT_DIR}/$(basename "${LATEST_DMG:-${DEFAULT_DMG_BASENAME}}")"
|
||||
fi
|
||||
|
||||
cat > "${CANONICAL_OUTPUT_DIR}/BUILD_INFO.txt" <<EOF
|
||||
|
||||
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 414 KiB |
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,83 @@
|
||||
"""Regenerate Tauri/desktop icons from heicode-logo.jpg."""
|
||||
from pathlib import Path
|
||||
from PIL import Image
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
SRC = ROOT / "heicode-logo.jpg"
|
||||
TAURI_ICONS = ROOT / "cc-haha" / "desktop" / "src-tauri" / "icons"
|
||||
PUBLIC = ROOT / "cc-haha" / "desktop" / "public"
|
||||
|
||||
def load_trimmed_square():
|
||||
img = Image.open(SRC).convert("RGB")
|
||||
# Find bbox of non-white pixels (treat >=250 RGB as white).
|
||||
gray = img.convert("L")
|
||||
mask = gray.point(lambda v: 0 if v >= 250 else 255)
|
||||
bbox = mask.getbbox()
|
||||
if bbox:
|
||||
img = img.crop(bbox)
|
||||
# Pad to square on white.
|
||||
w, h = img.size
|
||||
side = max(w, h)
|
||||
canvas = Image.new("RGB", (side, side), (255, 255, 255))
|
||||
canvas.paste(img, ((side - w) // 2, (side - h) // 2))
|
||||
return canvas
|
||||
|
||||
def to_rgba_with_transparent_white(img: Image.Image, threshold: int = 248) -> Image.Image:
|
||||
rgba = img.convert("RGBA")
|
||||
pixels = rgba.load()
|
||||
w, h = rgba.size
|
||||
for y in range(h):
|
||||
for x in range(w):
|
||||
r, g, b, _ = pixels[x, y]
|
||||
if r >= threshold and g >= threshold and b >= threshold:
|
||||
pixels[x, y] = (r, g, b, 0)
|
||||
return rgba
|
||||
|
||||
def main():
|
||||
base = load_trimmed_square()
|
||||
print(f"Trimmed source: {base.size}")
|
||||
|
||||
# PNG outputs (RGB on white → looks correct on any background, matches glass edges).
|
||||
sizes = {
|
||||
TAURI_ICONS / "32x32.png": 32,
|
||||
TAURI_ICONS / "64x64.png": 64,
|
||||
TAURI_ICONS / "128x128.png": 128,
|
||||
TAURI_ICONS / "128x128@2x.png": 256,
|
||||
TAURI_ICONS / "icon.png": 1024,
|
||||
PUBLIC / "app-icon.png": 1024,
|
||||
}
|
||||
for path, size in sizes.items():
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
base.resize((size, size), Image.LANCZOS).save(path, "PNG")
|
||||
print(f"wrote {path} ({size})")
|
||||
|
||||
# Windows Square*Logo / StoreLogo
|
||||
win_sizes = {
|
||||
"Square30x30Logo.png": 30,
|
||||
"Square44x44Logo.png": 44,
|
||||
"Square71x71Logo.png": 71,
|
||||
"Square89x89Logo.png": 89,
|
||||
"Square107x107Logo.png": 107,
|
||||
"Square142x142Logo.png": 142,
|
||||
"Square150x150Logo.png": 150,
|
||||
"Square284x284Logo.png": 284,
|
||||
"Square310x310Logo.png": 310,
|
||||
"StoreLogo.png": 50,
|
||||
}
|
||||
for name, size in win_sizes.items():
|
||||
path = TAURI_ICONS / name
|
||||
base.resize((size, size), Image.LANCZOS).save(path, "PNG")
|
||||
print(f"wrote {path} ({size})")
|
||||
|
||||
# ICO with multi-resolution.
|
||||
ico_path = TAURI_ICONS / "icon.ico"
|
||||
base.save(ico_path, format="ICO", sizes=[(16,16),(24,24),(32,32),(48,48),(64,64),(128,128),(256,256)])
|
||||
print(f"wrote {ico_path}")
|
||||
|
||||
# ICNS (PIL supports it). Required sizes: 16,32,64,128,256,512,1024.
|
||||
icns_path = TAURI_ICONS / "icon.icns"
|
||||
base.save(icns_path, format="ICNS", sizes=[(16,16),(32,32),(64,64),(128,128),(256,256),(512,512),(1024,1024)])
|
||||
print(f"wrote {icns_path}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||