release nouri 0.6.0 polish backup and pwa

This commit is contained in:
2026-04-12 17:46:18 +02:00
parent 9ff7a6d57c
commit 555fddab80
31 changed files with 1257 additions and 164 deletions
+106
View File
@@ -0,0 +1,106 @@
from __future__ import annotations
from pathlib import Path
from PIL import Image, ImageDraw
ROOT = Path(__file__).resolve().parents[1]
BRAND_DIR = ROOT / "nouri" / "static" / "brand"
BG_TOP = "#F6C394"
BG_BOTTOM = "#DE9862"
BOWL = "#FFF7EF"
STROKE = "#8C533B"
def rounded_gradient_icon(size: int, *, maskable: bool = False) -> Image.Image:
image = Image.new("RGBA", (size, size), (0, 0, 0, 0))
draw = ImageDraw.Draw(image)
radius = int(size * (0.28 if maskable else 0.24))
for y in range(size):
blend = y / max(size - 1, 1)
r1, g1, b1 = ImageColorTuple(BG_TOP)
r2, g2, b2 = ImageColorTuple(BG_BOTTOM)
color = (
int(r1 + (r2 - r1) * blend),
int(g1 + (g2 - g1) * blend),
int(b1 + (b2 - b1) * blend),
255,
)
draw.line((0, y, size, y), fill=color)
mask = Image.new("L", (size, size), 0)
ImageDraw.Draw(mask).rounded_rectangle((0, 0, size - 1, size - 1), radius=radius, fill=255)
image.putalpha(mask)
inset = int(size * (0.11 if maskable else 0.13))
inner = [inset, inset, size - inset, size - inset]
draw.rounded_rectangle(inner, radius=int(radius * 0.84), outline=(255, 255, 255, 54), width=max(2, size // 80))
bowl_top = int(size * 0.24)
bowl_left = int(size * 0.27)
bowl_right = int(size * 0.73)
bowl_bottom = int(size * 0.68)
draw.rounded_rectangle(
(bowl_left, bowl_top, bowl_right, bowl_bottom),
radius=int(size * 0.16),
fill=BOWL,
)
draw.rounded_rectangle(
(int(size * 0.31), int(size * 0.31), int(size * 0.69), int(size * 0.60)),
radius=int(size * 0.12),
fill=(239, 195, 159, 64),
)
bowl_curve_top = int(size * 0.56)
draw.pieslice(
(int(size * 0.24), bowl_curve_top, int(size * 0.76), int(size * 0.84)),
start=0,
end=180,
fill=(247, 179, 125, 255),
)
line_width = max(4, size // 26)
steam = [
(0.50, 0.31),
(0.56, 0.31),
(0.56, 0.40),
(0.45, 0.50),
(0.45, 0.58),
]
draw.line([(int(size * x), int(size * y)) for x, y in steam], fill=STROKE, width=line_width, joint="curve")
draw.line(
[(int(size * 0.45), int(size * 0.58)), (int(size * 0.56), int(size * 0.58))],
fill=STROKE,
width=line_width,
)
return image
def ImageColorTuple(hex_color: str) -> tuple[int, int, int]:
hex_color = hex_color.lstrip("#")
return tuple(int(hex_color[index:index + 2], 16) for index in (0, 2, 4))
def badge_icon(size: int) -> Image.Image:
image = Image.new("RGBA", (size, size), (0, 0, 0, 0))
draw = ImageDraw.Draw(image)
draw.rounded_rectangle((0, 0, size - 1, size - 1), radius=size // 3, fill=BG_BOTTOM)
draw.ellipse((size * 0.18, size * 0.18, size * 0.82, size * 0.82), fill=BOWL)
draw.rectangle((size * 0.34, size * 0.52, size * 0.66, size * 0.63), fill=STROKE)
return image
def save_assets() -> None:
BRAND_DIR.mkdir(parents=True, exist_ok=True)
rounded_gradient_icon(180).save(BRAND_DIR / "pwa-180.png")
rounded_gradient_icon(192).save(BRAND_DIR / "pwa-192.png")
rounded_gradient_icon(512).save(BRAND_DIR / "pwa-512.png")
rounded_gradient_icon(512, maskable=True).save(BRAND_DIR / "pwa-maskable-512.png")
badge_icon(96).save(BRAND_DIR / "pwa-badge.png")
if __name__ == "__main__":
save_assets()