Compare commits
3
Commits
bb0800a51d
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d25533397 | ||
|
|
da076701d3 | ||
|
|
767f77b70c |
No files matched your search
@@ -0,0 +1,157 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Generate favicons for calls24 landing page."""
|
||||||
|
|
||||||
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
|
import os
|
||||||
|
|
||||||
|
COLOR_ACCENT = "#22c55e"
|
||||||
|
COLOR_WHITE = "#ffffff"
|
||||||
|
COLOR_DARK = "#0a0a0a"
|
||||||
|
COLOR_GREEN_LIGHT = "#86efac"
|
||||||
|
|
||||||
|
OUTPUT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
LOGOS_DIR = os.path.join(OUTPUT_DIR, "logos")
|
||||||
|
|
||||||
|
def create_gradient_bg(img, size, color1=COLOR_DARK, color2="#111111"):
|
||||||
|
"""Create dark gradient background."""
|
||||||
|
for y in range(size):
|
||||||
|
ratio = y / size
|
||||||
|
r = int(color1[1:3], 16) + int((int(color2[1:3], 16) - int(color1[1:3], 16)) * ratio)
|
||||||
|
g = int(color1[3:5], 16) + int((int(color2[3:5], 16) - int(color1[3:5], 16)) * ratio)
|
||||||
|
b = int(color1[5:7], 16) + int((int(color2[5:7], 16) - int(color1[5:7], 16)) * ratio)
|
||||||
|
draw = ImageDraw.Draw(img)
|
||||||
|
draw.line([(0, y), (size, y)], fill=f"#{r:02x}{g:02x}{b:02x}")
|
||||||
|
|
||||||
|
def draw_wave_icon(draw, cx, cy, size, color=COLOR_ACCENT, width=0.6):
|
||||||
|
"""Draw the signature wave icon (bar chart style)."""
|
||||||
|
bar_w = size // 9
|
||||||
|
gap = size // 12
|
||||||
|
max_h = int(size * width)
|
||||||
|
heights = [0.35, 0.65, 1.0, 0.65, 0.35] # Wave shape
|
||||||
|
|
||||||
|
total_w = len(heights) * bar_w + (len(heights) - 1) * gap
|
||||||
|
start_x = cx - total_w // 2
|
||||||
|
|
||||||
|
for i, h_ratio in enumerate(heights):
|
||||||
|
x = start_x + i * (bar_w + gap)
|
||||||
|
h = int(max_h * h_ratio)
|
||||||
|
y = cy - h // 2
|
||||||
|
|
||||||
|
# Semi-transparent bars
|
||||||
|
alpha = int(255 * (0.3 + 0.7 * h_ratio / 1.0))
|
||||||
|
rgb = tuple(int(COLOR_ACCENT[i:i+2], 16) for i in (1, 3, 5))
|
||||||
|
fill_color = f"#{rgb[0]:02x}{rgb[1]:02x}{rgb[2]:02x}{alpha:02x}"
|
||||||
|
draw.rectangle([x, y, x + bar_w - 1, y + h], fill=fill_color)
|
||||||
|
|
||||||
|
def draw_wave_with_dot(draw, cx, cy, size, color=COLOR_ACCENT):
|
||||||
|
"""Draw wave icon with accent dot (center bar top)."""
|
||||||
|
bar_w = size // 9
|
||||||
|
gap = size // 12
|
||||||
|
max_h = int(size * 0.6)
|
||||||
|
heights = [0.35, 0.65, 1.0, 0.65, 0.35]
|
||||||
|
|
||||||
|
total_w = len(heights) * bar_w + (len(heights) - 1) * gap
|
||||||
|
start_x = cx - total_w // 2
|
||||||
|
|
||||||
|
# Draw bars
|
||||||
|
for i, h_ratio in enumerate(heights):
|
||||||
|
x = start_x + i * (bar_w + gap)
|
||||||
|
h = int(max_h * h_ratio)
|
||||||
|
y = cy - h // 2
|
||||||
|
|
||||||
|
if i == 2: # Center bar - full color
|
||||||
|
draw.rectangle([x, y, x + bar_w - 1, y + h], fill=color)
|
||||||
|
else:
|
||||||
|
alpha = int(255 * (0.3 + 0.7 * h_ratio))
|
||||||
|
rgb = tuple(int(color[ii:ii+2], 16) for ii in (1, 3, 5))
|
||||||
|
draw.rectangle([x, y, x + bar_w - 1, y + h],
|
||||||
|
fill=f"#{rgb[0]:02x}{rgb[1]:02x}{rgb[2]:02x}{alpha:02x}")
|
||||||
|
|
||||||
|
# Dot at top of center bar
|
||||||
|
if i == 2:
|
||||||
|
dot_r = bar_w // 2
|
||||||
|
draw.ellipse([x + bar_w//2 - dot_r, y - dot_r,
|
||||||
|
x + bar_w//2 + dot_r, y + dot_r], fill=COLOR_GREEN_LIGHT)
|
||||||
|
|
||||||
|
def generate_favicon_ico(png_files):
|
||||||
|
"""Generate ICO file from PNG files using ImageMagick."""
|
||||||
|
png_paths = [os.path.join(LOGOS_DIR, f) for f in png_files]
|
||||||
|
ico_path = os.path.join(LOGOS_DIR, "favicon.ico")
|
||||||
|
|
||||||
|
# Use ImageMagick to create multi-size ICO
|
||||||
|
os.system(f"convert {' '.join(png_paths)} -define icon:auto-resize=16,32,48 {ico_path} 2>/dev/null")
|
||||||
|
|
||||||
|
if os.path.exists(ico_path):
|
||||||
|
print(f"✓ favicon.ico (from PNGs)")
|
||||||
|
else:
|
||||||
|
# Fallback: create from largest
|
||||||
|
os.system(f"convert {png_paths[-1]} -define icon:auto-resize=16,32,48 {ico_path} 2>/dev/null")
|
||||||
|
print("✓ favicon.ico (fallback)")
|
||||||
|
|
||||||
|
def generate_png(size, filename):
|
||||||
|
"""Generate PNG favicon."""
|
||||||
|
img = Image.new("RGBA", (size, size), (0, 0, 0, 0))
|
||||||
|
draw = ImageDraw.Draw(img)
|
||||||
|
|
||||||
|
cx, cy = size // 2, size // 2
|
||||||
|
|
||||||
|
# Dark circle background with rounded corners
|
||||||
|
padding = size // 6
|
||||||
|
draw.rounded_rectangle([padding, padding, size-padding, size-padding],
|
||||||
|
radius=size//4, fill=COLOR_DARK)
|
||||||
|
|
||||||
|
draw_wave_with_dot(draw, cx, cy, size - size//3)
|
||||||
|
|
||||||
|
img.save(os.path.join(LOGOS_DIR, filename))
|
||||||
|
print(f"✓ {filename}")
|
||||||
|
|
||||||
|
def generate_apple_touch_icon():
|
||||||
|
"""Generate Apple Touch Icon (180x180)."""
|
||||||
|
img = Image.new("RGBA", (180, 180), (0, 0, 0, 0))
|
||||||
|
draw = ImageDraw.Draw(img)
|
||||||
|
|
||||||
|
# Dark background
|
||||||
|
draw.rounded_rectangle([0, 0, 179, 179], radius=36, fill=COLOR_DARK)
|
||||||
|
|
||||||
|
cx, cy = 90, 90
|
||||||
|
draw_wave_with_dot(draw, cx, cy, 120)
|
||||||
|
|
||||||
|
img.save(os.path.join(LOGOS_DIR, "apple-touch-icon.png"))
|
||||||
|
print("✓ apple-touch-icon.png")
|
||||||
|
|
||||||
|
def generate_web_manifest():
|
||||||
|
"""Generate site.webmanifest."""
|
||||||
|
manifest = {
|
||||||
|
"name": "calls24 — Анализ звонков",
|
||||||
|
"short_name": "calls24",
|
||||||
|
"description": "Автоматический анализ звонков за 24 часа",
|
||||||
|
"icons": [
|
||||||
|
{"src": "/logos/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png"},
|
||||||
|
{"src": "/logos/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png"},
|
||||||
|
],
|
||||||
|
"theme_color": "#22c55e",
|
||||||
|
"background_color": "#0a0a0a",
|
||||||
|
"display": "standalone"
|
||||||
|
}
|
||||||
|
|
||||||
|
import json
|
||||||
|
with open(os.path.join(LOGOS_DIR, "site.webmanifest"), "w") as f:
|
||||||
|
json.dump(manifest, f, ensure_ascii=False, indent=2)
|
||||||
|
print("✓ site.webmanifest")
|
||||||
|
|
||||||
|
# Generate all favicons
|
||||||
|
print("🎨 Generating favicons for calls24...\n")
|
||||||
|
|
||||||
|
# Generate PNGs first
|
||||||
|
generate_png(16, "favicon-16x16.png")
|
||||||
|
generate_png(32, "favicon-32x32.png")
|
||||||
|
generate_png(192, "android-chrome-192x192.png")
|
||||||
|
generate_png(512, "android-chrome-512x512.png")
|
||||||
|
generate_apple_touch_icon()
|
||||||
|
generate_web_manifest()
|
||||||
|
|
||||||
|
# Create ICO from PNGs
|
||||||
|
print()
|
||||||
|
generate_favicon_ico(["favicon-16x16.png", "favicon-32x32.png", "android-chrome-192x192.png"])
|
||||||
|
|
||||||
|
print("\n✅ All favicons generated in logos/ folder")
|
||||||
+2421
-578
File diff suppressed because it is too large.
Load diff
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1007 B |
Binary file not shown.
|
After Width: | Height: | Size: 183 B |
Binary file not shown.
|
After Width: | Height: | Size: 272 B |
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "calls24 — Анализ звонков",
|
||||||
|
"short_name": "calls24",
|
||||||
|
"description": "Автоматический анализ звонков за 24 часа",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/logos/android-chrome-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/logos/android-chrome-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"theme_color": "#22c55e",
|
||||||
|
"background_color": "#0a0a0a",
|
||||||
|
"display": "standalone"
|
||||||
|
}
|
||||||
Reference in new issue
Block a user