spb/ka-guardian
Public
Python 57.1%
Shell 13.8%
CSS 12%
JavaScript 10%
HTML 7.2%
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