Avatar shapes (circle, flower, rounded square, hexagon, heart, triangle): the own shape everywhere and, through the members service, the shapes other members published after proving the account with the code in the bio. Own accent colour and AMOLED, compact bottom bar, Material 3 profile and plus button, save menu (quality, audio only, photo posts without the watermark), fixed comment cards, "Luna (pre-release)" in the mod screen. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
165 lines
6.6 KiB
Python
165 lines
6.6 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
News cards for the Telegram post about LihonT 2 «Luna pre-release»: 1080x1350 PNGs in work/promo/cards/.
|
||
Uses the same screenshots and drawing helpers as the promo video (make_lihont_promo.py).
|
||
|
||
usage (from the repository root): py -B scripts/make_lihont_cards.py
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import math
|
||
import random
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
from PIL import Image
|
||
|
||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||
import make_lihont_promo as P # noqa: E402
|
||
|
||
CW, CH = 1080, 1350
|
||
OUT_DIR = P.ROOT / "work" / "promo" / "cards"
|
||
|
||
|
||
def backdrop(seed: int) -> Image.Image:
|
||
img = Image.new("RGBA", (CW, CH), P.BG + (255,))
|
||
rnd = random.Random(seed)
|
||
spots = [(0.25, 0.2), (0.8, 0.55), (0.3, 0.9)]
|
||
for n, blob_img in enumerate(P.ASSETS["blobs"]):
|
||
fx, fy = spots[n]
|
||
img.alpha_composite(blob_img, (int(CW * fx - blob_img.width / 2), int(CH * fy - blob_img.height / 2)))
|
||
for _ in range(12):
|
||
img.alpha_composite(P.ASSETS["flower"], (int(rnd.random() * CW), int(rnd.random() * CH)))
|
||
return img
|
||
|
||
|
||
def head(layer: Image.Image, title: str, sub: str, y: int = 150) -> None:
|
||
P.draw_text(layer, (CW / 2, y), title, 84, P.ON + (255,))
|
||
P.draw_text(layer, (CW / 2, y + 96), sub, 42, P.PRIMARY + (255,), bold=False)
|
||
|
||
|
||
def footer(layer: Image.Image) -> None:
|
||
P.draw_text(layer, (CW / 2, CH - 44), "LihonT 2 · Luna pre-release · @lihont", 32, P.MUTED + (255,), bold=False)
|
||
|
||
|
||
def card_hero() -> Image.Image:
|
||
img = backdrop(1)
|
||
layer = Image.new("RGBA", (CW, CH), (0, 0, 0, 0))
|
||
P.place(layer, P.ASSETS["blobs"][1], CW / 2, 470, 1.4)
|
||
P.place(layer, P.ASSETS["logo"], CW / 2, 470, 0.5)
|
||
P.draw_text(layer, (CW / 2, 830), "LihonT 2", 150, P.ON + (255,))
|
||
P.draw_text(layer, (CW / 2, 960), "Luna · пре-релиз", 64, P.PRIMARY + (255,))
|
||
P.draw_text(layer, (CW / 2, 1040), "Мод для TikTok в стиле Material 3", 44, P.MUTED + (255,), bold=False)
|
||
P.chip(layer, CW / 2, 1170, "Скачать: git.lihont.space", 1.0, size=48, fill=P.PRIMARY, fg=P.BG)
|
||
P.chip(layer, CW / 2, 1270, "Telegram · @lihont", 1.0, size=40)
|
||
img.alpha_composite(layer)
|
||
return img
|
||
|
||
|
||
def card_system() -> Image.Image:
|
||
img = backdrop(2)
|
||
layer = Image.new("RGBA", (CW, CH), (0, 0, 0, 0))
|
||
head(layer, "Всё в стиле Material 3", "Меню, настройки, нижняя панель")
|
||
P.place(layer, P.ASSETS["phones"]["menu"], 300, 930, 0.82, 6)
|
||
P.place(layer, P.ASSETS["phones"]["native"], 790, 990, 0.82, -5)
|
||
img.alpha_composite(layer)
|
||
return img
|
||
|
||
|
||
def card_comments() -> Image.Image:
|
||
img = backdrop(3)
|
||
layer = Image.new("RGBA", (CW, CH), (0, 0, 0, 0))
|
||
head(layer, "Новые комментарии", "Карточки, ручка шторки, поле-пилюля")
|
||
P.place(layer, P.ASSETS["phones"]["comments"], CW / 2, 930, 0.95)
|
||
P.chip(layer, 190, 560, "Карточки", 1.0, size=38)
|
||
P.chip(layer, 890, 470, "Ручка шторки", 1.0, size=38)
|
||
P.chip(layer, 860, 1230, "Поле-пилюля", 1.0, size=38)
|
||
img.alpha_composite(layer)
|
||
return img
|
||
|
||
|
||
def card_shapes() -> Image.Image:
|
||
img = backdrop(7)
|
||
layer = Image.new("RGBA", (CW, CH), (0, 0, 0, 0))
|
||
head(layer, "Форма аватарок", "Её видят все, у кого есть мод")
|
||
P.place(layer, P.ASSETS["phones"]["shapes"], CW / 2, 800, 0.62, -3)
|
||
for n, kind in enumerate(P.SHAPE_KINDS):
|
||
P.place(layer, P.shape_silhouette(kind, 120, P.PRIMARY), 190 + n * 140, 1230, 1.0)
|
||
P.chip(layer, 200, 560, "Круг, как в TikTok", 1.0, size=34)
|
||
P.chip(layer, 880, 640, "Без мода — круг", 1.0, size=34)
|
||
footer(layer)
|
||
img.alpha_composite(layer)
|
||
return img
|
||
|
||
|
||
def card_save() -> Image.Image:
|
||
img = backdrop(8)
|
||
layer = Image.new("RGBA", (CW, CH), (0, 0, 0, 0))
|
||
head(layer, "Скачать как надо", "Качество, звук, все фото")
|
||
P.place(layer, P.ASSETS["phones"]["save"], CW / 2, 930, 0.95)
|
||
P.chip(layer, 190, 520, "Без водяного знака", 1.0, size=36)
|
||
P.chip(layer, 890, 760, "Только звук", 1.0, size=36)
|
||
P.chip(layer, 880, 1230, "Фото карусели", 1.0, size=36)
|
||
img.alpha_composite(layer)
|
||
return img
|
||
|
||
|
||
def card_feed() -> Image.Image:
|
||
img = backdrop(4)
|
||
layer = Image.new("RGBA", (CW, CH), (0, 0, 0, 0))
|
||
head(layer, "Лента без лишнего", "Включай только нужное")
|
||
first, _second = P.ASSETS["mod_screens"]
|
||
phone = P.mount(P.ASSETS["mod_shell"], first)
|
||
P.place(layer, phone, 640, 960, 0.9)
|
||
for n, text in enumerate(["Без водяного знака", "Убрать рекламу", "Убрать эфиры", "Скрыть «Магазин»"]):
|
||
P.chip(layer, 230, 520 + n * 190, text, 1.0, size=34)
|
||
img.alpha_composite(layer)
|
||
return img
|
||
|
||
|
||
def card_badges() -> Image.Image:
|
||
img = backdrop(5)
|
||
layer = Image.new("RGBA", (CW, CH), (0, 0, 0, 0))
|
||
head(layer, "Значки у ников", "TikTok You, MargyT и нишевый мод")
|
||
profile = P.ASSETS["shots"]["profile"]
|
||
card = P.rounded(profile.crop((0, 0, 1080, 900)).resize((940, 783), Image.Resampling.LANCZOS), 56)
|
||
P.place(layer, card, CW / 2, 720, 1.0)
|
||
zoom = P.rounded(profile.crop((20, 166, 700, 270)).resize((880, 135), Image.Resampling.LANCZOS), 40)
|
||
ring = P.rounded(Image.new("RGBA", (896, 151), P.PRIMARY + (255,)), 48)
|
||
P.place(layer, ring, CW / 2, 1210)
|
||
P.place(layer, zoom, CW / 2, 1210)
|
||
footer(layer)
|
||
img.alpha_composite(layer)
|
||
return img
|
||
|
||
|
||
def card_icons() -> Image.Image:
|
||
img = backdrop(6)
|
||
layer = Image.new("RGBA", (CW, CH), (0, 0, 0, 0))
|
||
head(layer, "Иконки приложения", "Выбери свою или добавь свою")
|
||
spots = [(340, 560), (740, 560), (340, 980), (740, 980)]
|
||
for (slug, label), (x, y) in zip(P.ICONS, spots):
|
||
P.place(layer, P.ASSETS["icons"][slug], x, y, 1.0)
|
||
P.draw_text(layer, (x, y + 205), label, 40, P.ON + (255,))
|
||
footer(layer)
|
||
img.alpha_composite(layer)
|
||
return img
|
||
|
||
|
||
def main() -> None:
|
||
P.init_assets()
|
||
OUT_DIR.mkdir(parents=True, exist_ok=True)
|
||
cards = [("1_hero", card_hero), ("2_material3", card_system), ("3_comments", card_comments),
|
||
("4_shapes", card_shapes), ("5_save", card_save), ("6_feed", card_feed),
|
||
("7_badges", card_badges), ("8_icons", card_icons)]
|
||
for name, make in cards:
|
||
path = OUT_DIR / ("card_%s.png" % name)
|
||
make().convert("RGB").save(path)
|
||
print(path)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|