#!/usr/bin/env python3 """Rendu d'images sociales KA haute qualité via Chrome headless (HTML/CSS). Lit un JSON « creative » sur stdin et produit une image 1080x1350 spectaculaire. Plusieurs « genres » de mise en page, dégradés par site, photos produit réelles. Genres : hero (nombre géant), ranking (palmarès), spotlight (produit vedette photo), deal (aubaine prix), pulse (activité live). Sortie : chemin du PNG sur stdout. """ import json, sys, os, base64, datetime, urllib.request, ssl, hashlib, subprocess, glob CTX = ssl.create_default_context(); CTX.check_hostname = False; CTX.verify_mode = ssl.CERT_NONE def find_chrome(): cands = glob.glob(os.path.expanduser( "~/Library/Caches/ms-playwright/chromium_headless_shell-*/chrome-headless-shell-mac-arm64/chrome-headless-shell")) if cands: return sorted(cands)[-1] for p in ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"]: if os.path.exists(p): return p raise SystemExit("chrome-headless-shell introuvable") MOIS = ["janvier","février","mars","avril","mai","juin","juillet","août", "septembre","octobre","novembre","décembre"] # palettes de dégradé par site (2 arrêts sombres tintés + couleur d'accent vive) PALETTES = { "lou-ka": {"a":"#ff6a00","b":"#ff9d4d","g1":"#2a1403","g2":"#0d0b09"}, "immo-ka": {"a":"#e23744","b":"#ff6b76","g1":"#2a0c10","g2":"#0d0a0b"}, "food-ka": {"a":"#1f9d55","b":"#5fd08a","g1":"#07230f","g2":"#0a0d0b"}, "fabri-ka": {"a":"#c4532e","b":"#e8895f","g1":"#241009","g2":"#0d0a09"}, "resto-ka": {"a":"#f08c00","b":"#ffb84d","g1":"#241804","g2":"#0d0b08"}, "auto-ka": {"a":"#ff5a2a","b":"#ff8f6b","g1":"#26100a","g2":"#0d0a09"}, "vrai-prix":{"a":"#d9f26b","b":"#b8e04a","g1":"#16230a","g2":"#0b0d08"}, "trouve-ka":{"a":"#d9f26b","b":"#8fd3ff","g1":"#0a1a20","g2":"#080b0d"}, "sorti-ka": {"a":"#d9f26b","b":"#ff8fd0","g1":"#210a1c","g2":"#0c090c"}, "crea-ka": {"a":"#d9f26b","b":"#c08bff","g1":"#160a24","g2":"#0a090d"}, "job-ka": {"a":"#d9f26b","b":"#7ab8ff","g1":"#0a1522","g2":"#080b0d"}, } def pal(site): return PALETTES.get(site, PALETTES["vrai-prix"]) def data_uri(url): try: req = urllib.request.Request(url, headers={"User-Agent":"Mozilla/5.0 (Macintosh)"}) with urllib.request.urlopen(req, timeout=15, context=CTX) as r: raw = r.read() mime = "image/jpeg" if url.lower().endswith(".png"): mime="image/png" elif url.lower().endswith(".webp"): mime="image/webp" return f"data:{mime};base64," + base64.b64encode(raw).decode() except Exception: return None def esc(s): return (str(s).replace("&","&").replace("<","<").replace(">",">")) def today_str(): t = datetime.date.today() return f"{t.day} {MOIS[t.month-1]} {t.year}" FONT = "'Avenir Next','SF Pro Display','Helvetica Neue',Helvetica,Arial,sans-serif" def base_css(p): return f""" *{{margin:0;padding:0;box-sizing:border-box;-webkit-font-smoothing:antialiased;}} html,body{{width:1080px;height:1350px;overflow:hidden;font-family:{FONT};color:#fff;}} .stage{{position:relative;width:1080px;height:1350px; background: radial-gradient(1200px 700px at 82% -6%, {p['a']}44 0%, transparent 55%), radial-gradient(900px 900px at -12% 108%, {p['b']}33 0%, transparent 55%), linear-gradient(160deg, {p['g1']} 0%, {p['g2']} 100%);}} .grain{{position:absolute;inset:0;opacity:.045;mix-blend-mode:overlay; background-image:url("data:image/svg+xml;utf8,");}} .pad{{position:absolute;inset:0;padding:70px 72px 250px;display:flex;flex-direction:column;}} .eyebrow{{display:flex;align-items:center;gap:14px;font-weight:800;letter-spacing:.16em; font-size:22px;text-transform:uppercase;color:{p['a']};}} .eyebrow .dot{{width:40px;height:5px;border-radius:3px;background:{p['a']};}} .kabadge{{position:absolute;top:60px;right:72px;background:{p['a']};color:#0d0d0d; font-weight:900;font-size:40px;padding:8px 20px;border-radius:16px;transform:rotate(-4deg); box-shadow:0 10px 30px {p['a']}55;}} .site{{font-size:88px;font-weight:900;letter-spacing:-.02em;margin-top:26px;}} .tag{{font-size:31px;font-weight:600;color:#ffffffb0;margin-top:6px;}} .foot{{position:absolute;left:72px;right:72px;bottom:66px;display:flex;justify-content:space-between; align-items:center;font-weight:800;}} .foot .url{{font-size:34px;color:{p['a']};letter-spacing:.01em;}} .foot .brand{{font-size:26px;color:#ffffff88;letter-spacing:.12em;}} .sig{{position:absolute;left:72px;bottom:150px;font-size:20px;color:#ffffff66;font-weight:600; display:flex;align-items:center;gap:8px;}} .glass{{background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.12); border-radius:22px;backdrop-filter:blur(8px);}} """ def hdr(ins, p): return f"""