SPB Git

spb/admin-ka

Public

Reels: composition centree (fini le vide), tuiles auto-ajustees sur une ligne, balayage lumineux + barre de progression + halo anime

Simon-Pierre Boucher committed (Aug 23, 2026) parent b0eaf8e Browse files

1 changed file +84 −70

Unified Split
modified server/social/make_reel.py +84 −70
@@ -86,84 +86,98 @@ def build_spotlight(ins):
86 def build_html(ins): 86 def build_html(ins):
87 if ins.get('genre')=='spotlight' and (ins.get('product') or {}).get('image_data'): 87 if ins.get('genre')=='spotlight' and (ins.get('product') or {}).get('image_data'):
88 return build_spotlight(ins) 88 return build_spotlight(ins)
89− H=HREEL 89+ H=HREEL; p=pal(ins["site"])
90− pt=130 if H>=1600 else 80
91− pb=400 if H>=1600 else 220
92− p=pal(ins["site"])
93 tiles=ins.get("tiles") or [] 90 tiles=ins.get("tiles") or []
94 hl=str(ins["headline"]); L=len(hl) 91 hl=str(ins["headline"]); L=len(hl)
95− numsize = 150 if L>=9 else (180 if L>=7 else (230 if L>=5 else 300)) 92+ numsize = 150 if L>=9 else (185 if L>=7 else (235 if L>=5 else 300))
96− tiles_html="".join(f'<div class="tile t{i}"><div class="tv">{esc(v)}</div><div class="tl">{esc(l)}</div></div>' for i,(v,l) in enumerate(tiles)) 93+ tiles_html="".join('<div class="tile t'+str(i)+'"><div class="tv" data-fit>'+esc(v)+'</div><div class="tl">'+esc(l)+'</div></div>' for i,(v,l) in enumerate(tiles))
97− return f"""<!doctype html><html><head><meta charset="utf-8"><style> 94+ css = """
98− *{{margin:0;padding:0;box-sizing:border-box;-webkit-font-smoothing:antialiased}} 95+ *{margin:0;padding:0;box-sizing:border-box;-webkit-font-smoothing:antialiased}
99− html,body{{width:1080px;height:{H}px;overflow:hidden;font-family:{FONT};color:#fff}} 96+ html,body{width:1080px;height:__H__px;overflow:hidden;font-family:__FONT__;color:#fff}
100− .stage{{position:relative;width:1080px;height:{H}px; 97+ .stage{position:relative;width:1080px;height:__H__px;overflow:hidden;
101− background:radial-gradient(1200px 900px at 80% 0%, {p['a']}44,transparent 55%), 98+ background:radial-gradient(1300px 900px at 78% -5%, __A__55,transparent 55%),
102− radial-gradient(1000px 1000px at -10% 100%, {p['b']}33,transparent 55%), 99+ radial-gradient(1100px 1000px at -12% 105%, __B__3a,transparent 55%),
103− linear-gradient(160deg,{p['g1']},{p['g2']}); 100+ linear-gradient(160deg,__G1__,__G2__);background-size:150% 150%;animation:drift 14s ease-in-out infinite alternate}
104− background-size:140% 140%;animation:drift 12s ease-in-out infinite alternate}} 101+ @keyframes drift{0%{background-position:0% 0%}100%{background-position:100% 100%}}
105− @keyframes drift{{0%{{background-position:0% 0%}}100%{{background-position:100% 100%}}}} 102+ .mesh{position:absolute;inset:-20%;background:radial-gradient(closest-side, __A__22, transparent 70%);
106− .pad{{position:absolute;inset:0;padding:{pt}px 90px {pb}px;display:flex;flex-direction:column}} 103+ filter:blur(30px);animation:float 16s ease-in-out infinite alternate;opacity:.7}
107− .eyebrow{{display:flex;align-items:center;gap:16px;font-weight:800;letter-spacing:.16em;font-size:28px; 104+ @keyframes float{0%{transform:translate(-6%, -4%) scale(1)}100%{transform:translate(8%, 6%) scale(1.15)}}
108− text-transform:uppercase;color:{p['a']};opacity:0;animation:fadeUp .8s ease .2s forwards}} 105+ .pad{position:absolute;inset:0;padding:120px 90px 250px;display:flex;flex-direction:column}
109− .eyebrow .dot{{width:52px;height:6px;border-radius:3px;background:{p['a']}}} 106+ .eyebrow{display:flex;align-items:center;gap:16px;font-weight:800;letter-spacing:.16em;font-size:27px;
110− .kabadge{{position:absolute;top:120px;right:90px;background:{p['a']};color:#0d0d0d;font-weight:900; 107+ text-transform:uppercase;color:__A__;opacity:0;animation:fadeUp .8s ease .15s forwards}
111− font-size:56px;padding:12px 26px;border-radius:20px;transform:rotate(-4deg) scale(.4);opacity:0; 108+ .eyebrow .dot{width:52px;height:6px;border-radius:3px;background:__A__}
112− animation:pop .7s cubic-bezier(.2,1.4,.4,1) .4s forwards;box-shadow:0 16px 40px {p['a']}66}} 109+ .kabadge{position:absolute;top:118px;right:90px;background:__A__;color:#0d0d0d;font-weight:900;
113− .site{{font-size:120px;font-weight:900;letter-spacing:-.02em;margin-top:40px;opacity:0; 110+ font-size:54px;padding:10px 26px;border-radius:20px;transform:rotate(-4deg) scale(.4);opacity:0;
114− animation:fadeUp .8s ease .5s forwards}} 111+ animation:pop .7s cubic-bezier(.2,1.4,.4,1) .35s forwards;box-shadow:0 16px 46px __A__66}
115− .tag{{font-size:40px;font-weight:600;color:#ffffffb0;margin-top:12px;opacity:0;animation:fadeUp .8s ease .7s forwards}} 112+ .site{font-size:112px;font-weight:900;letter-spacing:-.02em;margin-top:34px;line-height:1;opacity:0;animation:fadeUp .8s ease .45s forwards}
116− .num{{font-size:{numsize}px;font-weight:900;line-height:.9;letter-spacing:-.03em;margin-top:80px;white-space:nowrap; 113+ .tag{font-size:38px;font-weight:600;color:#ffffffb0;margin-top:12px;opacity:0;animation:fadeUp .8s ease .65s forwards}
117− background:linear-gradient(180deg,#fff,{p['a']} 130%);-webkit-background-clip:text;-webkit-text-fill-color:transparent; 114+ .mid{flex:1;display:flex;flex-direction:column;justify-content:center;min-height:0}
118− filter:drop-shadow(0 16px 50px {p['a']}55);opacity:0;transform:translateY(40px) scale(.9); 115+ .numwrap{position:relative;display:inline-block;align-self:flex-start;opacity:0;transform:translateY(40px) scale(.92);animation:heroIn .95s cubic-bezier(.2,1,.3,1) .95s forwards}
119− animation:heroIn 1s cubic-bezier(.2,1,.3,1) 1.0s forwards}} 116+ .num{font-size:__NUM__px;font-weight:900;line-height:.92;letter-spacing:-.03em;white-space:nowrap;
120− .lab{{font-size:56px;font-weight:700;margin-top:30px;max-width:900px;line-height:1.12;opacity:0; 117+ background:linear-gradient(180deg,#fff,__A__ 135%);-webkit-background-clip:text;-webkit-text-fill-color:transparent;
121− animation:fadeUp .8s ease 1.4s forwards}} 118+ filter:drop-shadow(0 18px 55px __A__55)}
122− .tiles{{display:flex;gap:22px;margin-top:auto;margin-bottom:0}} 119+ .numwrap::after{content:"";position:absolute;top:0;left:-60%;width:45%;height:100%;
123− .tile{{flex:1;background:rgba(255,255,255,.07);border:1px solid rgba(255,255,255,.14);border-radius:26px; 120+ background:linear-gradient(100deg,transparent,rgba(255,255,255,.55),transparent);transform:skewX(-18deg);
124− padding:30px 26px;opacity:0;transform:translateY(30px);animation:fadeUp .7s ease forwards}} 121+ animation:shine 2.6s ease-in-out 1.6s infinite}
125− .t0{{animation-delay:1.8s}}.t1{{animation-delay:2.0s}}.t2{{animation-delay:2.2s}}.t3{{animation-delay:2.4s}} 122+ @keyframes shine{0%{left:-60%}55%{left:130%}100%{left:130%}}
126− .tv{{font-size:60px;font-weight:900;color:{p['a']}}} 123+ .lab{font-size:54px;font-weight:700;margin-top:26px;max-width:920px;line-height:1.12;opacity:0;animation:fadeUp .8s ease 1.35s forwards}
127− .tl{{font-size:29px;font-weight:600;color:#ffffffa0;margin-top:8px;line-height:1.2}} 124+ .tiles{display:flex;gap:20px;margin-top:56px}
128− .cta{{position:absolute;left:90px;right:90px;bottom:150px;opacity:0;animation:fadeUp .8s ease 2.8s forwards}} 125+ .tile{flex:1 1 0;min-width:0;background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.15);
129− .cta .go{{font-size:46px;font-weight:900;color:{p['a']}}} 126+ border-radius:26px;padding:28px 22px;opacity:0;transform:translateY(30px);animation:fadeUp .7s ease forwards;backdrop-filter:blur(6px)}
130− .cta .brand{{font-size:30px;font-weight:700;color:#ffffff88;letter-spacing:.12em;margin-top:6px}} 127+ .t0{animation-delay:1.7s}.t1{animation-delay:1.85s}.t2{animation-delay:2.0s}.t3{animation-delay:2.15s}
131− .sig{{position:absolute;left:90px;bottom:90px;font-size:26px;color:#ffffff66;font-weight:600;opacity:0; 128+ .tv{font-size:58px;font-weight:900;color:__A__;white-space:nowrap;line-height:1}
132− animation:fadeUp .8s ease 3.1s forwards}} 129+ .tl{font-size:27px;font-weight:600;color:#ffffffa8;margin-top:10px;line-height:1.15}
133− @keyframes fadeUp{{to{{opacity:1;transform:translateY(0)}}}} 130+ .cta{position:absolute;left:90px;right:90px;bottom:150px;opacity:0;animation:fadeUp .8s ease 2.5s forwards}
134− @keyframes pop{{to{{opacity:1;transform:rotate(-4deg) scale(1)}}}} 131+ .cta .go{font-size:46px;font-weight:900;color:__A__}
135− @keyframes heroIn{{to{{opacity:1;transform:translateY(0) scale(1)}}}} 132+ .cta .brand{font-size:30px;font-weight:700;color:#ffffff8c;letter-spacing:.12em;margin-top:6px}
136− </style></head><body> 133+ .sig{position:absolute;left:90px;bottom:92px;font-size:26px;color:#ffffff6a;font-weight:600;opacity:0;animation:fadeUp .8s ease 2.8s forwards}
137− <div class="stage"><div class="pad"> 134+ .prog{position:absolute;left:0;bottom:0;height:8px;background:__A__;width:0;animation:prog __DUR__s linear .2s forwards;box-shadow:0 0 20px __A__}
138− <div class="eyebrow"><span class="dot"></span>{esc(ins['label'].upper())} · {today().upper()}</div> 135+ @keyframes prog{to{width:100%}}
139− <div class="kabadge">KA</div> 136+ @keyframes fadeUp{to{opacity:1;transform:translateY(0)}}
140− <div class="site">{esc(ins['label'])}</div> 137+ @keyframes pop{to{opacity:1;transform:rotate(-4deg) scale(1)}}
141− {f'<div class="tag">{esc(ins.get("tagline",""))}</div>' if ins.get('tagline') else ''} 138+ @keyframes heroIn{to{opacity:1;transform:translateY(0) scale(1)}}
142− <div class="num" id="num" data-target="{esc(ins['headline'])}">{esc(ins['headline'])}</div> 139+ """
143− <div class="lab">{esc(ins['headline_label'])}</div> 140+ css=(css.replace("__H__",str(H)).replace("__FONT__",FONT).replace("__A__",p["a"])
144− {f'<div class="tiles">{tiles_html}</div>' if tiles else ''} 141+ .replace("__B__",p["b"]).replace("__G1__",p["g1"]).replace("__G2__",p["g2"])
145− <div class="cta"><div class="go">👉 www.{ins['site']}.com</div><div class="brand">GROUPE ·KA</div></div> 142+ .replace("__NUM__",str(numsize)).replace("__DUR__",str(DUR)))
146− <div class="sig">✶ Rédigé et publié par l'Agent KA</div> 143+ tag = ('<div class="tag">'+esc(ins.get("tagline",""))+'</div>') if ins.get("tagline") else ""
147− </div></div> 144+ tiles_block = ('<div class="tiles">'+tiles_html+'</div>') if tiles else ""
148− <script> 145+ body = ('<div class="stage"><div class="mesh"></div><div class="pad">'
149− // count-up si le nombre-héros est numérique 146+ '<div class="eyebrow"><span class="dot"></span>'+esc(ins["label"].upper())+' · '+today().upper()+'</div>'
150− (function(){{ 147+ '<div class="kabadge">KA</div>'
151− var el=document.getElementById('num'); var raw=el.getAttribute('data-target'); 148+ '<div class="site">'+esc(ins["label"])+'</div>'+tag+
152− if(/[,%\\/]/.test(raw)) return; 149+ '<div class="mid">'
153− var m=raw.match(/^[\\d\\u00a0\\u202f ]+/); if(!m) return; 150+ '<div class="numwrap"><div class="num" id="num" data-target="'+esc(ins["headline"])+'">'+esc(ins["headline"])+'</div></div>'
154− var digits=m[0].replace(/[^\\d]/g,''); if(digits.length<3) return; 151+ '<div class="lab">'+esc(ins["headline_label"])+'</div>'+tiles_block+
152+ '</div>'
153+ '<div class="cta"><div class="go">👉 www.'+ins["site"]+'.com</div><div class="brand">GROUPE ·KA</div></div>'
154+ '<div class="sig">✦ Rédigé et publié par l\'Agent KA</div>'
155+ '<div class="prog"></div>'
156+ '</div></div>')
157+ script = """<script>
158+ (function(){
159+ // ajuste la taille des valeurs de tuiles pour tenir sur une ligne
160+ document.querySelectorAll('.tv[data-fit]').forEach(function(el){
161+ var s=58; el.style.fontSize=s+'px';
162+ while(el.scrollWidth>el.clientWidth && s>26){ s-=3; el.style.fontSize=s+'px'; }
163+ });
164+ var el=document.getElementById('num'); if(!el) return; var raw=el.getAttribute('data-target');
165+ if(/[,%\/]/.test(raw)) return;
166+ var m=raw.match(/^[\d\u00a0\u202f ]+/); if(!m) return;
167+ var digits=m[0].replace(/[^\d]/g,''); if(digits.length<3) return;
155 var target=parseInt(digits,10); var suffix=raw.slice(m[0].length); 168 var target=parseInt(digits,10); var suffix=raw.slice(m[0].length);
156 var start=null, dur=1400, begin=1000; 169 var start=null, dur=1400, begin=1000;
157− function fmt(n){{return n.toLocaleString('fr-CA').replace(/[\\u00a0\\u202f,]/g,' ')}} 170+ function fmt(n){return n.toLocaleString('fr-CA').replace(/[\u00a0\u202f,]/g,' ')}
158− function step(ts){{ if(!start)start=ts; var t=ts-start; 171+ function step(ts){ if(!start)start=ts; var t=ts-start;
159− if(t<begin){{requestAnimationFrame(step);return;}} 172+ if(t<begin){requestAnimationFrame(step);return;}
160 var k=Math.min(1,(t-begin)/dur); var e=1-Math.pow(1-k,3); 173 var k=Math.min(1,(t-begin)/dur); var e=1-Math.pow(1-k,3);
161 el.textContent=fmt(Math.round(target*e))+suffix; 174 el.textContent=fmt(Math.round(target*e))+suffix;
162 if(k<1)requestAnimationFrame(step); 175 if(k<1)requestAnimationFrame(step);
163− }} 176+ }
164 el.textContent=fmt(0)+suffix; requestAnimationFrame(step); 177 el.textContent=fmt(0)+suffix; requestAnimationFrame(step);
165− }})(); 178+ })();
166− </script></body></html>""" 179+ </script>"""
180+ return "<!doctype html><html><head><meta charset=\"utf-8\"><style>"+css+"</style></head><body>"+body+script+"</body></html>"
167 181
168 def record_webm(html_path, out_webm, tmpdir): 182 def record_webm(html_path, out_webm, tmpdir):
169 from playwright.sync_api import sync_playwright 183 from playwright.sync_api import sync_playwright
170 184
Menu

Theme