#!/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()