Skip to content

Commit ce6b124

Browse files
committed
feat: add site branding assets and implement custom logo component
1 parent e1248d1 commit ce6b124

15 files changed

Lines changed: 153 additions & 11 deletions

generate_adaptive_assets.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import base64
2+
import io
3+
from PIL import Image, ImageDraw
4+
5+
# 1. Load source images
6+
logo_black = Image.open('public/ClientEcho_logo.png').convert('RGBA')
7+
logo_white = Image.open('public/ClientEcho_logo_white.png').convert('RGBA')
8+
w, h = logo_black.size
9+
10+
# 2. Build High-DPI Obsidian Squircle Icons (Optimized for Google Search 48x48 multiples, Chrome, & Apple Touch)
11+
def create_squircle_favicon(size, output_path):
12+
scale = 4
13+
hr_size = size * scale
14+
bg = Image.new('RGBA', (hr_size, hr_size), (0, 0, 0, 0))
15+
draw = ImageDraw.Draw(bg)
16+
radius = int(hr_size * 0.225)
17+
18+
# Modern dark obsidian tile (#111116)
19+
draw.rounded_rectangle([(0, 0), (hr_size - 1, hr_size - 1)], radius=radius, fill=(17, 17, 22, 255))
20+
21+
# White logo glyph with ~15% optical margin
22+
glyph_size = int(hr_size * 0.70)
23+
glyph = logo_white.resize((glyph_size, glyph_size), Image.Resampling.LANCZOS)
24+
offset = (hr_size - glyph_size) // 2
25+
bg.paste(glyph, (offset, offset), glyph)
26+
27+
final_img = bg.resize((size, size), Image.Resampling.LANCZOS)
28+
final_img.save(output_path, 'PNG', optimize=True)
29+
print(f'Created {output_path} ({size}x{size})')
30+
return final_img
31+
32+
# Google Search multiples of 48px + standard web sizes
33+
create_squircle_favicon(48, 'public/favicon-48x48.png')
34+
create_squircle_favicon(96, 'public/favicon-96x96.png')
35+
create_squircle_favicon(144, 'public/favicon-144x144.png')
36+
create_squircle_favicon(192, 'public/favicon-192x192.png')
37+
create_squircle_favicon(512, 'public/favicon-512x512.png')
38+
ico_source = create_squircle_favicon(180, 'public/apple-touch-icon.png')
39+
40+
# Generate multi-resolution .ico for Googlebot & standard browser root
41+
ico_source.save('public/favicon.ico', format='ICO', sizes=[(16, 16), (32, 32), (48, 48)])
42+
print('Created public/favicon.ico (16/32/48)')
43+
44+
# 3. Create Adaptive SVG favicon for modern browsers
45+
buf_black = io.BytesIO()
46+
logo_black.save(buf_black, format='PNG')
47+
b64_black = base64.b64encode(buf_black.getvalue()).decode('utf-8')
48+
49+
buf_white = io.BytesIO()
50+
logo_white.save(buf_white, format='PNG')
51+
b64_white = base64.b64encode(buf_white.getvalue()).decode('utf-8')
52+
53+
svg_content = f'''<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {w} {h}" width="{w}" height="{h}">
54+
<style>
55+
.icon-light {{ display: block; }}
56+
.icon-dark {{ display: none; }}
57+
@media (prefers-color-scheme: dark) {{
58+
.icon-light {{ display: none; }}
59+
.icon-dark {{ display: block; }}
60+
}}
61+
</style>
62+
<image class="icon-light" href="data:image/png;base64,{b64_black}" width="{w}" height="{h}" />
63+
<image class="icon-dark" href="data:image/png;base64,{b64_white}" width="{w}" height="{h}" />
64+
</svg>'''
65+
66+
with open('public/favicon.svg', 'w', encoding='utf-8') as f:
67+
f.write(svg_content)
68+
print('Created public/favicon.svg (Adaptive SVG)')

public/ClientEcho_logo.png

45.3 KB
Loading

public/ClientEcho_logo_white.png

26.4 KB
Loading

public/apple-touch-icon.png

16.5 KB
Loading

public/favicon-144x144.png

12.8 KB
Loading

public/favicon-192x192.png

17.7 KB
Loading

public/favicon-48x48.png

3.19 KB
Loading

public/favicon-512x512.png

56.2 KB
Loading

public/favicon-96x96.png

8.06 KB
Loading

public/favicon.ico

5.54 KB
Binary file not shown.

0 commit comments

Comments
 (0)