SPB Git forge

spb/ka-guardian

Public
26commits 1branches 0releases
4.5 MBsize
maindefault branch
19 days agolast push
Python 57.1% Shell 13.8% CSS 12% JavaScript 10% HTML 7.2%
6.0 KB · 119 lines python
Raw Blame History
1#!/usr/bin/env python32"""Génère les bannières de partage (Open Graph) des 3 sites gardiens.34Rendu HTML → Chromium (Playwright) → orchestrator/web/og-ka{2,4,6}.png5(2400×1260, ratio 1200:630). Langage visuel Groupe KA : papier, encre,6lime, ombres décalées dures + accent/battement ECG propres à chaque agent.78Usage (laptop) : python3 deploy/make_og_images.py9"""10import json11import tempfile12from pathlib import Path1314from playwright.sync_api import sync_playwright1516ROOT = Path(__file__).resolve().parent.parent17WEB = ROOT / "orchestrator" / "web"18TOPO = json.loads((ROOT / "topology.json").read_text())1920# couleur du battement ECG (celle des favicons, plus dense que l'accent pastel)21ECG = {"ka2": "#5b9df5", "ka4": "#ff9f2e", "ka6": "#a78bfa"}22SERVICES_LABEL = {23    "ka2": "lou·ka · immo·ka · resto·ka",24    "ka4": "auto·ka · food·ka · sorti·ka",25    "ka6": "fabri·ka · job·ka · créa·ka",26}2728TPL = """<!doctype html>29<html lang="fr-CA"><head><meta charset="utf-8">30<link rel="preconnect" href="https://fonts.googleapis.com">31<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>32<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;700&family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;600;700&display=swap" rel="stylesheet">33<style>34  * { box-sizing: border-box; margin: 0; }35  html, body { width: 1200px; height: 630px; overflow: hidden; }36  body { background: #f5f3ee; color: #141814; font-family: "Inter", system-ui, sans-serif;37    -webkit-font-smoothing: antialiased; position: relative; }38  .frame { position: absolute; inset: 26px; border: 3px solid #141814; border-radius: 22px;39    background: #fff; box-shadow: 14px 14px 0 #141814; padding: 52px 60px 46px; overflow: hidden; }40  .dots { position: absolute; inset: 0; border-radius: 20px;41    background-image: radial-gradient(#1418141a 1.6px, transparent 1.6px); background-size: 26px 26px; }42  .top { display: flex; justify-content: space-between; align-items: flex-start; position: relative; }43  .wordmark { font-family: "Space Grotesk", sans-serif; font-weight: 700; font-size: 120px;44    letter-spacing: -0.05em; line-height: 1; display: flex; align-items: center; }45  .wordmark .num { color: %%ACCENT2%%; }46  .wordmark .ka { background: #141814; color: #d9f26b; border-radius: 14px; margin-left: 20px;47    padding: 6px 24px 14px; display: inline-block; transform: rotate(-2deg); font-size: 0.42em; }48  .gk { font-family: "Space Grotesk", sans-serif; font-weight: 700; font-size: 30px; display: flex;49    align-items: center; gap: 14px; margin-top: 14px; }50  .gk .badge { background: #141814; color: #d9f26b; border-radius: 10px; padding: 2px 14px 6px;51    transform: rotate(-2deg); display: inline-block; }52  .pastille { width: 22px; height: 22px; border-radius: 50%; background: #d9f26b; border: 3px solid #141814; }53  .klabel { font-family: "JetBrains Mono", monospace; text-transform: uppercase; letter-spacing: 0.14em;54    color: #4d5551; font-size: 21px; font-weight: 700; margin-top: 26px; position: relative; }55  .tagline { font-family: "Space Grotesk", sans-serif; font-weight: 700; font-size: 62px;56    letter-spacing: -0.02em; margin-top: 12px; position: relative; line-height: 1.12; }57  .tagline .hl { background: %%ACCENT%%; border-radius: 12px; padding: 0 16px 4px; display: inline-block;58    transform: rotate(-1deg); }59  svg.ecg { position: absolute; left: 0; right: 0; bottom: 118px; width: 100%; }60  .bottom { position: absolute; left: 60px; right: 60px; bottom: 42px; display: flex;61    justify-content: space-between; align-items: center; }62  .chip { font-family: "JetBrains Mono", monospace; font-weight: 700; font-size: 26px;63    text-transform: uppercase; letter-spacing: 0.06em; border: 3px solid #141814; border-radius: 999px;64    background: #f5f3ee; padding: 10px 26px; box-shadow: 6px 6px 0 #141814; }65  .services { font-family: "JetBrains Mono", monospace; font-weight: 600; font-size: 22px; color: #4d5551; }66</style></head>67<body>68<div class="frame">69  <div class="dots"></div>70  <div class="top">71    <div class="wordmark">ka·<span class="num">%%NUM%%</span><span class="ka">guardian</span></div>72    <div class="gk">groupe<span class="badge">·KA</span><span class="pastille"></span></div>73  </div>74  <div class="klabel">agent gardien autonome · connecteurs du groupe ka</div>75  <div class="tagline">%%TAGLINE_HTML%%</div>76  <svg class="ecg" viewBox="0 0 1200 90" preserveAspectRatio="none">77    <polyline points="0,58 260,58 320,32 400,82 470,44 520,58 700,58 760,36 830,78 895,48 940,58 1200,58"78      fill="none" stroke="%%ECG%%" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>79  </svg>80  <div class="bottom">81    <div class="chip">%%DOMAIN%%</div>82    <div class="services">%%SERVICES%%</div>83  </div>84</div>85</body></html>"""8687# mot à surligner dans la tagline (image de marque : pastille accent de l'agent)88def tagline_html(agent: str, tagline: str) -> str:89    first, _, rest = tagline.partition(" ")90    return f'<span class="hl">{first}</span> {rest}'919293def main() -> None:94    with sync_playwright() as p:95        browser = p.chromium.launch()96        page = browser.new_page(viewport={"width": 1200, "height": 630}, device_scale_factor=2)97        for agent, me in TOPO["agents"].items():98            html = (TPL99                    .replace("%%NUM%%", agent[-1])100                    .replace("%%ACCENT%%", me["accent"])101                    .replace("%%ACCENT2%%", me["accent2"])102                    .replace("%%ECG%%", ECG[agent])103                    .replace("%%DOMAIN%%", me["domain"])104                    .replace("%%SERVICES%%", SERVICES_LABEL[agent])105                    .replace("%%TAGLINE_HTML%%", tagline_html(agent, me["tagline"])))106            tmp = Path(tempfile.gettempdir()) / f"og-{agent}.html"107            tmp.write_text(html)108            page.goto(tmp.as_uri())109            page.evaluate("document.fonts.ready.then(() => {})")110            page.wait_for_timeout(900)111            out = WEB / f"og-{agent}.png"112            page.screenshot(path=str(out))113            print(f"✓ {out.relative_to(ROOT)}")114        browser.close()115116117if __name__ == "__main__":118    main()119