From 1f73a0ea216879b89b4bfac271227f1c610c1310 Mon Sep 17 00:00:00 2001 From: garykocsis Date: Wed, 10 Jun 2026 03:23:33 -0400 Subject: [PATCH] feat: add project banner thumbnail 1200x630 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds docs/assets/banner.png (1200x630 Open Graph / submission thumbnail — dark-navy design system, shield icon, name, tagline, partner badges) and docs/build_banner.py to regenerate it reproducibly (cairosvg rasterize + Pillow compositing). Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/assets/banner.png | Bin 0 -> 27814 bytes docs/build_banner.py | 125 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 docs/assets/banner.png create mode 100644 docs/build_banner.py diff --git a/docs/assets/banner.png b/docs/assets/banner.png new file mode 100644 index 0000000000000000000000000000000000000000..f9e5f3abed528498d6860707b1392022276b216c GIT binary patch literal 27814 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
    2b+VIe|_4N|vIs9y(z`KYDi?zj);^ONn@@gQw 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

    pE=SBc6(meK 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 RGBA PIL image at px*px. cairosvg preferred; Pillow fallback.""" + try: + import cairosvg + + png_bytes = cairosvg.svg2png(url=SVG, output_width=px, output_height=px) + return Image.open(io.BytesIO(png_bytes)).convert("RGBA") + except Exception as e: # pragma: no cover - fallback path + print(f"cairosvg unavailable ({e}); drawing shield in Pillow") + return draw_icon_fallback(px) + + +def draw_icon_fallback(px): + """Reproduce logo-icon.svg geometry (64x64 viewBox) at px*px.""" + s = px / 64.0 + img = Image.new("RGBA", (px, px), (0, 0, 0, 0)) + d = ImageDraw.Draw(img) + # Approximate the gradient stroke with its midpoint colour (#CA2D98). + stroke = (202, 45, 152, 255) + sw = max(1, round(3 * s)) + pts = [ + (14, 8), (50, 8), (54, 12), (54, 30), (44, 52), + (32, 58), (20, 52), (10, 30), (10, 12), + ] + d.line([(x * s, y * s) for x, y in pts] + [(14 * s, 8 * s)], + fill=stroke, width=sw, joint="curve") + green = (0, 211, 149, 255) + for x, y, w, h in [(20.5, 23, 5.3, 24), (29.3, 13, 5.3, 34), (38.1, 29, 5.3, 18)]: + d.rounded_rectangle([x * s, y * s, (x + w) * s, (y + h) * s], + radius=1 * s, fill=green) + return img + + +def text_h(draw, text, font): + b = draw.textbbox((0, 0), text, font=font, anchor="la") + return b[3] - b[1] + + +def main(): + img = Image.new("RGB", (W, H), BG) + draw = ImageDraw.Draw(img) + cx = W // 2 + + name_font = load_font(ARIAL_BOLD, 72) + tag_font = load_font(ARIAL, 32) + badge_font = load_font(ARIAL, 16) + + name_h = text_h(draw, NAME, name_font) + tag_h = text_h(draw, TAGLINE, tag_font) + + # Vertically centre the icon+name+tagline trio in the canvas. + total = ICON_PX + GAP_ICON_NAME + name_h + GAP_NAME_TAG + tag_h + y = (H - total) // 2 + + icon = rasterize_icon(ICON_PX) + img.paste(icon, (cx - ICON_PX // 2, y), icon) + y += ICON_PX + GAP_ICON_NAME + + draw.text((cx, y), NAME, font=name_font, fill=WHITE, anchor="ma") + y += name_h + GAP_NAME_TAG + + draw.text((cx, y), TAGLINE, font=tag_font, fill=SLATE, anchor="ma") + + # Badges pinned near the bottom. + draw.text((cx, H - 48), BADGES, font=badge_font, fill=BADGE, anchor="mm") + + img.save(OUT, "PNG") + print(f"wrote {OUT} ({img.width}x{img.height})") + + +if __name__ == "__main__": + main()