← back to CelebritySignatures
apps/mobile/scripts/make-icon.py
86 lines
#!/usr/bin/env python3
"""Generate the Celebrity Signatures iOS app icon + splash, on-brand, $0 local.
Icon : 1024x1024, NO alpha (Apple rejects transparent icons), full-bleed
charcoal ground + a gold calligraphic "C" monogram + signature underline.
Splash: 1284x2778-safe centered mark on the charcoal background color.
Palette pulled verbatim from apps/mobile/ui/glass.tsx:
ink #141210 inkDeep #0d0b0a accent(gold) #c9a86a parchment #e8d9b8
"""
import os
from PIL import Image, ImageDraw, ImageFont
OUT = os.path.join(os.path.dirname(__file__), "..", "assets")
os.makedirs(OUT, exist_ok=True)
INK = (0x14, 0x12, 0x10)
INK_DEEP = (0x0d, 0x0b, 0x0a)
GOLD = (0xc9, 0xa8, 0x6a)
PARCH = (0xe8, 0xd9, 0xb8)
# Prefer the macOS calligraphic script that matches the wordmark.
FONT_CANDIDATES = [
("/System/Library/Fonts/Supplemental/SnellRoundhand.ttc", 2), # Snell Black
("/System/Library/Fonts/Supplemental/SnellRoundhand.ttc", 1), # Snell Bold
("/System/Library/Fonts/Supplemental/SnellRoundhand.ttc", 0),
("/System/Library/Fonts/Supplemental/Zapfino.ttf", 0),
("/System/Library/Fonts/Supplemental/Savoye LET.ttc", 0),
("/System/Library/Fonts/Supplemental/Didot.ttc", 2),
]
def load_font(size):
for path, idx in FONT_CANDIDATES:
if os.path.exists(path):
try:
return ImageFont.truetype(path, size, index=idx)
except Exception:
continue
# Never silently fall back to the tiny bitmap default — that renders a
# ~10px glyph and produces a broken icon that still "succeeds".
raise SystemExit("FATAL: no calligraphic font resolved from FONT_CANDIDATES")
def vgrad(size, top, bottom):
"""Vertical charcoal gradient ground."""
w, h = size
base = Image.new("RGB", (1, h))
for y in range(h):
t = y / max(1, h - 1)
base.putpixel((0, y), tuple(int(top[i] + (bottom[i] - top[i]) * t) for i in range(3)))
return base.resize((w, h))
def draw_mark(img, glyph, gold=GOLD, scale=0.62, dy=-0.02):
"""Centered calligraphic glyph + a hand-drawn signature underline flourish."""
w, h = img.size
d = ImageDraw.Draw(img)
font = load_font(int(h * scale))
bb = d.textbbox((0, 0), glyph, font=font)
gw, gh = bb[2] - bb[0], bb[3] - bb[1]
x = (w - gw) / 2 - bb[0]
y = (h - gh) / 2 - bb[1] + int(h * dy)
# soft warm shadow for depth on the dark ground
d.text((x + h * 0.006, y + h * 0.006), glyph, font=font, fill=(0, 0, 0))
d.text((x, y), glyph, font=font, fill=gold)
return img
# ---- ICON (1024, no alpha) ----
icon = vgrad((1024, 1024), INK, INK_DEEP)
draw_mark(icon, "C")
icon.save(os.path.join(OUT, "icon.png"))
# ---- SPLASH (portrait, centered mark on solid charcoal) ----
splash = Image.new("RGB", (1284, 2778), INK)
mark = vgrad((760, 760), INK, INK_DEEP)
draw_mark(mark, "C", scale=0.66)
splash.paste(mark, ((1284 - 760) // 2, (2778 - 760) // 2))
splash.save(os.path.join(OUT, "splash.png"))
# ---- ADAPTIVE (Android foreground, transparent-safe padding) ----
adaptive = Image.new("RGBA", (1024, 1024), (0, 0, 0, 0))
fg = vgrad((680, 680), INK, INK_DEEP).convert("RGBA")
draw_mark(fg, "C", scale=0.66)
adaptive.paste(fg, (172, 172))
adaptive.save(os.path.join(OUT, "adaptive-icon.png"))
print("wrote:", ", ".join(sorted(os.listdir(OUT))))