Initial release with production deploy config.
Add GitAtlas deploy assets, DATA_FILE support for isolated server data, and port documentation for sourcing.simosen.cn. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
PUBLIC = ROOT / "public"
|
||||
ASSETS = PUBLIC / "assets"
|
||||
EXT_ICONS = ROOT / "extension" / "icons"
|
||||
|
||||
JADE_DARK = "#073C31"
|
||||
JADE = "#0D6A54"
|
||||
TURQUOISE = "#249B90"
|
||||
PAPER = "#FFFDF5"
|
||||
PAPER_GREEN = "#E8F6EF"
|
||||
BRASS = "#A56F18"
|
||||
BRASS_LIGHT = "#FFE6A1"
|
||||
CINNABAR = "#C9544A"
|
||||
MUTED = "#65766A"
|
||||
|
||||
SERIF = "/System/Library/Fonts/Supplemental/Songti.ttc"
|
||||
SANS = "/Users/wangyuqiang/Library/Fonts/SourceHanSansCN-Regular.otf"
|
||||
SANS_HEAVY = "/Users/wangyuqiang/Library/Fonts/SourceHanSansCN-Heavy.otf"
|
||||
|
||||
|
||||
def font(path, size):
|
||||
return ImageFont.truetype(path, size)
|
||||
|
||||
|
||||
def rounded(draw, xy, radius, fill, outline=None, width=1):
|
||||
draw.rounded_rectangle(xy, radius=radius, fill=fill, outline=outline, width=width)
|
||||
|
||||
|
||||
def draw_mark(size=512):
|
||||
scale = size / 256
|
||||
image = Image.new("RGBA", (size, size), (0, 0, 0, 0))
|
||||
draw = ImageDraw.Draw(image)
|
||||
rounded(draw, (0, 0, size - 1, size - 1), int(56 * scale), PAPER)
|
||||
rounded(draw, (int(12 * scale), int(12 * scale), int(244 * scale), int(244 * scale)), int(48 * scale), JADE)
|
||||
rounded(
|
||||
draw,
|
||||
(int(25 * scale), int(25 * scale), int(231 * scale), int(231 * scale)),
|
||||
int(38 * scale),
|
||||
None,
|
||||
"#2E8F7D",
|
||||
max(1, int(2 * scale)),
|
||||
)
|
||||
stroke = max(8, int(13 * scale))
|
||||
corner = "#FFF8E8"
|
||||
segments = [
|
||||
((57, 98), (57, 70), (70, 57), (98, 57)),
|
||||
((158, 57), (186, 57), (199, 70), (199, 98)),
|
||||
((199, 158), (199, 186), (186, 199), (158, 199)),
|
||||
((98, 199), (70, 199), (57, 186), (57, 158)),
|
||||
]
|
||||
for points in segments:
|
||||
scaled = [(int(x * scale), int(y * scale)) for x, y in points]
|
||||
draw.line(scaled, fill=corner, width=stroke, joint="curve")
|
||||
|
||||
diamond = [(128, 72), (184, 128), (128, 184), (72, 128)]
|
||||
draw.polygon([(int(x * scale), int(y * scale)) for x, y in diamond], fill=BRASS_LIGHT)
|
||||
inner = [(128, 91), (165, 128), (128, 165), (91, 128)]
|
||||
draw.polygon([(int(x * scale), int(y * scale)) for x, y in inner], fill="#FFF8E8")
|
||||
draw.ellipse(
|
||||
(int(111 * scale), int(111 * scale), int(145 * scale), int(145 * scale)),
|
||||
fill=JADE,
|
||||
)
|
||||
draw.ellipse(
|
||||
(int(162 * scale), int(70 * scale), int(186 * scale), int(94 * scale)),
|
||||
fill=CINNABAR,
|
||||
outline="#FFF8E8",
|
||||
width=max(3, int(5 * scale)),
|
||||
)
|
||||
return image
|
||||
|
||||
|
||||
def center_text(draw, xy, text, font_obj, fill):
|
||||
x1, y1, x2, y2 = xy
|
||||
box = draw.textbbox((0, 0), text, font=font_obj)
|
||||
width = box[2] - box[0]
|
||||
height = box[3] - box[1]
|
||||
draw.text((x1 + (x2 - x1 - width) / 2, y1 + (y2 - y1 - height) / 2 - box[1]), text, font=font_obj, fill=fill)
|
||||
|
||||
|
||||
def draw_share_card():
|
||||
image = Image.new("RGB", (1200, 630), "#F8FFF9")
|
||||
draw = ImageDraw.Draw(image)
|
||||
for y in range(630):
|
||||
ratio = y / 629
|
||||
r = int(248 * (1 - ratio) + 255 * ratio)
|
||||
g = int(255 * (1 - ratio) + 245 * ratio)
|
||||
b = int(249 * (1 - ratio) + 222 * ratio)
|
||||
draw.line((0, y, 1200, y), fill=(r, g, b))
|
||||
|
||||
for x in range(0, 1200, 32):
|
||||
draw.line((x, 0, x, 630), fill="#D8E8DE", width=1)
|
||||
for y in range(0, 630, 32):
|
||||
draw.line((0, y, 1200, y), fill="#D8E8DE", width=1)
|
||||
|
||||
draw.ellipse((860, -228, 1380, 292), fill="#F3DE9E")
|
||||
draw.ellipse((-210, 360, 290, 860), fill="#DDF2EF")
|
||||
|
||||
mark = draw_mark(208)
|
||||
image.paste(mark, (66, 62), mark)
|
||||
|
||||
title_font = font(SERIF, 64)
|
||||
subtitle_font = font(SANS_HEAVY, 30)
|
||||
body_font = font(SANS, 22)
|
||||
small_font = font(SANS_HEAVY, 17)
|
||||
label_font = font(SANS, 15)
|
||||
|
||||
draw.text((66, 306), "选品采购台", font=title_font, fill=JADE_DARK)
|
||||
draw.text((69, 394), "从商品机会,到放心采购", font=subtitle_font, fill="#315F55")
|
||||
draw.text((69, 452), "采集竞品 · 匹配货源 · 利润测算 · 拿样决策", font=body_font, fill=MUTED)
|
||||
|
||||
rounded(draw, (660, 88, 1124, 540), 28, PAPER, "#B9D5C7", 2)
|
||||
draw.text((694, 132), "PRODUCT OPPORTUNITY FLOW", font=small_font, fill=MUTED)
|
||||
|
||||
cards = [
|
||||
((694, 174, 1092, 252), "#E2F3EE", "#B9D5C7", JADE, "发现高潜商品", "淘宝 / 小红书 / 更多平台线索"),
|
||||
((694, 290, 1092, 368), "#FFF3CB", "#D9BD79", BRASS, "匹配优质货源", "1688 厂家 / 价格 / 保障"),
|
||||
((694, 406, 1092, 502), JADE, JADE, PAPER, "形成采购决策", "筛选 / 利润 / 风险 / 拿样"),
|
||||
]
|
||||
for index, (box, fill, outline, accent, title, subtitle) in enumerate(cards):
|
||||
rounded(draw, box, 14, fill, outline, 1)
|
||||
x1, y1, x2, y2 = box
|
||||
if index == 0:
|
||||
draw.ellipse((x1 + 20, y1 + 19, x1 + 60, y1 + 59), fill=JADE)
|
||||
center_text(draw, (x1 + 20, y1 + 19, x1 + 60, y1 + 59), "+", font(SANS, 29), PAPER)
|
||||
text_fill = JADE_DARK
|
||||
sub_fill = MUTED
|
||||
elif index == 1:
|
||||
draw.rectangle((x1 + 22, y1 + 25, x1 + 61, y1 + 55), fill=BRASS)
|
||||
text_fill = "#5F4211"
|
||||
sub_fill = "#7A6645"
|
||||
else:
|
||||
draw.ellipse((x1 + 18, y1 + 26, x1 + 62, y1 + 70), fill=PAPER)
|
||||
draw.line((x1 + 30, y1 + 50, x1 + 38, y1 + 58, x1 + 53, y1 + 39), fill=JADE, width=5)
|
||||
text_fill = PAPER
|
||||
sub_fill = "#DDF2EF"
|
||||
draw.ellipse((x2 - 39, y1 + 18, x2 - 23, y1 + 34), fill=CINNABAR)
|
||||
draw.text((x1 + 76, y1 + 21), title, font=font(SANS_HEAVY, 21), fill=text_fill)
|
||||
draw.text((x1 + 76, y1 + 49), subtitle, font=label_font, fill=sub_fill)
|
||||
if index < 2:
|
||||
cx = 892
|
||||
draw.line((cx, y2 + 8, cx, y2 + 32), fill=BRASS, width=4)
|
||||
draw.line((cx - 8, y2 + 24, cx, y2 + 32, cx + 8, y2 + 24), fill=BRASS, width=4)
|
||||
|
||||
return image
|
||||
|
||||
|
||||
def main():
|
||||
ASSETS.mkdir(parents=True, exist_ok=True)
|
||||
EXT_ICONS.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
mark = draw_mark(512)
|
||||
mark.resize((256, 256), Image.Resampling.LANCZOS).save(ASSETS / "brand-mark.png")
|
||||
draw_share_card().save(ASSETS / "share-card.png")
|
||||
|
||||
icon_sizes = [16, 32, 48, 64, 128, 256]
|
||||
icons = [mark.resize((size, size), Image.Resampling.LANCZOS) for size in icon_sizes]
|
||||
icons[-1].save(PUBLIC / "favicon.ico", sizes=[(size, size) for size in icon_sizes])
|
||||
for size in [16, 32, 48, 128]:
|
||||
mark.resize((size, size), Image.Resampling.LANCZOS).save(EXT_ICONS / f"icon{size}.png")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user