PageSourceSearch

https://www.sparkstation.ai/_next/static/chunks/30684.1380108153300a9b.js

js sparkstation.ai collected 2026-10-05 12:42:29 UTC 315,675 bytes, 1 lines download raw bytes

1"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[30684],{3199:(e,t,a)=>{a.d(t,{$H:()=>y,Kd:()=>p,Lq:()=>v,NE:()=>d,Yu:()=>h,authHeader:()=>m,fV:()=>r,getValidJwtFromStorage:()=>f,jL:()=>g,p1:()=>u,pn:()=>l,pt:()=>b,z1:()=>c});let n="/login",o="ss_app";function i(){return"undefined"!=typeof location&&"https:"===location.protocol?"; Secure":""}function r(e){if("undefined"!=typeof document){if(e){let t=p(e),a=(null==t?void 0:t.exp)?Math.max(0,Math.floor(t.exp-Date.now()/1e3)):604800;document.cookie="".concat(o,"=1; Path=/; Max-Age=").concat(a,"; SameSite=Lax").concat(i());return}document.cookie="".concat(o,"=; Path=/; Max-Age=0; SameSite=Lax").concat(i())}}function l(e){try{localStorage.setItem("jwt",e)}catch(e){}r(e)}let s=new Set;function c(e){return s.add(e),()=>s.delete(e)}function d(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:n;h(),s.forEach(e=>{try{e()}catch(e){}}),e&&e.replace(t)}function p(e){if(!e||"string"!=typeof e)return null;try{let t=e.split(".")[1];if(!t)return null;let a=t.replace(/-/g,"+").replace(/_/g,"/"),n=a+"===".slice(0,(4-a.length%4)%4);return JSON.parse(atob(n))}catch(e){return null}}function u(){try{return localStorage.getItem("jwt")||null}catch(e){return null}}function h(){try{localStorage.removeItem("jwt")}catch(e){}r(null)}function m(e){let t=e||u();return t?{Authorization:"Bearer "+t}:{}}function f(){let e=u();if(!e)return null;let t=p(e);return!t||function(e){let t="string"==typeof e?p(e):e;return!!(null==t?void 0:t.exp)&&1e3*t.exp<Date.now()}(t)?(h(),null):e}function g(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:n;d(e,t)}function b(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"/";h(),window.location.replace(t)}function y(e,t){let a=arguments.length>2&&void 0!==arguments[2]?arguments[2]:n;return(null==e?void 0:e.status)===401&&(d(t,a),!0)}function v(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:n,a=f();return a||(g(e,t),null)}},22901:(e,t,a)=>{a.d(t,{TJ:()=>l,cZ:()=>s});var n=a(3199);let o=null,i=null;async function r(e){try{let t=await fetch("/api/v1/wallet/balance",{headers:(0,n.authHeader)(e)}),a=await t.json().catch(()=>null);return{ok:t.ok,data:a,threw:!1}}catch(e){return{ok:!1,data:null,threw:!0}}}async function l(e){let{force:t=!1}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(!e)return{ok:!1,data:null,threw:!1};let a=Date.now();if(!t&&o&&o.jwt===e&&o.exp>a)return o.result;if(!t&&i&&i.jwt===e)return i.promise;let n=(async()=>{let t=await r(e);return t.ok&&t.data&&!1!==t.data.status&&(o={jwt:e,result:t,exp:Date.now()+12e3}),t})().finally(()=>{i=null});return i={jwt:e,promise:n},n}async function s(e,t){let a=await l(e,t);return a?a.data:null}window.addEventListener("wallet:refresh",function(){o=null,i=null})},28587:(e,t,a)=>{function n(e){return String(e||"").toLowerCase().replace(/\./g,"-")}function o(e,t){if(!Array.isArray(e)||!t)return null;let a=n(t),o=e.find(e=>n(e.model_key)===a);if(o)return o;let i=null,r=0;for(let t of e){let e=n(t.model_key);e&&a.includes(e)&&e.length>r&&(i=t,r=e.length)}return i}function i(e,t){var a;let n=(null==e?void 0:e.plan_tier_multipliers)||{},o=Number(null!=(a=n[String(t||"free").toLowerCase()])?a:n.free);return Number.isFinite(o)?o:null}function r(e){var t,a;let{opcost:n,modelKey:r,chars:l,plan:s}=e,c=o((null==n?void 0:n.voice)||(null==n||null==(a=n.data)?void 0:a.voice)||[],r);if(!c)return null;let d=Number(null==(t=c.cost_config)?void 0:t.cost_per_unit),p=Number(l),u=i(c,s);if(!Number.isFinite(d)||!Number.isFinite(p)||p<=0||null==u)return null;let h=Math.ceil(d*p*u);return h>0?h:null}function l(e){var t,a;let{opcost:n,modelKey:r,plan:l,count:s=1}=e,c=o((null==n?void 0:n.music)||(null==n||null==(a=n.data)?void 0:a.music)||[],r);if(!c)return null;let d=Number(null==(t=c.cost_config)?void 0:t.cost_per_unit),p=i(c,l);if(!Number.isFinite(d)||null==p)return null;let u=Math.ceil(d*p),h=Math.max(1,Math.floor(Number(s)||1));return u>0?u*h:null}function s(e){var t;let{opcost:a,modelKey:n,plan:r}=e,l=o((null==a?void 0:a.llm)||(null==a||null==(t=a.data)?void 0:t.llm)||[],n);if(!l)return null;let s=Number(l.raw_cost_usd),c=i(l,r);if(!Number.isFinite(s)||s<=0||null==c)return null;let d=Math.ceil(s*c);return d>0?d:null}function c(e){var t,a,r,l,s,c;let{opcost:d,modelKey:p,resolution:u,plan:h,count:m=1}=e,f=o((null==d?void 0:d.image)||(null==d||null==(c=d.data)?void 0:c.image)||[],function(e){let t=n(e);return"gpt-image-2"===t||t.startsWith("gpt-image-2.")||t.startsWith("gpt-image-2-")?"gpt-image-2-1024x1024":e}(p));if(!f)return null;let g=i(f,h);if(null==g)return null;let b=Math.max(1,Math.floor(Number(m)||1));if("flat"===String((null==(t=f.cost_config)?void 0:t.billing_type)||"feature_matrix").toLowerCase()){let e=Number(null==(l=f.cost_config)?void 0:l.cost_per_unit);if(!Number.isFinite(e))return null;let t=Math.ceil(e*g);return t>0?t*b:null}let y=(null==(r=f.cost_config)||null==(a=r.pricing_matrix)?void 0:a.resolution)||{},v=function(e){let t=String(e||"").trim().toUpperCase();if("4K"===t||"MAX"===t)return"4k";if("2K"===t)return"2048";if("1K"===t)return"1024";let a=Math.round(Number(t));return 512===a?"512":1024===a?"1024":2048===a?"2048":4096===a?"4k":"1024"}(u),_=Number(null!=(s=y[v])?s:y[String(v)]);if(!Number.isFinite(_))return null;let w=Math.ceil(_*g);return w>0?w*b:null}function d(e){var t,a,n,i,r,l;let{opcost:s,modelKey:c,resolution:d,durationSec:p,withAudio:u,plan:h}=e,m=o((null==s?void 0:s.video)||(null==s||null==(l=s.data)?void 0:l.video)||[],c);if(!m)return null;let f=(null==(t=m.cost_config)?void 0:t.pricing_matrix)||{},g=f.resolution||{},b=f.audio||{},y=Number(null!=(i=g[d])?i:g[String(d)]);if(!Number.isFinite(y))return null;let v=Number(u?b.enabled:b.disabled)||0,_=Number(p);if(!Number.isFinite(_)||_<=0)return null;let w=String(h||"free").toLowerCase(),x=Number(null!=(r=null==(a=m.plan_tier_multipliers)?void 0:a[w])?r:null==(n=m.plan_tier_multipliers)?void 0:n.free);if(!Number.isFinite(x))return null;let k=Math.ceil((y+v)*_*x);return k>0?k:null}function p(e){let t=Number(e)||0;if(t>=1e3){let e=t/1e3;return"".concat(e%1==0?e:e.toFixed(1),"K")}return String(t)}a.d(t,{Gw:()=>s,HN:()=>r,qG:()=>d,th:()=>p,uk:()=>l,ul:()=>c})},30684:(e,t,a)=>{a.d(t,{mountAcsStudio:()=>i});var n=a(28587),o=a(22901);function i(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(!e)throw Error("ACS root required");let a=t||{},i=t=>e&&e.querySelector("#"+t)||null,r=e=>String(null!=e?e:"").replace(/[&<>"']/g,e=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[e]),l=e=>{try{return localStorage.getItem(e)}catch(e){return null}},s=(e,t)=>{try{null===t?localStorage.removeItem(e):localStorage.setItem(e,t)}catch(e){}},c="/api/v1/micro-ads/campaign-builder",d=c+"/gemini";function p(){try{return a.jwt||localStorage.getItem("jwt")||""}catch(e){return a.jwt||""}}function u(){return!!String(a.jobKey||"").trim()}function h(e){let t=Object.assign({},e||{}),n=String(a.jobKey||"").trim();if(n)return t["x-acs-job-key"]=n,t;let o=p();return o&&(t.Authorization="Bearer "+o),t}async function m(e,t){let a=Date.now(),n=1200,o=0;for(;Date.now()-a<3e5;){let t,a,i;await new Promise(e=>setTimeout(e,n)),n=Math.min(4e3,Math.round(1.4*n));try{t=await fetch("".concat(d,"/poll?jobId=").concat(encodeURIComponent(e)),{headers:h({}),cache:"no-store"}),a=await t.text()}catch(e){if(++o>5)throw e;continue}try{i=JSON.parse(a)}catch(e){i=null}if(200!==t.status||!i||!i.pending)return{r:t,txt:a,j:i}}let i=Error("".concat(t," timed out after ").concat(Math.round(300),"s"));throw i.httpStatus=504,i}async function f(e,t){let a,n=new URLSearchParams({model:String(e||"")});try{void 0!==ot&&ot&&ot.id&&n.set("campaign_id",String(ot.id))}catch(e){}let o=await fetch("".concat(d,"?").concat(n.toString()),{method:"POST",headers:h({"Content-Type":"application/json"}),body:JSON.stringify(t)}),i=await o.text();try{a=JSON.parse(i)}catch(e){a=null}if(202===o.status||o.ok&&a&&a.pending&&a.jobId){let t=await m(a.jobId,e);o=t.r,i=t.txt,a=t.j}if(401===o.status)throw Error("Session expired — sign in again to use Campaign Builder.");if(402===o.status){let e=Error(a&&(a.message||a.error)||"Insufficient tokens for this generation.");throw e.httpStatus=402,e.code=a&&a.code||"INSUFFICIENT_TOKENS",e.body=i,e}if(503===o.status){let e=Error(a&&(a.message||a.error)||"Token metering temporarily unavailable.");
1throw e.httpStatus=503,e.code=a&&a.code||"METERING_UNAVAILABLE",e.body=i,e}if(!o.ok){let t=/^\s*</.test(i),n=a&&(a.message||a.error)||(t?"timed out (HTTP ".concat(o.status,") — web search did not finish"):i.slice(0,260)),r=Error("".concat(e," ").concat(o.status,": ").concat(n));throw r.httpStatus=o.status,r.body=i,r}return a||JSON.parse(i)}let g=e=>new Promise(t=>setTimeout(t,e)),b=(e,t,a)=>Math.max(t,Math.min(a,e));function y(e){return(e||"x").toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"").slice(0,60)}let v={id:"nb2",label:"Nano Banana 2",model:"gemini-3.1-flash-image"},_={id:"nb2l",label:"Nano Banana 2 Lite",model:"gemini-3.1-flash-image-lite",fallback:v},w="gemini-3.8-flash",x="gemini-3.7-flash",k="gemini-3.8-flash",S=[{k:"ink",label:"Ink (dark ground)",def:"#101014"},{k:"paper",label:"Paper (light ground)",def:"#f7f7f5"},{k:"brand",label:"Brand",def:"#7c5dfa"},{k:"brand_alt",label:"Brand alt",def:"#4c37a8"},{k:"accent",label:"Accent (offer)",def:"#ec4899"},{k:"on_ink",label:"Text on ink",def:"#ffffff"},{k:"on_paper",label:"Text on paper",def:"#101014"},{k:"on_brand",label:"Text on brand",def:"#ffffff"}],E=["Poppins","Inter","Montserrat","Manrope","DM Sans","Playfair Display","Oswald","Bebas Neue","Anton","Noto Sans"],j={en:null,hi:"Noto Sans Devanagari",mr:"Noto Sans Devanagari",ta:"Noto Sans Tamil",te:"Noto Sans Telugu",ml:"Noto Sans Malayalam",kn:"Noto Sans Kannada",bn:"Noto Sans Bengali",hinglish:null},A={Latin:{lead:1,maxScale:1,descend:0},Devanagari:{lead:1.38,maxScale:.88,descend:.12,noTrackFit:!0},Bengali:{lead:1.38,maxScale:.88,descend:.12,noTrackFit:!0},Gurmukhi:{lead:1.34,maxScale:.9,descend:.1,noTrackFit:!0},Gujarati:{lead:1.34,maxScale:.9,descend:.1,noTrackFit:!0},Tamil:{lead:1.34,maxScale:.9,descend:.22},Telugu:{lead:1.4,maxScale:.86,descend:.2},Kannada:{lead:1.4,maxScale:.86,descend:.2},Malayalam:{lead:1.36,maxScale:.88,descend:.22},Oriya:{lead:1.36,maxScale:.88,descend:.2}};function C(e){let t=L.find(t=>t.k===e);return A[t?t.script:"Latin"]||A.Latin}let L=[{k:"en",label:"English",script:"Latin",bcp47:"en",dir:"ltr",def:!0},{k:"hinglish",label:"Hinglish",script:"Latin",bcp47:"en",dir:"ltr",note:"Hindi in Latin script — usually right for urban metro audiences"},{k:"hi",label:"Hindi",script:"Devanagari",bcp47:"hi",dir:"ltr"},{k:"mr",label:"Marathi",script:"Devanagari",bcp47:"mr",dir:"ltr"},{k:"ta",label:"Tamil",script:"Tamil",bcp47:"ta",dir:"ltr"},{k:"te",label:"Telugu",script:"Telugu",bcp47:"te",dir:"ltr"},{k:"ml",label:"Malayalam",script:"Malayalam",bcp47:"ml",dir:"ltr"},{k:"kn",label:"Kannada",script:"Kannada",bcp47:"kn",dir:"ltr"},{k:"bn",label:"Bengali",script:"Bengali",bcp47:"bn",dir:"ltr"},{k:"gu",label:"Gujarati",script:"Gujarati",bcp47:"gu",dir:"ltr"},{k:"pa",label:"Punjabi",script:"Gurmukhi",bcp47:"pa",dir:"ltr"},{k:"or",label:"Odia",script:"Oriya",bcp47:"or",dir:"ltr"}],T={headline:"Headline",banner_line:"Banner line",short_headline:"Short headline",long_headline:"Long headline",sub:"Supporting line",offer:"Offer",cta_label:"Call to action",legal_line:"Legal line",logo:"Logo"};function M(e){return L.find(t=>t.k===e)||L[0]}function N(e){return M(e).dir||"ltr"}let R={india:"IN",bharat:"IN","sri lanka":"LK",malaysia:"MY",singapore:"SG",uae:"AE","united arab emirates":"AE","saudi arabia":"SA",qatar:"QA",uk:"GB","united kingdom":"GB",usa:"US","united states":"US",canada:"CA",australia:"AU",nepal:"NP",bangladesh:"BD",pakistan:"PK"};function I(){var e;let t=String(void 0!==et&&(null==(e=et.camp)?void 0:e.market)||"India").trim().toLowerCase();return R[t]||(/^[a-z]{2}$/.test(t)?t.toUpperCase():"IN")}function O(e){return M(e).bcp47+"-"+I()}function q(e){let t=String(e||"");if(!t)return 0;try{if("undefined"!=typeof Intl&&Intl.Segmenter)return[...new Intl.Segmenter(void 0,{granularity:"grapheme"}).segment(t)].length}catch(e){}return[...t].length}let P=[{name:"primary_text",max_chars:125,truncation_note:"~125 chars before 'More'"},{name:"headline",max_chars:27,truncation_note:"truncates ~27 on mobile"},{name:"description",max_chars:27,optional:!0}],z=["Shop now","Learn more","Sign up","Book now","Get offer","Order now","Download"],B="https://support.google.com/google-ads/answer/1722096",F="https://support.google.com/google-ads/answer/7005917",U="https://www.facebook.com/business/ads-guide/image",D={registry_version:"1.0.0",verified_on:"2026-09-10",formats:[{id:"meta_feed_1x1",platform:"meta",surface:"social",placement:"FB/IG feed — square",w:1440,h:1440,min_w:600,min_h:600,max_bytes:0x1e00000,export:"jpeg",quality_ladder:[94,90,85],safe_areas:[],plate:"clean_lower@4:5",layout_class:"square",min_type_px:28,text_fields:P,cta_enum:z,source_url:U},{id:"meta_feed_4x5",platform:"meta",surface:"social",placement:"FB/IG feed — portrait",w:1440,h:1800,min_w:600,min_h:750,max_bytes:0x1e00000,export:"jpeg",quality_ladder:[94,90,85],safe_areas:[],plate:"clean_lower@4:5",layout_class:"square",min_type_px:28,text_fields:P,cta_enum:z,source_url:U},{id:"meta_story_9x16",platform:"meta",surface:"social",placement:"FB/IG story & reel",w:1440,h:2560,min_w:500,min_h:889,max_bytes:0x1e00000,export:"jpeg",quality_ladder:[94,90,85],safe_areas:[{edge:"top",px:358,why:"14
1% — profile name, handle and the top controls"},{edge:"bottom",px:896,why:"35% — caption, CTA and the Meta control bar (40% when a disclaimer runs)"},{edge:"left",px:86,why:"6% side reserve"},{edge:"right",px:86,why:"6% side reserve — the action rail sits here"}],plate:"clean_upper@9:16",layout_class:"story",min_type_px:34,text_fields:[{name:"headline",max_chars:40},{name:"primary_text",max_chars:80,optional:!0}],cta_enum:z,source_url:U},{id:"meta_right_column",platform:"meta",surface:"social",placement:"FB right column",w:1200,h:1200,min_w:254,min_h:254,max_bytes:0x1e00000,export:"jpeg",quality_ladder:[92,86,80],safe_areas:[],plate:"clean_lower@4:5",layout_class:"smallsquare",min_type_px:26,text_fields:[{name:"headline",max_chars:25}],cta_enum:z,source_url:U},{id:"ig_feed_1x1",platform:"instagram",surface:"social",placement:"IG feed — square",w:1440,h:1440,min_w:600,min_h:600,max_bytes:0x1e00000,export:"jpeg",quality_ladder:[94,90,85],safe_areas:[],plate:"clean_lower@4:5",layout_class:"square",min_type_px:28,text_fields:P,cta_enum:z,hashtags:!0,source_url:U},{id:"ig_story_9x16",platform:"instagram",surface:"social",placement:"IG story & reel",w:1440,h:2560,min_w:500,min_h:889,max_bytes:0x1e00000,export:"jpeg",quality_ladder:[94,90,85],safe_areas:[{edge:"top",px:358,why:"14% — profile name, handle and the top controls"},{edge:"bottom",px:896,why:"35% — caption, CTA and the IG control bar"},{edge:"left",px:86,why:"6% side reserve"},{edge:"right",px:86,why:"6% side reserve — the action rail sits here"}],plate:"clean_upper@9:16",layout_class:"story",min_type_px:34,text_fields:[{name:"headline",max_chars:40}],cta_enum:z,hashtags:!0,source_url:U},{id:"gdn_300x250",platform:"google_display",surface:"web",placement:"Inline rectangle",w:300,h:250,max_bytes:153600,export:"jpeg",quality_ladder:[88,80,72,64,56],safe_areas:[],plate:"clean_lower@4:5",layout_class:"smallsquare",min_type_px:11,text_fields:[{name:"banner_line",max_chars:34},{name:"cta_label",max_chars:14}],animation:{max_seconds:30,max_fps:5,must_stop:!0},source_url:B},{id:"gdn_336x280",platform:"google_display",surface:"web",placement:"Large rectangle",w:336,h:280,max_bytes:153600,export:"jpeg",quality_ladder:[88,80,72,64,56],safe_areas:[],plate:"clean_lower@4:5",layout_class:"smallsquare",min_type_px:11,text_fields:[{name:"banner_line",max_chars:34},{name:"cta_label",max_chars:14}],source_url:B},{id:"gdn_300x600",platform:"google_display",surface:"web",placement:"Half page",w:300,h:600,max_bytes:153600,export:"jpeg",quality_ladder:[88,80,72,64,56],safe_areas:[],plate:"clean_lower@9:16",layout_class:"tall",min_type_px:12,text_fields:[{name:"banner_line",max_chars:40},{name:"cta_label",max_chars:14}],source_url:B},{id:"gdn_160x600",platform:"google_display",surface:"web",placement:"Wide skyscraper",w:160,h:600,max_bytes:153600,export:"jpeg",quality_ladder:[86,78,70,62,54],safe_areas:[],plate:"clean_lower@9:16",layout_class:"tall",min_type_px:11,text_fields:[{name:"banner_line",max_chars:24},{name:"cta_label",max_chars:12}],source_url:B},{id:"gdn_728x90",platform:"google_display",surface:"web",placement:"Leaderboard",w:728,h:90,max_bytes:153600,export:"jpeg",quality_ladder:[88,80,72,64],safe_areas:[],plate:"clean_side@16:9",layout_class:"wide",min_type_px:11,text_fields:[{name:"banner_line",max_chars:40},{name:"cta_label",max_chars:14}],source_url:B},{id:"gdn_970x250",platform:"google_display",surface:"web",placement:"Billboard",w:970,h:250,max_bytes:153600,export:"jpeg",quality_ladder:[86,78,70,62,54],safe_areas:[],plate:"clean_side@16:9",layout_class:"wide",min_type_px:13,text_fields:[{name:"banner_line",max_chars:52},{name:"cta_label",max_chars:14}],source_url:B},{id:"gdn_320x50",platform:"google_display",surface:"web",placement:"Mobile banner",w:320,h:50,max_bytes:153600,export:"jpeg",quality_ladder:[86,78,70],safe_areas:[],plate:"clean_side@16:9",layout_class:"tiny",min_type_px:10,text_fields:[{name:"banner_line",max_chars:22},{name:"cta_label",max_chars:10}],source_url:B},{id:"gdn_320x100",platform:"google_display",surface:"web",placement:"Large mobile banner",w:320,h:100,max_bytes:153600,export:"jpeg",quality_ladder:[86,78,70],safe_areas:[],plate:"clean_side@16:9",layout_class:"tiny",min_type_px:11,text_fields:[{name:"banner_line",max_chars:28},{name:"cta_label",max_chars:12}],source_url:B},{id:"gdn_responsive_landscape",platform:"google_display",surface:"web",placement:"Responsive — landscape 1.91:1",w:1200,h:628,min_w:600,min_h:314,max_bytes:5242880,export:"jpeg",quality_ladder:[92,86,80],safe_areas:[],plate:"clean_side@16:9",layout_class:"wide",min_type_px:22,text_fields:[{name:"short_headline",max_chars:30,count:[1,5
1]},{name:"long_headline",max_chars:90},{name:"description",max_chars:90,count:[1,5]}],source_url:F},{id:"gdn_responsive_square",platform:"google_display",surface:"web",placement:"Responsive — square",w:1200,h:1200,min_w:300,min_h:300,max_bytes:5242880,export:"jpeg",quality_ladder:[92,86,80],safe_areas:[],plate:"clean_lower@4:5",layout_class:"square",min_type_px:24,text_fields:[{name:"short_headline",max_chars:30},{name:"long_headline",max_chars:90},{name:"description",max_chars:90}],source_url:F},{id:"youtube_display_300x250",platform:"youtube",surface:"web",placement:"YouTube display",w:300,h:250,max_bytes:153600,export:"jpeg",quality_ladder:[88,80,72,64],safe_areas:[],plate:"clean_lower@4:5",layout_class:"smallsquare",min_type_px:11,text_fields:[{name:"banner_line",max_chars:34},{name:"cta_label",max_chars:14}],source_url:B},{id:"iab_billboard_970x250",platform:"iab",surface:"web",placement:"IAB billboard",w:970,h:250,max_bytes:153600,export:"jpeg",quality_ladder:[86,78,70,62],safe_areas:[],plate:"clean_side@16:9",layout_class:"wide",min_type_px:13,text_fields:[{name:"banner_line",max_chars:52},{name:"cta_label",max_chars:14}],source_url:B},{id:"iab_half_page_300x600",platform:"iab",surface:"web",placement:"IAB half page",w:300,h:600,max_bytes:153600,export:"jpeg",quality_ladder:[88,80,72,64],safe_areas:[],plate:"clean_lower@9:16",layout_class:"tall",min_type_px:12,text_fields:[{name:"banner_line",max_chars:40},{name:"cta_label",max_chars:14}],source_url:B},{id:"iab_medium_250x250",platform:"iab",surface:"web",placement:"IAB square",w:250,h:250,max_bytes:153600,export:"jpeg",quality_ladder:[88,80,72,64],safe_areas:[],plate:"clean_lower@4:5",layout_class:"smallsquare",min_type_px:10,text_fields:[{name:"banner_line",max_chars:28},{name:"cta_label",max_chars:12}],source_url:B}]},H={meta:"Meta — Facebook",instagram:"Instagram",google_display:"Google Display",youtube:"YouTube",iab:"IAB / open web"},G={};function W(e){return(e||"clean_lower@4:5").split("@")[1]}function K(e){return(e||"clean_lower@4:5").split("@")[0]}G=Object.fromEntries(D.formats.map(e=>[e.id,e]));let J={clean_lower:{label:"clean lower",reserve:"the lower 42% of the frame",rule:"Compose so the subject, the product and every point of visual interest live in the UPPER 58% of the frame and the camera looks slightly down past them. The LOWER 42% must still be real photographed material that belongs to this same scene — a shallow-depth-of-field foreground, a plain lit surface such as a table, floor, wall, road, water, fabric or sky, or the soft shadow side of the set — carrying tone, grain and gentle light falloff, simply with nothing in it that competes for attention."},clean_upper:{label:"clean upper",reserve:"the upper 38% and the lower 18%",rule:"Compose so the subject and the product sit across the MIDDLE band of a tall frame. Above them, the photograph continues into open depth — sky, a far wall, ceiling light, out-of-focus interior, atmosphere — and below them it continues into a near surface or defocused foreground. Both of those regions must remain real photographed material from this same scene, with tone, grain and light falloff, simply with nothing in them that competes for attention."},clean_side:{label:"clean side",reserve:"the right 62% of the frame",rule:"Compose so the subject and the product sit in the LEFT 38% of the frame and the camera is offset to leave the room open to the right. The RIGHT 62% must still be real photographed material from this same scene — receding wall, open space, defocused depth, sky, a plain lit surface — carrying tone, grain and gentle light falloff, simply with nothing in it that competes for attention."},full_scene:{label:"full scene",reserve:"nothing",rule:"Compose the full frame freely — this plate carries no added type beyond a small logo."}},Y={feed_open:{label:"Open frame — photograph, type on a full-frame grade",family:"clean_lower",zones:[{role:"plate",x:0,y:0,w:1,h:1,fit:"cover",anchor:"top"},{role:"scrim",x:0,y:0,w:1,h:1,gradient:["ink@0","[email protected]"],adaptive:!0},{role:"logo",x:.07,y:.05,w:.22,h:.05,asset:"mono_light",align:"left",knockout:!0},{role:"text",x:.07,y:.585,w:.84,h:.175,field:"headline",font:"display",weight:800,min_px:.052,max_px:.105,max_lines:3,color:"on_ink",case:!0,leading:.99},{role:"text",x:.07,y:.782,w:.74,h:.05,field:"sub",font:"text",weight:400,min_px:.024,max_px:.034,max_lines:1,color:"[email protected]",optional:!0},{role:"text",x:.07,y:.848,w:.62,h:.04,field:"offer",font:"display",weight:800,min_px:.022,max_px:.032,max_lines:1,color:"accent",optional:!0},{role:"cta",x:.07,y:.902,w:.58,h:.044,field:"cta_label",font:"display",weight:700,min_px:.022,max_px:.032,optional:!0},{role:"text",x:.07,y:.955,w:.86,h:.026,field:"legal_line",font:"text",weight:400,min_px:.012,max_px:.017,max_lines:1,color:"[email protected]",optional:!0,fine_print:!0}]},small_open:{label:"Small square — open frame",family:"clean_lower",zone
1s:[{role:"plate",x:0,y:0,w:1,h:1,fit:"cover",anchor:"top"},{role:"scrim",x:0,y:0,w:1,h:1,gradient:["ink@0","[email protected]"],adaptive:!0},{role:"logo",x:.06,y:.045,w:.42,h:.11,asset:"mono_light",align:"left",knockout:!0},{role:"text",x:.06,y:.545,w:.88,h:.215,field:"banner_line",font:"display",weight:800,min_px:.054,max_px:.115,max_lines:2,color:"on_ink",case:!0,leading:1},{role:"text",x:.06,y:.79,w:.6,h:.062,field:"offer",font:"display",weight:800,min_px:.034,max_px:.052,max_lines:1,color:"accent",optional:!0},{role:"cta",x:.06,y:.872,w:.72,h:.078,field:"cta_label",font:"display",weight:700,min_px:.04,max_px:.062,optional:!0}]},tall_open:{label:"Tall — open frame",family:"clean_lower",zones:[{role:"plate",x:0,y:0,w:1,h:1,fit:"cover",anchor:"center"},{role:"scrim",x:0,y:0,w:1,h:1,gradient:["ink@0","[email protected]"],adaptive:!0},{role:"logo",x:.08,y:.028,w:.68,h:.058,asset:"mono_light",align:"left",knockout:!0},{role:"text",x:.08,y:.578,w:.84,h:.19,field:"banner_line",font:"display",weight:800,min_px:.032,max_px:.066,max_lines:3,color:"on_ink",case:!0,leading:1.04,anchor:"bottom"},{role:"text",x:.08,y:.788,w:.8,h:.042,field:"sub",font:"text",weight:400,min_px:.017,max_px:.026,max_lines:1,color:"[email protected]",optional:!0},{role:"text",x:.08,y:.842,w:.7,h:.042,field:"offer",font:"display",weight:800,min_px:.017,max_px:.028,max_lines:1,color:"accent",optional:!0},{role:"cta",x:.08,y:.9,w:.8,h:.05,field:"cta_label",font:"display",weight:700,min_px:.02,max_px:.032,optional:!0}]},story_open:{label:"Story — open frame inside the 14%/35% reserves",family:"clean_upper",zones:[{role:"plate",x:0,y:0,w:1,h:1,fit:"cover",anchor:"center"},{role:"scrim",x:0,y:0,w:1,h:1,gradient:["[email protected]","ink@0"],adaptive:!0},{role:"logo",x:.065,y:.15,w:.26,h:.04,asset:"mono_light",align:"left",knockout:!0},{role:"text",x:.065,y:.208,w:.83,h:.15,field:"headline",font:"display",weight:800,min_px:.026,max_px:.056,max_lines:3,color:"on_ink",case:!0,leading:1.02},{role:"text",x:.065,y:.378,w:.76,h:.042,field:"sub",font:"text",weight:400,min_px:.013,max_px:.021,max_lines:1,color:"[email protected]",optional:!0},{role:"text",x:.065,y:.434,w:.62,h:.038,field:"offer",font:"display",weight:800,min_px:.013,max_px:.022,max_lines:1,color:"accent",optional:!0},{role:"cta",x:.065,y:.488,w:.58,h:.042,field:"cta_label",font:"display",weight:700,min_px:.013,max_px:.021,optional:!0},{role:"text",x:.065,y:.562,w:.86,h:.022,field:"legal_line",font:"text",weight:400,min_px:.008,max_px:.012,max_lines:1,color:"[email protected]",optional:!0,fine_print:!0}]},wide_open:{label:"Wide — open frame, type right",family:"clean_side",zones:[{role:"plate",x:0,y:0,w:1,h:1,fit:"cover",anchor:"left"},{role:"scrim",x:0,y:0,w:1,h:1,gradient:["ink@0","[email protected]"],dir:"x",adaptive:!0},{role:"logo",x:.42,y:.115,w:.2,h:.22,asset:"mono_light",align:"left",knockout:!0},{role:"text",x:.42,y:.4,w:.36,h:.3,field:"short_headline",font:"display",weight:800,min_px:.075,max_px:.15,max_lines:2,color:"on_ink",case:!0,leading:1.02},{role:"text",x:.42,y:.745,w:.36,h:.095,field:"sub",font:"text",weight:400,min_px:.042,max_px:.058,max_lines:1,color:"[email protected]",optional:!0},{role:"cta",x:.815,y:.395,w:.165,h:.24,field:"cta_label",font:"display",weight:700,min_px:.085,max_px:.15,optional:!0}]},feed_split:{label:"Editorial split — photograph 5/7, brand field 2/7",family:"clean_lower",zones:[{role:"fill",x:0,y:0,w:1,h:1,bg:"brand"},{role:"plate",x:0,y:0,w:1,h:.7142857,fit:"cover",anchor:"top"},{role:"logo",x:.07,y:.748,w:.24,h:.04,asset:"mono_light",align:"left",knockout:!0},{role:"text",x:.07,y:.806,w:.86,h:.1,field:"headline",font:"display",weight:800,min_px:.04,max_px:.082,max_lines:2,color:"on_brand",case:!0,leading:1},{role:"text",x:.07,y:.916,w:.62,h:.036,field:"offer",font:"display",weight:800,min_px:.02,max_px:.03,max_lines:1,color:"on_brand",optional:!0},{role:"cta",x:.7,y:.912,w:.24,h:.04,field:"cta_label",font:"display",weight:700,min_px:.019,max_px:.028,color:"on_brand",optional:!0},{role:"text",x:.07,y:.962,w:.86,h:.024,field:"legal_line",font:"text",weight:400,min_px:.011,max_px:.016,max_lines:1,color:"[email protected]",optional:!0,fine_print:!0}]},small_split:{label:"Small square — split 5/8",family:"clean_lower",zone
1s:[{role:"fill",x:0,y:0,w:1,h:1,bg:"brand"},{role:"plate",x:0,y:0,w:1,h:.625,fit:"cover",anchor:"top"},{role:"logo",x:.06,y:.655,w:.5,h:.105,asset:"mono_light",align:"left",knockout:!0},{role:"text",x:.06,y:.778,w:.88,h:.104,field:"banner_line",font:"display",weight:800,min_px:.048,max_px:.096,max_lines:2,color:"on_brand",case:!0,leading:1},{role:"cta",x:.06,y:.898,w:.6,h:.076,field:"cta_label",font:"display",weight:700,min_px:.034,max_px:.05,color:"on_brand",optional:!0}]},tall_split:{label:"Tall — split 5/8",family:"clean_lower",zones:[{role:"fill",x:0,y:0,w:1,h:1,bg:"brand"},{role:"plate",x:0,y:0,w:1,h:.625,fit:"cover",anchor:"center"},{role:"logo",x:.09,y:.652,w:.66,h:.05,asset:"mono_light",align:"left",knockout:!0},{role:"text",x:.09,y:.722,w:.84,h:.135,field:"banner_line",font:"display",weight:800,min_px:.026,max_px:.054,max_lines:4,color:"on_brand",case:!0,leading:1.06},{role:"text",x:.09,y:.872,w:.7,h:.038,field:"offer",font:"display",weight:800,min_px:.015,max_px:.026,max_lines:1,color:"on_brand",optional:!0},{role:"cta",x:.09,y:.926,w:.8,h:.048,field:"cta_label",font:"display",weight:700,min_px:.018,max_px:.03,color:"on_brand",optional:!0}]},story_split:{label:"Story — photograph 2/5 over a brand field, inside the reserves",family:"clean_upper",zones:[{role:"fill",x:0,y:0,w:1,h:1,bg:"brand"},{role:"plate",x:0,y:0,w:1,h:.42,fit:"cover",anchor:"center"},{role:"logo",x:.065,y:.15,w:.26,h:.038,asset:"mono_light",align:"left",knockout:!0},{role:"text",x:.065,y:.455,w:.86,h:.105,field:"headline",font:"display",weight:800,min_px:.024,max_px:.048,max_lines:3,color:"on_brand",case:!0,leading:1.04},{role:"text",x:.065,y:.575,w:.76,h:.03,field:"sub",font:"text",weight:400,min_px:.012,max_px:.018,max_lines:1,color:"[email protected]",optional:!0},{role:"cta",x:.065,y:.614,w:.56,h:.034,field:"cta_label",font:"display",weight:700,min_px:.013,max_px:.02,color:"on_brand",optional:!0}]},wide_split:{label:"Wide — photograph 3/8 left, brand field right",family:"clean_side",zones:[{role:"fill",x:0,y:0,w:1,h:1,bg:"brand"},{role:"plate",x:0,y:0,w:.375,h:1,fit:"cover",anchor:"center"},{role:"logo",x:.415,y:.115,w:.19,h:.21,asset:"mono_light",align:"left",knockout:!0},{role:"text",x:.415,y:.4,w:.38,h:.32,field:"banner_line",font:"display",weight:800,min_px:.1,max_px:.26,max_lines:2,color:"on_brand",case:!0,leading:1},{role:"cta",x:.825,y:.4,w:.155,h:.24,field:"cta_label",font:"display",weight:700,min_px:.08,max_px:.14,color:"on_brand",optional:!0}]},tiny_bar:{label:"Tiny bar — logo, line, CTA type",family:"clean_side",zones:[{role:"fill",x:0,y:0,w:1,h:1,bg:"brand"},{role:"plate",x:0,y:0,w:.16,h:1,fit:"cover",anchor:"center"},{role:"logo",x:.185,y:.17,w:.32,h:.64,asset:"mono_light",align:"left",knockout:!0},{role:"text",x:.525,y:.18,w:.24,h:.62,field:"banner_line",font:"display",weight:800,min_px:.15,max_px:.32,max_lines:2,color:"on_brand",case:!0,leading:1.02},{role:"cta",x:.775,y:.24,w:.205,h:.52,field:"cta_label",font:"display",weight:700,min_px:.12,max_px:.22,color:"on_brand",optional:!0,arrow:!1}]}},V=[{k:"open",label:"Open frame (recommended)",note:"the photograph fills the frame and the type sits on it under a grade that spans the whole picture — no panel, no band, no boundary anywhere"},{k:"split",label:"Editorial split",note:"the photograph takes a whole fraction of the frame and a flat brand-colour field takes the rest, with one hard straight edge between them"}],$={square:{open:"feed_open",split:"feed_split"},story:{open:"story_open",split:"story_split"},smallsquare:{open:"small_open",split:"small_split"},tall:{open:"tall_open",split:"tall_split"},wide:{open:"wide_open",split:"wide_split"},tiny:{open:"tiny_bar",split:"tiny_bar"}};function X(e,t){var a;if(e.template&&Y[e.template])return Y[e.template];let n=$[e.layout_class||"square"]||$.square;return Y[n[t||(void 0!==et?null==(a=et.plan)?void 0:a.style:null)||"open"]]||Y[n.open]||Y.feed_open}let Q=[{k:"pathos",label:"Pathos — emotion",emoji:"❤️",blurb:"Ads that move first and argue second. Strong at launch, occasion and awareness."},{k:"ethos",label:"Ethos — credibility",emoji:"\uD83C\uDF93",blurb:"Borrowed or earned authority. Every device here is evidence-bound — no evidence, no device."},{k:"logos",label:"Logos — logic",emoji:"\uD83E\uDDEE",blurb:"Facts, specs, maths and demonstration. Strongest where price or doubt is the objection."}
1,{k:"self",label:"Self-persuasion",emoji:"\uD83E\uDE9E",blurb:"The reader convinces themselves. Strong at consideration; weak on cold traffic."}],Z=[{key:"pathos_nostalgia",appeal:"pathos",label:"Nostalgia",emoji:"\uD83D\uDD70",what:"Anchors the product to a remembered, warmer version of the same moment.",needs:[],fits:{objectives:["awareness","launch","consideration"],good:["food","home","apparel","confectionery","media","travel"],bad:["fintech","saas","insurance","b2b"]},copy_shape:"a remembered moment named plainly, product as the way back to it",family:"clean_lower",plate_direction:"a warm, lived-in domestic moment with the product naturally present; slightly aged colour, soft window light, real clutter, nothing styled or luxurious",brief_hint:"which remembered moment — school tiffin, first salary, grandmother's kitchen"},{key:"pathos_belonging",appeal:"pathos",label:"Belonging / in-group",emoji:"\uD83E\uDEC2",what:"Shows the group the buyer wants to be inside, using the product as the membership token.",needs:[],fits:{objectives:["awareness","consideration","launch"],good:["apparel","sports","beverage","gaming","fitness","education"],bad:["insurance","pharma"]},copy_shape:"second-person plural, the group named, no hard sell",family:"clean_lower",plate_direction:"a real group of people mid-activity, unposed, shared attention, the product in genuine use inside the group",brief_hint:"which group, doing what"},{key:"pathos_occasion",appeal:"pathos",label:"Festival & occasion",emoji:"\uD83E\uDE94",what:"Ties the purchase to a dated cultural moment that supplies its own urgency.",needs:[],fits:{objectives:["offer","launch","conversion","awareness"],good:["apparel","jewellery","food","electronics","home","gifting"],bad:["b2b","saas"]},copy_shape:"occasion named, product as the thing that completes it",family:"clean_lower",plate_direction:"the occasion's real visual language for the stated market — authentic decor, dress and light, never generic festive stock imagery",brief_hint:"which festival or occasion, and which market's version of it"},{key:"pathos_relief",appeal:"pathos",label:"Relief from a named friction",emoji:"\uD83D\uDE2E‍\uD83D\uDCA8",what:"Names one specific daily irritation and shows it gone.",needs:["objections"],fits:{objectives:["conversion","consideration","retargeting"],good:["appliance","home","saas","service","personal_care","tooling"],bad:["jewellery","gifting"]},copy_shape:"the friction in the buyer's own words, then the release",family:"clean_lower",plate_direction:"the exact moment after the friction is gone — a visibly easier posture, the product mid-use, ordinary real light",brief_hint:"the one friction to name"},{key:"pathos_pride",appeal:"pathos",label:"Pride of ownership",emoji:"✨",what:"Frames the product as something the buyer will be seen owning.",needs:[],fits:{objectives:["awareness","launch","consideration"],good:["electronics","apparel","auto","watches","jewellery","home"],bad:["insurance","pharma","commodity"]},copy_shape:"restrained, short, no adjectives stacked",family:"clean_lower",plate_direction:"the product held or worn with quiet confidence, one strong directional light, deep clean background, no crowd",brief_hint:"seen by whom, in what setting"},{key:"pathos_joy",appeal:"pathos",label:"Joy-of-use",emoji:"\uD83D\uDE4C",what:"Sells the feeling of the product working, not its specification.",needs:[],fits:{objectives:["awareness","consideration","launch"],good:["food","beverage","personal_care","appliance","toys","apparel"],bad:["insurance","b2b"]},copy_shape:"sensory verb led, present tense",family:"clean_lower",plate_direction:"the peak sensory instant of use — steam, pour, snap, stretch, first bite — caught mid-action with honest physics",brief_hint:"the exact instant to catch"},{key:"pathos_regret",appeal:"pathos",label:"Anticipated regret",emoji:"⏳",what:"Makes the cost of not acting concrete. Gated: needs a real deadline or real stock limit.",needs:["offer"],fits:{objectives:["offer","conversion","retargeting"],good:["travel","apparel","electronics","events","education"],bad:["pharma","health","finance","kids"]},copy_shape:"the deadline stated as fact, never manufactured panic",family:"clean_lower",plate_direction:"the product in the moment just before it is gone — an emptying shelf, a closing door — restrained, never alarming",brief_hint:"the real deadline or real limit"},{key:"ethos_founder",appeal:"ethos",label:"Founder-led",emoji:"\uD83E\uDDD1‍\uD83C\uDF73",what:"The person who made it stands behind it. Needs no external proof.",needs:[],fits:{objectives:["awareness","consideration","launch"],good:["food","personal_care","apparel","craft","service","d2c"],bad:["commodity"]},copy_shape:"first person, specific, one concrete decision they made",family:"clean_lower",plate_direction:"one person in their real workplace with the product, hands visible and working, documentary light, no studio polish",brief_hint:"the founder's one concrete claim about how 
1it is made"},{key:"ethos_craft",appeal:"ethos",label:"Craft / process proof",emoji:"\uD83E\uDEB5",what:"Shows how it is made so the quality argues itself. Needs no external proof.",needs:[],fits:{objectives:["awareness","consideration"],good:["food","apparel","furniture","personal_care","craft","beverage"],bad:["saas","fintech"]},copy_shape:"process step named with its real duration or material",family:"clean_lower",plate_direction:"a single real production step mid-motion — hands, tools, material — honest workshop light, no lab coats, no clipboards",brief_hint:"which process step is the actual proof"},{key:"ethos_expert",appeal:"ethos",label:"Named expert / practitioner",emoji:"\uD83E\uDE7A",what:"A real, named, rights-cleared practitioner endorses it.",needs:[],evidence_required:"expert",fits:{objectives:["consideration","conversion"],good:["health","personal_care","pharma","pet","nutrition","dental"],bad:["gaming","gifting"]},copy_shape:"the expert named with their real credential, claim quoted verbatim",family:"clean_lower",plate_direction:"a professional setting appropriate to the real named practitioner's field, the product present and in scale — no invented uniforms, badges or diplomas",brief_hint:"the named expert and the exact quoted line"},{key:"ethos_clinical",appeal:"ethos",label:"Clinical / lab result",emoji:"\uD83E\uDDEA",what:"A real study or test result, quoted as written.",needs:[],evidence_required:"clinical",fits:{objectives:["consideration","conversion"],good:["health","personal_care","nutrition","appliance","auto","battery"],bad:["apparel","gifting"]},copy_shape:"the number, the sample, the source — nothing rounded up",family:"clean_lower",plate_direction:"the product photographed with clinical clarity on a clean neutral ground — no glassware, no microscopes, no charts, no lab coats",brief_hint:"which result, and the sample size as published"},{key:"ethos_certification",appeal:"ethos",label:"Certification / seal",emoji:"\uD83D\uDEE1",what:"A real certification mark the client can produce.",needs:[],evidence_required:"certification",fits:{objectives:["consideration","conversion"],good:["food","electrical","appliance","toys","auto","construction","organic"],bad:["media","gaming"]},copy_shape:"the mark named exactly, with its standard number",family:"clean_lower",plate_direction:"the product on a clean confident ground with room for the real certification mark to be placed by the compositor — never draw a seal, badge or laurel",brief_hint:"which certification, and where the mark file is"},{key:"ethos_award",appeal:"ethos",label:"Award",emoji:"\uD83C\uDFC6",what:"A real award, named with its year and body.",needs:[],evidence_required:"award",fits:{objectives:["awareness","consideration"],good:["media","food","auto","appliance","apparel","service"],bad:["pharma"]},copy_shape:"award, body and year, no embellishment",family:"clean_lower",plate_direction:"the product presented cleanly with space for the real award mark — never draw a trophy, medal, laurel or badge",brief_hint:"award name, body, year"},{key:"ethos_longevity",appeal:"ethos",label:"Longevity",emoji:"\uD83D\uDCC5",what:"Time in business as the credential.",needs:[],evidence_required:"longevity",fits:{objectives:["awareness","consideration"],good:["food","apparel","jewellery","service","auto","finance","education"],bad:["saas"]},copy_shape:"the founding year stated, one line on what has not changed",family:"clean_lower",plate_direction:"the product with one honest visual marker of continuity — a worn tool, an old storefront, a hand that has done this a long time",brief_hint:"the year, and the thing that hasn't changed"},{key:"ethos_crowd",appeal:"ethos",label:"Crowd proof",emoji:"\uD83D\uDC65",what:"A real customer or user count.",needs:[],evidence_required:"crowd",fits:{objectives:["consideration","conversion","retargeting"],good:["saas","d2c","service","app","education","finance"],bad:["luxury"]},copy_shape:"the number as supplied, with what those people did",family:"clean_lower",plate_direction:"many real people in one honest frame using or holding the product — never draw star ratings, review counts or testimonial cards",brief_hint:"the real number and its source"},{key:"ethos_creator",appeal:"ethos",label:"Creator endorsement",emoji:"\uD83C\uDFA4",what:"A real creator with a rights agreement.",needs:[],evidence_required:"creator",fits:{objectives:["awareness","consideration","conversion"],good:["apparel","beauty","gaming","food","app","fitness"],bad:["insurance","pharma"]},copy_shape:"the creator's own register, their name shown",family:"full_scene",plate_direction:"a single creator-style frame — phone-height, real room, direct address — the product in hand and legible",brief_hint:"creator name and the rights basis"},{key:"logos_comparison",appeal:"logos",label:"Head-to-head comparison",emoji:"⚖",what:"This versus the alternative on one axis that matters.",needs:["features"],fits:{objectives:["consideration","conversion"],good:["appliance","electronics","saas","auto","tooling","finance"],bad:["gifting","confectionery"]}
1,copy_shape:"one axis, two values, no adjectives",family:"clean_lower",plate_direction:"the product beside the honest alternative it replaces, same light and scale for both, no labels or arrows drawn in",brief_hint:"the one axis to compare on"},{key:"logos_spec_hero",appeal:"logos",label:"Spec hero",emoji:"\uD83D\uDCD0",what:"One specification, made the whole argument.",needs:["features"],fits:{objectives:["consideration","conversion","launch"],good:["electronics","appliance","auto","tooling","battery","saas"],bad:["food","gifting","apparel"]},copy_shape:"the number first, the unit stated, one clause of consequence",family:"clean_lower",plate_direction:"the product shot so the spec is physically visible — the port, the capacity, the thickness — hard directional light, clean ground",brief_hint:"which single spec carries it"},{key:"logos_cost_per_use",appeal:"logos",label:"Cost-per-use maths",emoji:"\uD83E\uDDFE",what:"Reframes price as a per-use or per-day number the buyer can accept.",needs:["price"],fits:{objectives:["conversion","consideration","offer"],good:["appliance","subscription","tooling","durable","education","saas"],bad:["impulse","gifting","confectionery"]},copy_shape:"number-led, unit stated, no adjectives",family:"clean_lower",plate_direction:"the product in ordinary repeated use, domestic real light, nothing aspirational or styled",brief_hint:"which unit divides best — per day, per wash, per cup"},{key:"logos_demo",appeal:"logos",label:"Demonstration / proof shot",emoji:"\uD83D\uDD2C",what:"A single filmable interaction that proves the claim.",needs:[],fits:{objectives:["consideration","conversion","launch"],good:["appliance","personal_care","tooling","cleaning","food","auto"],bad:["finance","insurance"]},copy_shape:"what is happening, stated flatly",family:"clean_lower",plate_direction:"the single most convincing physical interaction mid-action, honest physics, product doing real work — no split screens, no arrows, no before/after panels",brief_hint:"the one interaction that proves it"},{key:"logos_mechanism",appeal:"logos",label:"Mechanism / ingredient",emoji:"⚙",what:"Explains why it works, in one visual idea.",needs:["features"],fits:{objectives:["consideration","awareness"],good:["personal_care","nutrition","appliance","auto","cleaning","saas"],bad:["gifting","apparel"]},copy_shape:"the mechanism named in plain words, one consequence",family:"clean_lower",plate_direction:"the working part or key ingredient shown in real macro detail beside the product — no diagrams, no callouts, no cutaway graphics",brief_hint:"which part or ingredient does the work"},{key:"logos_before_after",appeal:"logos",label:"Before → after",emoji:"\uD83D\uDD00",what:"The change, in one frame. Category-gated — health and finance treat this as a regulated claim.",needs:[],fits:{objectives:["conversion","consideration"],good:["cleaning","home","auto","tooling","repair","garden"],bad:["health","pharma","finance","weight","beauty"]},copy_shape:"the change described in measurable terms",family:"clean_lower",plate_direction:"one object mid-transition so both states are true in a single honest frame — identical light and angle either side, no split-panel graphic",brief_hint:"what changes, and how it is measured"},{key:"self_situation",appeal:"self",label:"Situational identification",emoji:"\uD83D\uDCCD",what:"Describes a situation so precisely the right buyer recognises themselves.",needs:[],fits:{objectives:["consideration","retargeting","awareness"],good:["saas","service","home","apparel","fitness","education","finance"],bad:[]},copy_shape:"'if you…' opening, the situation exact, no promise yet",family:"clean_lower",plate_direction:"the situation itself, product incidental and honest in frame, documentary light, a real room",brief_hint:"the situation, as specifically as possible"},{key:"self_question",appeal:"self",label:"Question headline",emoji:"❓",what:"Asks the question whose answer is the purchase.",needs:[],fits:{objectives:["consideration","awareness","retargeting"],good:["saas","finance","education","health","service","insurance"],bad:["confectionery"]}
1,copy_shape:"one question, answerable yes or no, never rhetorical filler",family:"clean_lower",plate_direction:"a single image that poses the question visually — an unfinished action, a choice mid-air — product present, no text",brief_hint:"the question worth asking"},{key:"self_which_one",appeal:"self",label:"Which one are you",emoji:"\uD83D\uDD24",what:"Two or three recognisable types; the buyer picks and self-sorts.",needs:["variants"],fits:{objectives:["consideration","awareness"],good:["apparel","beauty","food","beverage","app","education"],bad:["b2b","insurance"]},copy_shape:"the types named in the audience's own words",family:"clean_lower",plate_direction:"two or three honest variants side by side, identical light and scale, nothing labelled",brief_hint:"the two or three types"},{key:"self_checklist",appeal:"self",label:"Checklist",emoji:"☑",what:"A short list the buyer silently ticks.",needs:["features"],fits:{objectives:["consideration","conversion"],good:["saas","service","finance","home","tooling","education"],bad:["confectionery","gifting"]},copy_shape:"three items, parallel grammar, no adjectives",family:"clean_lower",plate_direction:"a clean confident product frame with generous quiet space for a short list — no ticks, boxes or lists drawn in",brief_hint:"the three items"}
1,{key:"self_choose",appeal:"self",label:"Choose-your-variant",emoji:"\uD83C\uDF9B",what:"Makes the decision about which, not whether.",needs:["variants"],fits:{objectives:["conversion","consideration","offer"],good:["apparel","beauty","food","electronics","furniture"],bad:["insurance","saas"]},copy_shape:"the choice offered, price parity implied",family:"clean_lower",plate_direction:"the variant range laid out honestly, identical angle and light for each, evenly spaced",brief_hint:"which variants to show"},{key:"self_unfinished",appeal:"self",label:"Unfinished thought",emoji:"…",what:"Stops one beat early so the reader completes it.",needs:[],fits:{objectives:["awareness","consideration"],good:["media","apparel","beverage","app","travel"],bad:["pharma","finance","insurance","health"]},copy_shape:"a line that stops one beat early, no ellipsis gimmick",family:"clean_lower",plate_direction:"an image caught one instant before its resolution — the pour before it lands, the step before the door",brief_hint:"what the reader should finish"}],ee=Object.fromEntries(Z.map(e=>[e.key,e])),et={intakeMode:"url",brand:{brand_slug:"",brand_name:"",legal_name:"",logo:{},palette:{},type:{},voice:{},legal:{},evidence:[]},product:{url:"",images:[],spec:{}},claims:[],camp:{objective:"conversion",audience:"",market:"India",competitor:"",campaign:""},fit:{},sel:{},plan:{variations:1,tier:"final",langs:["en"],formats:[],style:"open",scope:"approved",typo:{}},plates:[],copy:{},creatives:[],brief:{},refs:{read:null,applied:{}},running:!1,abort:!1},ea={gemini:""};function en(){return{brand_slug:"",brand_name:"",legal_name:"",logo:{},palette:{},type:{},voice:{},legal:{},evidence:[],_dirty:!1,_id:null}}function eo(){et.brand=en(),S.forEach(e=>{et.brand.palette[e.k]=e.def}),et.product={url:"",images:[],spec:{}},et.claims=[],et.fit={},et.sel={},et.refs={read:null,applied:{}},et.brief={},eA&&Object.keys(eA).forEach(e=>delete eA[e]),"object"==typeof nL&&Object.keys(nL).forEach(e=>delete nL[e]),ei=""}let ei="";function er(){return i("in_url")&&(et.product.url=(i("in_url").value||"").trim()),et.product.spec=n0(et.product.spec),et.product.spec.page_url=et.product.url||"",i("in_notes")&&(et.product.spec.intake_notes=(i("in_notes").value||"").trim()),et.product.url}function el(e,t,a){let n=i(e);n&&(n.className="status-line show "+(a||"run"),n.innerHTML=t,t||(n.className="status-line")),t&&ec(t)}let es=null;function ec(e,t,a){if(!u())return;let n=String(e||"").replace(/<[^>]+>/g," ").replace(/\s+/g," ").trim().slice(0,490);n&&(clearTimeout(es),es=setTimeout(()=>{let e={message:n};null!=t&&(e.progress_done=t),null!=a&&(e.progress_total=a),nV("job.progress",e).catch(()=>{})},700))}function ed(e){return(e||"").trim().replace(/^```(?:json)?\s*/i,"").replace(/```\s*$/,"").trim()}function ep(e){let t=ed(e),a=t.indexOf("{"),n=t.lastIndexOf("}");return JSON.parse(a>=0?t.slice(a,n+1):t)}function eu(e){let t=e.indexOf("{");if(t<0)return"";let a=0,n=!1,o=!1;for(let i=t;i<e.length;i++){let r=e[i];if(n){if(o){o=!1;continue}if("\\"===r){o=!0;continue}'"'===r&&(n=!1);continue}if('"'===r){n=!0;continue}if("{"===r)a++;else if("}"===r&&!--a)return e.slice(t,i+1)}return e.slice(t)}function eh(e){let t=e=>e.replace(/\[\s*\d+(?:\s*,\s*\d+)*\s*\](?=\s*[,}\]\n])/g,"").replace(/[\u00b9\u00b2\u00b3\u2070-\u209f]/g,""),a="",n="",o=!1,i=!1;for(let r=0;r<e.length;r++){let l=e[r];if(o){if(a+=l,i){i=!1;continue}if("\\"===l){i=!0;continue}'"'===l&&(o=!1);continue}if('"'===l){a+=t(n),n="",a+=l,o=!0;continue}n+=l}return(a+=t(n)).replace(/,(\s*[}\]])/g,"$1")}function em(e){let t="",a=!1,n=!1,o=!1;for(let i=0;i<e.length;i++){let r=e[i];if(a){if(n){t+=r,n=!1;continue}if("\\"===r){t+=r,n=!0;continue}if('"'===r){t+=r,a=!1;continue}if("\n"===r){t+="\\n",o=!0;continue}if("\r"===r){t+="\\r",o=!0;continue}if("	"===r){t+="\\t",o=!0;continue}let e=r.charCodeAt(0);if(e<32){t+="\\u"+e.toString(16).padStart(4,"0"),o=!0;continue}t+=r;continue}if('"'===r){a=!0,t+=r;continue}t+=r}return o?t:e}function ef(e){let t=String(null==e?"":e),a=(t.match(/```(?:json)?\s*([\s\S]*?)```/i)||[])[1],n=e=>{let t=eu(e);return[e,t,em(t),eh(t),em(eh(t)),eh(em(t))]},o=[];a&&o.push(...n(a)),o.push(...n(ed(t)));let i=null;for(let e of o)if(e&&e.trim())try{return JSON.parse(e)}catch(e){i=e}
1let r=Error("the model's answer was not valid JSON"+(i?" ("+i.message+")":""));throw r.raw=t.slice(0,1800),r}function eg(e){return new Promise((t,a)=>{let n=new FileReader;n.onload=()=>t(n.result),n.onerror=a,n.readAsDataURL(e)})}function eb(e){return/^https?:\/\//i.test(String(e||""))}function ey(){let e=void 0!==nY&&nY.info&&nY.info.media_key||"";return e?"&mk=".concat(encodeURIComponent(e)):""}function ev(e){return eb(e)&&void 0!==nY&&nY.ok?"".concat(nY.base,"?a=proxy&u=").concat(encodeURIComponent(e)).concat(ey()):e}async function e_(e){if(/^data:/.test(e))return e;let t=eb(e)?[ev(e),e]:[e],a=null;for(let e of t)try{let t=/\/api\/v1\/micro-ads\/campaign-builder/.test(String(e)),a=await fetch(e,t?{headers:h()}:{});if(!a.ok)throw Error("HTTP "+a.status);return await eg(await a.blob())}catch(e){a=e}throw Error("could not read that image ("+(a?a.message:"unknown")+"). If it is on a brand CDN, the API proxy handles it — check ⚙ Diagnostics.")}function ew(e){let t=String(e).match(/^data:(.+?);base64,(.*)$/);return t?{inline_data:{mime_type:t[1],data:t[2]}}:null}let ex=w;async function ek(e){let{json:t=!0,tools:a=null,thinking:n="low",model:o=null,search:i=!1,temp:r=null,maxTokens:l=null}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};ea.gemini=ea.gemini||"server";let s=l||(i||"high"===n?65536:16384),c=async function(o){var l,c;let d=arguments.length>1&&void 0!==arguments[1]?arguments[1]:n,p={contents:[{parts:Array.isArray(e)?e:[{text:e}]}],generationConfig:{temperature:null===r?.9:r,maxOutputTokens:s}};i?p.tools=[{google_search:{}}]:a?p.tools=a:t&&(p.generationConfig.responseMimeType="application/json"),/^gemini-3/.test(o)&&(p.generationConfig.thinkingConfig={thinkingLevel:d});let u=await f(o,p),h=(null==(l=u.candidates)?void 0:l[0])||{},m=((null==(c=h.content)?void 0:c.parts)||[]).map(e=>e.text||"").join(""),g=h.finishReason||"";if(g&&"STOP"!==g&&"MAX_TOKENS"!==g){let e=Error("".concat(o," stopped early (").concat(g,")")+("SAFETY"===g||"PROHIBITED_CONTENT"===g?" — the prompt or the page tripped a safety filter":""));throw e.finishReason=g,e.partial=m,e}if("MAX_TOKENS"===g){let e=u.usageMetadata||{},t=Error("".concat(o," ran out of output budget after ").concat(e.candidatesTokenCount||"?"," answer tokens")+(e.thoughtsTokenCount?" and ".concat(e.thoughtsTokenCount," thinking tokens"):"")+" of ".concat(s," — the answer is cut off, not malformed."));throw t.finishReason="MAX_TOKENS",t.partial=m,t.budget=s,t}if(!i)return m;let b=h.groundingMetadata||{};return{text:m,sources:(b.groundingChunks||[]).map(e=>{var t,a;return{title:(null==(t=e.web)?void 0:t.title)||"",uri:(null==(a=e.web)?void 0:a.uri)||""}}).filter(e=>e.title||e.uri),queries:b.webSearchQueries||[],model:o}},d=o||ex;try{return await c(d)}catch(t){let e=504===t.httpStatus||502===t.httpStatus||408===t.httpStatus||/timed out|gateway timeout|504/i.test(String(t.message||""));if(i&&"high"===n&&e)return await c(d,"low");if(!o&&d!==x&&(404===t.httpStatus||/not\s*found|unknown model|not supported/i.test(t.body||"")))return ex=x,await c(x);throw t}}async function eS(e,t,a,n){ea.gemini=ea.gemini||"server";let o="draft"===n?_:v,i=async n=>{var o,i,r,l,s;let c={contents:[{parts:[{text:e},...t||[]]}],generationConfig:{responseModalities:["IMAGE"],imageConfig:{aspectRatio:a}}},d=await f(n,c),p=((null==(r=d.candidates)||null==(i=r[0])||null==(o=i.content)?void 0:o.parts)||[]).find(e=>e.inline_data||e.inlineData);if(!p){let e=(null==(s=d.candidates)||null==(l=s[0])?void 0:l.finishReason)||"";throw Error("Nano Banana returned no image".concat(e?" ("+e+")":""," — retry, or loosen the prompt."))}let u=p.inline_data||p.inlineData;return"data:".concat(u.mime_type||u.mimeType,";base64,").concat(u.data)};try{return await i(o.model)}catch(e){if(o.fallback&&(404===e.httpStatus||/not\s*found|unknown model|invalid model|not supported/i.test(e.body||"")))return await i(o.fallback.model);throw e}}async function eE(){let e=et.product.images.filter(e=>e.sel||e.hero).sort((e,t)=>!!t.hero-!!e.hero);if(!e.length)throw Error("No reference images selected — pick at least the hero in step 1.");let t=[];for(let a of e.slice(0,8)){let e=ew(a.dataUrl||await e_(a.src).catch(()=>null));e&&t.push(e)}if(!t.length)throw Error("Reference images could not be read — re-upload or re-scan.");return t}function ej(e){try{if(/m\.media-amazon\.com\/images\/I\//.test(e))return e.replace(/\._[^.]*_\./,"._SL1500_.");if(/rukminim\d*\.flixcart\.com\/image\//.test(e))return e.replace(/\/image\/\d+\/\d+\//,"/image/1500/1500/");
1if(/assets\.myntassets\.com/.test(e))return e.replace(/\/h_\d+,q_\d+/,"/h_1500,q_90")}catch(e){}return e}let eA={};async function eC(e,t){let a=e+(t&&t.nocss?"|nocss":"");if(eA[a])return eA[a];if(void 0!==nY&&nY.ok)try{let n={u:e};t&&t.nocss&&(n.nocss="1");let o=await nV("page",null,n);if(o.html){let e=o.html+(o.css?"\n<style>"+o.css+"</style>":"");return eA[a]=e,e}}catch(e){eL=e.message}try{let t=await fetch(e,{mode:"cors"});if(t.ok){let e=await t.text();return eA[a]=e,e}}catch(e){}try{let t=await fetch("https://r.jina.ai/"+e);if(t.ok){let e=await t.text();return eT=!0,eA[a]=e,e}}catch(e){}return""}let eL="",eT=!1;function eM(e,t){let a=new Set,n=new Set,o=e=>{if(e)try{let o=new URL(e,t).href;if(!/^https?:/.test(o)||/\.(svg|gif)(\?|$)/i.test(o))return;let i=o.match(/m\.media-amazon\.com\/images\/I\/([A-Za-z0-9%+-]+)\./);if(i){if(n.has(i[1]))return;n.add(i[1])}a.add(ej(o))}catch(e){}};return eI(e).forEach(e=>{/^https?:\/\//i.test(e)&&/\.(?:jpg|jpeg|png|webp)(?:[?#]|$)/i.test(e)&&o(e)}),(e.match(/<meta[^>]+property=["']og:image["'][^>]*>/gi)||[]).forEach(e=>{let t=e.match(/content=["']([^"']+)/);t&&o(t[1])}),[...a].slice(0,80)}function eN(e){return new Promise(t=>{let a=new Image;a.referrerPolicy="no-referrer";let n=setTimeout(()=>t(null),9e3);a.onload=()=>{clearTimeout(n),t(a.naturalWidth>=300?{src:e,w:a.naturalWidth,h:a.naturalHeight}:null)},a.onerror=()=>{clearTimeout(n),t(null)},a.src=e})}async function eR(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[],a=arguments.length>2?arguments[2]:void 0;a||el("s1status","<span class='spin'></span> Harvesting real images from the page…","run");let n=await eC(e),o=[...new Set([...eM(n||"",e),...t.map(ej)])],i=[];for(let e=0;e<o.length&&((await Promise.all(o.slice(e,e+6).map(eN))).filter(Boolean).forEach(e=>i.push(e)),!(i.length>=14));e+=6);return i.sort((e,t)=>t.w*t.h-e.w*e.h),{pageText:(n||"").replace(/<[^>]+>/g," ").replace(/\s+/g," ").slice(0,24e3),images:i.slice(0,12)}}function eI(e){if(!e)return[];let t=e.match(/[A-Za-z0-9._~:\/?#@!$&*+,;=%-]{8,}/g)||[];return t.length>4e4?t.slice(0,4e4):t}function eO(e,t){let a=[],n=new Set,o=(e,o,i)=>{if(e)try{let r=new URL(e,t).href;if(!/^https?:/.test(r)||n.has(r))return;n.add(r),a.push({src:r,why:o,score:i})}catch(e){}};return(e.match(/"logo"\s*:\s*(\{[^}]*"url"\s*:\s*"[^"]+"|"[^"]+")/gi)||[]).forEach(e=>{o((e.match(/"url"\s*:\s*"([^"]+)"/)||e.match(/"logo"\s*:\s*"([^"]+)"/)||[])[1],"schema.org logo",100)}),(e.match(/<meta[^>]+(?:property|name)=["'](?:og:logo|twitter:image:src)["'][^>]*>/gi)||[]).forEach(e=>{let t=e.match(/content=["']([^"']+)/);t&&o(t[1],"og:logo",80)}),(e.match(/<img[^>]+>/gi)||[]).forEach(e=>{if(!/logo|wordmark|brand[-_]?mark|site[-_]?logo/i.test(e))return;let t=e.match(/(?:data-src|data-lazy-src|srcset|src)=["']([^"'\s]+)/);t&&o(t[1],"img marked logo",70)}),eI(e).forEach(e=>{/logo|wordmark/i.test(e)&&/\.(?:svg|png|webp|jpg|jpeg)(?:[?#]|$)/i.test(e)&&(/^https?:\/\//i.test(e)||/^\//.test(e))&&o(e,"path says logo",60)}),(e.match(/<link[^>]+rel=["'][^"']*apple-touch-icon[^"']*["'][^>]*>/gi)||[]).forEach(e=>{let t=e.match(/href=["']([^"']+)/);t&&o(t[1],"apple-touch-icon",40)}),(e.match(/<link[^>]+rel=["'][^"']*icon[^"']*["'][^>]*>/gi)||[]).forEach(e=>{let t=e.match(/href=["']([^"']+)/);t&&o(t[1],"favicon",20)}),o(new URL("/favicon.ico",t).href,"favicon.ico",10),a.sort((e,t)=>t.score-e.score).slice(0,14)}function eq(e){return new Promise(t=>{let a=new Image;a.referrerPolicy="no-referrer";let n=setTimeout(()=>t(null),9e3);a.onload=()=>{clearTimeout(n);let e=a.naturalWidth,o=a.naturalHeight;t(e>=40&&o>=16?{w:e,h:o}:null)},a.onerror=()=>{clearTimeout(n),t(null)},a.src=e})}async function eP(e,t){let a=eO(t||"",e),n=[];for(let e=0;e<a.length;e+=4)(await Promise.all(a.slice(e,e+4).map(async e=>{let t=ev(e.src),a=await eq(t);return a?Object.assign({},e,a,{url:t}):null}))).filter(Boolean).forEach(e=>n.push(e));return n.forEach(e=>{let t=e.w/e.h;e.rank=e.score+26*(t>=1.6&&t<=7)+(e.w>=180?16:8*(e.w>=90))+14*!!/\.svg(\?|$)/i.test(e.src)-24*!!/favicon|\.ico(\?|$)/i.test(e.src)}),n.sort((e,t)=>t.rank-e.rank)}function ez(e,t,a){return"#"+[e,t,a].map(e=>b(Math.round(e),0,255).toString(16).padStart(2,"0")).join("")}function eB(e){let t=tx(e),a=t.r/255,n=t.g/255,o=t.b/255,i=Math.max(a,n,o),r=M
1ath.min(a,n,o),l=i-r,s=0;l&&(s=(i===a?(n-o)/l+6*(n<o):i===n?(o-a)/l+2:(a-n)/l+4)*60);let c=(i+r)/2;return{h:s,s:l?l/(1-Math.abs(2*c-1)):0,l:c}}function eF(e,t,a){let n=tx(e),o=tx(t);return ez(n.r+(o.r-n.r)*a,n.g+(o.g-n.g)*a,n.b+(o.b-n.b)*a)}function eU(e,t){return eF(e,"#000000",t)}function eD(e){let t=String(e||"").trim();if(/^#[0-9a-f]{6}$/i.test(t))return t.toLowerCase();if(/^#[0-9a-f]{3}$/i.test(t))return"#"+t.slice(1).split("").map(e=>e+e).join("").toLowerCase();let a=/^rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)(?:[\s,/]+([\d.]+))?/i.exec(t);if(!a)return t.slice(0,24);let n=ez(+a[1],+a[2],+a[3]);return void 0!==a[4]&&1>+a[4]?"".concat(n," at ").concat(Math.round(100*a[4]),"%"):n}function eH(e,t){let a=tx(e),n=Math.max(0,Math.min(1,Number.isFinite(t)?t:1));return"rgba(".concat(a.r,",").concat(a.g,",").concat(a.b,",").concat(n,")")}function eG(e){return tS("#ffffff",e)>=tS("#0d0d12",e)?"#ffffff":"#0d0d12"}async function eW(e){let t={};for(let a of e){let e,n;if(!a)continue;try{e=await ns(ev(a))}catch(e){continue}let o=document.createElement("canvas"),i=o.width=72,r=o.height=72,l=o.getContext("2d");try{l.drawImage(e,0,0,i,r)}catch(e){continue}try{n=l.getImageData(0,0,i,r).data}catch(e){continue}for(let e=0;e<n.length;e+=4){if(n[e+3]<200)continue;let a=n[e],o=n[e+1],i=n[e+2],r=Math.max(a,o,i),l=Math.min(a,o,i),s=(.2126*a+.7152*o+.0722*i)/255;if((r?(r-l)/r:0)<.28||s<.08||s>.94)continue;let c=[a>>5,o>>5,i>>5].join(","),d=t[c]||(t[c]={n:0,r:0,g:0,b:0});d.n++,d.r+=a,d.g+=o,d.b+=i}}return Object.values(t).sort((e,t)=>t.n-e.n).slice(0,14).map(e=>({weight:e.n,hex:ez(e.r/e.n,e.g/e.n,e.b/e.n)}))}function eK(e){let t={},a=(e,a)=>{let n=e.toLowerCase();t[n]=(t[n]||0)+a};return(e.match(/#[0-9a-f]{6}\b/gi)||[]).forEach(e=>a(e,1)),(e.match(/#[0-9a-f]{3}\b/gi)||[]).forEach(e=>{a("#"+e.slice(1).split("").map(e=>e+e).join(""),1)}),(e.match(/rgba?\(\s*\d+\s*,\s*\d+\s*,\s*\d+/gi)||[]).forEach(e=>{let t=e.match(/\d+/g).map(Number);a(ez(t[0],t[1],t[2]),1)}),Object.entries(t).map(e=>{let[t,a]=e;return{hex:t,weight:a}}).filter(e=>{let t=eB(e.hex);return t.s>=.25&&t.l>.1&&t.l<.92}).sort((e,t)=>t.weight-e.weight).slice(0,14)}function eJ(e,t){if(!e.length)return null;let a=(t||[]).map(e=>e.hex||e).filter(Boolean),n=e=>{let t=eB(e);return t.s>=.12&&t.l>=.04&&t.l<=.94},o=a.find(n)||(e.find(e=>n(e.hex))||{}).hex||null;if(!o)return null;let i=eB(o).h,r=e=>{let t=Math.abs(eB(e).h-i);return Math.min(t,360-t)>38},l=a.find(e=>r(e)&&n(e))||(e.find(e=>r(e.hex)&&(e=>{let t=eB(e);return t.s>=.45&&t.l>=.32&&t.l<=.8})(e.hex))||{}).hex||a.find(r)||(e.find(e=>r(e.hex))||{hex:eF(o,"#ff8a00",.55)}).hex,s=eF(eU(o,.86),"#0b0b10",.55),c=eF("#ffffff",o,.05);return{brand:o,accent:l,brand_alt:eU(o,.4),ink:s,paper:c,on_brand:eG(o),on_ink:eG(s),on_paper:eG(c)}}function eY(t,a){(!a||!1!==a.persist)&&ot&&nY&&nY.ok&&t>0&&(ot.stage=ol(t),of(!0));for(let a=0;a<=6;a++){let n=i("step"+a),o=e.querySelector('.step-tab[data-step="'.concat(a,'"]'));n&&n.classList.toggle("hidden",a!==t),o&&(o.classList.toggle("active",a===t),o.classList.toggle("done",a>0&&a<t))}window.scrollTo({top:0,behavior:"smooth"}),t>0&&"function"==typeof o_&&o_(),0===t&&oe(),2===t&&t4(),3===t&&(t8(),t7()),4===t&&aU(),5===t&&(nK(),ic(),oK()),6===t&&(io(),ic(),ip(),oW(),oq(),oB(),oU())}function eV(e,t,a){let n="string"==typeof e?i(e):e;n&&(t?(n.dataset._h||(n.dataset._h=n.innerHTML),n.disabled=!0,n.setAttribute("aria-busy","1"),n.innerHTML="<span class='spin'></span> "+(a||"Working…")):(n.disabled=!1,n.removeAttribute("aria-busy"),n.dataset._h&&(n.innerHTML=n.dataset._h,delete n.dataset._h),e2()))}let e$={llm:null,image:null,plan:null};function eX(e){return null!=e&&e>0?" \xb7 ⚡ ".concat((0,n.th)(e)):""}function eQ(){let e=e$.plan,t=e$.llm;return t?(0,n.Gw)({opcost:t,modelKey:w,plan:e})||(0,n.Gw)({opcost:t,modelKey:x,plan:e})||(0,n.Gw)({opcost:t,modelKey:"gemini-2.5-flash",plan:e}):null}function eZ(){let e=e$.plan,t=e$.image;if(!t)return null;let a=et.plan&&"draft"===et.plan.tier?_.model:v.model;return(0,n.ul)({opcost:t,modelKey:a,resolution:"2K",plan:e,count:1})||(0,n.ul)({opcost:t,modelKey:v.model,resolution:"2K",plan:e,count:1})}function e0(e,t){return null==e?null:e*Math.max(1,Math.floor(Number(t)||1))}function e1(e,t,a){let n=i(e);if(!n)return;let o=t||n.dataset.acsLabel||"";o&&(n.dataset.acsLabel=o);let l=r(o)+(null!=a?'<span class="acs-tok">'.concat(r(eX(a)),"</span>"):"");if(null!=a&&(n.title="Estimated Spark tokens for this click"),n.getAttribute("aria-busy")){n.dataset._h=l;return}n.innerHTML=l}
1function e2(){if(u())return;let e=eQ(),t=eZ(),a=function(){try{let e="function"==typeof t3?t3():[];if(!et.plates||!et.plates.length)return e.length;return e.filter(e=>{let t=et.plates.find(t=>t.id===e.id);return!t||"done"!==t.status}).length}catch(e){return 0}}(),n="function"==typeof nn?nn().length:0,o=(et.plates||[]).filter(e=>"done"===e.status&&e.dataUrl&&!e.seeded).length,r="function"==typeof aW?aW().length:0,l=function(){try{return("function"==typeof nn?nn():[]).filter(e=>{let[t,a,n]=e,o=et.copy["".concat(t,"|").concat(a,"|").concat(n)];return!o||!!o._failed||(o._issues||[]).some(e=>"fail"===e.level)}).length}catch(e){return 0}}();e1("btnIntel","✦ Build brand & product intelligence",e0(e,1)),e1("btnKitIntel","✦ Read the kit",e0(e,1)),e1("btnReadRefs","✦ Read every image",e0(e,1)),e1("btnBrief","\uD83D\uDD0E Build the brief",e0(e,2)),e1("btnGenPlates","✨ Generate all plates",e0(t,a||1)),e1("btnRedoBands","⌗ Regenerate banded plates",r?e0(t,r):null),e1("btnCopyGen","✍ Write copy (all devices \xd7 languages)",e0(e,n||1));let s=i("btnCopyRetry");e1("btnCopyRetry",s&&s.dataset.acsRetryLabel?s.dataset.acsRetryLabel:"↻ Retry unfinished",l?e0(e,l):null),e1("btnQcPlates","\uD83D\uDD2C QC plates",o?e0(e,o):e0(e,1))}async function e4(){if(u())return;let e=p();if(e)try{var t,a;let n=h(),[i,r,l]=await Promise.all([fetch("/api/v1/wallet/op-cost?category=llm",{headers:n}).then(e=>e.json()).catch(()=>null),fetch("/api/v1/wallet/op-cost?category=image",{headers:n}).then(e=>e.json()).catch(()=>null),(0,o.cZ)(e).catch(()=>null)]);(null==i?void 0:i.status)&&i.enabled&&i.opcost&&(e$.llm=i.opcost),(null==r?void 0:r.status)&&r.enabled&&r.opcost&&(e$.image=r.opcost);let s=null!=(a=null==l||null==(t=l.balance)?void 0:t.data)?a:null==l?void 0:l.balance;(null==l?void 0:l.status)&&l.enabled&&s&&(e$.plan=s.plan||null),e2()}catch(e){}}let e5=new Set;function e6(e){return e5.has(e)}async function e8(e,t,a,n){if(e5.has(e))return{skipped:!0};e5.add(e),t&&eV(t,!0,a);try{return await n()}finally{e5.delete(e),t&&eV(t,!1)}}let e3=["s0status","s1status","s2status","s3status","s4status","s5status","s6status","ceStatus","dpStatus","exStatus","refsStatus","brStatus2"];function e7(){for(var e=arguments.length,t=Array(e),a=0;a<e;a++)t[a]=arguments[a];(t.length?t:e3).forEach(e=>{let t=i(e);t&&(t.innerHTML="",t.className="status-line")})}function e9(e){i("lightboxImg").src=e,i("lightbox").classList.add("show")}function te(e,t){let a=document.createElement("a");a.href=e,a.download=t,a.click()}function tt(e,t,a){let n=new Blob([e],{type:a||"application/json"}),o=document.createElement("a");o.href=URL.createObjectURL(n),o.download=t,o.click(),setTimeout(()=>URL.revokeObjectURL(o.href),4e3)}function ta(){let e=!!(ea.gemini||p()),t=i("kchip_gemini");t&&(t.textContent=e?"set ✓":"not set",t.className="chip "+(e?"ok":"bad"));let a=i("keyStatusDot");a&&(a.textContent=e?"set ✓":"not set",a.className="chip "+(e?"ok":"bad"))}let tn=new Set;function to(e){if(tn.has(e))return;tn.add(e);let t=document.createElement("link");t.rel="stylesheet",t.href="https://fonts.googleapis.com/css2?family="+e.replace(/ /g,"+")+":wght@400;600;700;800&display=swap",document.head.appendChild(t)}async function ti(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:6e3,a=[...new Set(e.filter(Boolean))];a.forEach(to);let n=[];for(let e of a)for(let t of[400,700,800])n.push(document.fonts.load("".concat(t,' 40px "').concat(e,'"')).catch(()=>null));await Promise.race([Promise.all(n),g(t)]);try{await Promise.race([document.fonts.ready,g(500)])}catch(e){}return a.filter(e=>{try{return document.fonts.check('700 40px "'.concat(e,'"'))}catch(e){return!1}})}function tr(){let e=et.brand.type||{},t=[e.display_family||"Poppins",e.text_family||"Inter","Noto Sans"];return et.plan.langs.slice().forEach(e=>{j[e]&&t.push(j[e])}),[...new Set(t)]}function tl(e,t){let a=new DOMParser().parseFromString(e||"","text/html");if(!a.querySelector("base")){let e=a.createElement("base");e.setAttribute("href",t),(a.head||a.documentElement).prepend(e)}return a}function ts(e,t){if(!e)return"";try{let a=new URL(String(e).trim(),t).href;return/^https?:/.test(a)?a:""}catch(e){return""}}function tc(e,t){if(!e)return"";let a="",n=-1;
1return String(e).split(",").forEach(e=>{let t=e.trim().split(/\s+/);if(!t[0])return;let o=parseFloat((t[1]||"").replace(/[^\d.]/g,""))||0;o>=n&&(n=o,a=t[0])}),ts(a,t)}function td(e){if(!e)return"";let t=String(e).trim().toLowerCase(),a={white:"#ffffff",black:"#000000"};if(a[t])return a[t];let n=t.match(/^#([0-9a-f]{3,8})$/);if(n){let e=n[1];return 3===e.length?"#"+e.split("").map(e=>e+e).join(""):4===e.length?"#"+e.slice(0,3).split("").map(e=>e+e).join(""):6===e.length?"#"+e:8===e.length?"#"+e.slice(0,6):""}if(n=t.match(/^rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)/)){let e=e=>Math.max(0,Math.min(255,Math.round(+e))).toString(16).padStart(2,"0");return"#"+e(n[1])+e(n[2])+e(n[3])}return""}let tp=/^(organization|corporation|localbusiness|ngo|educationalorganization|governmentorganization|brand|onlinestore|store)$/i;function tu(e,t){let a=function(e){let t=[];return e.querySelectorAll('script[type="application/ld+json"]').forEach(e=>{let a;try{a=JSON.parse(e.textContent.trim())}catch(e){return}let n=e=>{if(e){if(Array.isArray(e))return void e.forEach(n);"object"==typeof e&&(t.push(e),e["@graph"]&&n(e["@graph"]),Object.values(e).forEach(e=>{e&&"object"==typeof e&&n(e)}))}};n(a)}),t}(e),n=e=>a.filter(t=>{let a=t["@type"];return(Array.isArray(a)?a:[a]).some(t=>e.test(String(t||"")))}),o=n(tp)[0]||null,i=n(/^website$/i)[0]||null,r={name:"",legal:"",logo:"",desc:"",sameAs:[]},l=e=>e?"string"==typeof e?ts(e,t):Array.isArray(e)?l(e[0]):ts(e.url||e.contentUrl||"",t):"";return o&&(r.name=String(o.name||o.alternateName||"").trim(),r.legal=String(o.legalName||"").trim(),r.logo=l(o.logo)||l(o.image),r.desc=String(o.description||"").trim(),r.sameAs=(Array.isArray(o.sameAs)?o.sameAs:[o.sameAs]).filter(Boolean).map(String)),!r.name&&i&&(r.name=String(i.name||"").trim()),r}function th(e,t){var a;let n,o=tl(e,t),i=(e,t)=>{let a=o.querySelector(e);return a?(a.getAttribute(t||"content")||"").trim():""},r=e=>{for(let t of e){let e=i('meta[property="'.concat(t,'"]'))||i('meta[name="'.concat(t,'"]'));if(e)return e}return""},l=tu(o,t),s=(()=>{try{return new URL(t).hostname.replace(/^www\./,"")}catch(e){return""}})(),c=((null==(a=o.querySelector("title"))?void 0:a.textContent)||"").trim(),d=c.split(/\s+[|–—·]\s+|\s+-\s+/).map(e=>e.trim()).filter(Boolean),p=d.length>1?d[d.length-1]:"",u=l.name||r(["og:site_name","application-name","apple-mobile-web-app-title"])||(p&&p.length<=40?p:"")||s.split(".")[0].replace(/^\w/,e=>e.toUpperCase()),h=l.desc||r(["og:description","twitter:description","description"]),m=[],f=new Set,g=(e,a,n)=>{let o=ts(e,t);!o||f.has(o)||(f.add(o),m.push({src:o,why:a,score:n}))};l.logo&&g(l.logo,"schema.org Organization logo",100),g(r(["og:logo"]),"og:logo",86),o.querySelectorAll("link[rel]").forEach(e=>{let t=(e.getAttribute("rel")||"").toLowerCase(),a=e.getAttribute("href");a&&(/mask-icon/.test(t)?g(a,"mask-icon (vector mark)",84):/apple-touch-icon/.test(t)?g(a,"apple-touch-icon",78):/\bicon\b/.test(t)&&g(a,"favicon",55))}),o.querySelectorAll("img,image,source").forEach(e=>{let a=[e.getAttribute("src"),e.getAttribute("data-src"),e.getAttribute("alt"),e.getAttribute("class"),e.getAttribute("id"),e.getAttribute("title")].join(" ").toLowerCase();/logo|wordmark|brand[-_ ]?mark/.test(a)&&g(e.getAttribute("src")||e.getAttribute("data-src")||e.getAttribute("xlink:href")||tc(e.getAttribute("srcset"),t),"marked up as a logo",72)});let b=[];o.querySelectorAll("header svg, nav svg, [class*=logo] svg, [class*=Logo] svg, a[href='/'] svg").forEach(e=>{if(b.length>=3)return;let t=+(e.getAttribute("width")||0),a=+(e.getAttribute("height")||0),n=(e.getAttribute("viewBox")||"").split(/[\s,]+/).map(Number),o=t&&a?t/a:4===n.length&&n[3]?n[2]/n[3]:0;if(0===e.querySelectorAll("path,polygon,rect,circle,ellipse").length||o&&(o<.6||o>12))return;let i="";try{let t=e.cloneNode(!0);t.getAttribute("xmlns")||t.setAttribute("xmlns","http://www.w3.org/2000/svg");let a=new XMLSerializer().serializeToString(t);a.length<6e4&&(i="data:image/svg+xml;charset=utf-8,"+encodeURIComponent(a))}catch(e){}i&&b.push({src:i,why:"inline SVG in the masthead",score:90,inline:!0})}
1),b.forEach(e=>{f.has(e.src)||(f.add(e.src),m.push(e))});let y=[],v=new Set,_=(e,a)=>{let n=ts(e,t);!(!n||v.has(n))&&(/\.(svg|gif)(\?|$)/i.test(n)||(v.add(n),y.push({src:n,why:a})))};["og:image","og:image:url","og:image:secure_url","twitter:image","twitter:image:src"].forEach(e=>_(r([e]),e)),o.querySelectorAll("picture source").forEach(e=>_(tc(e.getAttribute("srcset"),t),"picture source")),o.querySelectorAll("img").forEach(e=>{let a=e.getAttribute("src")||e.getAttribute("data-src")||e.getAttribute("data-lazy-src")||tc(e.getAttribute("srcset"),t),n=[e.getAttribute("alt"),e.getAttribute("class")].join(" ").toLowerCase();/logo|icon|sprite|avatar|pixel|tracking/.test(n)||_(a,"page image")});let w=[],x=(e,t,a)=>{let n=td(e);n&&!/^#(fff|ffffff|000|000000)$/.test(n)&&w.push({hex:n,why:t,score:a})};x(r(["theme-color"]),"meta theme-color",95),o.querySelectorAll("meta[name='msapplication-TileColor']").forEach(e=>x(e.getAttribute("content"),"tile colour",80));let k=[...o.querySelectorAll("style")].map(e=>e.textContent||"").join("\n"),S=/--[\w-]{0,48}(?:brand|primary|accent|secondary|theme)[\w-]{0,48}\s*:\s*([^;}]{1,200})/gi,E=0;for(;(n=S.exec(k))&&E<30;)x(n[1],"CSS brand variable",88),E++;[...o.querySelectorAll("script,style,noscript,svg,iframe")].forEach(e=>{e&&e.remove()});let j=e=>[...o.querySelectorAll(e)].map(e=>(e.textContent||"").replace(/\s+/g," ").trim()).filter(e=>e.length>2&&e.length<400),A=[...new Set(j("h1,h2,h3"))].slice(0,40),C=o.querySelector("main,[role=main],article")||o.body,L=(C&&C.textContent||"").replace(/\s+/g," ").trim(),T=[...new Set(j("li"))].filter(e=>e.length>12).slice(0,40),M=[...new Set([...o.querySelectorAll("nav a,header a")].map(e=>(e.textContent||"").replace(/\s+/g," ").trim()).filter(e=>e&&e.length<40))].slice(0,50);return{name:u,legal:l.legal,desc:h,titleRaw:c,host:s,logos:m.sort((e,t)=>t.score-e.score).slice(0,12),images:y.slice(0,60),colours:w.sort((e,t)=>t.score-e.score).slice(0,12),headings:A,lists:T,navWords:M,sameAs:l.sameAs,text:L.slice(0,28e3),chars:(e||"").length}}function tm(e,t){if(!t)return e;let a=new Set(e.logos.map(e=>e.src)),n=e.logos.concat(t.logos.filter(e=>!a.has(e.src)).map(e=>({...e,why:e.why+" (site root)"}))),o=new Set(e.colours.map(e=>e.hex)),i=e.colours.concat(t.colours.filter(e=>!o.has(e.hex)).map(e=>({...e,why:e.why+" (site root)"})));return{...e,name:e.name||t.name,legal:e.legal||t.legal,desc:e.desc||t.desc,logos:n.sort((e,t)=>t.score-e.score).slice(0,14),colours:i.sort((e,t)=>t.score-e.score).slice(0,14),navWords:[...new Set(e.navWords.concat(t.navWords))].slice(0,60),rootUsed:!0}}async function tf(e,t){let a=e=>{t&&t(e)};a("reading the page…");let n=await eC(e);if(!n)return null;let o=th(n,e),i=function(e){try{return new URL(e).origin+"/"}catch(e){return""}}(e),r=o.logos.length?o.logos[0].score:0;if(i&&i.replace(/\/$/,"")!==e.replace(/\/$/,"")&&(r<80||!o.colours.length||!o.name)){a("checking the site root for the brand mark…");try{let e=await eC(i);e&&(o=tm(o,th(e,i)))}catch(e){}}try{var l;let t=null==(l=tl(n,e).querySelector("link[rel~=manifest]"))?void 0:l.getAttribute("href");if(t){let a=ts(t,e),n=await tb(a);if(n&&Array.isArray(n.icons)){let e=n.icons.map(e=>({src:ts(e.src,a),area:String(e.sizes||"").split("x").reduce((e,t)=>e*(+t||0),1)||0})).filter(e=>e.src).sort((e,t)=>t.area-e.area)[0];e&&!o.logos.some(t=>t.src===e.src)&&o.logos.unshift({src:e.src,why:"web app manifest icon",score:82})}n&&n.theme_color&&o.colours.unshift({hex:td(n.theme_color),why:"manifest theme colour",score:92}),n&&n.name&&!o.name&&(o.name=n.name)}}catch(e){}return o.colours=o.colours.filter(e=>e.hex),o}async function tg(e){let t=[];for(let a=0;a<e.length;a+=4)(await Promise.all(e.slice(a,a+4).map(async e=>{if(/^data:/.test(e.src)){let t=await eq(e.src);return t?Object.assign({},e,t,{url:e.src}):null}let t=ev(e.src),a=await eq(t);return a?Object.assign({},e,a,{url:t}):null}))).filter(Boolean).forEach(e=>t.push(e));return t.forEach(e=>{let t=e.w/Math.max(1,e.h);e.rank=(e.score||0)+26*(t>=1.6&&t<=7)+(e.w>=180?16:8*(e.w>=90))+(/^data:image\/svg/.test(e.src)||/\.svg(\?|$)/i.test(e.src)?14:0)-24*!!/favicon|\.ico(\?|$)/i.test(e.src)}),t.sort((e,t)=>
1t.rank-e.rank)}async function tb(e){try{if(void 0!==nY&&nY.ok){let t=await nV("page",null,{u:e,nocss:"1"});if(t&&t.html)return JSON.parse(t.html)}}catch(e){}try{let t=await fetch(e);if(t.ok)return await t.json()}catch(e){}return null}function ty(){var e,t,a,n,o,l,s,c,d;let p=et.brand.palette||{};i("tokRow").innerHTML=S.map(e=>{let t=p[e.k]||e.def;return'<div class="tok"><span class="sw" id="sw_'.concat(e.k,'" style="background:').concat(r(t),'"></span>\n      <span class="tk"><b>').concat(r(e.label),'</b><input id="tk_').concat(e.k,'" value="').concat(r(t),'"></span></div>')}).join(""),S.forEach(e=>{let t=i("tk_"+e.k);t.addEventListener("input",()=>{et.brand._dirty=!0,et.brand.palette[e.k]=t.value.trim(),i("sw_"+e.k).style.background=t.value.trim()})}),["t_display","t_text"].forEach(e=>{let t=i(e);t.options.length||(t.innerHTML=E.map(e=>"<option>".concat(e,"</option>")).join(""))});let u=et.brand.type||{};i("t_display").value=u.display_family||"Poppins",i("t_text").value=u.text_family||"Inter",i("t_case").value=u.case||"sentence",i("t_clear").value=null!=(c=null==(e=et.brand.logo)?void 0:e.clear_space_multiple)?c:.5,i("t_minlogo").value=null!=(d=null==(t=et.brand.logo)?void 0:t.min_height_px)?d:24,i("t_cat").value=(null==(a=et.brand.legal)?void 0:a.category)||"general",i("t_knock")&&(i("t_knock").value=(null==(n=et.brand.logo)?void 0:n.knockout)||"auto"),i("t_register").value=(null==(o=et.brand.voice)?void 0:o.register)||"",i("t_banned").value=((null==(l=et.brand.voice)?void 0:l.banned_words)||[]).join(", "),i("t_legal").value=((null==(s=et.brand.legal)?void 0:s.mandatory_lines)||[]).join("\n"),tv()}function tv(){[["logo_primary","primary","primary"],["logo_mono_dark","mono_dark","mono \xb7 for light bg"],["logo_mono_light","mono_light","mono \xb7 for dark bg"]].forEach(e=>{var t;let[a,n,o]=e,l=i(a),s=null==(t=et.brand.logo)?void 0:t[n];if(!l)return;l.className="logo-box "+("mono_light"===n?"dark":"light")+(s?" has":""),l.innerHTML=(s?'<img src="'.concat(s,'" alt="">'):"")+'<div class="tiny">'.concat(r(o),"</div>")+(s?'<button type="button" class="rm" data-logo-rm="'.concat(r(n),'" title="Remove this logo" aria-label="Remove this logo">✕</button>'):"");let c=l.querySelector(".rm");c&&(c.onclick=e=>{e.preventDefault(),e.stopPropagation(),et.brand.logo&&delete et.brand.logo[n],et.brand._dirty=!0,tv(),tC(),oc(!0)})})}function t_(){return et.brand.brand_name=i("f_brand").value.trim()||et.brand.brand_name,et.brand.type={display_family:i("t_display").value,text_family:i("t_text").value,case:i("t_case").value},et.brand.logo=Object.assign({},et.brand.logo,{clear_space_multiple:parseFloat(i("t_clear").value)||.5,min_height_px:parseInt(i("t_minlogo").value)||24,knockout:i("t_knock")?i("t_knock").value:"auto"}),et.brand.voice={register:i("t_register").value.trim(),banned_words:i("t_banned").value.split(",").map(e=>e.trim()).filter(Boolean)},et.brand.legal={category:i("t_cat").value,mandatory_lines:i("t_legal").value.split("\n").map(e=>e.trim()).filter(Boolean)},S.forEach(e=>{var t;et.brand.palette[e.k]=((null==(t=i("tk_"+e.k))?void 0:t.value)||e.def).trim()}),et.brand.brand_slug=y(et.brand.brand_name||"brand"),et.brand}function tw(e){var t;let[a,n]=String(e||"").split("@"),o=(et.brand.palette||{})[a]||(null==(t=S.find(e=>e.k===a))?void 0:t.def)||"#000000";if(void 0===n)return o;if("transparent"===a)return"rgba(0,0,0,0)";let i=tx(o);return"rgba(".concat(i.r,",").concat(i.g,",").concat(i.b,",").concat(parseFloat(n),")")}function tx(e){let t=String(e).replace("#","").trim();
1return 3===t.length&&(t=t.split("").map(e=>e+e).join("")),/^[0-9a-f]{6}$/i.test(t)||(t="000000"),{r:parseInt(t.slice(0,2),16),g:parseInt(t.slice(2,4),16),b:parseInt(t.slice(4,6),16)}}function tk(e){let t=tx(e),a=e=>(e/=255)<=.03928?e/12.92:Math.pow((e+.055)/1.055,2.4);return .2126*a(t.r)+.7152*a(t.g)+.0722*a(t.b)}function tS(e,t){let a=tk(e),n=tk(t);return(Math.max(a,n)+.05)/(Math.min(a,n)+.05)}function tE(){t_();let e=[];[["on_ink","ink"],["on_paper","paper"],["on_brand","brand"],["on_brand","accent"]].forEach(t=>{let[a,n]=t,o=tS(et.brand.palette[a],et.brand.palette[n]);o<3&&e.push("".concat(a," on ").concat(n," = ").concat(o.toFixed(2),":1"))});let t=i("contrastChip");return e.length?(t.className="chip bad",t.textContent="contrast: "+e.length+" failing",el("s1status","Failing pairs (need ≥3:1 for display type, ≥4.5:1 for body): "+e.join(" \xb7 ")+". Fix the token or the compositor will draw illegible type.","err")):(t.className="chip ok",t.textContent="contrast ok",el("s1status","All text/ground token pairs clear 3:1.","okk")),e}function tj(e){try{return new URL(e).host.replace(/^www\./,"")}catch(e){return""}}async function tA(){let e=(et.product.spec.destination_url||"").trim(),t=(i("in_url").value||"").trim(),a=e||t||et.product.url||"";if(e&&t&&tj(t)&&tj(e)&&tj(t)!==tj(e)&&el("brStatus2",'The intake box points at <span class="mono">'.concat(r(tj(t)),'</span> but this campaign\'s product URL is <span class="mono">').concat(r(tj(e)),"</span> — using the campaign's own URL. Clear the destination in the product spec if you meant the other one."),"warn"),!a)return el("brStatus2","No URL to read. Put the brand or product URL in the intake box, or set the product's destination URL below.","err");i("in_url").value=a,eV("btnBrandAssets",!0,"Fetching…");try{el("brStatus2","<span class='spin'></span> Reading the page for logo files and brand colours — a heavy site can take twenty seconds…","run"),eT=!1,eL="";let e=await eC(a);if(!e)throw Error("the page could not be read — check the URL, or upload the logo by hand"+(eL?" ("+eL+")":""));if(eT)throw Error("only a markdown rendering of that page could be read, which has no logo files or CSS in it. "+(nY.ok?"The server-side fetcher failed"+(eL?": "+eL:"")+" — see ⚙ Diagnostics.":"Start WAMP so the module's own fetcher can read the raw page.")+" Upload the logo by hand meanwhile.");et.brand._pageRaw=e.slice(0,7e5);let t=null;try{t=await tf(a,e=>el("brStatus2","<span class='spin'></span> "+r(e),"run"))}catch(e){}et.brand._harvest=t||null;let n=[];if(t&&t.logos.length&&(n=await tg(t.logos)),t)try{if(t.name&&!et.brand.brand_name){et.brand.brand_name=t.name;let e=i("b_name");e&&(e.value=t.name)}t.legal&&!et.brand.legal_name&&(et.brand.legal_name=t.legal),t.desc&&!et.product.spec.positioning&&(et.product.spec.positioning=t.desc,tP())}catch(e){}n.length||(n=await eP(a,e)),et.brand.logoCandidates=n,et.brand._dirty=!0,n.length&&(et.brand.logo=Object.assign({},et.brand.logo,{primary:n[0].url,_from:n[0].src}),tv()),tC();let o=await tL();el("brStatus2",n.length?"Found ".concat(n.length," logo candidate").concat(1===n.length?"":"s",' — the best one (<span class="mono">').concat(r(n[0].why),"</span>, ").concat(n[0].w,"\xd7").concat(n[0].h,") is set as the primary. Click any other to use it instead.").concat(o?" Palette sampled from the site CSS and the logo pixels.":""):"No logo file found on that page. Upload one below — every creative reserves a logo zone, and without it each one comes back with a logo warning.",n.length?"okk":"err")}catch(e){el("brStatus2","Failed: "+r(e.message),"err")}eV("btnBrandAssets",!1)}function tC(){let e=i("logoCands"),t=et.brand.logoCandidates||[];if(e){if(!t.length){e.innerHTML="";return}e.innerHTML='<label>Logo candidates from the site <span class="hint">click to set as primary \xb7 shift-click for the dark-ground version \xb7 ✕ removes</span></label>\n    <div class="logo-slot">'.concat(t.map((e,t)=>{var a;return'\n      <div class="logo-box '.concat((null==(a=et.brand.logo)?void 0:a.primary)===e.url?"has":""," ").concat(t%2?"dark":"light",'" data-lc="').concat(t,'" title="').concat(r(e.why)," \xb7 ").concat(e.w,"\xd7").concat(e.h,'">\n        <img src="').concat(r(e.url),'" alt="">\n        <div class="tiny">').concat(r(e.why)," \xb7 ").concat(e.w,"\xd7").concat(e.h,'</div>\n        <button type="button" class="rm" data-lc-rm="').concat(t,'" title="Remove this logo candidate" aria-label="Remove this logo candidate">✕</button></div>')}).join(""),"</div>
1"),e.querySelectorAll("[data-lc]").forEach(e=>e.onclick=a=>{if(a.target.closest(".rm"))return;let n=t[+e.dataset.lc];n&&(a.shiftKey?et.brand.logo.mono_light=n.url:et.brand.logo.primary=n.url,et.brand._dirty=!0,tv(),tC(),el("brStatus2","Logo set".concat(a.shiftKey?" as the dark-ground version":"",". Re-draw any composed creatives in step 6 to pick it up."),"okk"),oc(!0))}),e.querySelectorAll("[data-lc-rm]").forEach(e=>e.onclick=a=>{a.preventDefault(),a.stopPropagation();let n=+e.dataset.lcRm,o=t[n];o&&et.brand.logo&&["primary","mono_dark","mono_light"].forEach(e=>{et.brand.logo[e]===o.url&&delete et.brand.logo[e]}),et.brand.logoCandidates.splice(n,1),et.brand._dirty=!0,tv(),tC(),oc(!0)})}}async function tL(){var e;let t=[null==(e=et.brand.logo)?void 0:e.primary,...et.product.images.filter(e=>e.hero||e.sel).slice(0,4).map(e=>e.dataUrl||e.src)].filter(Boolean),a=await eW(t),n=et.brand._pageRaw?eK(et.brand._pageRaw):[],o=et.brand._harvest,i=(o&&o.colours||[]).filter(e=>e.hex).map(e=>({hex:e.hex,weight:1e6+1e3*(e.score||0),why:e.why})),r={};i.forEach(e=>r[e.hex]=(r[e.hex]||0)+e.weight),n.forEach(e=>r[e.hex]=(r[e.hex]||0)+3*e.weight),a.forEach(e=>r[e.hex]=(r[e.hex]||0)+e.weight/40);let l=eJ(Object.entries(r).map(e=>{let[t,a]=e;return{hex:t,weight:a}}).sort((e,t)=>t.weight-e.weight),i.concat(n));if(!l)return null;et.brand.palette=Object.assign({},et.brand.palette,l);let s=i.map(e=>"".concat(e.hex).concat(e.why?" ("+e.why.replace(/\s*\(site root\)/,"")+")":""));return et.brand._paletteSource=i.length?"".concat(i.length," stated colour").concat(1===i.length?"":"s"," — ").concat(s.join("; ")," — plus ").concat(n.length," counted from the stylesheet and ").concat(a.length," sampled from the images"):"".concat(n.length," colours counted from the stylesheet and ").concat(a.length," sampled from the images"),ty(),l}async function tT(){eV("btnPalette",!0,"Sampling…");try{let e=await tL();if(e){let t=tE();el("brStatus2","Palette sampled (".concat(r(et.brand._paletteSource||""),') — brand <span class="mono">').concat(r(e.brand),'</span>, accent <span class="mono">').concat(r(e.accent),'</span>, ink <span class="mono">').concat(r(e.ink),"</span>. ").concat(t.length?"Some pairs still fail contrast — see above.":"All text/ground pairs clear 3:1."),t.length?"err":"okk")}else el("brStatus2","Nothing to sample — fetch the site or add a logo and product shots first.","err")}catch(e){el("brStatus2","Failed: "+r(e.message),"err")}eV("btnPalette",!1)}function tM(){return'HARD RULES — these decide whether the output is usable:\n- NEVER invent a certification, award, rating, review count, statistic, endorsement, price or discount. If the page does not state it, return "" or [].\n- Colours must be sampled from the page\'s own styling. Do not invent a "brand-appropriate" palette. If you cannot tell, return "" for that role.\n- Fonts must be families actually declared on the page. Prefix a guess with ~.\n- Every proof_points entry must carry the source sentence it came from, verbatim.\n- objections[] is your own read of purchase friction. It is never rendered as ad text; it only steers strategy.\n- Prefix any field you inferred rather than read with ~.\nReturn ONLY this JSON:\n'.concat('{\n "brand":{"brand_name":"","legal_name":"",\n   "palette":{"ink":"#hex","paper":"#hex","brand":"#hex","brand_alt":"#hex","accent":"#hex","on_ink":"#hex","on_paper":"#hex","on_brand":"#hex"},\n   "type":{"display_family":"","text_family":"","case":"sentence|title|upper"},\n   "voice":{"register":"","banned_words":[]},\n   "legal":{"category":"general|health|finance|alcohol|kids","mandatory_lines":[]}},\n "product":{"product_name":"","brand_name":"","category":"broad › specific","price_display":"","price_was":"","offer_display":"",\n   "tagline":"max 6 words","destination_url":"","occasion":"","variants_available":"",\n   "key_features":["5-8 verifiable selling points"],\n   "objections":["3-5 reasons a buyer hesitates — inference, never rendered"],\n   "proof_points":[{"text":"","source":"the sentence or page it came from"}
1],\n   "landing_page_promise":"",\n   "target_group":{"audience":"","region":""},\n   "image_urls":["largest product images seen on the page"]}}')}function tN(e,t){return"You are a brand strategist and product analyst preparing an advertising brief.\nRead the page at ".concat(e," and extract the brand system and the product facts an ad team needs.\n").concat(t?"Operator notes (authoritative, may not be on the page): "+t+"\n":"","\n").concat(tM())}function tR(e){return"You are a brand strategist reading a brand kit. The attached images are some mix of logo files, brand guideline pages and product photographs.\nExtract the brand system and product facts from what is VISIBLE.\n".concat(e?"Operator notes (authoritative): "+e+"\n":"","\n").concat(tM())}function tI(){let e="kit"===et.intakeMode?tR(i("in_kitnotes").value.trim()):tN(i("in_url").value.trim()||"<url>",i("in_notes").value.trim());i("pre_intel").textContent=e}function tO(e){var t;let a=e.brand||{},n=e.product||{};et.brand._dirty=!0,et.brand.brand_name=a.brand_name||n.brand_name||et.brand.brand_name,et.brand.legal_name=a.legal_name||"",S.forEach(e=>{let t=(a.palette||{})[e.k];t&&/^#?[0-9a-f]{3,8}$/i.test(String(t).trim())&&(et.brand.palette[e.k]=String(t).trim().replace(/^(?!#)/,"#"))}),a.type&&(et.brand.type=Object.assign({},et.brand.type,{display_family:E.includes((a.type.display_family||"").replace(/^~/,""))?a.type.display_family.replace(/^~/,""):et.brand.type.display_family||"Poppins",text_family:E.includes((a.type.text_family||"").replace(/^~/,""))?a.type.text_family.replace(/^~/,""):et.brand.type.text_family||"Inter",case:a.type.case||"sentence"})),a.voice&&(et.brand.voice=Object.assign({},et.brand.voice,a.voice)),a.legal&&(et.brand.legal=Object.assign({},et.brand.legal,a.legal));let o=et.product.spec;return["product_name","brand_name","category","price_display","price_was","offer_display","tagline","destination_url","occasion","variants_available","landing_page_promise"].forEach(e=>{void 0!==n[e]&&(o[e]=n[e])}),o.key_features=n.key_features||o.key_features||[],o.objections=n.objections||o.objections||[],o.proof_points=(n.proof_points||o.proof_points||[]).map(e=>"string"==typeof e?{text:e,source:""}:e),o.target_group=n.target_group||o.target_group||{},!et.camp.audience&&(null==(t=o.target_group)?void 0:t.audience)&&(i("c_audience").value=o.target_group.audience),tP(),ty(),tU(),tW(),i("brandChip").className="chip ok",i("brandChip").textContent=(et.brand.brand_name||"brand")+" loaded",n.image_urls||[]}let tq={product_name:"f_product",brand_name:"f_brand",category:"f_category",price_display:"f_price",price_was:"f_price_was",offer_display:"f_offer",tagline:"f_tagline",destination_url:"f_dest",occasion:"f_occasion"};function tP(){let e=et.product.spec;Object.entries(tq).forEach(t=>{let[a,n]=t;i(n)&&(i(n).value=e[a]||"")}),i("f_features").value=(e.key_features||[]).join("\n"),i("f_objections").value=(e.objections||[]).join("\n"),i("f_proof").value=(e.proof_points||[]).map(e=>e.text+(e.source?" | "+e.source:"")).join("\n"),i("specChip").className="chip "+(e.product_name?"ok":"bad"),i("specChip").textContent=e.product_name?"spec ready":"incomplete"}function tz(){let e=et.product.spec;return Object.entries(tq).forEach(t=>{let[a,n]=t;i(n)&&(e[a]=i(n).value.trim())}),e.key_features=i("f_features").value.split("\n").map(e=>e.trim()).filter(Boolean),e.objections=i("f_objections").value.split("\n").map(e=>e.trim()).filter(Boolean),e.proof_points=i("f_proof").value.split("\n").map(e=>e.trim()).filter(Boolean).map(e=>{let t=e.lastIndexOf("|");return t>0?{text:e.slice(0,t).trim(),source:e.slice(t+1).trim()}:{text:e,source:""}}),e}async function tB(){let e=i("in_url").value.trim();if(!e)return el("s1status","Paste a brand or product URL first.","err");eV("btnIntel",!0,"Reading…");try{et.product.url=e;let t=await eR(e,[],!1);el("s1status","<span class='spin'></span> Extracting brand system and product facts…","run");let a=null;try{let t=await ek([{text:tN(e,i("in_notes").value.trim())}],{json:!0,thinking:"low",tools:[{url_context:{}}]});a=ep(t)}catch(o){el("s1status","<span class='spin'></span> URL context unavailable — re-extracting from harvested page text…","run");let n=await ek([{text:tN(e,i("in_notes").value.trim())+"\n\nThe page could not be fetched directly. Work ONLY from this extracted page text:\n"+t.pageText}],{json:!0,thinking:"low"});a=ep(n)}let n=tO(a);
1n.length&&(await eR(e,n,!0)).images.forEach(e=>{et.product.images.some(t=>t.src===e.src)||et.product.images.push({src:e.src,w:e.w,h:e.h})}),t.images.forEach(e=>{et.product.images.some(t=>t.src===e.src)||et.product.images.push({src:e.src,w:e.w,h:e.h})}),et.product.images.length&&!et.product.images.some(e=>e.hero)&&(et.product.images[0].hero=!0,et.product.images[0].sel=!0),tU(),await oc(!0),el("s1status","Brand + product intelligence built. ".concat(et.product.images.length," verified images."),"okk")}catch(e){if(el("s1status","Failed: "+r(e.message),"err"),u())throw e}eV("btnIntel",!1)}async function tF(){let e=et.product.images.filter(e=>e.kit||e.dataUrl);if(!e.length)return el("s1status","Drop at least one guideline page or product photo first.","err");eV("btnKitIntel",!0,"Reading…");try{let t=[{text:tR(i("in_kitnotes").value.trim())}];for(let a of e.slice(0,8)){let e=ew(a.dataUrl);e&&t.push(e)}let a=await ek(t,{json:!0,thinking:"low"});tO(ep(a)),el("s1status","Brand kit read. Values it could not verify are prefixed ~ — check them.","okk")}catch(e){if(el("s1status","Failed: "+r(e.message),"err"),u())throw e}eV("btnKitIntel",!1)}function tU(){let e=i("imgGrid"),t=et.product.images;i("imgCount")&&(i("imgCount").textContent=t.length?"\xb7 ".concat(t.length," images, ").concat(t.filter(e=>e.sel||e.hero).length," selected"):""),e&&(e.innerHTML=t.map((e,t)=>'\n    <div class="img-cell '.concat(e.hero?"hero":e.sel?"sel":"",'" data-i="').concat(t,'">\n      <img src="').concat(e.dataUrl||e.src,'" alt="" loading="lazy">\n      ').concat(e.hero?'<span class="tag herotag">hero</span>':e.sel?'<span class="tag">ref</span>':"",'\n      <button type="button" class="rm" data-rm="').concat(t,'" title="Remove this image" aria-label="Remove this image">✕</button>\n      ').concat(e.w?'<span class="viewlab">'.concat(e.w,"\xd7").concat(e.h,"</span>"):"","\n    </div>")).join("")||'<div class="tiny">No images yet — harvest a page or upload product photos.</div>',e.querySelectorAll(".img-cell").forEach(e=>{let t=+e.dataset.i;e.onclick=e=>{e.target.closest(".rm")||(et.product.images[t].sel=!et.product.images[t].sel,tU())},e.ondblclick=()=>{et.product.images.forEach(e=>e.hero=!1),et.product.images[t].hero=!0,et.product.images[t].sel=!0,tU()},e.oncontextmenu=e=>{e.preventDefault(),e9(et.product.images[t].dataUrl||et.product.images[t].src)}}),e.querySelectorAll(".rm").forEach(e=>e.onclick=t=>{var a;t.preventDefault(),t.stopPropagation(),a=+e.dataset.rm,et.product.images[a]&&(et.product.images.splice(a,1),et.product.images.length&&!et.product.images.some(e=>e.hero)&&(et.product.images[0].hero=!0,et.product.images[0].sel=!0),tU(),oc(!0))}))}async function tD(e,t){for(let a of e){if(!/^image\//.test(a.type))continue;let e=await eg(a),n=await new Promise(t=>{let a=new Image;a.onload=()=>t({w:a.naturalWidth,h:a.naturalHeight}),a.onerror=()=>t({}),a.src=e});et.product.images.push(Object.assign({dataUrl:e,src:e,sel:!0},n,t||{}))}!et.product.images.some(e=>e.hero)&&et.product.images.length&&(et.product.images[0].hero=!0),tU(),oc(!0)}function tH(){let e=et.brand.evidence||[];i("evChip").textContent=e.length+" item"+(1===e.length?"":"s"),i("evChip").className="chip "+(e.length?"ok":""),i("evList").innerHTML=e.map((e,t)=>'<div class="ev-row">\n    <span class="chip acc">'.concat(r(e.kind),'</span>\n    <span class="grow"><b style="font-size:12px">').concat(r(e.claim_text),'</b>\n      <div class="tiny">source: ').concat(r(e.source||"— not stated"),"</div></span>\n    ").concat(e.asset_dataUrl?'<img src="'.concat(e.asset_dataUrl,'" style="height:34px;border-radius:5px">
1'):"",'\n    <button class="btn small ghost" data-ev="').concat(t,'">✕</button></div>')).join("")||'<div class="tiny">No evidence — ethos narrows to founder-led and craft.</div>',i("evList").querySelectorAll("[data-ev]").forEach(e=>e.onclick=()=>{et.brand.evidence.splice(+e.dataset.ev,1),et.brand._dirty=!0,tH(),tW(),tZ()})}function tG(e){return(et.brand.evidence||[]).some(t=>t.kind===e)}function tW(){var e;let t=et.product.spec,a=[],n=[],o=(e,t,o,i)=>{let r=String(e||"").trim();if(r&&(r=r.replace(/\s*\|\s*[^|]{0,40}$/,"").trim())){if("legal"!==t){let e=(e=>{if(e.length>140)return"too long to be a claim";if((e.match(/\|/g)||[]).length>=2)return"looks like an SEO page title";if(/\b(home|shop now|read more|click here|privacy policy|terms|cookie)\b/i.test(e)&&e.length<24)return"navigation text";let t=e.toLowerCase().split(/\s+/).filter(e=>e.length>3),a=new Set(t);return t.length>=6&&a.size<=.55*t.length?"the same phrase repeated":null})(r);if(e)return void n.push({text:r,why:e})}a.some(e=>e.text.toLowerCase()===r.toLowerCase())||a.push({text:r,kind:t,provenance:o,source:i||""})}};o(t.product_name,"name","client_input"),o(t.brand_name||et.brand.brand_name,"name","client_input"),o(t.tagline,"tagline","client_input"),o(t.offer_display,"offer","client_input"),o(t.price_display,"price","client_input"),o(t.price_was,"price","client_input"),(t.key_features||[]).forEach(e=>o(e,"proof","client_input")),(t.proof_points||[]).forEach(e=>o(e.text,"proof",e.source?"page_extract":"client_input",e.source)),(et.brand.evidence||[]).forEach(e=>o(e.claim_text,"proof","brand_evidence",e.source)),((null==(e=et.brand.legal)?void 0:e.mandatory_lines)||[]).forEach(e=>o(e,"legal","client_input")),et.claims=a,et.claimsRejected=n;let l=i("claimChip");l&&(l.textContent=a.length+" renderable claims",l.className="chip "+(a.length?"ok":"warn"));let s=i("claimTbl");return s&&(s.innerHTML="<tr><th>Claim</th><th>Kind</th><th>Provenance</th><th>Source</th></tr>"+a.map(e=>"<tr><td>".concat(r(e.text),'</td><td class="mono">').concat(r(e.kind),'</td><td class="mono">').concat(r(e.provenance),'</td><td class="tiny">').concat(r(e.source||"—"),"</td></tr>")).join("")+(n.length?'<tr><td colspan="4" class="tiny" style="color:var(--warn)">Kept out of the lockbox: '.concat(n.map(e=>r(e.text.slice(0,60))+" <i>("+r(e.why)+")</i>").join(" \xb7 "),"</td></tr>"):"")),a}let tK=/(\d+(?:[.,]\d+)?\s*%)|(\bno\.?\s*1\b)|(#1\b)|(\b(?:best|fastest|strongest|safest|cheapest|highest|lowest|world[- ]?class|unbeatable)\b)|(\bclinically\b)|(\b\d+\s*x\b)|(\b\d{3,}(?:[.,]\d{3})*\+?\s*(?:users?|customers?|families|reviews?|ratings?|doctors?|dentists?|experts?|mothers?)\b)|(\b\d(?:\.\d)?\s*(?:★|stars?)\b)|(\bawards?\b|\baward[- ]winning\b)|(\bcertified\b)|(\bISO\b)|(\bpatented\b)|(\bguaranteed\b)|(\bdoctor[- ]recommended\b)|(\bclinically proven\b)/gi,tJ=/\b\d[\d.,]*\s*(?:%|x|g|mg|ml|l|kg|hours?|days?|weeks?|years?|nutrients?|vitamins?|stages?|litres?|liters?)?\b/gi;function tY(e,t){let a,n=String(e||"");if(!n.trim())return[];let o=function(){let e=et.product.spec||{};return[...et.claims.map(e=>e.text),...e.key_features||[],...(e.proof_points||[]).map(e=>e.text||""),e.price_display,e.price_was,e.offer_display,e.tagline,e.product_name,e.brand_name].filter(Boolean).join(" ").toLowerCase()}(),i=e=>o.includes(String(e).toLowerCase().trim()),r=[],l=t?tJ:tK;for(l.lastIndex=0;a=l.exec(n);){let e=(a[0]||"").trim();e&&(!t||/\d/.test(e))&&(i(e)||r.includes(e)||r.push(e))}return r}let tV=[[/fintech|bank|loan|credit|insur|invest|mutual fund|stock|trading|upi|payment/i,["fintech","finance","insurance"]],[/saas|software|platform|api|crm|dashboard|b2b|enterprise/i,["saas","b2b"]],[/pharma|medicine|tablet|syrup|supplement|vitamin|clinic|dental|health|wellness/i,["health","pharma","nutrition","dental"]],[/skin|hair|serum|cream|shampoo|soap|cosmetic|beauty|makeup|lipstick|fragrance|perfume/i,["personal_care","beauty"]],[/shoe|sneaker|apparel|shirt|dress|saree|kurta|jean|jacket|wear|fashion|watch/i,["apparel","watches"]],[/jewel|gold|diamond|ring|necklace/i,["jewellery","luxury"]],[/snack|biscuit|chocolate|coffee|tea|juice|drink|beverage|food|masala|oil|atta|rice|dairy/i,["food","beverage","confectionery"]],[/phone|laptop|tv|earbud|headphone|camera|speaker|tablet|charger|battery|electronic/i,["electronics","battery"]],[/mixer|grinder|washing|fridge|refrigerat|ac |air condition|purifier|appli
1ance|cooker|fan |geyser/i,["appliance"]],[/car|bike|scooter|tyre|auto|motor|ev /i,["auto"]],[/sofa|mattress|furniture|chair|table|home|decor|kitchen/i,["home","furniture"]],[/course|class|exam|tuition|school|college|learn|edtech/i,["education"]],[/game|gaming|console|esport/i,["gaming"]],[/travel|hotel|flight|holiday|trip|resort/i,["travel"]],[/gym|fitness|protein|yoga|running/i,["fitness","sports"]],[/clean|detergent|floor|toilet|dishwash|stain/i,["cleaning"]],[/drill|tool|hardware|paint|cement|plumb/i,["tooling","construction","repair"]],[/app\b|mobile app|android|ios/i,["app"]],[/news|ott|stream|music|magazine|media/i,["media"]],[/toy|kids|baby|diaper/i,["toys","kids"]],[/pet|dog|cat food/i,["pet"]],[/plant|garden|seed/i,["garden"]],[/subscription|plan|monthly|annual/i,["subscription"]],[/gift|hamper/i,["gifting"]],[/d2c|direct-to-consumer|handmade|artisan|craft/i,["d2c","craft"]]];function t$(){let e=et.product.spec,t=[e.category,e.product_name,e.brand_name,(e.key_features||[]).join(" ")].join(" "),a=new Set;
1return tV.forEach(e=>{let[n,o]=e;n.test(t)&&o.forEach(e=>a.add(e))}),/₹\s*[1-9]\d{4,}|\b[1-9]\d{4,}\b/.test(e.price_display||"")&&a.add("durable"),a.size||a.add("commodity"),[...a]}function tX(e){let t=et.product.spec,a={price:!!(t.price_display||"").trim(),offer:!!(t.offer_display||"").trim(),features:(t.key_features||[]).length>=2,variants:!!(t.variants_available||"").trim(),objections:(t.objections||[]).length>=1};return(e.needs||[]).filter(e=>!a[e])}function tQ(){let e=t$(),t=et.camp.objective;Z.forEach(a=>{var n;let o=6,i=[],r=tX(a);if(a.evidence_required&&!tG(a.evidence_required)){et.fit[a.key]={score:1,why:"needs real ".concat(a.evidence_required," evidence — none on file"),src:"heuristic",blocked:"evidence"};return}r.length&&(o=2,i.push("missing "+r.join(", "))),(a.fits.objectives||[]).includes(t)?(o+=2,i.push("suits "+t)):(o-=1,i.push("not a natural "+t+" device"));let l=e.filter(e=>(a.fits.good||[]).includes(e)),s=e.filter(e=>(a.fits.bad||[]).includes(e));l.length&&(o+=2,i.push("category fit: "+l.join("/"))),s.length&&(o-=5,i.push("category error: "+s.join("/"))),(null==(n=et.brand.legal)?void 0:n.category)==="health"&&"logos_before_after"===a.key&&(o=Math.min(o,2),i.push("before/after is a regulated claim in health")),et.fit[a.key]={score:b(Math.round(o),0,10),why:i.join(" \xb7 "),src:"heuristic"}})}function tZ(){"function"==typeof av?av():tQ(),t4(),"function"==typeof aA&&aA()}function t0(){i("c_objective")&&(i("c_objective").value=et.camp.objective||"conversion"),i("c_audience")&&(i("c_audience").value=et.camp.audience||""),i("c_market")&&(i("c_market").value=et.camp.market||"India"),i("c_competitor")&&(i("c_competitor").value=et.camp.competitor||""),i("c_campaign")&&(i("c_campaign").value=et.camp.campaign||"")}function t1(){if(!i("nc_name"))return;let e=i("nc_name").value.trim();if(e&&(et.camp.campaign=e,ot&&(ot.name=e),i("c_campaign")&&(i("c_campaign").value=e)),i("nc_objective")){let e=i("nc_objective").value;et.camp.objective=e,ot&&(ot.objective=e),i("c_objective")&&(i("c_objective").value=e)}
1if(i("nc_audience")){let e=i("nc_audience").value.trim();et.camp.audience=e,ot&&(ot.audience=e),i("c_audience")&&(i("c_audience").value=e)}if(i("nc_market")){let e=i("nc_market").value;et.camp.market=e,ot&&(ot.market=e),i("c_market")&&(i("c_market").value=e)}i("nc_brand")&&ot&&(ot.brand_id=i("nc_brand").value||"")}function t2(){var e;on&&!oa&&function(){let e=i("step0"),t=i("newCampCard");return!(!e||!t||e.classList.contains("hidden"))&&"none"!==t.style.display}()&&t1(),i("c_objective")&&(et.camp.objective=i("c_objective").value),i("c_audience")&&(et.camp.audience=i("c_audience").value.trim()),i("c_market")&&(et.camp.market=i("c_market").value),i("c_competitor")&&(et.camp.competitor=i("c_competitor").value.trim()),et.camp.campaign=((null==(e=i("c_campaign"))?void 0:e.value)||"").trim()}function t4(){let e=i("devGroups");if(!e)return;Object.keys(et.fit).length||tQ(),e.innerHTML=Q.map(e=>{var t;let a=Z.filter(t=>t.appeal===e.k).sort((e,t)=>{var a,n;return((null==(a=et.fit[t.key])?void 0:a.score)||0)-((null==(n=et.fit[e.key])?void 0:n.score)||0)}),n=a[0];return'<div class="var-group">\n      <div class="var-group-label">'.concat(e.emoji," ").concat(r(e.label),'<span class="ln"></span>\n        <span class="tiny" style="text-transform:none;letter-spacing:0">').concat(r(e.blurb),'</span></div>\n      <div class="tiny" style="margin:-4px 0 9px">Recommended here: <b style="color:var(--acc2)">').concat(r(n?n.label:"—"),"</b>").concat(n&&(null==(t=et.fit[n.key])?void 0:t.why)?" — "+r(et.fit[n.key].why):"",'</div>\n      <div class="var-grid">').concat(a.map(e=>(function(e){var t,a,n,o,i;let l=et.fit[e.key]||{score:0,why:""},s=!!(null==(t=et.sel[e.key])?void 0:t.on),c=l.score<4;return'<div class="var-card '.concat(s?"on":""," ").concat(c?"blocked":"",'" data-k="').concat(e.key,'">\n    <div class="vc-check">').concat(s?"✓":"",'</div>\n    <div class="vc-top"><div class="vc-emoji">').concat(e.emoji,'</div>\n      <div style="min-width:0"><div class="vc-name">').concat(r(e.label),'\n        <span class="fitb ').concat((o=l.score)>=8?"best":o>=6?"good":o>=4?"weak":"no",'" style="margin-left:5px">').concat((i=l.score)>=8?"★ best":i>=6?"✓ good":i>=4?"⚠ weak":"✖ unsuitable"," ").concat(l.score,'/10</span></div>\n        <div class="vc-hint">').concat(r(e.what),'</div></div></div>\n    <div class="vc-why ').concat(c?"no":"",'">').concat(r(l.why||"—")).concat("ai"===l.src?" \xb7 AI":"","</div>\n    ").concat(e.evidence_required&&!tG(e.evidence_required)?'<div class="vc-lock">Locked — needs real <b>'.concat(r(e.evidence_required),"</b> evidence on file. No invented seals, studies, experts or star clusters.</div>"):"","\n    ").concat((null==(a=et.sel[e.key])?void 0:a.override)?'<div class="vc-lock">⚠ Gate overridden: '.concat(r(et.sel[e.key].override),"</div>"):"",'\n    <div class="vc-edit"><label class="tiny">Brief <span class="hint">').concat(r(e.brief_hint),'</span></label>\n      <textarea data-brief="').concat(e.key,'" placeholder="').concat(r(e.brief_hint),'">').concat(r((null==(n=et.sel[e.key])?void 0:n.brief)||""),"</textarea></div>\n  </div>")})(e)).join(""),"</div></div>")}).join(""),e.querySelectorAll(".var-card").forEach(e=>{let t=e.dataset.k;e.onclick=e=>{var a,n;if("TEXTAREA"===e.target.tagName)return;let o=et.fit[t]||{score:0};if(o.score<4&&!(null==(a=et.sel[t])?void 0:a.override)){return void(n="<b>".concat(r(ee[t].label),"</b> is gated at <b>").concat(o.score,"/10</b> — ").concat(r(o.why||"unsuitable"),".<br><br>\n          ").concat(ee[t].evidence_required?"This is an <b>evidence-bound</b> device. The clean fix is to add real ".concat(r(ee[t].evidence_required)," evidence in step 1 — no invented seals, studies, experts or star clusters. Override only if the evidence exists and you are waiting on the file."):"The gate exists to stop category errors reaching the generator. Override only if you know something the scoring doesn't."),ox={onConfirm:e=>{et.sel[t]=et.sel[t]||{brief:""},et.sel[t].on=!0,et.sel[t].override=e,oS("intelligence","gate.override","".concat(t," (").concat(o.score,"/10) — ").concat(e)),t4(),oh(),el("s2status","Gate overridden on <b>".concat(r(ee[t].label),"</b> — recorded in the audit trail."),"warn")}},i("ovWhat").innerHTML=n,i("ov_reason").value="",el("ovStatus","",""),i("ovModal").classList.add("show"),setTimeout(()=>i("ov_reason").focus(),50))}et.sel[t]=et.sel[t]||{on:!1,brief:""},et.sel[t].on=!et.sel[t].on,t4(),oh()}}
1),e.querySelectorAll("textarea[data-brief]").forEach(e=>{e.oninput=()=>{et.sel[e.dataset.brief]=et.sel[e.dataset.brief]||{on:!0,brief:""},et.sel[e.dataset.brief].brief=e.value,oh()}});let t=Object.values(et.sel).filter(e=>e.on).length;i("devChip").textContent=t+" selected",i("devChip").className="chip "+(t?"acc":"bad"),tW()}function t5(){return Object.entries(et.sel).filter(e=>{var t;let[a,n]=e;return n.on&&(((null==(t=et.fit[a])?void 0:t.score)||0)>=4||n.override)}).map(e=>{let[t]=e;return t})}function t6(){let e=i("langBar");e&&(e.innerHTML=L.map(e=>{let t=et.plan.langs.includes(e.k);return'<button type="button" class="'.concat(t?"on":"",'" data-lc="').concat(e.k,'"\n      title="').concat(r(e.script)," script").concat(e.note?" — "+r(e.note):"",'">').concat(r(e.label),"</button>")}).join(""),e.querySelectorAll("[data-lc]").forEach(e=>e.onclick=()=>{let t=e.dataset.lc;if(et.plan.langs.includes(t))if(!(et.plan.langs.length>1))return el("s3status","A campaign has to ship in at least one language.","warn");else et.plan.langs=et.plan.langs.filter(e=>e!==t);else et.plan.langs.push(t);et.plan.langs=L.map(e=>e.k).filter(e=>et.plan.langs.includes(e)),t6(),t7(),ic(),"function"==typeof oK&&oK(),oh()}))}function t8(){let e=i("fmtGroups"),t={};D.formats.forEach(e=>{(t[e.platform]=t[e.platform]||[]).push(e)}),i("regChip").textContent="registry v"+D.registry_version+" \xb7 "+D.verified_on,e.innerHTML=Object.entries(t).map(e=>{let[t,a]=e;return'\n    <div class="fmt-group"><div class="var-group-label">'.concat(r(H[t]||t),'<span class="ln"></span></div>\n    <div class="fmt-grid">').concat(a.map(e=>{let t=et.plan.formats.includes(e.id),a=e.w/e.h,n=Math.min(26,Math.max(8,a>=1?26:26*a)),o=Math.min(26,Math.max(6,a>=1?26/a:26));return'<div class="fmt-pill '.concat(t?"on":"",'" data-f="').concat(e.id,'">\n        <span class="box" style="width:').concat(n,"px;height:").concat(o,'px"></span>\n        <span class="fx"><b>').concat(r(e.placement),"</b><span>").concat(e.w,"\xd7").concat(e.h," \xb7 ").concat(e.max_bytes/1024|0,"KB</span></span></div>")}).join(""),"</div></div>")}).join(""),e.querySelectorAll(".fmt-pill").forEach(e=>e.onclick=()=>{let t=e.dataset.f;et.plan.formats=et.plan.formats.includes(t)?et.plan.formats.filter(e=>e!==t):[...et.plan.formats,t],t8(),t7(),oh()})}function t3(){let e=t5(),t=new Set,a=new Set(et.plan.formats.map(e=>{var t;return null==(t=G[e])?void 0:t.plate}).filter(Boolean)),n=[];return e.forEach(e=>{for(let o=1;o<=et.plan.variations;o++)a.forEach(a=>{let i="".concat(e,"_v").concat(o,"_").concat(K(a),"_").concat(W(a).replace(":","x"));t.has(i)||(t.add(i),n.push({id:i,deviceKey:e,vn:o,family:K(a),aspect:W(a),plateKey:a,status:"queued"}))})}),n}function t7(){t2();let e=t3(),t=t5().length,a=t*et.plan.variations*et.plan.formats.length*et.plan.langs.length,n=t*et.plan.variations*et.plan.langs.length;i("planSummary").innerHTML='\n    <div class="sm"><b>'.concat(t,'</b><span>devices</span></div>\n    <div class="sm"><b>').concat(et.plan.variations,'</b><span>variations each</span></div>\n    <div class="sm"><b>').concat(et.plan.formats.length,'</b><span>formats</span></div>\n    <div class="sm"><b>').concat(et.plan.langs.length,'</b><span>languages</span></div>\n    <div class="sm acc"><b>').concat(e.length,'</b><span>paid plates (NB2)</span></div>\n    <div class="sm"><b>').concat(n,'</b><span>copy calls (text)</span></div>\n    <div class="sm acc"><b>').concat(a,"</b><span>creatives composed free</span></div>");let o=V.find(e=>e.k===(et.plan.style||"scrim"));i("styleNote")&&o&&(i("styleNote").textContent=o.note),i("planChip").textContent="".concat(e.length," plates → ").concat(a," creatives"),i("planChip").className="chip "+(e.length?"acc":"bad"),i("tierNote").textContent="draft"===et.plan.tier?"Lite runs the same plate set cheaper; falls back to NB2 if the key rejects the Lite ID.":"Nano Banana 2 — live-verified request shape, the default for final plates.",e2()}let t9={page_intent:"",b2b:null,category:"",sector:"",audiences:[],purchase_cycle_months:null,in_market_share:null,horizon_months:null,ceps:[],proof:[],brand_position:"",brand_campaigns:[],competitors:[],avoid:[],confidence:"",sources:[],notes:"",research:"",queries:[]};function ae(){return et.brief=Object.assign({},t9,et.brief||{}),et.brief}function at(e){if(null==e||""===e)return null;let t=Number(e);return Number.isFinite(t)?t:null}let aa={awareness:24,launch:12,consideration:12,lead:6,conversion:3,offer:1,retention:6};function an(){let e=at(ae().horizon_months);return null!==e&&e>0?e:aa[et.camp.objective]||6}function ao(){let e=ae(),t=at(e.in_market_share);if(null!==t&&t>0)return Math.max(0,Math.min(1,t>1?t/100:t));let a=at(e.purchase_cycle_months);return null!==a&&a>0?Math.max(.01,Math.min(1,1/Math.max(1,a))):e.b2b?.08:.35}function ai(){return!!(ae().proof||[]).some(e=>e&&e.verifiable)||(et.claims||[]).some(e=>e&&/client|cert|lab|audit|third/i.test(String(e.provenance||e.source||"")))}let ar=/sustainab|green|carbon|climate|emission|net[- ]?zero|recycl|circular|esg|biodiversit|water|purpose|social|diversity|inclusion/i;function al(){return!!(ae().proof||[]).some(e=>e&&e.verifiable&&ar.test(String(e.claim||"")))||(et.claims||[]).some(e=>e&&ar.test(String(e.text||""))&&/client|cert|lab|audit|third/i.test(String(e.provenance||e.source||"")))}function as(){return(ae().ceps||[]).some(e=>e&&e.seasonal&&(at(e.commonality)||0)>=4)}function ac(){return as()&&!ap()}let ad=["corporate-purpose","employer-brand","investor","policy"];function ap(){return ad.includes(ae().page_intent)}function au(e){let t=String(e||"").trim();return t?(/^[aeiou]/i.test(t)?"an ":"a ")+t:"this"}let ah=["pathos_occasion"],am=["ethos_certification","ethos_longevity","ethos_craft"],af=["pathos_nostalgia","pathos_pride"],ag=["logos_spec_hero","logos_comparison","logos_cost_per_use","logos_before_after","logos_demo","logos_mechanism","self_choose","self_which_one"];function ab(e){let t=ae();
1return ah.includes(e.key)&&!ac()?as()?"this is ".concat(au(t.page_intent)," page — the seasonal buying situation in this category belongs to the product business, not to the ask on this page, so an occasion frame would borrow a moment that has nothing to do with what is being asked for"):"no seasonal buying situation exists for this category — an occasion frame would attach the brand to a moment nobody buys in":ap()&&ag.includes(e.key)?"this is ".concat(au(t.page_intent)," page — there is no product claim to argue, so a feature angle has nothing to say"):am.includes(e.key)&&ar.test([t.page_intent,t.brand_position,t.category,et.product.spec.category].join(" "))&&!al()?"a sustainability or purpose claim needs third-party-verifiable proof on file — an unverifiable one measurably damages the brand":""}function ay(e){var t,a,n;let o=ae(),i=an(),r=ao(),l=[],s=5,c=(e,t)=>{e&&(s+=e,l.push("".concat(e>0?"+":"").concat(e," ").concat(t)))};"pathos"===e.appeal&&c(i>=18?3:i>=12?2:i>=6?1:-1,i>=12?"emotional work compounds over a ".concat(i,"-month horizon (49% vs 23% very large profit effects at 3+ years)"):"a ".concat(i,"-month horizon is too short for emotional work to pay back")),"logos"===e.appeal&&(c(i<=6?2:i<=12?0:-2,i<=6?"rational angles carry short-horizon conversion, which is what a ".concat(i,"-month plan needs"):"rational effects plateau and do not compound over ".concat(i," months")),c(r>=.25?2:r>=.1?1:-2,r>=.1?"about ".concat(Math.round(100*r),"% of this audience is plausibly in-market"):"only about ".concat(Math.round(100*r),"% of this audience is in-market, so an argument has few takers")),c(ai()?2:-3,ai()?"a verifiable, non-parity claim is on file":"no verifiable claim on file — 86% of B2B buyers already see no difference worth paying for, so an unproven spec claim adds nothing"),(ae().audiences||[]).some(e=>e&&e.technical)&&c(2,"the audience is technically competent and does compare specs")),"ethos"===e.appeal&&(c(ai()?2:-2,ai()?"credibility claims are backed by evidence on file":"credibility angles need evidence and there is none on file"),i>=12&&c(1,"credibility compounds over a long horizon")),"self"===e.appeal&&(c(1,"self-persuasion travels well in a feed and needs no claim"),i>=12&&c(1,"identification builds memory rather than arguing a case")),((null==(t=e.fits)?void 0:t.objectives)||[]).includes("awareness")&&c(1,"carries salience, the effect that predicts both short and long-term results"),af.includes(e.key)&&c(-2,"loyalty framing: 0.6 business effects against 2.4 for acquisition, and no loyalty campaign in the databank moved price sensitivity");let d=(o.ceps||[]).map(e=>String(e.cep||"").toLowerCase()).join(" ");d&&(e.cep_words||[]).some(e=>d.includes(e))&&c(2,"speaks to a category entry point this audience actually has"),((null==(a=e.fits)?void 0:a.objectives)||[]).includes(et.camp.objective)&&c(1,"suits the stated objective");let p=t$(),u=p.filter(t=>{var a;return((null==(a=e.fits)?void 0:a.bad)||[]).includes(t)}),h=p.filter(t=>{var a;return((null==(a=e.fits)?void 0:a.good)||[]).includes(t)});h.length&&c(2,"category fit: "+h.join("/")),u.length&&c(-5,"category error: "+u.join("/")),o.b2b&&((null==(n=e.fits)?void 0:n.bad)||[]).includes("b2b")&&c(-4,"this device does not work in B2B");let m=tX(e);return(m.length&&c(-3,"missing input: "+m.join(", ")),e.evidence_required&&!tG(e.evidence_required))?{score:1,why:"needs real ".concat(e.evidence_required," evidence — none on file"),blocked:"evidence"}:{score:Math.max(0,Math.min(10,Math.round(s))),why:l.join(" \xb7 ")}}function av(){Z.forEach(e=>{let t=ab(e);if(t){et.fit[e.key]={score:0,why:t,src:"rule",blocked:"veto"};return}let a=ay(e);et.fit[e.key]=Object.assign({src:ae().page_intent?"frame":"heuristic"},a)})}let a_={memory:{rank:1,label:"memory / permission",why:"most of this audience cannot take a conversion action, so the CTA asks for attention or permission instead",words:["Learn more","See more","Watch more","Find out more","Get details","Subscribe","Follow page","Read the report"]},interest:{rank:2,label:"low-commitment interest",why:"a real but small in-market share, and a cycle long enough that a hard ask would be premature",words:["Learn more","Get details","Download","Sign up","Get quote","Contact us","Request time","Book now","Apply now"]},action:{rank:3,label:"conversion",why:"a large share of this audie
1nce can act now, and the cycle is short enough for the ask to land",words:["Shop now","Buy now","Order now","Book now","Get offer","Sign up","Add to cart","Install app"]}};function aw(){let e=ae(),t=ao(),a=at(e.purchase_cycle_months);return ap()||null!==a&&a>=9?"memory":t>=.3&&(null===a||a<=3)?"action":t>=.1?"interest":"memory"}function ax(){let e=a_[aw()].words.map(e=>e.toLowerCase()),t=a2(),a=t.filter(t=>e.includes(String(t).toLowerCase()));if(a.length)return a;for(let e of["action","interest","memory"]){let a=t.filter(t=>a_[e].words.map(e=>e.toLowerCase()).includes(String(t).toLowerCase()));if(a.length)return a}return t}function ak(){let e=aw(),t=ae(),a=ao(),n=["".concat(a_[e].label," — ").concat(a_[e].why)];n.push("about ".concat(Math.round(100*a),"% of the audience is plausibly in-market"));let o=at(t.purchase_cycle_months);return null!==o&&n.push("a ".concat(o,"-month purchase cycle")),ap()&&n.push("this is a ".concat(t.page_intent," page, so there is no purchase to ask for")),n.join(" \xb7 ")}function aS(e){let t=et.product.spec,a=[e&&e.name,t.brand_name,t.product_name].filter(Boolean)[0]||et.product.url||"this organisation",n=e?[e.name?"site name: ".concat(e.name):"",e.desc?"what the page says it is about: ".concat(e.desc):"",(e.headings||[]).length?"headings: ".concat(e.headings.slice(0,14).join(" | ")):""].filter(Boolean).join("\n"):"";return"You are a planning director doing desk research before writing a campaign brief for ".concat(a,".\n\nSearch the web. Do not answer from memory — every numbered point below needs you to look something up, and a point you could not verify should say so plainly rather than be filled in with something plausible.\n\nWHAT WE ALREADY KNOW, FROM THEIR OWN PAGE\nURL: ").concat(et.product.url||t.destination_url||"—","\nmarket we are advertising in: ").concat(et.camp.market||"—","\n").concat(n,"\n\nRESEARCH THESE, IN THIS ORDER, AND SAY WHERE EACH ANSWER CAME FROM\n\n1. What this organisation actually sells, to whom, and whether the buyer is a business or a consumer. If it sells to businesses through distributors, say that.\n2. How it has advertised in the last five years: named campaigns, taglines, the years they ran, the markets they ran in, and the medium. Quote the tagline exactly if you find it.\n3. What it says about itself in its own words — its positioning line, purpose statement or brand claim.\n4. Its competitors in ").concat(et.camp.market||"this market",", and what each of them claims. Name them.\n5. Claims this brand can actually stand behind, and who could confirm each one — a named certification, an audited report, a regulator, a published study. Separate these from claims it merely asserts.\n6. Anything in this category that advertising is not allowed to say, or that would read as greenwashing, in ").concat(et.camp.market||"this market",". Include the regulator or code if there is one.\n7. How long a buying decision takes in this category, and what actually triggers someone to start looking. Not a guess — find someone who has written about it.\n8. Anything a planner would want to know about this brand in ").concat(et.camp.market||"this market",' specifically, and could not work out from the website.\n\nWrite prose, not JSON. Be specific: names, years, numbers, taglines. Where the search turned up nothing, write "not found" for that point — that is a useful answer and inventing something is not.')}function aE(e,t){let a=et.product.spec,n=e?["URL: ".concat(et.product.url||a.destination_url||""),e.name?"site name: ".concat(e.name):"",e.desc?"description: ".concat(e.desc):"",(e.headings||[]).length?"headings on the page: ".concat(e.headings.slice(0,25).join(" | ")):"",(e.navWords||[]).length?"site navigation: ".concat(e.navWords.slice(0,30).join(" | ")):"",(e.text||"").slice(0,9e3)].filter(Boolean).join("\n"):"(no page was fetched)";return"You are a planning director briefing an advertising campaign. Establish FACTS. Where you are not sure, say so — a wrong fact here produces a wrong campaign.\n\nYou are not searching now. Everything you need is below: the page the client gave us, and desk research that was just done with web search. Use the research where it answers a question, and the page where it does not. Where neither does, lower the confidence rather than 
1inventing a figure.\n\nDESK RESEARCH JUST COMPLETED".concat(t?" (from web search, cited)":" — NONE WAS AVAILABLE","\n").concat(t||"(the research pass returned nothing — mark confidence low)","\n\nTHE PAGE THE CLIENT GAVE US\n").concat(n,"\n\nWHAT THE OPERATOR HAS TYPED SO FAR\nproduct/service: ").concat(a.product_name||"—","\nbrand: ").concat(a.brand_name||"—","\ncategory: ").concat(a.category||"—","\nstated objective: ").concat(et.camp.objective||"—","\nstated audience: ").concat(et.camp.audience||"—","\nmarket: ").concat(et.camp.market||"—",'\n\nANSWER THESE, AND BE HONEST WHEN THE ANSWER IS "NOT THIS"\n\npage_intent — exactly one of: product-sell, ecommerce, lead-gen, corporate-purpose, employer-brand, investor, policy.\n  A page about a company\'s people, values or sustainability is NOT a product page. Say employer-brand or corporate-purpose.\nb2b — true or false. A specialty chemicals, industrial, ingredient or enterprise business is b2b even when its consumer-facing site looks friendly.\ncategory, sector — plain words.\naudiences — who actually decides, as objects {role, in_market_share (0-1), technical (bool), decision_power (1-5)}.\n  For B2B, remember most of the audience is not in the market at any moment; estimate it from how often they actually buy.\npurchase_cycle_months — how long from first interest to a decision, for THIS category. A capital plant is years; a supermarket refill is weeks.\nin_market_share — 0-1, the share of the audience able to act now.\nhorizon_months — over what period should this campaign be judged, given the objective.\nceps — the CATEGORY ENTRY POINTS: the real situations in which demand for this category arises, as\n  {cep, commonality 1-10 (how much category demand flows through it), competitiveness 1-10 (how crowded with competitor advertising), credibility 1-10 (can this brand plausibly own it), seasonal (bool)}.\n  Be strict. A festival or holiday is a category entry point ONLY where the festival genuinely originates demand for this category — gifting, sweets, apparel, jewellery, paint, consumer durables, travel. It is NOT one for industrial inputs, enterprise contracts, employer brand or corporate reputation. Agriculture is the honest exception: planting and spray windows are real seasonal entry points.\nproof — verifiable claims this brand can actually make, as {claim, source, verifiable (bool)}.\n  A sustainability or purpose claim counts only if a named third party could confirm it. Mark vague ones verifiable:false.\nbrand_position — how the brand positions itself, in its own words if you can find them.\nbrand_campaigns — campaigns or taglines it has actually run, with years, taken from the research above. Leave this empty rather than guessing; an invented campaign is worse than none.\ncompetitors — who it competes with.\navoid — anything this brand or category must not say: regulated claims, greenwashing risks, competitor names.\nconfidence — high, medium or low, about this brief as a whole.\nnotes — one or tw
1o sentences a planner would want to know and could not guess.\n\nReturn ONLY JSON with exactly those keys.')}async function aj(){if(et.running)return el("s2status","Something is already running.","warn");et.running=!0,e7("s2status"),eV("btnBrief",!0,"Researching…");try{let e=null,t=(et.product.url||et.product.spec.destination_url||"").trim();if(t){el("s2status","<span class='spin'></span> Reading the site…","run");try{e=await tf(t,e=>el("s2status","<span class='spin'></span> "+r(e),"run"))}catch(e){}}el("s2status","<span class='spin'></span> Searching the web for how this brand actually advertises…","run");let a="",n=[],o=[];try{let t=await ek([{text:aS(e)}],{json:!1,model:w,search:!0,thinking:"high"});a=(t&&void 0!==t.text?t.text:String(t||"")).trim(),n=t&&t.sources||[],o=t&&t.queries||[]}catch(e){a="",et.brief_research_error=e.message||String(e)}el("s2status","<span class='spin'></span> ".concat(n.length?"Read ".concat(n.length," source").concat(1===n.length?"":"s"," — framing"):"No sources came back — framing from the page alone"," the campaign…"),"run");let i=null,l=null;for(let t of["high","low"])try{let n=await ek([{text:aE(e,a)}],{json:!0,model:w,search:!1,thinking:t,temp:.4});i=ef(n&&void 0!==n.text?n.text:n),l=null;break}catch(e){if(l=e,"MAX_TOKENS"!==e.finishReason)break}if(l||!i)throw l||Error("the frame came back empty");let s=ae();Object.keys(t9).forEach(e=>{void 0!==i[e]&&(s[e]=i[e])}),s.sources=n,s.queries=o,s.research=a.slice(0,12e3),s.fetchedSite=!!e,av(),aA(),t4(),await oc(!0);let c=Z.filter(e=>{var t;return(null==(t=et.fit[e.key])?void 0:t.blocked)==="veto"}).length;el("s2status","Brief built".concat(s.fetchedSite?" from the site":""," and ")+(s.sources.length?"grounded in ".concat(s.sources.length," web source").concat(1===s.sources.length?"":"s"):"<b>not grounded in any web source</b> — the search returned nothing".concat(et.brief_research_error?" ("+r(et.brief_research_error)+")":"",", so treat the market facts as unverified"))+" — "+"".concat(r(s.page_intent||"unclassified")).concat(s.b2b?" \xb7 B2B":"")+"".concat(null!==at(s.purchase_cycle_months)?" \xb7 ".concat(at(s.purchase_cycle_months),"-month cycle"):"")+" \xb7 ".concat(Math.round(100*ao()),"% in-market \xb7 ").concat(an(),"-month horizon")+(c?". ".concat(c," device").concat(1===c?"":"s"," ruled out on the evidence, with the reason on each card."):"."),"okk")}catch(t){let e=t.raw||t.partial||"";el("s2status","Could not build the brief: "+r(t.message)+(e?'<details style="margin-top:6px"><summary style="cursor:pointer">what the model actually returned</summary><pre class="tiny" style="white-space:pre-wrap">'.concat(r(e),"</pre></details>"):""),"err")}et.running=!1,eV("btnBrief",!1)}function aA(){let e=i("briefBox");if(!e)return;let t=ae();if(!t.page_intent){e.innerHTML='<div class="tiny">No brief yet. Give step 1 a website URL and press <b>Build the brief</b> — it reads the site, searches for how this brand actually advertises, and establishes the facts every later step is scored against.</div>';return}let a=(e,t,a)=>t?'<span class="chip '.concat(a||"",'">').concat(r(e),": <b>").concat(r(t),"</b></span>"):"",n=(t.ceps||[]).slice(0,8).map(e=>{var t,a,n;return"<tr>\n      <td>".concat(r(e.cep||"")).concat(e.seasonal?' <span class="chip" style="padding:1px 5px">seasonal</span>':"",'</td>\n      <td class="mono">').concat(null!=(t=e.commonality)?t:"—",'</td><td class="mono">').concat(null!=(a=e.competitiveness)?a:"—",'</td>\n      <td class="mono">').concat(null!=(n=e.credibility)?n:"—","</td></tr>")}).join("");e.innerHTML='\n    <div class="bf-chips">\n      '.concat(a("page",t.page_intent,"acc")).concat(a("market type",t.b2b?"B2B":"B2C"),"\n      ").concat(a("c
1ategory",t.category),"\n      ").concat(a("cycle",null!==at(t.purchase_cycle_months)?at(t.purchase_cycle_months)+" months":""),"\n      ").concat(a("in-market",Math.round(100*ao())+"%"),"\n      ").concat(a("horizon",an()+" months"),"\n      ").concat(a("proof on file",ai()?"yes":"none",""),"\n      ").concat(a("confidence",t.confidence,"high"===t.confidence?"ok":"low"===t.confidence?"bad":"warn"),"\n    </div>\n    ").concat(t.brand_position?'<p class="tiny" style="margin:8px 0"><b>Positioning.</b> '.concat(r(t.brand_position),"</p>"):"","\n    ").concat((t.brand_campaigns||[]).length?'<p class="tiny" style="margin:6px 0"><b>What they have actually run.</b> '.concat(r((t.brand_campaigns||[]).map(e=>"string"==typeof e?e:[e.name,e.year,e.tagline].filter(Boolean).join(" ")).join(" \xb7 ")),"</p>"):"","\n    ").concat((t.audiences||[]).length?'<p class="tiny" style="margin:6px 0"><b>Who decides.</b> '.concat(r((t.audiences||[]).map(e=>"".concat(e.role).concat(e.technical?" (technical)":"").concat(null!==at(e.in_market_share)?" ~".concat(Math.round(100*at(e.in_market_share)),"% in-market"):"")).join(" \xb7 ")),"</p>"):"","\n    ").concat(n?'<div class="card-head" style="margin:10px 0 4px"><h3 style="font-size:12px">Category entry points — the situations demand actually arises in</h3></div>\n      <table class="tbl"><tr><th>Entry point</th><th>Commonality</th><th>Crowding</th><th>Credibility</th></tr>'.concat(n,'</table>\n      <p class="tiny" style="margin-top:5px">An occasion or festival angle is only allowed when one of these is seasonal and carries real category demand. That is why this table exists.</p>'):"","\n    ").concat((t.avoid||[]).length?'<p class="tiny" style="margin:8px 0;color:var(--warn)"><b>Must not say.</b> '.concat(r((t.avoid||[]).join(" \xb7 ")),"</p>"):"","\n    ").concat(t.notes?'<p class="tiny" style="margin:6px 0">'.concat(r(t.notes),"</p>"):"",'\n    <div class="card-head" style="margin:10px 0 4px"><h3 style="font-size:12px">Call to action</h3></div>\n    <p class="tiny">').concat(r(ak()),"<br>Allowed on the formats in this plan: <b>").concat(r(ax().join(", ")||"—"),"</b></p>\n    ").concat((t.sources||[]).length?'<details class="tiny" style="margin-top:8px"><summary style="cursor:pointer;color:var(--acc2)">'.concat(t.sources.length," web source").concat(1===t.sources.length?"":"s").concat((t.queries||[]).length?" \xb7 ".concat(t.queries.length," search").concat(1===(t.queries||[]).length?"":"es"):"","</summary>\n          ").concat((t.queries||[]).length?'<div style="margin-top:6px"><b>Searched for.</b> '.concat(r((t.queries||[]).join(" \xb7 ")),"</div>"):"",'\n          <div style="margin-top:6px">').concat(t.sources.map(e=>e.uri?'<div>\xb7 <a href="'.concat(r(e.uri),'" target="_blank" rel="noreferrer">').concat(r(e.title||e.uri),"</a></div>"):"<div>\xb7 ".concat(r(e.title||""),"</div>")).join(""),"</div></details>"):'<p class="tiny" style="margin-top:8px;color:var(--warn)"><b>No web sources.</b> This brief rests on the page alone — the market facts above are the model\'s estimate, not something it looked up. Treat the cycle, in-market share and competitor claims as unverified.</p>',"\n    ").concat(t.research?'<details class="tiny" style="margin-top:6px"><summary style="cursor:pointer;color:var(--acc2)">The desk research this brief was built from</summary>\n      <pre class="tiny" style="white-space:pre-wrap;margin-top:6px">'.concat(r(t.research),"</pre></details>"):"")}let aC={"past-ad":"a previous ad or campaign creative",guideline:"a brand guideline or style-guide page",logo:"a logo file or lockup","product-photo":"a product photograph",packaging:"packaging or a pack shot",screenshot:"a screenshot of a site, app or dashboard",other:"something else"};function aL(e){let t=e=>null!=e&&""!==e;return t(e)?Array.isArray(e)?e.filter(t):"object"==typeof e?Object.values(e).flatMap(e=>Array.isArray(e)?e:[e]).filter(t):[e]:[]}function aT(e){if("string"==typeof e)return e.trim();if(!e||"object"!=typeof e)return"";for(let t of["name","family","typeface","font","printed","value","label"])if("string"==typeof e[t]&&e[t].trim())return e[t].trim();return""}function aM(e){for(var t=arguments.length,a=Array(t>1?t-1:0),n=1;n<t;n++)a[n-1]=arguments[n];if("string"==typeof e)return e.trim();if(!e||"object"!=typeof e)return"";for(let t of a.concat(["text","value","claim","line"]))if("string"==typeof e[t]&&e[t].trim())return e[t].trim();return""}function aN(){return et.refs&&"object"==typeof et.refs||(et.refs={read:null,applied:{}}),et.refs.applied||(et.refs.applied={}),et.refs}function aR(){let e=et.product.images||[],t=e.filter(e=>e.sel||e.hero||e.kit);return(t.length?t:e.filter(e=>e.dataUrl||e.kit)).filter(e=>e.dataUrl||e.src).slice(0,10)}function aI(e){return"You are a brand strategist and art director going through a client's reference folder. ".concat(e," image").concat(1===e?"":"s"," ").concat(1===e?"is":"are",' attached, in order.\n\nRead each one. Report only what is VISIBLE in that image. This is a reading, not a judgement — an operator will decide what to act on.\n\nFOR EVERY IMAGE\nkind — exactly one of: past-ad, guideline, logo, product-photo, packaging, screenshot, other.\nwhat — one sentence on what the image is.\ntext_verbatim — EVERY piece of legible text in the image, transcribed exactly, in reading order, as an array of str
1ings. Keep the original language and script. Do not translate, do not tidy, do not summarise. If a word is cut off or unreadable, write it as far as you can read it and append "…". This is the single most valuable field: a guideline page states the brand\'s hex codes and font names here, and a previous ad states its own headline and CTA here.\ncolours — hex codes that are PRINTED IN THE IMAGE AS TEXT (a guideline page listing #004A96), as {hex, label, printed:true}. Separately, colours you can see being used, as {hex, label, printed:false} — estimate those from what you see and mark them printed:false. Never invent a hex for a colour you cannot see.\nfonts — an ARRAY of objects, one per typeface NAME you can read printed in the image: [{name:"Montserrat"}]. Names only — a description like "bold sans-serif" is not a name and belongs in "type_character" instead. Empty array if no name is printed.\ntype_character — one string describing the typography actually shown (serif or sans, weight, case, tight or loose tracking).\n\nADDITIONALLY, WHEN kind IS past-ad OR screenshot OF AN AD\nlayout — {archetype: "full-bleed-photo" | "split" | "solid-colour" | "product-on-white" | "collage" | "other",\n          text_position: where the copy block sits (e.g. "lower left, over the photograph"),\n          text_share: roughly what fraction of the frame the copy occupies, 0-1,\n          logo_position, logo_share (0-1),\n          printed_cta_button: true only if an actual drawn button or pill containing the CTA is visible,\n          scrim: true if a gradient or wash sits between the photograph and the type}\nheadline — the headline exactly as it appears.\ncta_wording — the call-to-action wording exactly as it appears, or "" if there is none.\nclaims — every claim the ad makes, as {claim, kind: "certification"|"statistic"|"award"|"superlative"|"offer"|"feature"|"other"}, transcribed not paraphrased.\nlegal — any small print, disclaimer or mandatory line, verbatim.\ntone — two or three words on the register.\n\nADDITIONALLY, WHEN kind IS guideline\nrules — the do/don\'t rules stated on the page, verbatim, as an array.\nlogo_rules — clear-space, minimum-size or knockout instructions, verbatim.\n\nHARD RULES\n- Transcribe. Do not paraphrase, translate or improve any text_verbatim, headline, cta_wording, claim or legal line.\n- Never invent a hex, a font name, a certification, a statistic or a price. Absent means "" or [].\n- printed:true is reserved for a hex you can literally read as characters in the image. Everything else is printed:false.\n- If an image is too low-resolution or too cropped to read, say so in "what" and return empty arrays. An honest blank beats a plausible guess.\n\nReturn ONLY JSON: {"images":[ one object per attached image, in the same order ]}')}async function aO(){let e=aR();if(!e.length)return el("refsStatus","Nothing to read yet — upload a guideline page, a pack shot or a screenshot of a previous ad, or select some of the harvested images.","err");let t=aN();return e8("refs",i("btnReadRefs"),"Reading…",async()=>{try{el("refsStatus","<span class='spin'></span> Reading ".concat(e.length," image").concat(1===e.length?"":"s"," — transcribing every legible word, which is slower than looking at them and worth it…"),"run");let a=[{text:aI(e.length)}];for(let t of e){let e=ew(t.dataUrl||t.src);if(e)a.push(e);else try{let e=await e_(t.src),n=ew(e);n&&a.push(n)}catch(e){}}let n=await ek(a,{json:!0,thinking:"high",temp:.2}),o=(ef(n).images||[]).map((t,a)=>Object.assign({},t,{i:a,thumb:(e[a]||{}).dataUrl||(e[a]||{}).src||""}));t.read={at:Date.now(),images:o,merged:aq(o)},aB();let i={};o.forEach(e=>{i[e.kind||"other"]=(i[e.kind||"other"]||0)+1});let r=t.read.merged;el("refsStatus","Read ".concat(o.length," image").concat(1===o.length?"":"s"," — ")+Object.entries(i).map(e=>{let[t,a]=e;return"".concat(a," ").concat(aC[t]||t)}).join(", ")+". Found ".concat(r.printedColours.length," printed hex code").concat(1===r.printedColours.length?"":"s",", ")+"".concat(r.claims.length," claim").concat(1===r.claims.length?"":"s",", ").concat(r.ctas.length," CTA wording").concat(1===r.ctas.length?"":"s"," ")+"and ".concat(r.words," words of transcribed text. Nothing has been applied — review below."),"okk"),await oc(!0)}catch(e){el("refsStatus","Could not read the references: "+r(e.message)+(e.raw?'<details style="margin-top:6px"><summary style="cursor:pointer">what the model actually returned</summary><pre class="tiny" style="white-space:pre-wrap">'.concat(r(e.raw),"</pre></details>"):""),"err")}})}function aq(e){let t=[],a=new Set,n=new Set,o=[],i=[],r=new Set,l=[],s=[],c=[],d=[],p=[],u=[],h=0;
1return e.forEach(e=>{aL(e.text_verbatim).forEach(e=>{h+=String(e).trim().split(/\s+/).filter(Boolean).length}),aL(e.colours).forEach(i=>{let r=td(i&&i.hex);if(!r)return;let l={hex:r,label:(i.label||"").slice(0,60),from:e.i};if(i.printed){if(a.has(r))return;a.add(r),t.push(l);let e=o.findIndex(e=>e.hex===r);e>=0&&(o.splice(e,1),n.delete(r));return}a.has(r)||n.has(r)||(n.add(r),o.push(l))}),aL(e.fonts).forEach(t=>{let a=aT(t);!(!a||a.length>40||/\s(serif|sans|bold|light|regular|tracking|weight|case)\b/i.test(a))&&(r.has(a.toLowerCase())||(r.add(a.toLowerCase()),i.push({name:a,from:e.i})))}),aL(e.claims).forEach(t=>{let a=aM(t,"claim");a&&l.push({claim:a,kind:t&&t.kind||"other",from:e.i})});let m=aM(e.cta_wording,"cta","wording");m&&s.push({text:m,from:e.i});let f=aM(e.headline,"headline");f&&c.push({text:f,from:e.i}),aL(e.legal).forEach(t=>{let a=aM(t,"legal");a&&d.push({text:a,from:e.i})}),aL(e.rules).concat(aL(e.logo_rules)).forEach(t=>{let a=aM(t,"rule");a&&p.push({text:a,from:e.i})}),e.layout&&"past-ad"===e.kind&&u.push(Object.assign({from:e.i},e.layout,{tone:e.tone||""}))}),{printedColours:t,observedColours:o,fonts:i,claims:l,ctas:s,headlines:c,legals:d,rules:p,layouts:u,words:h}}function aP(e){let t=aN(),a=t.read&&t.read.merged;if(!a)return;let n=a=>{t.applied[e]=Date.now(),aB(),el("refsStatus",a,"okk")};if("colours"===e){let e=et.brand._harvest||(et.brand._harvest={logos:[],colours:[],images:[]}),t=0,o=a.printedColours.filter(t=>!e.colours.some(e=>e.hex===t.hex)).map(e=>({hex:e.hex,why:"printed in the brand guideline"+(e.label?" as "+e.label:""),score:94}));return t=o.length,e.colours.splice(0,0,...o),e.colours=e.colours.filter(e=>e.hex),tL().then(()=>ty()),n("".concat(t," printed colour").concat(1===t?"":"s"," added to the brand's declared palette and the tokens re-derived. The hex codes the guideline states now outrank anything sampled."))}if("fonts"===e){let e=a.fonts.map(e=>e.name).find(e=>E.some(t=>t.toLowerCase()===e.toLowerCase()));if(!e)return el("refsStatus","None of the typefaces named in the references (".concat(r(a.fonts.map(e=>e.name).join(", ")||"none"),") are in the module's licensed set, so nothing was changed. Set the families by hand in Brand tokens — the compositor can only draw what it can load."),"warn");let t=E.find(t=>t.toLowerCase()===e.toLowerCase());return et.brand.type=Object.assign({},et.brand.type,{display_family:t}),et.brand._dirty=!0,ty(),n("Display family set to ".concat(r(t),", named in the references."))}if("claims"===e){let e=et.product.spec;e.proof_points=(e.proof_points||[]).slice();let o=0;return a.claims.forEach(a=>{!e.proof_points.some(e=>(e.text||"")===a.claim)&&(e.proof_points.push({text:a.claim,source:"transcribed from a previous campaign asset (".concat(aC[(t.read.images[a.from]||{}).kind]||"reference image",") — not verified"),verified:!1}),o++)}),tP(),tW(),n("".concat(o," claim").concat(1===o?"":"s"," added to the ledger, each marked unverified and sourced to the asset it was transcribed from. Approve them in the claims panel before any of them can reach a creative."))}if("copy"===e){let e=[...a.headlines.map(e=>e.text),...a.ctas.map(e=>e.text)];return et.brand._refCopy=e,n("".concat(e.length," line").concat(1===e.length?"":"s"," of previous campaign copy kept as reference. The copywriter pass is told what this brand has already said, so it can match the register instead of guessing at it — and so it does not hand you back a headline you ran last year."))}if("brief"===e){let e=ae(),a=az();return e.research=(e.research?e.research+"\n\n":"")+a,e.refsRead=t.read.images.length,aA(),n("".concat(aN().read.merged.words,' words of transcribed reference text added to the brief\'s evidence. Re-run "Build the brief" to have the framing read it — the market facts will be drawn from your own assets as well as the web.'))}if("layout"===e)return et.brand._refLayout=a.layouts,n("".concat(a.layouts.length," previous layout").concat(1===a.layouts.length?"":"s"," recorded. The design plan can be checked against how this brand actually lays an ad out, rather than against a house default."))}function az(){let e=aN().read;if(!e)return"";let t=["REFERENCE ASSETS THE OPERATOR SUPPLIED (transcribed from the images, not from the web)"];
1return e.images.forEach((e,a)=>{let n=["Image ".concat(a+1," — ").concat(aC[e.kind]||e.kind||"reference").concat(e.what?": "+e.what:"")],o=aM(e.headline,"headline"),i=aM(e.cta_wording,"cta");o&&n.push('  headline as it appeared: "'.concat(o,'"')),i&&n.push('  call to action as it appeared: "'.concat(i,'"'));let r=aL(e.claims).map(e=>aM(e,"claim")).filter(Boolean);r.length&&n.push("  claims made: ".concat(r.map(e=>'"'.concat(e,'"')).join("; ")));let l=aL(e.legal).map(e=>aM(e,"legal")).filter(Boolean);l.length&&n.push("  small print: ".concat(l.map(e=>'"'.concat(e,'"')).join("; "))),e.layout&&n.push("  layout: ".concat([e.layout.archetype,e.layout.text_position,e.layout.printed_cta_button?"a printed CTA button was used":"no printed CTA button",e.layout.scrim?"a scrim sits under the type":""].filter(Boolean).join(", ")));let s=aL(e.rules).concat(aL(e.logo_rules)).map(e=>aM(e,"rule")).filter(Boolean);s.length&&n.push("  stated rules: ".concat(s.join(" | ")));let c=aL(e.text_verbatim).map(e=>String(e).trim()).filter(Boolean);c.length&&n.push("  all legible text: ".concat(c.join(" | "))),t.push(n.join("\n"))}),t.join("\n\n").slice(0,2e4)}function aB(){let e=i("refsBox");if(!e)return;let t=aN(),a=t.read,n=i("btnRefsClear");if(n&&n.classList.toggle("hidden",!a),!a){let t=aR().length;e.innerHTML='<div class="tiny">'.concat(t?"".concat(t," image").concat(1===t?"":"s"," ready to read."):"No images yet."," Press <b>Read every image</b>.</div>");return}let o=a.merged,l=e=>a.images[e]?"image ".concat(e+1):"a reference",s=(e,a,n,o,i)=>{if(!n)return"";let l=!!t.applied[e];return'<div class="ref-g '.concat(l?"done":"",'">\n      <div class="ref-h"><b>').concat(r(a),'</b><span class="chip">').concat(n,"</span>\n        ").concat(l?'<span class="chip ok">applied</span>':'<button class="btn small primary" data-refapply="'.concat(e,'">').concat(r(i),"</button>"),'</div>\n      <div class="ref-b">').concat(o,"</div></div>")},c=a.images.map((e,t)=>'\n    <div class="ref-card">\n      '.concat(e.thumb?'<img src="'.concat(r(e.thumb),'" alt="" loading="lazy">'):"",'\n      <div>\n        <div class="ref-k">').concat(r(aC[e.kind]||e.kind||"reference"),'</div>\n        <div class="tiny">').concat(r(e.what||""),"</div>\n        ").concat(aL(e.text_verbatim).length?'<details class="tiny" style="margin-top:5px"><summary style="cursor:pointer;color:var(--acc2)">'.concat(aL(e.text_verbatim).length," pieces of text read from image ").concat(t+1,'</summary>\n          <div style="margin-top:4px">').concat(aL(e.text_verbatim).map(e=>"<div>\xb7 ".concat(r(String(e)),"</div>")).join(""),"</div></details>"):'<div class="tiny" style="color:var(--warn);margin-top:4px">No legible text in this one.</div>',"\n      </div></div>")).join("");e.innerHTML='\n    <div class="ref-cards">'.concat(c,"</div>\n    ").concat(s("colours","Hex codes printed in the references",o.printedColours.length,o.printedColours.map(e=>'<span class="ref-sw"><i style="background:'.concat(r(e.hex),'"></i>').concat(r(e.hex)).concat(e.label?" \xb7 "+r(e.label):"",' <span class="tiny">(').concat(r(l(e.from)),")</span></span>")).join(""),"Use these as the brand's palette"),"\n    ").concat(s("fonts","Typefaces named in the references",o.fonts.length,o.fonts.map(e=>"<div>\xb7 ".concat(r(e.name),' <span class="tiny">(').concat(r(l(e.from)),")</span>").concat(E.some(t=>t.toLowerCase()===e.name.toLowerCase())?"":' <span class="chip bad">not licensed here</span>',"</div>")).join(""),"Set the display family"),"\n    ").concat(s("claims","Claims previous assets actually made",o.claims.length,o.claims.map(e=>"<div>\xb7 <b>".concat(r(e.kind),"</b> — “").concat(r(e.claim),'” <span class="tiny">(').concat(r(l(e.from)),")</span></div>")).join("")+'<p class="tiny" style="margin-top:6px">These are transcriptions, not verifications. They go into the ledger unverified — that a previous ad made a claim is evidence the brand is willing to make it, not evidence it is true.</p>',"Add to the claims ledger, unverified"),"\n    ").concat(s("copy","Headlines and CTA wording from previous campaigns",o.headlines.length+o.ctas.length,[...o.headlines.map(e=>"<div>\xb7 headline — “".concat(r(e.text),'” <span class="tiny">(').concat(r(l(e.from)),")</span></div>")),...o.ctas.map(e=>"<div>\xb7 CTA — “".concat(r(e.text),'” <span class="tiny">(').concat(r(l(e.from)),")</span></div>"))].join(""),"Give the copywriter the brand's own register"),"\n    ").concat(s("layout","How this brand actually lays an ad out",o.layouts.length,o.layouts.map(e=>"<div>\xb7 ".concat(r([e.archetype,e.text_position,Number.isFinite(+e.text_share)?"type takes about ".concat(Math.round(100*e.text_share),"% of the frame"):"",e.printed_cta_button?"a printed CTA button was used":"no printed CTA button",e.scrim?"a scrim under the type":"",e.tone].filter(
1Boolean).join(" \xb7 ")),' <span class="tiny">(').concat(r(l(e.from)),")</span></div>")).join(""),"Record these conventions"),"\n    ").concat(s("brief","Everything legible, as evidence for the brief",o.words,'<p class="tiny">'.concat(o.words," words transcribed across ").concat(a.images.length," image").concat(1===a.images.length?"":"s",'. Added to the brief\'s evidence, the framing pass reads your own assets alongside the web — which is usually where the real facts are.</p>\n       <details class="tiny" style="margin-top:6px"><summary style="cursor:pointer;color:var(--acc2)">what would be added</summary>\n       <pre class="tiny" style="white-space:pre-wrap;margin-top:6px">').concat(r(az().slice(0,4e3)),"</pre></details>"),"Add to the brief's evidence"),"\n    ").concat(o.observedColours.length?'<details class="tiny" style="margin-top:8px"><summary style="cursor:pointer;color:var(--acc2)">'.concat(o.observedColours.length,' colours it could see but that were not printed as hex codes</summary>\n      <div style="margin-top:6px">').concat(o.observedColours.map(e=>'<span class="ref-sw"><i style="background:'.concat(r(e.hex),'"></i>').concat(r(e.hex)).concat(e.label?" \xb7 "+r(e.label):"","</span>")).join(""),"\n      <p style=\"margin-top:6px\">These are the model's estimate from the pixels, not a stated value, so they are kept out of the palette. The site's own CSS and the logo pixels are a better source and are already used.</p></div></details>"):""),e.querySelectorAll("[data-refapply]").forEach(e=>e.onclick=()=>aP(e.dataset.refapply))}function aF(e){var t;let a=ee[e.deviceKey],n=et.product.spec,o=J[e.family],i=((null==(t=et.sel[e.deviceKey])?void 0:t.brief)||"").trim(),r=e.vn>1?"\nVARIATION ".concat(e.vn,": this must be a genuinely different creative idea from variation ").concat(e.vn-1," — a different setting, different moment, different cast, different time of day. Not a re-roll of the same shot."):"";return"Photorealistic advertising photography for a paid image ad. Single still frame, commercial quality, shot on a full-frame camera with a fast prime lens.\n\nSUBJECT: ".concat(n.product_name||"the product").concat(n.brand_name?" by "+n.brand_name:"").concat(n.category?" ("+n.category+")":"",".\n\nPERSUASION DEVICE — ").concat(a.label," (").concat(a.appeal,"): ").concat(a.what,"\nCREATIVE DIRECTION: ").concat(a.plate_direction,".").concat(i?" Operator brief: "+i+".":"").concat(r,"\n\n").concat("Reproduce the EXACT product from the reference image(s) — every colour, material, texture, proportion, shape, hardware detail and surface finish precisely as shown. This is the real product being advertised; it must be identical, not a look-alike. Never redesign it.","\n\nCOMPOSITION — ").concat(o.label,": ").concat(o.rule,"\nThis is a compositional instruction about where to point the camera, not an instruction to leave part of the canvas blank. The quiet region is photographed scene that happens to be uncluttered.\n\n").concat("FULL-BLEED PHOTOGRAPH — ABSOLUTE:\nThis is one continuous photograph that bleeds off all four edges of the frame. It is NOT a poster, a layout, a card or a template.\n- No letterboxing, pillarboxing, matte bars, borders, frames, margins, mounts, mats or rebates.\n- No flat panel, block, strip, bar or band of solid or near-solid colour anywhere — above, below, beside or behind the subject.\n- No cream, white, beige, grey, black or brand-colour blank area sitting at the top or bottom of the image.\n- No vignette ring, no rounded corners, no drop shadow around the image, no paper or canvas edge.\n- Nothing that looks like space left for text. If a region of the frame is quiet, it is quiet because the camera and the light made it quiet — a real surface, real depth, real shadow, real sky — with visible photographic tone, grain and falloff, never a fill.\nEvery pixel, corner to corner, is photographed scene.","\n\n").concat("ZERO TEXT — ABSOLUTE:\nRender NO readable text of any kind. No headline, no tagline, no price, no badge, no sticker, no label, no signage, no packaging copy, no watermark, no logo, no wordmark, no monogram, no UI, no captions, no numbers, no lettering in the background, no writing on clothing or props. If the real product carries printed text on its own surface, keep that surface turned, softened or out of focus so no glyph is legible. Every word and mark in the finished advertisement is composited afterwards from the brand's own font and logo files. A single legible character makes this plate unusable.","\n\n").concat('CREDIBILITY-PROXY BAN:\nDo not render any visual that implies proof we have not been given: no white lab coats, stethoscopes, clipboards, lab glassware, microscopes, test tubes; no seals, gold medallions, laurel wreaths, ribbons, shields, certificates or award badges; no star ratings, review cards, testimonial quotes or "as seen in" walls; no graphs, charts, meters or percentage dials; no invented certification marks. Real marks are placed later from the client\'s own files.',"\n\n").concat(function(){let e=et.brand.palette||{},t=[["brand",e.brand],["accent",e.accent],["ink",e.ink],["paper",e.paper]].filter(e=>{let[,t]=e;return t}).map(e=>
1{let[t,a]=e;return"".concat(t," ").concat(a)}).join(", ");return"PALETTE MOOD (photographic, not graphic): build the set, wardrobe, props and light so the frame naturally reads in the brand's colour world — ".concat(t||"neutral",". Express this through real materials, surfaces and light temperature. Do NOT draw colour blocks, swatches, gradients or graphic shapes; the flat brand colour is applied later by the compositor.")}(),"\n\n").concat("ANTI-SLOP GUARANTEES:\n- Anatomy: correct hands, fingers and limbs. No extra or fused digits.\n- Product exactness: never redesign, morph, recolour, duplicate or float the product. Where a face would be unclear at an angle, frame it away instead of inventing it.\n- Real physics: honest weight, drape, liquid behaviour, contact shadows and material response.\n- Scene coherence: one consistent lighting direction, no ghosting, no teleporting props.\n- No generic AI aesthetics: no unmotivated luxury mood, no meaningless bokeh haze, no dead centre-framed emptiness, no plastic skin.","\n\nREGION REALISM: ").concat(et.camp.market," — the people, wardrobe, interiors, streets, vehicles (right-hand drive where applicable) and props must be authentic to ").concat(et.camp.market,", never a generic western stand-in.").concat(et.camp.audience?" Cast and setting must read as: "+et.camp.audience+".":"")}function aU(){let e=i("plateGrid");if(!e)return;et.plates.length||(et.plates=t3().map(e=>Object.assign(e,{prompt:aF(e)})));let t=et.plates.filter(e=>"done"===e.status).length,a=et.plates.filter(e=>e.bands&&e.bands.band).length;i("plateChip").textContent="".concat(t,"/").concat(et.plates.length," plates")+(a?" \xb7 ".concat(a," banded"):""),i("plateChip").className="chip "+(a?"warn":t===et.plates.length&&t?"ok":"acc");let n=i("btnRedoBands");n&&(n.disabled=!a),e.innerHTML=et.plates.map((e,t)=>{let a=ee[e.deviceKey],n=e.status,o="done"===n?"done":"fail"===n?"fail":"",i="done"===n?"d":"running"===n?"r":"fail"===n?"f":"q";return'<div class="cat-card '.concat(o,'">\n      <div class="cat-thumb" data-z="').concat(t,'">\n        ').concat(e.dataUrl?'<img src="'.concat(e.dataUrl,'" alt="">'):'<div class="ph"><span class="em">'.concat(a.emoji,"</span>").concat(r(n),"</div>"),'\n        <span class="cat-badge">').concat(r(a.label)," \xb7 v").concat(e.vn,'</span>\n        <span class="cat-state ').concat(i,'">').concat(r(n),'</span>\n      </div>\n      <div class="cat-info">\n        <div class="cat-title">').concat(r(J[e.family].label)," \xb7 ").concat(r(e.aspect),'</div>\n        <div class="cat-meta">').concat(r(e.deviceKey)).concat(e.ms?" \xb7 "+(e.ms/1e3).toFixed(1)+"s":"").concat(e.qc?" \xb7 QC "+e.qc.verdict:"",'</div>\n        <div class="cat-actions">\n          ').concat("running"===n?'<button class="btn small" disabled aria-busy="1"><span class="spin"></span> Regenerating…</button>':'<button class="btn small" data-re="'.concat(t,'" ').concat(et.running?"disabled":"",">↻ Regenerate").concat(r(eX(eZ())),"</button>"),"\n          ").concat(e.dataUrl?'<button class="btn small ghost" data-dl="'.concat(t,'">⬇</button>'):"",'\n          <button class="btn small ghost dev-only" data-pr="').concat(t,'">⧉ Prompt</button>\n        </div>\n        ').concat(e.err?'<div class="cat-err">'.concat(r(e.err),"</div>"):"","\n        ").concat(e.bands&&e.bands.band?'<div class="cat-err" style="color:#e07a5f">⌗ letterboxing detected — '.concat(r(e.bands.flags.join(", ")),". Re-roll this plate.</div>"):"","\n        ").concat(e.qc&&e.qc.notes?'<div class="cat-err" style="color:'.concat("UNSCORED"===e.qc.verdict?"#8f8fae":"#d4b46a",'">').concat("UNSCORED"===e.qc.verdict?"⚑ ":"").concat(r(e.qc.notes),"</div>"):"","\n      </div></div>")}).join("")||'<div class="tiny">Nothing planned — pick devices in step 2 and formats in step 3.</div>',e.querySelectorAll("[data-z]").forEach(e=>e.onclick=()=>{let t=et.plates[+e.dataset.z];t.dataUrl&&e9(t.dataUrl)}),e.querySelectorAll("[data-re]").forEach(e=>e.onclick=async()=>{let t=+e.dataset.re,a=et.plates[t];if(!a)return;let n=await e8("plate:"+a.id,e,"Regenerating…",async()=>(e7("s4status"),await aJ(t,!0),await oE(),!0));n&&n.skipped&&el("s4status","That plate is already being regenerated — one call is in flight.","warn")}
1),e.querySelectorAll("[data-dl]").forEach(e=>e.onclick=()=>{let t=et.plates[+e.dataset.dl];te(t.dataUrl,t.id+".png")}),e.querySelectorAll("[data-pr]").forEach(e=>e.onclick=()=>{var t;let a=et.plates[+e.dataset.pr];null==(t=navigator.clipboard)||t.writeText(a.prompt),el("s4status","Plate prompt copied.","okk")}),e2()}function aD(e,t,a){let n=(a,n)=>{let o=(n*t+a)*4;return .2126*e[o]+.7152*e[o+1]+.0722*e[o+2]},o=(e,o)=>{let i=o?t:a,r=0,l=0;for(let t=0;t<i;t++){let a=o?n(t,e):n(e,t);r+=a,l+=a*a}let s=r/i;return{mean:s,sd:Math.sqrt(Math.max(0,l/i-s*s))}},i={},r=e=>{let n=e?"h":"v";if(!i[n]){let r=e?a:t,l=[];for(let t=0;t<r;t++)l.push(o(t,e));i[n]=l}return i[n]},l=(e,t)=>{let a=((e,t)=>{let a=r(e),n=a.length,o=Math.max(2,Math.round(.32*n)),i=e=>t?e:n-1-e,l=a[i(0)];if(l.sd>6)return{px:0,frac:0,mean:+l.mean.toFixed(1),sd:+l.sd.toFixed(2)};let s=0;for(;s<o;s++){let e=a[i(s)];if(e.sd>6||Math.abs(e.mean-l.mean)>2.5)break}return{px:s,frac:+(s/n).toFixed(4),mean:+l.mean.toFixed(1),sd:+l.sd.toFixed(2)}})(e,t),n=((e,t)=>{let a=r(e),n=a.length,o=e=>t?e:n-1-e,i=e=>a[o(Math.max(0,Math.min(n-1,e)))].mean,l=Math.max(4,Math.round(.44*n)),s=0,c=0;for(let e=2;e<l;e++){let t=Math.abs((i(e)+i(e+1))/2-(i(e-2)+i(e-1))/2);t>s&&(s=t,c=e)}let d={jump:+s.toFixed(1),px:0,frac:0,outerSd:0,coreSd:0,panelMean:0};if(s<40||c<Math.max(2,.03*n))return d;let p=0,u=0;for(let e=0;e<c;e++)p+=a[o(e)].mean,u=Math.max(u,a[o(e)].sd);let h=0,m=0;for(let e=Math.round(.3*n);e<Math.round(.7*n);e++)h+=a[e].sd,m++;let f=h/(m||1);return d.outerSd=+u.toFixed(1),d.coreSd=+f.toFixed(1),d.panelMean=+(p/(c||1)).toFixed(1),(u<.5*f||u<22)&&(d.px=c,d.frac=+(c/n).toFixed(4)),d})(e,t),o=n.frac>=.05;return{flat:a,step:n,mean:a.mean,sd:a.sd,frac:o?Math.max(a.frac,n.frac):0,kind:o?"panel":""}},s=l(!0,!0),c=l(!0,!1),d=l(!1,!0),p=l(!1,!1),u=[],h=(e,t)=>{t.frac<.035||u.push("".concat(e," panel ").concat((100*t.frac).toFixed(1),"% (edge steps ").concat(0|t.step.jump," levels)"))};return h("top",s),h("bottom",c),h("left",d),h("right",p),{top:s,bottom:c,left:d,right:p,flags:u,band:u.length>0,worst:Math.max(s.frac,c.frac,d.frac,p.frac)}}async function aH(e){let t=await new Promise((t,a)=>{let n=new Image;n.onload=()=>t(n),n.onerror=()=>a(Error("plate decode failed")),n.src=e}),a=t.naturalWidth||t.width,n=t.naturalHeight||t.height;if(!a||!n)throw Error("plate has no pixels");let o=Math.min(240,a),i=Math.max(2,Math.round(n*o/a)),r=document.createElement("canvas");r.width=o,r.height=i;let l=r.getContext("2d",{willReadFrequently:!0});l.drawImage(t,0,0,o,i);let s=aD(l.getImageData(0,0,o,i).data,o,i);return s.wh=a+"x"+n,s}async function aG(){let e=et.plates.filter(e=>"done"===e.status&&e.dataUrl&&!e.seeded&&!e.bands);if(!e.length)return 0;for(let t of e)try{t.bands=await aH(t.dataUrl)}catch(e){t.bands=null}return aU(),e.filter(e=>e.bands&&e.bands.band).length}function aW(){return et.plates.filter(e=>"done"===e.status&&!e.seeded&&e.bands&&e.bands.band)}async function aK(){let e=aW();if(!e.length)return el("s4status","No plate is carrying a flat edge band. Nothing to re-roll.","okk");if(et.running)return el("s4status","Something is already running — stop it first.","warn");e7("s4status"),et.running=!0,et.abort=!1,eV("btnRedoBands",!0,"Re-rolling…"),eV("btnGenPlates",!0,"Re-rolling…");let t=0;for(let a of e){if(et.abort)break;let e=et.plates.indexOf(a);await aJ(e,!0),!(et.plates[e].bands&&et.plates[e].bands.band)&&t++}et.running=!1,eV("btnRedoBands",!1),eV("btnGenPlates",!1);let a=aW().length;el("s4status","Re-rolled ".concat(e.length," banded plate").concat(1===e.length?"":"s"," — ").concat(t," came back clean, ").concat(a," still banded."),a?"err":"okk")}async function aJ(e,t){let a=et.plates[e];if(!a||"running"===a.status||"done"===a.status&&!t)return;a.status="running",a.err="",a.qc=null,a.bands=null,aU();let n=Date.now();try{let e=await eE();a.prompt=aF(a),a.dataUrl=await eS(a.prompt,e,a.aspect,et.plan.tier);try{a.bands=await aH(a.dataUrl)}catch(e){a.bands=null}a.status="done",a.ms=Date.now()-n}catch(e){a.status="fail",a.err=e.message}aU()}async function aY(){if(et.running)return el("s4status","Something is already running — stop it first.","warn");if(e7("s4status","s6status"),t2(),tz(),tW(),et.plates=t3().map(e=>et.plates.find(t=>
1t.id===e.id&&"done"===t.status)||Object.assign(e,{prompt:aF(e)})),!et.plates.length)return el("s4status","Nothing to generate — pick devices and formats first.","err");if(!et.product.images.some(e=>e.sel||e.hero))return el("s4status","No reference image selected. Plates must reproduce the real product — pick a hero in step 1.","err");et.running=!0,et.abort=!1,eV("btnGenPlates",!0,"Generating…");let e=et.plates.map((e,t)=>t).filter(e=>"done"!==et.plates[e].status),t=0,a=e.length,n=Date.now(),o=()=>{if(i("plateFill").style.width=(a?t/a*100:0)+"%",i("plateProg").textContent="".concat(t,"/").concat(a," plates"),t){let e=(Date.now()-n)/t;i("plateEta").textContent="~"+Math.ceil((a-t)*e/1e3)+"s left"}ec("".concat(t,"/").concat(a," plates"),t,a)};o();let r=e.slice(),l=async()=>{for(;r.length&&!et.abort;){let e=r.shift();await aJ(e,!1),t++,o()}};await Promise.all(Array.from({length:Math.min(2,r.length)},l)),et.running=!1,eV("btnGenPlates",!1);let s=et.plates.filter(e=>"done"===e.status).length,c=et.plates.filter(e=>"fail"===e.status).length,d=aW().length;el("s4status","".concat(s," plates ready").concat(c?", "+c+" failed — retry those cards":"").concat(d?", ".concat(d," came back with a flat matte band at an edge — hit “Regenerate banded plates”"):"",". Step 5 composes every format and language over them at no further cost."),c||d?"err":"okk"),i("plateEta").textContent=""}function aV(){t2(),tz(),et.plates=t3().map(e=>Object.assign(e,{prompt:aF(e)}));let e=et.brand.palette||{};
1et.plates.forEach((t,a)=>{let[n,o]=t.aspect.split(":").map(Number),i=Math.round(1024*o/n),r=document.createElement("canvas");r.width=1024,r.height=i;let l=r.getContext("2d"),s=l.createLinearGradient(0,0,1024,i);s.addColorStop(0,"#d8c3a0"),s.addColorStop(.55,"#8d6b4a"),s.addColorStop(1,"#3a2a1e"),l.fillStyle=s,l.fillRect(0,0,1024,i);for(let e=0;e<340;e++){let e=1024*Math.random(),t=Math.random()*i,a=8+70*Math.random();l.fillStyle="rgba(".concat(180+70*Math.random()|0,",").concat(150+70*Math.random()|0,",").concat(110+70*Math.random()|0,",").concat(.05+.1*Math.random(),")"),l.beginPath(),l.arc(e,t,a,0,7),l.fill()}let c=t.family,d=512,p=.32*i,u=.2*Math.min(1024,i);"clean_upper"===c&&(p=.62*i),"clean_side"===c&&(d=194.56,p=.5*i,u=.24*Math.min(1024,i)),l.fillStyle=e.accent||"#ec4899",l.beginPath(),l.arc(d,p,u,0,7),l.fill(),l.fillStyle="rgba(255,255,255,.10)",l.beginPath(),l.arc(d-.3*u,p-.3*u,.55*u,0,7),l.fill(),t.dataUrl=r.toDataURL("image/jpeg",.9),t.status="done",t.ms=0,t.seeded=!0}),aU(),el("s4status","".concat(et.plates.length," placeholder plates seeded (dev only — no API call). The compositor, fitter, safe-area and byte checks all run against these."),"okk")}function a$(e,t){let a=e.scores||{},n=Object.keys(a).map(e=>Number(a[e])).filter(e=>Number.isFinite(e)),o=aX(e.defects||e.critical),i=[];n.length<5&&i.push("fewer than five dimensions were scored"),n.length&&n.every(e=>e===n[0])&&i.push("every dimension scored exactly ".concat(n[0]," — a real audit varies")),n.length&&n.every(e=>e<=1)&&i.push("every dimension scored 0 or 1, including ones unrelated to each other"),o.length>=Math.max(5,n.length-1)&&i.push("".concat(o.length," defects for ").concat(n.length," dimensions — one per dimension is a filled-in template, not an observation"));let r=o.filter(e=>String(e&&(e.where||e.detail)||"").trim().length>8).length;return o.length>=3&&0===r&&i.push("no defect says where in the frame it is"),t&&t.bands&&!t.bands.band&&2>=Number(a.band_free)&&i.push("it reports letterboxing where our own pixel measurement found none"),{ok:0===i.length,reasons:i}}function aX(e){return Array.isArray(e)?e:e&&"object"==typeof e?Object.values(e):[]}async function aQ(){let e=et.plates.filter(e=>"done"===e.status&&e.dataUrl&&!e.seeded);if(!e.length)return el("s6status","No generated plates to QC (seeded placeholders are skipped).","err");if(et.running)return el("s6status","Something is already running — stop it first.","warn");et.running=!0,e7("s6status"),eV("btnQcPlates",!0,"Auditing…");let t=0,a=0;for(let n of e){if(et.abort)break;try{let e=[{text:function(e){let t=et.product.spec,a=J[e.family],n=e.bands?e.bands.band?"Our own pixel measurement DID find letterboxing: ".concat(e.bands.flags.join(", "),"."):"Our own pixel measurement found NO letterboxing on any edge of this image.":"";return'You are auditing one photograph that will be used as a master ad plate. Type is added later, by us, not by you.\n\nLook at the image and report ONLY what you can actually see in it. Most plates are fine; an empty\n"defects" list is the normal, expected answer. Do not invent a defect to fill the list, and do not\nreport a defect class just because it appears below — the list below is what we ask about, not a\nprediction of what is there.\n\nWHAT THIS IMAGE IS OF\nproduct / service: '.concat(t.product_name||"—"," by ").concat(t.brand_name||"—","\nintended idea: ").concat(ee[e.deviceKey].label," — ").concat(ee[e.deviceKey].what,"\ncomposition asked for: ").concat(a.rule,"\n").concat(n,'\n\nSCORE each dimension 0-10, where 10 means "no problem at all" and is the normal score:\n text_free        — any legible word, wordmark, badge, label or number visible in the photograph?\n reserve_clean    — is the region the composition asked to keep quiet actually uncluttered?\n band_free        — does the photograph bleed to all four edges, with no matte bar or panel?\n product_fidelity — does what is shown match the product described above?\n proxy_free       — any lab coat, seal, medal, laurel, star rating, chart or certificate?\n anatomy          — hands, fingers and limbs correct?\n physics          — weight, drape, liquid and contact shadows honest?\n ai_slop          — free of plastic skin, unmotivated haze and dead centre-framing?\n region_realism   — authentic to ').concat(et.camp.market,'?\n\nRULES FOR THE DEFECTS LIST — read these carefully:\n \xb7 Include an entry ONLY for a dimension you scored 5 or below.\n \xb7 Every entry MUST say WHERE in the frame the defect is, in your own words\n   ("the lower-left third", "the woman\'s right hand", "a 15% bar along the bottom").\n   If you cannot point to where it is, you cannot see it, so leave it out.\n \xb7 A good photograph returns "defects": [] and scores of 8-10 across the board.\n   That is the most common correct answer. Return it without hesitation.\n\nReturn ONLY this JSON:\n{"scores":{"text_free":10,"reserve_clean":10,"band_free":10,"product_fidelity":10,"proxy_free":10,"anatomy":10,"physics":10,"ai_slop":10,"region_realism"
1:10},\n"defects":[{"class":"","where":"","detail":""}],\n"notes":"one line, max 20 words","verdict":"PASS|REVIEW|FAIL"}')}(n)},ew(n.dataUrl)].filter(Boolean),o=await ek(e,{json:!0,model:k}),i=ep(o),r=i.scores||{},l=Object.keys(r).map(e=>Number(r[e])).filter(e=>Number.isFinite(e)),s=a$(i,n);if(!s.ok){n.qc={scores:r,verdict:"UNSCORED",notes:"the auditor's answer was not believable: "+s.reasons[0],defects:[],implausible:s.reasons},a++;continue}let c=aX(i.defects||i.critical).filter(e=>!Number.isFinite(+(e||{}).score)||5>=+e.score).filter(e=>String(e&&(e.where||e.detail)||"").trim().length>8),d=l.length?Math.min(...l):10,p=l.length?l.reduce((e,t)=>e+t,0)/l.length:10,u=i.verdict||"PASS";u=c.length>=2&&d<=3?"FAIL":c.length>=1||d<6||p<7?"REVIEW":"PASS",n.qc={scores:r,verdict:u,notes:i.notes||"",defects:c,worst:l.length?d:null,avg:+p.toFixed(1)},"FAIL"===u&&t++}catch(e){n.qc={verdict:"UNSCORED",notes:"QC call failed: "+e.message,scores:{},defects:[]},a++}}et.running=!1,aU(),aZ(),ip(),el("s6status","Plate QC — ".concat(e.length-t-a," pass, ").concat(t," FAIL")+(a?", ".concat(a," unscored because the auditor's answer was not believable (all-zero scores, or a defect per dimension with no location). Those plates are untouched — an unbelievable audit is a failed call, not a verdict."):"")+". A failing plate is a paid re-plate; a failing composition is a free re-draw.",t?"err":a?"warn":"okk"),eV("btnQcPlates",!1)}function aZ(){et.creatives.forEach(e=>{e.checks.length;e.checks=e.checks.filter(e=>!e.k.startsWith("plate_"));let t=et.plates.find(t=>t.id===e.plateId);t&&t.qc&&"PASS"!==t.qc.verdict&&"UNSCORED"!==t.qc.verdict&&e.checks.push({k:"plate_"+t.qc.verdict.toLowerCase(),level:"FAIL"===t.qc.verdict?"fail":"warn",msg:"plate QC "+t.qc.verdict+(t.qc.notes?": "+t.qc.notes:"")+((t.qc.defects||[]).length?" — "+t.qc.defects.map(e=>"".concat(e.class||"defect"," (").concat(e.where||"?",")")).join("; "):"")}),t&&t.bands&&t.bands.band&&e.checks.push({k:"plate_banded",level:"warn",msg:"the plate behind this creative is letterboxed — "+t.bands.flags.join(", ")+". Re-roll the plate in step 4."}),e.checks.length,e.verdict=nD(e.checks)})}function a0(){return et.plan.formats.map(e=>G[e]).filter(Boolean)}function a1(){var e,t,a,n,o,i;let r={};return a0().forEach(e=>(e.text_fields||[]).forEach(e=>{null!=e.max_chars&&(r[e.name]=null==r[e.name]?e.max_chars:Math.min(r[e.name],e.max_chars))})),r.headline=null!=(e=r.headline)?e:40,r.banner_line=null!=(t=r.banner_line)?t:34,r.short_headline=null!=(a=r.short_headline)?a:30,r.sub=null!=(n=r.sub)?n:48,r.cta_label=null!=(o=r.cta_label)?o:14,r.offer=null!=(i=r.offer)?i:24,r}function a2(){let e=new Set;return a0().forEach(t=>(t.cta_enum||[]).forEach(t=>e.add(t))),e.size||["Shop now","Learn more","Buy now","Get offer","Book now"].forEach(t=>e.add(t)),[...e]}function a4(e){let t=L.find(t=>t.k===e);return t?t.label:e}function a5(){return[...new Set(a0().map(e=>e.platform))]}function a6(){let e=(et.brand&&et.brand._refCopy||[]).filter(e=>String(e||"").trim()).slice(0,12),t=et.brand&&et.brand._refLayout||[];if(!e.length&&!t.length)return"";let a=[];return e.length&&a.push("WHAT THIS BRAND HAS ALREADY SAID — transcribed from previous campaign assets:\n".concat(e.map(e=>'- "'.concat(e,'"')).join("\n"),"\nMatch this register. Do NOT reuse any of these lines or a near-paraphrase of one: they have already run.")),t.length&&a.push("HOW THIS BRAND LAYS AN AD OUT, from those same assets: ".concat(t.map(e=>[e.archetype,e.text_position,e.tone].filter(Boolean).join(", ")).join(" / "),".\nThat is context for the register, not an instruction about length — the character limits below are absolute.")),a.join("\n\n")}function a8(e,t,a){var n;let o=ee[e],i=et.product.spec,r=a1(),l=et.brand.voice||{},s=L.find(e=>e.k===a)||L[0],c=a5(),d=c.some(e=>["meta","instagram"].includes(e)),p=c.some(e=>["google_display","youtube","iab"].includes(e)),u="hinglish"===a?"Write Hindi-English code-mixed copy in LATIN script (Hinglish) — the register urban metro India actually reads.":"Latin"===s.script?"Write in English.":"Write in ".concat(s.label,", in ").concat(s.script," script. This is TRANSCREATION, not translation — write what a ").concat(s.label," copywriter would write, not a rendering of the English. Keep the brand name and product name in Latin script inside the sentence, as Indian advertising does.");return"You are a senior advertising copywriter producing the complete text kit for ONE image ad campaign. The visual
1s are already made. You write everything the media buyer needs to launch on each platform without writing another word themselves.\n\nPRODUCT: ".concat(i.product_name||"—"," by ").concat(i.brand_name||et.brand.brand_name||"—").concat(i.category?" ("+i.category+")":"","\nPRICE: ").concat(i.price_display||"—").concat(i.price_was?" (was "+i.price_was+")":"","   OFFER: ").concat(i.offer_display||"none","\nFEATURES: ").concat((i.key_features||[]).join(" | ")||"—","\nBUYER OBJECTIONS: ").concat((i.objections||[]).join(" | ")||"—","\nOBJECTIVE: ").concat(et.camp.objective,"   AUDIENCE: ").concat(et.camp.audience||"—","   MARKET: ").concat(et.camp.market,"\nLANDING PAGE PROMISES: ").concat(i.landing_page_promise||"—","\nPLATFORMS IN THIS CAMPAIGN: ").concat(c.join(", ")||"—","\n\nDEVICE — ").concat(o.label," (").concat(o.appeal,"): ").concat(o.what,"\nCOPY SHAPE FOR THIS DEVICE: ").concat(o.copy_shape,"\n").concat((null==(n=et.sel[e])?void 0:n.brief)?"OPERATOR BRIEF: "+et.sel[e].brief:"","\n").concat(t>1?"VARIATION "+t+": a different angle on the same device — not a reword of variation "+(t-1)+".":"","\n\nLANGUAGE: ").concat(u,"\nBRAND VOICE: ").concat(l.register||"clear, direct, no jargon","\n").concat((l.banned_words||[]).length?"BANNED WORDS (never use): "+l.banned_words.join(", "):"","\n").concat(a6(),"\n\nCLAIM LOCKBOX — the ONLY facts you may state, character for character:\n").concat(et.claims.map(e=>"- [".concat(e.kind,"] ").concat(e.text)).join("\n")||"- (none — write with zero factual claims)",'\nYou may NOT introduce any percentage, ranking, superlative ("best", "No.1", "world-class"), rating,\nreview count, award, certification, "clinically proven", multiplier or guarantee that is not in that\nlist. Omission is success; approximation is failure. A line that makes no factual claim always beats\na line that invents one.\n\nTWO REGISTERS, AND DO NOT MIX THEM:\n \xb7 TEXT THAT IS PRINTED INTO THE IMAGE — headline, banner_line, short_headlines, offer, cta_label.\n   These are typeset by our compositor. NO emoji, NO hashtags, NO line breaks, no trailing full stop\n   on a headline of three words or fewer. Short, high-contrast, readable at a glance.\n   CASE: write them in SENTENCE CASE — first word capitalised, then only proper nouns and the brand\'s\n   own names. Never Title Case Every Word. "Transfers: 24x7 Vs Branch Hours" is amateur work; "Transfers\n   at 24x7, not branch hours" is what a real ad says. Do not use ALL CAPS for emphasis either; the\n   compositor owns case and weight.\n \xb7 TEXT THAT IS PASTED INTO THE PLATFORM — fb_primary_text, ig_caption, descriptions, yt_description.\n   Written like a human posting, ').concat(d?"emoji encouraged where they carry meaning (1-3, never decorative rows)":"plain",".\n\nFIELDS AND LIMITS — a field over its limit is rejected:\n headline ≤ ").concat(r.headline," chars — the feed headline, must survive mobile truncation\n banner_line ≤ ").concat(r.banner_line," chars — small display banners, usually 3-5 words\n short_headlines — EXACTLY 5, each ≤ 30 chars, all genuinely different").concat(p?" (Google rotates all five)":"","\n long_headline ≤ 90 chars\n descriptions — EXACTLY 3, each ≤ 90 chars, all genuinely different\n sub ≤ ").concat(r.sub,' chars — the supporting line under the headline in the image\n offer — "" or verbatim from the lockbox\n cta_label ≤ ').concat(r.cta_label," chars, EXACTLY one of: ").concat(a2().join(" | "),'\n fb_primary_text ≤ 125 chars — the Facebook/Instagram feed body. 1-2 emoji maximum.\n ig_caption 150-400 chars — 3-5 SHORT lines separated by \n, a hook first line, one line of substance,\n   a soft CTA last line. 2-4 emoji placed where they mean something. NO hashtags inside the caption.\n hashtags — 8 to 14, each starting with #, no spaces inside a tag: 2-3 broad reach, 3-5 category,\n   2-3 branded or campaign. Lower case or CamelCase, never SHOUTING.\n yt_description ≤ 150 chars\n alt_text — one plain sentence describing the image for a screen reader. No emoji, no hashtags.\n\nNever write "Elevate", "Redefine", "Introducing", "Crafted for you", "Unleash", "Experience the\ndifference", "Say goodbye to", "Look no further" or any variant of that AI-advertising register.\n\nReturn ONLY this JSON:\n{"headline":"","banner_line":"","short_headlines":["","","","",""],"long_headline":"","descriptions":["","",""],\n "sub":"","offer":"","cta_label":"","fb_primary_text":"","ig_caption":"","hashtags":[],\n "yt_description":"","alt_text":""}')}function a3(e){return e.short_headlines=a7(e.short_headlines).map(e=>
1String(e||"").trim()).filter(Boolean),e.descriptions=a7(e.descriptions).map(e=>String(e||"").trim()).filter(Boolean),e.hashtags=a7(e.hashtags).map(e=>String(e||"").trim().replace(/^#*/,"#")).filter(e=>e.length>1),e.short_headline=e.short_headline||e.short_headlines[0]||e.headline||"",e.description=e.description||e.descriptions[0]||"",e.primary_text=e.primary_text||e.fb_primary_text||"",e}function a7(e){return Array.isArray(e)?e:e?[e]:[]}let a9=/[\u{1F300}-\u{1FAFF}\u{2600}-\u{27BF}\u{FE0F}\u{1F1E6}-\u{1F1FF}]/u,ne=["headline","banner_line","short_headline","long_headline","sub","offer","cta_label"];function nt(e,t,a){var n;a3(e);let o=a1(),i=[],r=e=>{var t;return"long_headline"===e?90:"fb_primary_text"===e?125:"ig_caption"===e?2200:"yt_description"===e?150:"alt_text"===e?200:"descriptions"===e?90:null!=(t=o[e])?t:60};ne.concat(["fb_primary_text","ig_caption","yt_description","alt_text"]).forEach(t=>{let a=q(e[t]||"");a>r(t)&&i.push({k:"len_"+t,level:"warn",msg:"".concat(t," is ").concat(a,"/").concat(r(t)," characters — it will truncate")})}),e.short_headlines.forEach((e,t)=>{let a=q(e);a>30&&i.push({k:"len_short_headline",level:"warn",msg:"short headline ".concat(t+1," is ").concat(a,"/30")})}),e.descriptions.forEach((e,t)=>{let a=q(e);a>90&&i.push({k:"len_description",level:"warn",msg:"description ".concat(t+1," is ").concat(a,"/90")})}),a5().includes("google_display")&&e.short_headlines.length<3&&i.push({k:"google_assets",level:"warn",msg:"Google rotates up to 5 short headlines — only ".concat(e.short_headlines.length," written")});let l=a2();e.cta_label&&!l.includes(e.cta_label)&&i.push({k:"cta_enum",level:"warn",msg:'CTA "'.concat(e.cta_label,"\" is not one of the platform's own CTAs — allowed: ").concat(l.slice(0,4).join(", "),"…")}),ne.forEach(t=>{a9.test(e[t]||"")&&i.push({k:"emoji_in_art",level:"warn",msg:"".concat(t," contains an emoji — that text is typeset into the image, so it is being removed")})}),ne.forEach(t=>{e[t]&&(e[t]=String(e[t]).replace(RegExp(a9.source,"gu"),"").replace(/\s{2,}/g," ").trim())});let s=((null==(n=et.brand.voice)?void 0:n.banned_words)||[]).map(e=>e.toLowerCase()).filter(Boolean),c=[...ne,"fb_primary_text","ig_caption"].map(t=>e[t]||"").join(" ").toLowerCase();s.forEach(e=>{c.includes(e)&&i.push({k:"banned",level:"fail",msg:'banned word "'.concat(e,'" used')})});let d=[...ne,"fb_primary_text","ig_caption","yt_description",...e.short_headlines,...e.descriptions],p=[],u=[];d.forEach(t=>{let a=ne.includes(t)||["fb_primary_text","ig_caption","yt_description"].includes(t)?e[t]||"":t;tY(a,!1).forEach(e=>{p.includes(e)||p.push(e)}),tY(a,!0).forEach(e=>{u.includes(e)||u.push(e)})}),p.length&&i.push({k:"claim",level:"fail",msg:"claim(s) with no source in the lockbox: "+p.join(", ")+" — remove them or add the evidence in step 1"});let h=u.filter(e=>!p.includes(e));return h.length&&i.push({k:"number",level:"warn",msg:"number(s) not traced to a registered claim: "+h.slice(0,6).join(", ")+" — check they are right"}),i}function na(){let e=new Set(et.plan.langs),t=new Set(t5()),a=0;return Object.keys(et.copy).forEach(n=>{let[o,i,r]=n.split("|");e.has(r)&&t.has(o)&&!(+i>et.plan.variations)?delete et.copy[n]._stale:(et.copy[n]._stale=!0,a++)}),a}function nn(){let e=t5(),t=[];return e.forEach(e=>{for(let a=1;a<=et.plan.variations;
1a++)et.plan.langs.forEach(n=>t.push([e,a,n]))}),t}async function no(e,t,a){var n,o;let i="".concat(e,"|").concat(t,"|").concat(a),r=null,l=[],s="";for(let n=0;n<3;n++)try{let o=a8(e,t,a);n>0&&(o+="\n\nYOUR PREVIOUS ATTEMPT WAS REJECTED: ".concat(l.map(e=>e.msg).join(" \xb7 "),". Fix exactly that and return the JSON again."));let i=await ek([{text:o}],{json:!0,thinking:"high"}),c=ep(i);if(l=nt(c,e,a).filter(e=>"fail"===e.level),r=c,s="",!l.length)break}catch(e){l=[{k:"call",level:"fail",msg:s=e.message||String(e)}],await g(400+600*n)}if(!r){let e=et.copy[i];return e&&!e._failed?(null==(o=e._issues)?void 0:o.some(e=>"fail"===e.level))?"flagged":"ok":(et.copy[i]={_failed:!0,_err:s||"the model returned nothing usable after 3 attempts",_issues:[{k:"call",level:"fail",msg:s||"generation failed after 3 attempts"}]},"failed")}return a3(r),r.legal_line=((null==(n=et.brand.legal)?void 0:n.mandatory_lines)||[])[0]||"",r._issues=nt(r,e,a),r._failed=!1,et.copy[i]=r,r._issues.some(e=>"fail"===e.level)?"flagged":"ok"}async function ni(e){if(t2(),tz(),tW(),!t5().length)return el("s5status","No devices selected — go back to step 2.","err");if(!et.plan.langs.length)return el("s5status","No language selected — pick at least one in step 3.","err");let t=nn(),a=e&&e.length?t.filter(t=>{let[a,n,o]=t;return e.includes("".concat(a,"|").concat(n,"|").concat(o))}):t;if(!a.length)return el("s5status","Nothing to write.","err");if(et.running)return el("s5status","Something is already running — stop it first.","warn");et.running=!0,e7("s5status","s6status"),eV("btnCopyGen",!0,"Writing…"),eV("btnCopyRetry",!0,"Writing…"),et.abort=!1;let n={ok:0,flagged:0,failed:0},o=0,r=Date.now();for(let[e,t,l]of(i("pre_copy").textContent=a8(a[0][0],a[0][1],a[0][2]),a)){if(et.abort)break;el("s5status","Writing ".concat(a4(l)," \xb7 ").concat(ee[e].label).concat(et.plan.variations>1?" \xb7 v"+t:""," — ").concat(o+1," of ").concat(a.length,"…"),""),n[await no(e,t,l)]++,o++,ec("copy ".concat(o,"/").concat(a.length),o,a.length),i("compFill").style.width=o/a.length*100+"%";let s=(Date.now()-r)/o;i("compProg").textContent="copy ".concat(o,"/").concat(a.length)+(o<a.length?" \xb7 ~".concat(Math.ceil((a.length-o)*s/1e3),"s left"):""),na(),oK(),"function"==typeof oW&&oW()}let l=0,s=new Set(a.map(e=>{let[t,a,n]=e;return"".concat(t,"|").concat(a,"|").concat(n)})),c=et.creatives.filter(e=>"approved"!==e.state&&s.has("".concat(e.deviceKey,"|").concat(e.vn,"|").concat(e.lang)));if(c.length){for(let e of(el("s5status","Copy written. Re-drawing ".concat(c.length," creatives so the board matches the new text…"),"run"),await ti(tr()),c)){if(et.abort)break;await iu(e),l++}await oA(),ip()}et.running=!1,eV("btnCopyGen",!1),eV("btnCopyRetry",!1),nK(),na(),oK(),o_&&o_();let d=Object.values(et.copy).filter(e=>e._stale).length;el("s5status",["".concat(n.ok+n.flagged," of ").concat(a.length," cell").concat(1===a.length?"":"s"," written this run"),n.flagged?"".concat(n.flagged," carry a hard issue after 3 attempts and are flagged in step 6"):"",n.failed?"".concat(n.failed," failed outright — those cells show the error and can be retried on their own"):"",l?"".concat(l," creatives re-drawn from the new text (free — no plate regenerated)"):"",et.abort?"stopped early":"",d?"".concat(d," stored bundle").concat(1===d?"":"s"," belong to languages or devices no longer in the plan and are being ignored"):""].filter(Boolean).join(" \xb7 ")+".",n.failed||n.flagged?"err":"okk")}async function nr(){let e=nn().map(e=>{let[t,a,n]=e;return"".concat(t,"|").concat(a,"|").concat(n)}).filter(e=>{let t=et.copy[e];return!t||t._failed||(t._issues||[]).some(e=>"fail"===e.level)});if(!e.length)return el("s5status","Every cell in the plan is written and clean.","okk");await ni(e),await oj()}let nl={};function ns(e){return nl[e]?Promise.resolve(nl[e]):new Promise((t,a)=>{let n=new Image;n.crossOrigin="anonymous",n.onload=()=>{nl[e]=n,t(n)},n.onerror=()=>a(Error("image load failed")),n.src=e})}function nc(e,t){return e?"upper"===t?e.toUpperCase():"title"===t?e.replace(/\w\S*/g,e=>e[0].toUpperCase()+e.slice(1)):e:""}
1function nd(e,t,a,n){let o=String(t).trim().split(/\s+/).filter(Boolean);if(!o.length)return[];let i=[],r="";for(let t of o){let o=r?r+" "+t:t;if(e.measureText(o).width<=a){r=o;continue}if(r)i.push(r),r=t;else{let o="";for(let r of function(e){try{if("undefined"!=typeof Intl&&Intl.Segmenter)return[...new Intl.Segmenter(void 0,{granularity:"grapheme"}).segment(e)].map(e=>e.segment)}catch(e){}return String(e).split("")}(t))if(e.measureText(o+r).width<=a?o+=r:(o&&i.push(o),o=r),i.length>n)return null;r=o}if(e.measureText(r).width>a&&i.length>=n||i.length>n)return null}return r&&i.push(r),i.length<=n?i:null}function np(e,t,a,n,o){let{family:i,weight:r,minPx:l,maxPx:s,maxLines:c,leading:d}=o,p=l,u=s,h=null;for(let o=0;o<14&&p<=u;o++){let o=(p+u)/2;e.font="".concat(r," ").concat(o,"px ").concat(i);let l=nd(e,t,a,c),s=l?l.length*o*d:1/0;l&&s<=n?(h={size:o,lines:l},p=o+.5):u=o-.5}if(h)return h;e.font="".concat(r," ").concat(l,"px ").concat(i);let m=nd(e,t,a,c);return m&&m.length*l*d<=n?{size:l,lines:m}:null}let nu=new Set(["text","button","chip","logo"]);function nh(e,t){return e.x<t.x+t.w&&e.x+e.w>t.x&&e.y<t.y+t.h&&e.y+e.h>t.y}function nm(e,t){let a=(e.zones||[]).filter(e=>nu.has(e.role)).map(e=>({z:e,r:{x:e.x*t.w,y:e.y*t.h,w:e.w*t.w,h:e.h*t.h}})),n=[];for(let e=0;e<a.length;e++)for(let t=e+1;t<a.length;t++)a[e].z.optional&&a[t].z.optional||nh(a[e].r,a[t].r)&&n.push("".concat(a[e].z.role,":").concat(a[e].z.field||a[e].z.asset||""," \xd7 ").concat(a[t].z.role,":").concat(a[t].z.field||a[t].z.asset||""));return n}function nf(e,t){let a=[];return(t.safe_areas||[]).forEach(n=>{let o;"top"===n.edge&&(o={x:0,y:0,w:t.w,h:n.px}),"bottom"===n.edge&&(o={x:0,y:t.h-n.px,w:t.w,h:n.px}),"left"===n.edge&&(o={x:0,y:0,w:n.px,h:t.h}),"right"===n.edge&&(o={x:t.w-n.px,y:0,w:n.px,h:t.h}),o&&e.x<o.x+o.w&&e.x+e.w>o.x&&e.y<o.y+o.h&&e.y+e.h>o.y&&a.push(n)}),a}function ng(e,t){let a;try{a=e.getImageData(Math.max(0,0|t.x),Math.max(0,0|t.y),Math.max(1,0|t.w),Math.max(1,0|t.h)).data}catch(e){return{mean:.5,sd:.2}}let n=Math.max(4,4*Math.floor(a.length/4/1200)),o=0,i=0,r=0;for(let e=0;e<a.length;e+=n){let t=(.2126*a[e]+.7152*a[e+1]+.0722*a[e+2])/255;i+=t,r+=t*t,o++}if(!o)return{mean:.5,sd:.2};let l=i/o;return{mean:l,sd:Math.sqrt(Math.max(0,r/o-l*l))}}function nb(e,t){let a=String(e||"").trim(),n=/^rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)(?:[\s,/]+([\d.]+))?/i.exec(a);if(n){let e=(.2126*n[1]+.7152*n[2]+.0722*n[3])/255,a=void 0===n[4]?1:Math.max(0,Math.min(1,+n[4]));return a*e+(1-a)*t}let o=/^#[0-9a-f]{6}$/i.test(a)?a:/^#[0-9a-f]{3}$/i.test(a)?"#"+a.slice(1).split("").map(e=>e+e).join(""):null;return o?tk(o):t}function ny(e,t,a){let n=a||4.5,o=e-t;return 1e-4>Math.abs(o)?1:Math.max(0,Math.min(1,o>0?(n*(t+.05)-.05-t)/o:((t+.05)/n-.05-t)/o))}function nv(e,t){return e+(!t-e)*.45}function n_(e,t){let a=Math.max(0,Math.min(1,t)),n=nb(e,a);return(Math.max(n,a)+.05)/(Math.min(n,a)+.05)}function nw(e){return!e.fine_print&&((e.max_px||0)>=.04||(e.min_px||0)>=.032)?3:4.5}function nx(e,t,a,n){let o,i=tk(t),r=tk(a),l=e.mean,s=n||4.5;o=i>=.5?(i+.05)/s-.05:s*(i+.05)-.05;let c=0;return i>=.5?l>o&&r<l&&(c=(l-o)/(l-r)):l<o&&r>l&&(c=(o-l)/(r-l)),b(c+=Math.min(.22,.75*e.sd),0,.92)}function nk(e){return e<=0?0:e>=1?1:Math.pow(e*e*(3-2*e),.62)}function nS(e){let t=e.gradient||[];if(t.length<2)return!1;let a=e=>"transparent"===e?0:void 0===String(e).split("@")[1]?1:parseFloat(String(e).split("@")[1]);return a(t[0])>a(t[t.length-1])}function nE(e,t,a){let n="x"===e.dir,o=nS(e),i=Math.max(1,n?t.w:t.h),r=n?t.x:t.y,l=b(((n?a.x:a.y)-r)/i,0,1),s=b(((n?a.x+a.w:a.y+a.h)-r)/i,0,1),c=o?1-l:l,d=o?1-s:s;return b(Math.min(c,d)+.4*Math.abs(c-d),.04,1)}function nj(e,t,a,n){let o="x"===t.dir,i=nS(t),r=0,l=null;
1n.forEach(e=>{let t=Math.max(.08,nk(e.t)),a=e.need/t;a>r&&(r=a,l=e)}),r=b(r,0,.76);let s=e.createLinearGradient(a.x,a.y,o?a.x+a.w:a.x,o?a.y:a.y+a.h),c=tx(tw("ink"));for(let e=0;e<=24;e++){let t=e/24,a=r*nk(i?1-t:t);s.addColorStop(t,"rgba(".concat(c.r,",").concat(c.g,",").concat(c.b,",").concat(a.toFixed(4),")"))}e.fillStyle=s,e.fillRect(a.x,a.y,a.w,a.h);let d=n.filter(e=>r*nk(e.t)<e.need-.02);return{peak:r,short:d,worst:l,atWorst:l?r*nk(l.t):0,needWorst:l?l.need:0}}function nA(e,t,a,n,o,i){let r=Math.min(i,o/2,n/2);e.beginPath(),e.moveTo(t+r,a),e.arcTo(t+n,a,t+n,a+o,r),e.arcTo(t+n,a+o,t,a+o,r),e.arcTo(t,a+o,t,a,r),e.arcTo(t,a,t+n,a,r),e.closePath()}function nC(e,t){if(!e)return"";for(let a of({banner_line:["banner_line","short_headline","headline"],short_headline:["short_headline","headline","banner_line"],headline:["headline","short_headline","banner_line"],sub:["sub","description","yt_description"],offer:["offer"],cta_label:["cta_label"],legal_line:["legal_line"],primary_text:["fb_primary_text","primary_text"],description:["description"]})[t]||[t]){let t=e[a];if(Array.isArray(t)&&t.length&&String(t[0]).trim())return String(t[0]).trim();if("string"==typeof t&&t.trim())return t.trim()}return"short_headline"===t&&Array.isArray(e.short_headlines)&&e.short_headlines[0]?e.short_headlines[0]:"description"===t&&Array.isArray(e.descriptions)&&e.descriptions[0]?e.de
1scriptions[0]:""}let nL={};function nT(e){let t="a:"+(e.src||"").slice(-60);if(void 0!==nL[t])return nL[t];let a=document.createElement("canvas"),n=a.width=Math.min(80,e.naturalWidth||80),o=a.height=Math.min(80,e.naturalHeight||80),i=a.getContext("2d");i.drawImage(e,0,0,n,o);let r=0,l=n*o;try{let e=i.getImageData(0,0,n,o).data;for(let t=3;t<e.length;t+=4)e[t]<250&&r++}catch(e){return nL[t]=!1}return nL[t]=r/l>.06}let nM={align:"start",size:1,leading:1};function nN(e){return e.field||e.role}function nR(e,t){let a=t&&t.zones&&t.zones[nN(e)];if(!a)return e;let n=Object.assign({},e);return["x","y","w","h"].forEach(e=>{Number.isFinite(+a[e])&&(n[e]=+a[e])}),a.align&&(n._align=a.align),Number.isFinite(+a.size)&&(n._size=+a.size),Number.isFinite(+a.leading)&&(n._leading=+a.leading),"string"==typeof a.ink&&/^#[0-9a-f]{6}$/i.test(a.ink)&&(n._ink=a.ink),n}function nI(){return(!et.plan.typo||"object"!=typeof et.plan.typo||Array.isArray(et.plan.typo))&&(et.plan.typo={}),et.plan.typo}function nO(e,t){let a=nI();return Object.assign({},nM,a[e]||{},(t?a["cr:"+t.id]:null)||{})}function nq(e,t){let a="rtl"===N(t);return"center"===e?"center":"end"===e?a?"left":"right":a?"right":"left"}function nP(e){return!e||"start"===(e.align||"start")&&1==(+e.size||1)&&1==(+e.leading||1)}async function nz(e,t,a,n,o){var i,l,s,c,d;let p=document.createElement("canvas");p.width=e.w,p.height=e.h;let u=p.getContext("2d");u.textBaseline="top";let h=X(e),m=[],f=e.h,g=e.min_type_px||10,b=(null==(i=et.brand.type)?void 0:i.case)||"sentence",y=Object.assign({},nM,o||{}),v=new Set;nq(y.align,n);let _=Math.max(.7,Math.min(1.3,+y.size||1)),w=Math.max(.85,Math.min(1.6,+y.leading||1));u.fillStyle=tw("ink"),u.fillRect(0,0,e.w,e.h);let x=!!(o&&o.zones&&Object.keys(o.zones).length),k=h.zones.map(e=>nR(e,y)),S=t=>({x:Math.round(t.x*e.w),y:Math.round(t.y*e.h),w:Math.round(t.w*e.w),h:Math.round(t.h*e.h)}),E=[],A=[],L=[],M=[];k.forEach(e=>{"fill"===e.role||"plate"===e.role?E.push(e):"scrim"===e.role?A.push(e):"panel"===e.role?L.push(e):M.push(e)}),nm({zones:k},e).forEach(e=>m.push({k:"zone_overlap",level:x?"info":"warn",msg:x?"two elements you moved now overlap (".concat(e,")"):"two content zones overlap on this layout (".concat(e,") — text may run under another element")}));let N=L.map(S),R={},I={},O=e=>N.some(t=>e.x>=t.x-2&&e.y>=t.y-2&&e.x+e.w<=t.x+t.w+2&&e.y+e.h<=t.y+t.h+2);for(let o of[...E,...A,...L,...M]){let i=S(o);if("fill"===o.role){u.fillStyle=tw(o.bg),u.fillRect(i.x,i.y,i.w,i.h);continue}if("scrim"===o.role){if(!1===o.adaptive){!function(e,t,a,n){let o="x"===t.dir,i=e.createLinearGradient(a.x,a.y,o?a.x+a.w:a.x,o?a.y:a.y+a.h),r=t.gradient||[];r.forEach((e,t)=>{let a=1===r.length?0:t/(r.length-1);if("transparent"===e)return void i.addColorStop(a,"rgba(0,0,0,0)");let n=String(e).split("@"),o=(void 0===n[1]?1:parseFloat(n[1]))*1;i.addColorStop(a,o>=1?tw(n[0]):tw(n[0]+"@"+o.toFixed(3)))}),e.fillStyle=i,e.fillRect(a.x,a.y,a.w,a.h)}(u,o,i);continue}let e=[];if(M.forEach(t=>{if("text"!==t.role||t.fine_print)return;let n=S(t);!(!nh(n,i)||O(n))&&nC(a,t.field)&&e.push({z:t,r:n})}),!e.length)continue;let t=Math.min(...e.map(e=>e.r.x)),n=Math.min(...e.map(e=>e.r.y)),l=ng(u,{x:t,y:n,w:Math.max(...e.map(e=>e.r.x+e.r.w))-t,h:Math.max(...e.map(e=>e.r.y+e.r.h))-n});if(l.mean>.58&&l.sd<.24){e.forEach(e=>{R[e.z.field]="dark"}),m.push({k:"scrim",level:"info",msg:"the plate is light here (".concat((100*l.mean).toFixed(0),"% luminance), so the type was set dark and no scrim was applied")});continue}let s=e.map(e=>{let t=!e.z.fine_print&&((e.z.max_px||0)>=.04||(e.z.min_px||0)>=.032),a=ng(u,e.r);return{field:e.z.field,t:nE(o,i,e.r),need:nx(a,tw("on_ink"),tw("ink"),t?3:4.5)}}).filter(e=>e.need>.02);if(!s.length){m.push({k:"scrim",level:"info",msg:"the plate is already dark enough under the type — no gradient applied"});continue}let c=nj(u,o,i,s);if(c.short.length){let e=c.needWorst-c.atWorst;m.push({k:"scrim",level:e>=.05?"warn":"info",msg:"the gradient reaches ".concat((100*c.atWorst).toFixed(0),"% under “").concat(r(c.short[0].field),"” against a ").concat((100*c.needWorst).toFixed(0),"% target — ").concat((100*e).toFixed(0)," points short. Going further would slab the bottom edge, so the type is haloed instead.")+(e>=.05?" If it still reads thin, re-roll this plate for a quieter region under the type.":" Inside tolerance.")})}else m.push({k:"scrim",level:"info",msg:"one continuous gradient, 0% to ".concat((100*c.peak).toFixed(0),"% at the extreme edge — every line clears its contrast target on the ramp, so there is no band edge anywhere")});continue}if("panel"===o.role){o.shadow?(u.save(),u.shadowColor="rgba(0,0,0,.34)",u.shadowBlur=Math.max(4,.1*i.h),u.shadowOffsetY=Math.max(2,.035*i.h),u.fillStyle=tw(o.bg||"paper"),nA(u,i.x,i.y,i.w,i.h,(o.radius||.02)*e.w),u.fill(),u.restore(
1)):(u.fillStyle=tw(o.bg||"paper"),nA(u,i.x,i.y,i.w,i.h,(o.radius||.02)*e.w),u.fill());continue}if("plate"===o.role){if(!(null==t?void 0:t.dataUrl)){m.push({k:"plate_missing",level:"fail",msg:"no plate image"});continue}try{let e=await ns(t.dataUrl);!function(e,t,a,n){let o=t.naturalWidth/t.naturalHeight,i=a.w/a.h,r=t.naturalWidth,l=t.naturalHeight,s=0,c=0;o>i?(r=t.naturalHeight*i,s="left"===n?0:"right"===n?t.naturalWidth-r:(t.naturalWidth-r)/2):(l=t.naturalWidth/i,c="top"===n?0:"bottom"===n?t.naturalHeight-l:(t.naturalHeight-l)/2),e.drawImage(t,s,c,r,l,a.x,a.y,a.w,a.h)}(u,e,i,o.anchor||"center")}catch(e){m.push({k:"plate_missing",level:"fail",msg:"plate failed to load"})}continue}if("logo"===o.role){let t,a=.5>function(e,t){let a;t.w,t.h;try{a=e.getImageData(0|t.x,0|t.y,Math.max(1,0|t.w),Math.max(1,0|t.h)).data}catch(e){return .5}let n=0,o=0,i=Math.max(4,4*Math.floor(a.length/4/900));for(let e=0;e<a.length;e+=i)n+=(.2126*a[e]+.7152*a[e+1]+.0722*a[e+2])/255,o++;return o?n/o:.5}(u,i),n=function(e){let t=et.brand.logo||{};return e&&t.mono_light?{src:t.mono_light,mode:"as-is"}:!e&&t.mono_dark?{src:t.mono_dark,mode:"as-is"}:t.primary?{src:t.primary,mode:"auto"}:t.mono_light?{src:t.mono_light,mode:e?"as-is":"auto"}:t.mono_dark?{src:t.mono_dark,mode:e?"auto":"as-is"}:null}(a);if(!n){o.optional||m.push({k:"logo_missing",level:"warn",msg:"no logo on file — add one in step 1 (Fetch from site, or upload). Every other zone drew fine."});continue}try{t=await ns(n.src)}catch(e){m.push({k:"logo_missing",level:"warn",msg:"the logo file could not be loaded"});continue}let r=Math.min(i.w/t.naturalWidth,i.h/t.naturalHeight),p=t.naturalWidth*r,h=t.naturalHeight*r,f=Math.max(8,Math.min((null==(l=et.brand.logo)?void 0:l.min_height_px)||24,.2*e.h));if(h<f){m.push({k:"logo_small",level:"warn",msg:"logo would render ".concat(h.toFixed(0),"px, below the ").concat(f.toFixed(0),"px effective minimum for ").concat(e.w,"\xd7").concat(e.h," — dropped")});continue}let g="right"===o.align?i.x+i.w-p:"center"===o.align?i.x+(i.w-p)/2:i.x,b=i.y+(i.h-h)/2,y=nf({x:g,y:b,w:p,h:h},e);y.length&&m.push({k:"safe_area",level:"fail",msg:"logo overlaps the ".concat(y[0].edge," reserve (").concat(y[0].why,")")});let v=(null==(s=et.brand.logo)?void 0:s.knockout)||"auto",_=t;"never"!==v&&nT(t)?_=function(e,t){let a=document.createElement("canvas");a.width=e.naturalWidth||1,a.height=e.naturalHeight||1;let n=a.getContext("2d");return n.drawImage(e,0,0),n.globalCompositeOperation="source-in",n.fillStyle=t,n.fillRect(0,0,a.width,a.height),a}(t,a?tw("on_ink"):tw("on_paper")):!nT(t)&&a&&m.push({k:"logo_ground",level:"warn",msg:"the logo file is opaque and this placement puts it on a dark photograph. No white box was drawn — upload a white-knockout SVG or PNG in step 1, or use a layout with a colour field for the mark."}),u.drawImage(_,g,b,p,h);let w=(null!=(d=null==(c=et.brand.logo)?void 0:c.clear_space_multiple)?d:.5)*h;o._excl={x:g-w,y:b-w,w:p+2*w,h:h+2*w};continue}let p=y.text&&"string"==typeof y.text[o.field]&&y.text[o.field].trim()?y.text[o.field]:nC(a,o.field);if(!p){v.add(nN(o)),o.optional||m.push({k:"copy_missing_"+o.field,level:"warn",msg:"no copy for ".concat(o.field)});continue}let h=o.case?nc(p,b):p,x=nq(o._align||y.align,n),k=Math.max(.6,Math.min(1.6,Number.isFinite(o._size)?o._size:_)),A=Math.max(.8,Math.min(1.8,Number.isFinite(o._leading)?o._leading:w)),L=(e,t)=>{if("left"===x)return t.x;let a=u.measureText(e).width;return"center"===x?t.x+Math.max(0,(t.w-a)/2):t.x+Math.max(0,t.w-a)},N=function(e,t){let a=et.brand.type||{},n="display"===e?a.display_family||"Poppins":a.text_family||"Inter",o=j[t];return(o?'"'.concat(o,'",'):"")+'"'.concat(n,'","Noto Sans",system-ui,sans-serif')}(o.font||"text",n),q=o.fine_print?8:g,P=C(n),z=Math.max(o.min_px*f*k,q),B=Math.max(o.max_px*f*P.maxScale*k,z),F=nf(i,e);if(F.length&&m.push({k:"safe_area",level:"fail",msg:"".concat(o.field," overlaps the ").concat(F[0].edge," reserve (").concat(F[0].why,")")}),"cta"===o.role){let e="dark"===R[o.field],t=!1!==o.rule,a=t?.78*i.h:i.h,n=!1===o.arrow?h:h+"  →",r=np(u,n,i.w,a,{family:N,weight:o.weight||700,minPx:z,maxPx:B,maxLines:1,leading:1.02});if(!r&&!1!==o.arrow&&(r=np(u,n=h,i.w,a,{family:N,weight:o.weight||700,minPx:z,maxPx:B,maxLines:1,leading:1.02}))&&m.push({k:"cta_arrow",level:"info",msg:"the arrow was dropped from the call to action — no room for it at a legible size on this format"}),!r&&q<z&&(r=np(u,n,i.w,a,{family:N,weight:o.weight||700,minPx:q,maxPx:z,maxLines:1,leading:1.02}))&&m.push({k:"legibility",level:"warn",msg:"the call to action dropped to ".concat(r.size.toFixed(0),"px to fit this placement")}),!r){m.push(o.optional?{k:"zone_dropped",level:"info",msg:"the call to action had no room at a legible size here, so it was left out — the platform renders its own CTA anyway"}:{k:"fit_fail",level:"fail",msg:'the call to action ("'.concat(h.slice(0,30),'") will not fit at the ').concat(q,"px floor")});continue}u.save(),u.font="".concat(o.weight||700," ").concat(r.size,"px ").concat(N);let l=r.lines[0],s=Math.min(i.w,u.measureText(l).width),c=ng(u,i).mean,d=nw(o),p=o.color?tw(o.color):e?tw("on_paper"):tw("on_ink"),f="";if(!o._ink&&o.color&&n_(p,c)<d){let t=e?tw("on_paper"):tw("on_ink");n_(t,c)>n_(p,c)&&(f=String(o.color),p=t)}let g=o._ink||p,b=!O(i)&&E.some(e=>"plate"===e.role),y=nb(g,c)>=.5;I[nN(o)]={col:g,hex:o._ink||eD(p),chosen:!!o._ink,ground:c,halo:b,haloDark:y,target:d,swapped:f},f&&m.push({k:"ink_swap",level:"info",msg:"the call to action asks for the ".concat(f," colour, which reads at ").concat(n_(tw(f),c).toFixed(1),":1 here — below the ").concat(d,":1 requirement — so it was set in the readable ink instead.")}),b&&(u.shadowColor=y?"rgba(0,0,0,.55)":"rgba(255,255,255,.62)",u.shadowBlur=.22*r.size,u.shadowOffsetY=y?.035*r.size:0),u.fillStyle=g;let v=L(l,i);if(u.fillText(l,v,i.y),u.shadowColor="transparent",u.shadowBlur=0,u.shadowOffsetY=0,t){let e=Math.max(1,Math.round(.06*r.size)),t=Math.round(i.y+1.06*r.size);u.fillStyle=g,u.fillRect(v,t,Math.round(s),e)}
1u.restore();continue}if("rule"===o.role){u.save(),u.fillStyle=tw(o.color||"[email protected]"),u.fillRect(i.x,i.y,i.w,Math.max(1,Math.round(e.h*(o.weight_px||.0016)))),u.restore();continue}let U=(o.leading||1.12)*P.lead*A,D=e=>e*(P.descend||0),H=Math.max(4,i.h-D(z)),G=np(u,h,i.w,H,{family:N,weight:o.weight||700,minPx:z,maxPx:B,maxLines:o.max_lines||1,leading:U});if(G&&P.descend&&G.lines.length*G.size*U+D(G.size)>i.h){let e=Math.max(4,i.h-D(G.size)),t=np(u,h,i.w,e,{family:N,weight:o.weight||700,minPx:z,maxPx:B,maxLines:o.max_lines||1,leading:U});t&&(G=t)}if(!G&&q<z&&(G=np(u,h,i.w,Math.max(4,i.h-D(q)),{family:N,weight:o.weight||700,minPx:q,maxPx:z,maxLines:o.max_lines||1,leading:U}))&&m.push({k:"legibility",level:"warn",msg:"".concat(o.field," dropped to ").concat(G.size.toFixed(0),"px (below the ").concat(z.toFixed(0),"px design minimum) to fit")}),!G){m.push(o.optional?{k:"zone_dropped",level:"info",msg:"the optional ".concat(o.field," had no room at a legible size on ").concat(e.w,"\xd7").concat(e.h,", so it was left out")}:{k:"fit_fail",level:"fail",msg:"".concat(o.field,' ("').concat(h.slice(0,42)).concat(h.length>42?"…":"",'") will not fit ').concat(o.max_lines||1," line(s) at the ").concat(q,"px legibility floor")});continue}u.save(),u.font="".concat(o.weight||700," ").concat(G.size,"px ").concat(N);let W="dark"===R[o.field],K=String(o.color||"").split("@")[1],J=ng(u,i).mean,Y=nw(o),V=W?tw("on_paper"+(K?"@"+K:"")):tw(o.color),$="";if(!o._ink&&!W&&o.color&&n_(V,J)<Y){let e=tw("on_ink"+(K?"@"+K:""));n_(e,J)>n_(V,J)&&($=String(o.color),V=e)}let X=!O(i)&&E.some(e=>"plate"===e.role),Q=nb(o._ink||V,J)>=.5,Z=X?nv(J,Q):J,ee=/^#[0-9a-f]{6}$/i.test(o._ink||"")?o._ink:eD(V).split(" ")[0],ea=/^#[0-9a-f]{6}$/i.test(ee)?ee:Q?"#ffffff":"#000000",en=K?parseFloat("0"+K):null;if(null!==en){let e=ny(tk(ea),Z,Y);e>en+.005&&(m.push({k:"fine_print_alpha",level:"info",msg:"".concat(T[nN(o)]||o.field," was raised to ").concat(Math.round(100*e),"% opacity from the template's ").concat(Math.round(100*en),"% — at ").concat(Math.round(100*en),"% it measured below the ").concat(Y,":1 requirement on this plate. Fine print may recede; it may not become unreadable.")}),en=e)}u.fillStyle=null!==en?eH(ea,en):o._ink||V,X&&(u.shadowColor=Q?"rgba(0,0,0,.55)":"rgba(255,255,255,.62)",u.shadowBlur=.22*G.size,u.shadowOffsetY=Q?.035*G.size:0),I[nN(o)]={col:u.fillStyle,hex:eD(u.fillStyle),chosen:!!o._ink,ground:J,halo:X,haloDark:Q,target:Y,swapped:$},$&&m.push({k:"ink_swap",level:"info",msg:"".concat(T[nN(o)]||o.field," asks for the ").concat($," colour, which reads at ").concat(n_(tw($),J).toFixed(1),":1 on this plate — below the ").concat(Y,":1 requirement — so it was set in the readable ink instead. Pick a colour by hand in the editor to override.")});let eo=G.lines.length*G.size*U,ei="bottom"===o.anchor?i.y+(i.h-eo):i.y,er=G.size*(U-1)*.35;G.lines.forEach((e,t)=>u.fillText(e,L(e,i),ei+er+t*G.size*U)),u.restore(),!o.fine_print&&G.size<=q+.6&&m.push({k:"legibility",level:"warn",msg:"".concat(o.field," sits at the ").concat(q,"px legibility floor for this placement")})}return Object.entries(I).forEach(e=>{let[t,a]=e,n=a.halo?nv(a.ground,a.haloDark):a.ground,o=n_(a.col,n);a.ratio=o,a.pass=o>=a.target-.05,a.effGround=n,a.pass||m.push({k:"contrast",level:a.chosen?"fail":"warn",msg:"".concat(T[t]||t," is set in ").concat(a.hex," and reads at ").concat(o.toFixed(1),":1 against a ").concat(a.target,":1 requirement")+" on the ".concat(Math.round(100*a.ground),"% luminance behind it")+(a.halo?" (the halo is counted)":"")+(a.chosen?". This colour was chosen by hand — clear it in the editor to go back to the automatic choice.":". Re-roll the plate, or pick the ink by hand in the editor.")})}),{canvas:p,checks:m,editable:k.filter(e=>["text","cta","logo"].includes(e.role)).map(e=>({key:nN(e),role:e.role,field:e.field||"",label:T[nN(e)]||nN(e),x:e.x,y:e.y,w:e.w,h:e.h,align:e._align||y.align,size:Number.isFinite(e._size)?e._size:y.size,leading:Number.isFinite(e._leading)?e._leading:y.leading,ink:e._ink||"",autoInk:(I[nN(e)]||{}).chosen?"":(I[nN(e)]||{}).hex||"",contrast:I[nN(e)]?{ratio:+I[nN(e)].ratio.toFixed(2),target:I[nN(e)].target,pass:I[nN(e)].pass,ground:+I[nN(e)].ground.toFixed(3),halo:!!I[nN(e)].halo,swapped:I[nN(e)].swapped||"",hex:I[nN(e)].hex}:null,optional:!!e.optional,fine:!!e.fine_print,drawn:!v.has(nN(e))}))}}function nB(e,t){return new Promise(a=>e.toBlob(a,"image/jpeg",t))}async function nF(e,t){let a=t.quality_ladder||[92,86,80,72,64];for(let n of a){let a=await nB(e,n/100);if(a&&a.size<=t.max_bytes)return{blob:a,bytes:a.size,quality:n,scale:1}}let n=1,o=e;for(let i=0;i<6;i++){n*=.9;let i=Math.round(t.w*n),r=Math.round(t.h*n);if(i<(t.min_w||40)||r<(t.min_h||40))break;
1let l=document.createElement("canvas");l.width=i,l.height=r,l.getContext("2d").drawImage(e,0,0,i,r);let s=await nB(l,a[a.length-1]/100);if(s&&s.size<=t.max_bytes)return{blob:s,bytes:s.size,quality:a[a.length-1],scale:n};o=l}let i=await nB(o,a[a.length-1]/100);return{blob:i,bytes:i?i.size:0,quality:a[a.length-1],scale:n,over:!0}}function nU(e){return new Promise(t=>{let a=new FileReader;a.onload=()=>t(a.result),a.readAsDataURL(e)})}function nD(e){return e.some(e=>"fail"===e.level)?"FAIL":e.some(e=>"warn"===e.level)?"REVIEW":"PASS"}function nH(e,t){let a=[];return(t.text_fields||[]).forEach(n=>{if(null==n.max_chars)return;let o=nC(e,n.name)||(null==e?void 0:e[n.name])||"",i=q(o);o&&i>n.max_chars&&a.push({k:"trunc_"+n.name,level:"warn",msg:"".concat(n.name," ").concat(i,"/").concat(n.max_chars," — ").concat(n.truncation_note||"will truncate on "+t.platform)})}),a}function nG(e,t,a){let n=a.plate;return et.plates.find(a=>a.deviceKey===e&&a.vn===t&&a.plateKey===n&&"done"===a.status)||et.plates.find(a=>a.deviceKey===e&&a.vn===t&&a.family===K(n)&&"done"===a.status)||et.plates.find(a=>a.deviceKey===e&&a.vn===t&&"done"===a.status)}async function nW(){if(et.running)return el("s5status","Something is already running — stop it first.","warn");e7("s5status","s6status"),t2(),tz(),tW();let e=t5(),t=a0();if(!e.length||!t.length)return el("s5status","Need at least one device and one format.","err");if(!et.plates.some(e=>"done"===e.status))return el("s5status","No plates yet — generate them in step 4 (or seed placeholders in dev mode).","err");let a=await ti(tr()),n=tr().filter(e=>!a.includes(e)),o=[];e.forEach(e=>{for(let a=1;a<=et.plan.variations;
1a++)et.plan.langs.forEach(n=>t.forEach(t=>o.push({k:e,v:a,l:n,f:t})))});let r={};et.creatives.forEach(e=>{e.state&&"new"!==e.state&&(r[e.id]={state:e.state,comment:e.comment||""})}),et.creatives=[],et.abort=!1,et.running=!0,eV("btnCompose",!0,"Composing…"),et.__keepStates=r;let l=0,s=0,c=Date.now();for(let e of o){var d;if(et.abort)break;let t=et.copy["".concat(e.k,"|").concat(e.v,"|").concat(e.l)],a=nG(e.k,e.v,e.f),r={id:"".concat(e.k,"|v").concat(e.v,"|").concat(e.l,"|").concat(e.f.id),deviceKey:e.k,vn:e.v,lang:e.l,fmtId:e.f.id,plateId:(null==a?void 0:a.id)||"",checks:[],state:"new"};t||r.checks.push({k:"copy_missing",level:"fail",msg:"no copy for this device \xd7 language — run the copy engine"});try{let{canvas:o,checks:i}=await nz(e.f,a,t,e.l,nO(e.l,r));r.checks.push(...i),n.length&&j[e.l]&&n.includes(j[e.l])&&r.checks.push({k:"font_missing",level:"fail",msg:"".concat(j[e.l]," did not load — ").concat(a4(e.l)," glyphs may render as boxes")}),r.checks.push(...nH(t,e.f)),(null==t?void 0:t._issues)&&r.checks.push(...t._issues.filter(e=>"fail"===e.level));let l=await nF(o,e.f);r.dataUrl=l.blob?await nU(l.blob):"",r.bytes=l.bytes,r.quality=l.quality,r.scale=l.scale,l.over?r.checks.push({k:"weight",level:"fail",msg:"".concat((l.bytes/1024).toFixed(0),"KB exceeds the ").concat(e.f.max_bytes/1024|0,"KB cap even at the quality floor")}):l.scale<1&&r.checks.push({k:"weight",level:"warn",msg:"downscaled to ".concat((100*l.scale).toFixed(0),"% to meet the ").concat(e.f.max_bytes/1024|0,"KB cap")})}catch(e){r.checks.push({k:"draw",level:"fail",msg:"compose failed: "+e.message})}if(r.verdict=nD(r.checks),r.appeal=(null==(d=ee[e.k])?void 0:d.appeal)||"",r.ad_name=ig(r),r.dest_url=im(r),"FAIL"===r.verdict&&s++,et.creatives.push(r),++l%4==0||l===o.length){i("compFill").style.width=l/o.length*100+"%",i("compProg").textContent="".concat(l,"/").concat(o.length," creatives");let e=(Date.now()-c)/l;i("compEta").textContent="~"+Math.ceil((o.length-l)*e/1e3)+"s left",ec("".concat(l,"/").concat(o.length," creatives"),l,o.length),await g(0)}}let p=0;et.__keepStates&&(et.creatives.forEach(e=>{let t=et.__keepStates[e.id];t&&(e.state=t.state,e.comment=t.comment,p++)}),delete et.__keepStates),aZ(),et.running=!1,eV("btnCompose",!1),i("compEta").textContent="",i("compChip").textContent="".concat(et.creatives.length," creatives"),el("s5status","".concat(et.creatives.length," creatives composed").concat(s?" — ".concat(s," carry a hard failure (see step 6)"):" — all clean").concat(p?", ".concat(p," kept their earlier approval or comment"):"").concat(n.length?". Fonts that did not load: "+n.join(", "):"","."),s||n.length?"err":"okk"),nK(),nJ()}function nK(){let e=Object.entries(et.sel).filter(e=>
1{let[t,a]=e;return a.on}).map(e=>{let[t]=e;return t}),t=[];e.forEach(e=>{for(let a=1;a<=et.plan.variations;a++)t.push(["".concat(e,"|").concat(a),"".concat(ee[e].label," \xb7 v").concat(a)])});let a=i("prevDevice");if(a){let e=a.value;a.innerHTML=t.map(e=>{let[t,a]=e;return'<option value="'.concat(t,'">').concat(r(a),"</option>")}).join(""),t.some(t=>t[0]===e)&&(a.value=e)}let n=i("prevLang");if(n){let e=n.value;n.innerHTML=et.plan.langs.map(e=>'<option value="'.concat(e,'">').concat(r(a4(e)),"</option>")).join(""),et.plan.langs.includes(e)&&(n.value=e)}}async function nJ(){var e,t;let a=i("prevStage");if(!a)return;let n=((null==(e=i("prevDevice"))?void 0:e.value)||"").split("|"),o=n[0],l=+n[1]||1,s=(null==(t=i("prevLang"))?void 0:t.value)||et.plan.langs[0];if(!o)return;let c=["meta_feed_4x5","meta_story_9x16","gdn_300x250","gdn_728x90","gdn_970x250"].filter(e=>et.plan.formats.includes(e)).slice(0,5),d=c.length?c:et.plan.formats.slice(0,4);if(!d.length){a.innerHTML="<span class='tiny'>No formats selected.</span>";return}await ti(tr()),a.innerHTML="<span class='tiny'><span class='spin'></span> drawing…</span>";let p=[];for(let e of d){let t=G[e],a=nG(o,l,t),n=et.copy["".concat(o,"|").concat(l,"|").concat(s)];try{let{canvas:e,checks:o}=await nz(t,a,n,s,nO(s)),i=Math.min(1,300/Math.max(t.w,1.2*t.h));p.push('<div class="sh"><img src="'.concat(e.toDataURL("image/jpeg",.9),'" style="width:').concat(Math.round(t.w*i),'px">\n        <span>').concat(r(t.placement)," \xb7 ").concat(t.w,"\xd7").concat(t.h).concat(o.some(e=>"fail"===e.level)?" \xb7 <b style='color:#fca5a5'>"+o.filter(e=>"fail"===e.level).length+" fail</b>":"","</span></div>"))}catch(t){p.push('<div class="sh"><span>'.concat(r(e),": ").concat(r(t.message),"</span></div>"))}}a.innerHTML=p.join(""),a.querySelectorAll("img").forEach(e=>e.onclick=()=>e9(e.src))}let nY={base:c,ok:!1,info:null};async function nV(e,t,a){let n,o=new URLSearchParams(Object.assign({a:e},a||{})).toString(),i=t?{method:"POST",headers:h({"Content-Type":"application/json"}),body:JSON.stringify(t)}:{method:"GET",headers:h()},r=await fetch("".concat(nY.base,"?").concat(o),i),l=await r.text();try{n=JSON.parse(l)}catch(t){throw Error("api ".concat(e," returned non-JSON (").concat(r.status,"): ").concat(l.slice(0,180)))}if(!r.ok||!1===n.ok)throw Error(n.error||"api ".concat(e," failed (").concat(r.status,")"));return n}async function n$(e,t,n,o){if(a.autoRun||u()||!nY.ok||!ot||!ot.id){await t(),"function"==typeof n&&await n();return}try{await oc(!0);let t=await nV("job.start",{type:e,campaign_id:ot.id}),a=t.job&&t.job.label||e;el(o,"".concat(r(a)," is running on the server").concat(t.reused?" (already in progress)":"",". You can close this tab — it will keep going. Reopen the campaign when it finishes."),"run")}catch(a){let e=String(a&&a.message||a);if(/already running/i.test(e))return void el(o,r(e),"warn");el(o,"Could not queue a background job (".concat(r(e),"). Running in this tab instead."),"warn"),await t(),"function"==typeof n&&await n()}}async function nX(e){et.abort=!0,e&&el(e,"Stopping…","warn");try{u()?await nV("job.cancel",{id:String(a.jobId||"")}):ot&&ot.id&&await nV("job.cancel",{campaign_id:ot.id})}catch(e){}}async function nQ(){let e=String(a.jobType||n6().get("type")||"").trim();window.__ACS_JOB_DONE=!1,window.__ACS_JOB_ERROR="",function(){if(!u())return;let e=async()=>{try{let e=await nV("job.get",null,{id:String(a.jobId||"")});e.job&&e.job.cancel_requested&&(et.abort=!0)}catch(e){}};e(),setInterval(e,2500)}();try{if("intel"===e){if(!(i("in_url")&&i("in_url").value.trim()))throw Error("Paste a brand or product URL first.");await tB(),await oc(!0)}else if("kit_intel"===e){if(!et.product.images.filter(e=>e.kit||e.dataUrl).length)throw Error("Drop at least one guideline page or product photo first.");await tF(),await oc(!0)}else if("plates"===e)await aY(),await oE();else if("copy"===e)await ni(),await oj();else if("qc"===e)await aQ(),await oE();else if("compose"===e)await nW(),await oA();else throw Error("Unknown job type: "+e);try{await nV("job.finish",{ok:!0})}catch(e){}}catch(e){window.__ACS_JOB_ERROR=String(e&&e.message||e);try{await nV("job.finish",{ok:!1,error:String(e&&e.message||e)})}catch(e){}}finally{window.__ACS_JOB_DONE=!0}}function nZ(e,t){if(!e)return"";let a=String(e).replace(/^\/+/,"");return/^https?:\/\//i.test(a)||a.startsWith("/product_assets/")||a.startsWith("/api/")?a+(t?(a.includes("?")?"&":"?")+"v="+encodeURIComponent(t):""):a.includes("a=file")||a.startsWith("api.php")?a.replace(/^api\.php/,nY.base)+(t?(String(a).includes("?")?"&":"?")+"v="+encodeURIComponent(t):"")+(a.includes("mk=")?"":ey()):"/product_assets/"+a+(t?"?v="+encodeURIComponent(t):"")}function n0(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:{}}function n1(e){return Array.isArray(e)?e:[]}async function n2(e,t){if(!ot||!e)throw Error("export id missing");let a=new URLSearchParams({a:"export.download",id:String(e),campaign_id:String(ot.id)}),n=await fetch("".concat(nY.base,"?").concat(a),{headers:h()}),o=String(n.headers.get("content-type")||"");if(!n.ok||o.includes("application/json")){let e="Could not download the zip";try{let t=await n.json();e=t.error||t.message||e}catch(e){}throw Error(e)}let i=await n.blob(),r=URL.createObjectURL(i),l=document.createElement("a");l.href=r,l.download=t||"campaign-export.zip",document.body.appendChild(l),l.click(),l.remove(),setTimeout(()=>URL.revokeObjectURL(r),4e3)}async function n4(){try{let a=await nV("ping");nY.ok=!0,nY.info=a;let n=a.db&&a.storage_writable,o=i("dbChip");if(o){var e,t;
1o.className="chip "+(n?"ok":"warn"),o.textContent=n?"DB ok \xb7 ".concat(null!=(t=null==(e=a.counts)?void 0:e.acs_campaign)?t:0," campaigns"):"DB problem"}return n||el("s0status",n5(a),"err"),a}catch(t){nY.ok=!1;let e=i("dbChip");return e&&(e.className="chip bad",e.textContent="no backend"),el("s0status","<b>Running without the backend.</b> ".concat(r(t.message),'<br>\n      Serve this folder over <span class="mono">http://localhost/sparkstation/ad-creative-studio/</span> with WAMP running (Apache <b>and</b> MySQL).\n      Everything still works in-memory — campaigns just won\'t be saved and export is download-only.'),"err"),null}}function n5(e){let t=(e,t,a)=>"<tr><td>".concat(r(e),'</td><td class="mono" style="color:').concat(a?"var(--ok)":"var(--bad)",'">').concat(r(String(t)),"</td></tr>");return'<table class="tbl">\n    '.concat(t("PHP",e.php,!0),"\n    ").concat(t("MySQL (database sparkstation)",e.db?"connected":"FAILED — "+(e.db_error||""),e.db),"\n    ").concat(t("storage/ writable",e.storage_writable?"yes":"NO — check folder permissions",e.storage_writable),"\n    ").concat(t("ZipArchive (per-platform .zip)",e.zip?"available":"missing — exports write folders only, no zip",e.zip),"\n    ").concat(t("GD",e.gd?"available":"missing",e.gd),"\n    ").concat(t("module root",e.root,!0),"\n    ").concat(Object.entries(e.counts||{}).map(e=>{let[a,n]=e;return t(a,n,!0)}).join(""),'\n  </table>\n  <p class="tiny" style="margin-top:8px">This module makes <b>no server-side HTTPS calls</b> — every Gemini and Nano Banana request goes straight from your browser to Google, so the WAMP <span class="mono">curl.cainfo</span> problem that stalled Spark cannot affect it.</p>')}function n6(){try{if(null!=a.searchParams)return new URLSearchParams(a.searchParams);return new URLSearchParams(location.search||"")}catch(e){return new URLSearchParams}}function n8(e){try{"function"==typeof a.onCampaignOpened&&a.onCampaignOpened(String(e||""))}catch(e){}}async function n3(e){try{let t=await nV("brand.list");i("nc_brand")&&(i("nc_brand").innerHTML='<option value="">— new brand, set up in step 1 —</option>'+(t.brands||[]).map(e=>'<option value="'.concat(r(e.id),'">').concat(r(e.name),"</option>")).join("")),e&&i("nc_brand")&&(i("nc_brand").value=e)}catch(e){}}function n7(e){if(i("nc_name")){if(!e){i("nc_name").value="",i("nc_brand")&&(i("nc_brand").value=""),i("nc_objective")&&(i("nc_objective").value="conversion"),i("nc_audience")&&(i("nc_audie
1nce").value=""),i("nc_market")&&(i("nc_market").value="India");return}if(i("nc_name").value=e.name||et.camp.campaign||"",i("nc_brand")&&(i("nc_brand").value=e.brand_id||""),i("nc_objective")&&(i("nc_objective").value=e.objective||et.camp.objective||"conversion"),i("nc_audience")&&(i("nc_audience").value=e.audience||et.camp.audience||""),i("nc_market")){let t=e.market||et.camp.market||"India",a=i("nc_market");[...a.options].some(e=>e.value===t||e.textContent===t)&&(a.value=t)}}}function n9(e){var t;let a=null==(t=i("newCampCard"))?void 0:t.querySelector(".card-head h3");a&&(a.textContent=e?"Campaign":"New campaign");let n=i("btnCreateCamp");if(n){let t=e?"Save &amp; continue →":"Create &amp; open →";n.getAttribute("aria-busy")?n.dataset._h=t:(n.innerHTML=t,delete n.dataset._h)}let o=i("btnNewCampClose");o&&(o.style.display=e?"none":"")}function oe(){let t=i("newCampCard");if(!t)return;let a=i("step0");a&&(a.classList.remove("hidden"),a.style.display="block");let n=i("campListCard");n&&(n.style.display="none");let o=!!ot;o||(on=!1),n9(o),n7(ot),t.style.display="block",e.querySelectorAll("[id^=step]").forEach(e=>{"step0"!==e.id&&/^step\d+$/.test(e.id)&&(e.classList.add("hidden"),e.style.display="")}),e.querySelectorAll(".step-tab").forEach(e=>{e.classList.toggle("active","0"===e.getAttribute("data-step"))}),(async()=>{await n3((null==ot?void 0:ot.brand_id)||""),n7(ot),on=o,!o&&i("nc_name")&&i("nc_name").focus();try{t.scrollIntoView({block:"start",behavior:"smooth"})}catch(e){}})()}let ot=null,oa=!1,on=!1;async function oo(){if(nY.ok)try{let e=(await nV("camp.list",null,{state:i("campState").value})).campaigns||[];i("campList").innerHTML=e.length?'<table class="tbl">\n      <tr><th>Campaign</th><th>Brand</th><th>Stage</th><th>Plates</th><th>Creatives</th><th>Approved</th><th>Exports</th><th>Updated</th><th></th></tr>\n      '.concat(e.map(e=>"<tr>\n        <td><b>".concat(r(e.name),'</b><div class="tiny mono">').concat(r(e.slug),"</div></td>\n        <td>").concat(r(e.brand_name||"—"),"</td>\n        <td>").concat(function(e){let t=n0(e.stage_gates);return oi.map(e=>{var a,n;let[o,i]=e,l=(null==(a=t[o])?void 0:a.status)||"";return'<span class="stagechip '.concat(r(l),'" title="').concat(r(o+((null==(n=t[o])?void 0:n.note)?": "+t[o].note:" — not signed off")),'">').concat("ready"===l?"✓ ":"").concat(r(i),"</span>")}).join("")}(e),'</td>\n        <td class="mono">').concat(e.plates,'</td><td class="mono">').concat(e.creatives,'</td>\n        <td class="mono" style="color:').concat(e.approved?"var(--ok)":"inherit",'">').concat(e.approved,'</td>\n        <td class="mono">').concat(e.exports,'</td>\n        <td class="tiny">').concat(r((e.updated_at||"").slice(0,16)),'</td>\n        <td style="white-space:nowrap">\n          <button class="btn small primary" data-open="').concat(e.id,'">Open</button>\n          <button class="btn small ghost" data-dup="').concat(e.id,'">Duplicate</button>\n          <button class="btn small ghost" data-arch="').concat(e.id,'">').concat("active"===i("campState").value?"Archive":"Restore","</button>\n        </td></tr>")).join(""),"</table>"):'<div class="tiny">No '.concat(r(i("campState").value)," campaigns yet. Create one to begin — every plate, creative, approval and export is stored against it.</div>"),i("campList").querySelectorAll("[data-open]").forEach(e=>e.onclick=()=>or(e.dataset.open)),i("campList").querySelectorAll("[data-dup]").forEach(t=>t.onclick=()=>{let a=e.find(e=>e.id===t.dataset.dup);oy=t.dataset.dup,i("dupFrom").textContent=a?a.name:"",i("in_dupName").value=(a?a.name:"campaign")+" copy",i("dupRow").classList.remove("hidden"),i("in_dupName").focus()}),i("campList").querySelectorAll("[data-arch]").forEach(e=>e.onclick=async()=>{await nV("camp.archive",{id:e.dataset.arch,state:"active"===i("campState").value?"archived":"active"}),oo()})}catch(e){el("s0status","Could not list campaigns: "+r(e.message),"err")}}let oi=[["intake","Brand"],["intelligence","Strategy"],["plan","Plan"],["plates","Plates"],["compose","Compose"],["review","Review"]];async function or(e){el("s0status","<span class='spin'></span>
1 Loading campaign…","run"),oa=!0,on=!1,clearTimeout(op),op=null,od=!1;try{let t=await nV("camp.get",null,{id:e});ot=t.campaign;let a=t.campaign;t.brand||eo(),t.brand&&(et.brand=Object.assign({brand_slug:t.brand.slug,brand_name:t.brand.name},n0(t.brand.tokens)),et.brand.palette=n0(et.brand.palette),et.brand.type=n0(et.brand.type),et.brand.logo=n0(et.brand.logo),et.brand.voice=n0(et.brand.voice),et.brand.legal=n0(et.brand.legal),et.brand.evidence=n1(t.brand.evidence),et.brand._dirty=!1,et.brand._id=t.brand.id,S.forEach(e=>{var t;(null==(t=et.brand.palette)?void 0:t[e.k])||(et.brand.palette=et.brand.palette||{},et.brand.palette[e.k]=e.def)})),et.product.spec=n0(a.spec),et.product.images=n1(a.refs).map(e=>Object.assign({},e,e.file?{src:nZ(e.file),dataUrl:null}:{}));let n=n0(et.product.spec),o=String(et.product.url||n.page_url||n.source_url||n.destination_url||"").trim();et.product.url=o,i("in_url")&&(i("in_url").value=o),i("in_notes")&&null!=n.intake_notes&&(i("in_notes").value=String(n.intake_notes||"")),ei=o,et.claims=n1(t.claims),et.camp={objective:a.objective,audience:a.audience,market:a.market,competitor:a.competitor,campaign:a.name},et.fit=n0(a.fit),et.sel=n0(a.sel),et.brief=Object.assign({},t9,n0(a.plan).brief||{}),et.refs=Object.assign({read:null,applied:{}},n0(a.plan).refs||{}),e7(),et.plan=Object.assign({variations:1,tier:"final",langs:["en"],formats:[],style:"open",scope:"approved",typo:{}},n0(a.plan)),et.plan.typo=n0(et.plan.typo),et.plan.style||(et.plan.style="open"),et.plan.formats=n1(et.plan.formats).filter(e=>G[e]),et.plan.langs=n1(et.plan.langs).length?n1(et.plan.langs):["en"],et.gates=n0(a.gates),et.plates=n1(t.plates).map(e=>({id:e.plate_uid,plateKey:e.plate_key,deviceKey:e.device_key,vn:+e.variation,family:e.family,aspect:e.aspect,prompt:e.prompt,model:e.model,status:e.status,dataUrl:e.file?nZ(e.file,e.version||e.updated_at):"",file:e.file,bytes:+e.bytes,ms:+e.ms,seeded:1==+e.seeded,locked:1==+e.locked,qc:e.qc_json&&"{}"!==e.qc_json?n0(JSON.parse(e.qc_json)):null,err:e.err||""})),et.copy={},n1(t.copy).forEach(e=>{let t=n0(JSON.parse(e.fields_json||"{}"));t._issues=n1(JSON.parse(e.issues_json||"[]")),t._edited=1==+e.edited,t._id=e.id,t._locked=1==+e.locked,et.copy["".concat(e.device_key,"|").concat(e.variation,"|").concat(e.lang)]=t}),et.creatives=n1(t.creatives).map(e=>({id:e.creative_uid,deviceKey:e.device_key,appeal:e.appeal,vn:+e.variation,lang:e.lang,fmtId:e.format_id,platform:e.platform,plateId:"",dataUrl:e.file?nZ(e.file,e.version||e.updated_at):"",file:e.file,bytes:+e.bytes,quality:+e.quality,scale:+e.scale,verdict:e.verdict,state:e.state,checks:n1(JSON.parse(e.checks_json||"[]")),ad_name:e.ad_name,dest_url:e.dest_url,comment:e.comment||""})),ty(),tH(),tU(),tP(),t0(),i("c_campaign").value=a.name,n9(!0),await n3(a.brand_id||""),n7(a),on=!0,t6(),document.querySelectorAll("#varSeg .opt").forEach(e=>e.classList.toggle("sel",+e.dataset.v===et.plan.variations)),document.querySelectorAll("#tierSeg .opt").forEach(e=>e.classList.toggle("sel",e.dataset.v===et.plan.tier)),i("brandChip").className="chip ok",i("brandChip").textContent=(et.brand.brand_name||"brand")+" loaded",tW();let r=a.stage&&"intake"!==a.stage&&({intake:1,intelligence:2,plan:3,plates:4,compose:5,review:6,export:6})[a.stage]||1;os(),oz(),eY(r,{persist:!1}),n8(e),el("s0status","",""),aG().then(e=>{e&&el("s4status","".concat(e," stored plate").concat(1===e?"":"s"," carry a flat matte band at an edge — “Regenerate banded plates” re-rolls just those."),"err")})}catch(e){el("s0status","Could not open that campaign: "+r(e.message),"err")}finally{oa=!1}}function ol(e){return["campaigns","intake","intelligence","plan","plates","compose","review"][e]||"intake"}function os(){let e=i("campChip");e&&(e.className="chip "+(ot?"ok":"bad"),e.textContent=ot?ot.name:"no campaign")}async function oc(e){if(nY.ok&&ot&&!oa){tz(),t2(),er(),et.brand._dirty&&t_();try{if(et.brand.brand_name&&(et.brand._dirty||!ot.brand_id)){var t;let e=await nV("brand.save",{name:et.brand.brand_name,slug:et.brand.brand_slug,tokens:{palette:et.brand.palette,type:et.brand.type,logo:et.brand.logo,voice:et.brand.voice,legal:et.brand.legal},evidence:et.brand.evidence});et.brand._id=e.id,(null==(t=e.tokens)?void 0:t.logo)&&(et.brand.logo=Object.assign({},et.brand.logo,e.tokens.logo)),ot.brand_id=e.id,et.brand._dirty=!1}await nV("camp.save",{id:ot.id,brand_id:ot.brand_id||"",name:i("c_campaign").value||ot.name,slug:ot.slug,objective:et.camp.objective,audience:et.camp.audience,market:et.camp.market,competitor:et.camp.competitor,spec:et.product.spec,plan:Object.assign({},et.plan,{brief:et.brief||{},refs:et.refs||{read:null,applied:{}}}),fit:et.fit,sel:et.sel,refs:et.product.images.map(e=>({src:e.file?"":e.src,file:e.file||"",w:e.w,h:e.h,sel:!!e.sel,hero:!!e.hero,kit:!!e.kit})),registry_version:D.registry_version,stage:ot.stage||"intake"}),await nV("claims.save",{campaign_id:ot.id,claims:et.claims}),od=!1,e||ob("Saved")}catch(e){od=!0,ob("Save failed: "+e.message,!0)}}}let od=!1,op=null,ou=null;function oh(){ot&&!oa&&(od=!0,om())}function om(){nY.ok&&ot&&!oa&&(clearTimeout(op),op=setTimeout(()=>{op=null,of(!0)},1600))}function of(e){return(clearTimeout(op),op=null,nY.ok&&ot&&!oa)?ou||(ou=Promise.resolve().then(()=>oc(!1!==e)).finally(()=>{ou=null,od&&om()})):Promise.resolve()}function og(e){let t=e=>{let t=e.target;!(e=>{if(!e||!e.closest)return!0;let t=e.id||"";return!!("in_imgUrl"===t||"in_dupName"===t||"man_intel"===t||"ov_reason"===t||/^nc_/.test(t)&&!on||e.closest(".ed-wrap"))})(t)&&/^(INPUT|SELECT|TEXTAREA)$/.test(t.tagName||"")&&oh()};e.addEventListener("input",t),e.addEventListener("change",t),og._win||(og._win=!0,document.addEventListener("visibilitychange",()=>{"hidden"===document.visibilityState&&of(!0)}),window.addEventListener("beforeunload",e=>{(od||op||ou)&&(of(!0),e.preventDefault(),e.returnValue="")}
1))}function ob(e,t){let a=i("saveToast");a&&(a.textContent=e,a.className="chip "+(t?"bad":"ok"),clearTimeout(a._t),a._t=setTimeout(()=>{a.textContent="",a.className="chip"},2600))}let oy=null;async function ov(){let e=(i("in_dupName").value||"").trim();if(!oy||!e)return el("s0status","Give the copy a name.","err");eV("btnDupGo",!0,"Copying…");try{let t=await nV("camp.duplicate",{id:oy,name:e});i("dupRow").classList.add("hidden"),oy=null,await oo(),await or(t.id)}catch(e){el("s0status",r(e.message),"err")}eV("btnDupGo",!1)}function o_(){for(let e=1;e<=6;e++){let t=i("step"+e);if(!t)continue;let a=t.querySelector(".gatebar");a||((a=document.createElement("div")).className="card sub gatebar",t.appendChild(a));let n=ol(e),o=(et.gates||{})[n]||{},l=o.status||"open";a.innerHTML='<div class="card-head" style="margin-bottom:8px">\n      <h3 style="font-size:13px">Stage gate — '.concat(r(oi[e-1][1]),'</h3>\n      <span class="fitb ').concat("ready"===l?"best":"override"===l?"weak":"",'">').concat(r(l),"</span>\n      ").concat(o.at?'<span class="tiny">'.concat(r(o.by||"operator")," \xb7 ").concat(r(String(o.at).slice(0,16)),"</span>"):"",'\n      <button class="btn small ghost" data-log="1">\uD83D\uDD50 Audit trail</button>\n      <button class="btn small ').concat("ready"===l?"ok":"",'" data-gate="').concat(n,'">').concat("ready"===l?"✓ Signed off":"Mark stage ready",'</button>\n    </div>\n    <input class="gatenote" data-note="').concat(n,'" placeholder="Note for the record — what you checked, what you\'re accepting" value="').concat(r(o.note||""),'">\n    ').concat(o.note&&"open"!==l?'<div class="tiny" style="margin-top:6px">Recorded: '.concat(r(o.note),"</div>"):""),a.querySelector("[data-gate]").onclick=()=>ow(n,a.querySelector("[data-note]").value),a.querySelector("[data-log]").onclick=ok}}async function ow(e,t){if(!nY.ok||!ot)return void ob("No campaign open — gates need the backend",!0);await oc(!0);try{et.gates=(await nV("camp.stage",{id:ot.id,stage:e,status:"ready",note:t})).gates,ot.stage=e,o_(),ob("Stage signed off")}catch(e){ob(e.message,!0)}}let ox=null;async function ok(){if(!nY.ok||!ot)return ob("Audit trail needs the backend",!0);i("logModal").classList.add("show");try{let e=await nV("events",null,{campaign_id:ot.id,limit:200});i("logTbl").innerHTML="<tr><th>When</th><th>Stage</th><th>Action</th><th>Detail</th><th>By</th></tr>"+(e.events||[]).map(e=>'<tr><td class="tiny mono">'.concat(r(String(e.created_at).slice(0,19)),'</td>\n        <td class="mono">').concat(r(e.stage),'</td><td class="mono">').concat(r(e.action),"</td>\n        <td>").concat(r(e.detail),'</td><td class="tiny">').concat(r(e.actor),"</td></tr>")).join("")}catch(e){i("logTbl").innerHTML='<tr><td class="tiny">'.concat(r(e.message),"</td></tr>")}}async function oS(e,t,a){if(nY.ok&&ot)try{await nV("event.add",{campaign_id:ot.id,stage:e,action:t,detail:a})}catch(e){}}async function oE(){if(nY.ok&&ot)try{let e=await nV("plate.save",{campaign_id:ot.id,plates:et.plates.map(e=>({id:e.id,plateKey:e.plateKey,deviceKey:e.deviceKey,vn:e.vn,family:e.family,aspect:e.aspect,prompt:e.prompt,model:"draft"===et.plan.tier?_.model:v.model,status:e.status,ms:e.ms,seeded:!!e.seeded,qc:e.qc,err:e.err,dataUrl:/^data:/.test(e.dataUrl||"")?e.dataUrl:""}))});Object.entries(e.plates||{}).forEach(e=>{let[t,a]=e,n=et.plates.find(e=>e.id===t);n&&a.file&&(n.file=a.file,/^data:/.test(n.dataUrl||"")&&(n.dataUrl=n.dataUrl))}),ob("Plates saved")}catch(e){ob("Plate save failed: "+e.message,!0)}}async function oj(){if(nY.ok&&ot)try{await nV("copy.save",{campaign_id:ot.id,copy:et.copy}),ob("Copy saved")}catch(e){ob("Copy save failed: "+e.message,!0)}}async function oA(e){if(nY.ok&&ot)try{let t=await nV("creative.save",{campaign_id:ot.id,force:!!e,creatives:et.creatives.map(e=>{var t,a,n,o;return{id:e.id,plateId:e.plateId,deviceKey:e.deviceKey,appeal:(null==(t=ee[e.deviceKey])?void 0:t.appeal)||"",vn:e.vn,lang:e.lang,fmtId:e.fmtId,platform:(null==(a=G[e.fmtId])?void 0:a.platform)||"",w:(null==(n=G[e.fmtId])?void 0:n.w)||0,h:(null==(o=G[e.fmtId])?void 0:o.h)||0,bytes:e.bytes,quality:e.quality,scale:e.scale,verdict:e.verdict,state:e.state,checks:e.checks,ad_name:ig(e),dest_url:im(e),dataUrl:/^data:/.test(e.dataUrl||"")?e.dataUrl:""}})});ob("".concat(t.saved," creatives saved").concat(t.skipped_locked?" \xb7 ".concat(t.skipped_locked," approved kept"):"")),await oq(),await oB()}catch(e){ob("Creative save failed: "+e.message,!0)}}async function oC(e,t){if(nY.ok&&ot)try{await nV("creative.state",{campaign_id:ot.id,items:e,label:t}),await oq(),await oB()}catch(e){ob(e.message,!0)}}let oL=[["headline","Headline — printed into the image",1,"str"],["banner_line","Banner line — small display formats, 3-5 words",1,"str"],["sub","Sub-line under the headline",1,"str"],["offer","Offer chip — verbatim from the lockbox or empty",1,"str"],["cta_label","CTA label — must be one the platform offers",1,"str"],["short_headlines","Google short headlines — one per line, 5 of them, ≤30 each",5,"list"],["long_headline","Google long headline ≤90",2,"str"],["descriptions","Descriptions — one per line, ≤90 each",3,"list"],["fb_primary_text","Facebook / Instagram primary text — pasted, emoji fine, ≤125",3,"str"],["ig_caption","Instagram caption — pasted, short lines, 2-4 emoji, no hashtags here",5,"str"],["hashtags","Hashtags — one per line or space separated",3,"tags"],["yt_description","YouTube description ≤150",2,"str"],["alt_text","Alt text — accessibility, and a real Meta field",2,"str"]],oT=null,oM=null;function oN(e){var t;let a=et.copy[e];if(!a)return ob("No copy for that combination yet",!0);oT=e,oM=JSON.parse(JSON.stringify(a));let[n,o,l]=e.split("|");i("ceWho").textContent="".concat((null==(t=ee[n])?void 0:t.label)||n," \xb7 v").concat(o," \xb7 ").concat(a4(l));let s=a1();i("ceFields").innerHTML=oL.map(e=>{var t;let[n,o,i,l]=e,c="long_headline"===n?90:"fb_primary_text"===n?125:"ig_caption"===n?2200:"yt_description"===n?150:"alt_text"===n?200:"short_headlines"===n?30:"descriptions"===n?90:null!=(t=s[n])?t:60,d=a[n],p="list"===l?Array.isArray(d)?d.join("\n"):d||"":"tags"===l?(a.hashtags||[]).join(" "):d||"",u="list"===l?"".concat((Array.isArray(d)?d:[]).length," lines \xb7 ≤").concat(c," each"):"tags"===l?"".concat((a.hashtags||[]).length," tags"):"".concat(String(p).length,"/").concat(c);return'<div class="field"><label>
1'.concat(r(o),'\n      <span class="hint" id="cc_').concat(n,'">').concat(r(u),"</span></label>\n      ").concat(i>1?'<textarea data-cf="'.concat(n,'" data-kind="').concat(l,'" data-max="').concat(c,'" style="min-height:').concat(24*i,'px">').concat(r(p),"</textarea>"):'<input data-cf="'.concat(n,'" data-kind="').concat(l,'" data-max="').concat(c,'" value="').concat(r(p),'">'),"</div>")}).join("")+'<div class="tiny">Platform CTAs available: '.concat(r(a2().join(" \xb7 ")),"</div>"),i("ceFields").querySelectorAll("[data-cf]").forEach(e=>e.oninput=()=>oI()),oI(),i("copyModal").classList.add("show")}function oR(){let e={};return i("ceFields").querySelectorAll("[data-cf]").forEach(t=>{let a=t.dataset.kind,n=t.dataset.cf;"list"===a?e[n]=t.value.split("\n").map(e=>e.trim()).filter(Boolean):"tags"===a?e[n]=t.value.split(/[\s,]+/).map(e=>e.trim()).filter(Boolean).map(e=>e.replace(/^#*/,"#")):e[n]=t.value}),e}function oI(){var e;let t=oR(),a=[];i("ceFields").querySelectorAll("[data-cf]").forEach(e=>{let t=e.dataset.cf,n=e.dataset.kind,o=+e.dataset.max,r=i("cc_"+t);if("list"===n){let n=e.value.split("\n").map(e=>e.trim()).filter(Boolean),i=n.filter(e=>q(e)>o);r&&(r.textContent="".concat(n.length," lines \xb7 ≤").concat(o," each"),r.style.color=i.length?"var(--bad)":""),i.length&&a.push("".concat(t,": ").concat(i.length," line(s) over ").concat(o," characters")),"short_headlines"===t&&n.length<3&&a5().includes("google_display")&&a.push("Google rotates up to 5 short headlines — only ".concat(n.length," here"))}else if("tags"===n){let t=e.value.split(/[\s,]+/).filter(Boolean);r&&(r.textContent="".concat(t.length," tags"),r.style.color=t.length>15?"var(--warn)":""),t.length>15&&a.push("".concat(t.length," hashtags — Instagram allows 30 but past ~15 reads as spam"))}else{let n=q(e.value||"");r&&(r.textContent="".concat(n,"/").concat(o),r.style.color=n>o?"var(--bad)":n>.9*o?"var(--warn)":""),n>o&&a.push("".concat(t," is ").concat(n,"/").concat(o," — it will truncate"))}});let n=["headline","banner_line","sub","offer","cta_label","long_headline"],o=[],r=[];n.concat(["fb_primary_text","ig_caption","yt_description"]).forEach(e=>{tY(t[e],!1).forEach(e=>{o.includes(e)||o.push(e)}),tY(t[e],!0).forEach(e=>{r.includes(e)||r.push(e)})}),(t.short_headlines||[]).concat(t.descriptions||[]).forEach(e=>
1{tY(e,!1).forEach(e=>{o.includes(e)||o.push(e)})}),o.length&&a.push("<b>claim with no source:</b> ".concat(o.join(", ")," — not in the lockbox"));let l=r.filter(e=>!o.includes(e));l.length&&a.push("numbers not traced to a claim: ".concat(l.slice(0,6).join(", "))),n.forEach(e=>{a9.test(t[e]||"")&&a.push("".concat(e," has an emoji — that text is printed into the image, so it will be stripped"))});let s=((null==(e=et.brand.voice)?void 0:e.banned_words)||[]).filter(e=>e&&[...n,"fb_primary_text","ig_caption"].map(e=>t[e]||"").join(" ").toLowerCase().includes(e.toLowerCase()));return s.length&&a.push("banned word(s): ".concat(s.join(", "))),t.cta_label&&!a2().includes(t.cta_label)&&a.push('CTA "'.concat(t.cta_label,'" is not one the platform offers')),a.length?el("ceStatus",a.join("<br>"),"err"):el("ceStatus","Clean — inside every limit, every claim traced, no emoji in printed type.","okk"),i("ceHint").textContent="".concat(Object.keys(et.copy).length," bundles in this campaign"),a}async function oO(){let e=oR(),t=et.copy[oT];Object.assign(t,e),a3(t),t._edited=!0,t._issues=nt(t,oT.split("|")[0],oT.split("|")[2]),i("copyModal").classList.remove("show"),await oj(),await oS("compose","copy.edit","".concat(oT," edited by hand"));let a=et.creatives.filter(e=>"".concat(e.deviceKey,"|").concat(e.vn,"|").concat(e.lang)===oT&&"approved"!==e.state);for(let e of(el("s5status","<span class='spin'></span> Re-drawing ".concat(a.length," creatives from the edited copy…"),"run"),a))await iu(e);await oA(),ip(),oK(),oW(),nJ();let n=a.filter(e=>"FAIL"===e.verdict).length;el("s5status","Copy saved and ".concat(a.length," creatives re-drawn — no plate was regenerated, so this cost nothing.").concat(n?" ".concat(n," still blocking."):""),n?"err":"okk")}async function oq(){let e=i("pfMatrix");if(e){if(!nY.ok||!ot){e.innerHTML='<div class="tiny">Per-platform export needs the backend — without it use the ⬇ buttons below, which download from the browser.</div>';return}try{let t=(await nV("export.platforms",null,{campaign_id:ot.id})).platforms||[];e.innerHTML=t.length?'<table class="tbl">\n      <tr><th>Platform</th><th>Creatives</th><th>Clean</th><th>Approved</th><th>Failed</th><th>Export</th></tr>\n      '.concat(t.map(e=>"<tr>\n        <td><b>".concat(r(H[e.platform]||e.platform),'</b></td>\n        <td class="mono">').concat(e.total,'</td>\n        <td class="mono">').concat(e.clean,'</td>\n        <td class="mono" style="color:').concat(+e.approved?"var(--ok)":"inherit",'">').concat(e.approved,'</td>\n        <td class="mono" style="color:').concat(+e.failed?"var(--bad)":"inherit",'">').concat(e.failed,'</td>\n        <td><button class="btn small primary" data-exp="').concat(r(e.platform),'">Export pack</button></td>\n      </tr>')).join(""),'\n      <tr><td colspan="5" class="tiny">Each pack is written on its own — its own folder, its own upload sheet, its own manifest and zip.</td>\n        <td><button class="btn small" data-exp="all">Export all, separately</button></td></tr>\n      </table>'):'<div class="tiny">Nothing composed yet.</div>',e.querySelectorAll("[data-exp]").forEach(e=>e.onclick=()=>oF(e.dataset.exp,"all",e))}catch(t){e.innerHTML='<div class="tiny">'.concat(r(t.message),"</div>")}}}function oP(){return et.plan.group||"platform"}function oz(){let e=i("ex_root");if(!e)return;let t=e.closest(".field")||e.parentElement;t&&(t.style.display="none")}async function oB(){let e=i("lgMatrix");if(e){if(!nY.ok||!ot){e.innerHTML='<div class="tiny">Per-language export needs the backend.</div>';return}try{let t=(await nV("export.languages",null,{campaign_id:ot.id})).languages||[];e.innerHTML=t.length?'<table class="tbl">\n      <tr><th>Language</th><th>Locale</th><th>Creatives</th><th>Clean</th><th>Approved</th><th>Failed</th><th>Export</th></tr>\n      '.concat(t.map(e=>"<tr>\n        <td><b>".concat(r(a4(e.lang)),'</b></td>\n        <td class="mono tiny">').concat(r(O(e.lang)),'</td>\n        <td class="mono">').concat(e.total,'</td>\n        <td class="mono">').concat(e.clean,'</td>\n        <td class="mono" style="color:').concat(+e.approved?"var(--ok)":"inherit",'">').concat(e.approved,'</td>\n        <td class="mono" style="color:').concat(+e.failed?"var(--bad)":"inherit",'">').concat(e.failed,'</td>\n        <td><button class="btn small primary" data-lgexp="').concat(r(e.lang),'">
1Export ').concat(r(a4(e.lang)),"</button></td>\n      </tr>")).join(""),'\n      <tr><td colspan="6" class="tiny">A language pack carries every platform for that language, foldered as the grouping above says, and named with its BCP-47 locale so a DAM or an ad-platform bulk upload can key off it.</td>\n        <td><button class="btn small" data-lgexp="all">Export every language, separately</button></td></tr>\n      </table>'):'<div class="tiny">Nothing composed yet.</div>',e.querySelectorAll("[data-lgexp]").forEach(e=>e.onclick=()=>oF("all",e.dataset.lgexp,e))}catch(t){e.innerHTML='<div class="tiny">'.concat(r(t.message),"</div>")}}}async function oF(e,t,a){if(!nY.ok||!ot)return ob("Export needs you to be signed in",!0);e7("exStatus"),eV(a,!0,"Exporting…");try{let a=await nV("export.run",{campaign_id:ot.id,platform:e,lang:t||"all",group:oP(),scope:et.plan.scope||"approved",zip:!0}),n=Object.entries(a.counts_by_platform||{}).map(e=>{let[t,a]=e;return"".concat(H[t]||t,": ").concat(a)}).join(" \xb7 "),o=["all"===e?"All platforms":H[e]||e,t&&"all"!==t?a4(t):"all"===t&&a.by_language?"every language, separately":"all languages"].filter(Boolean).join(" \xb7 "),i=Object.entries(a.counts_by_language||{}).map(e=>{let[t,a]=e;return"".concat(a4(t),": ").concat(a)}).join(" \xb7 ");if(a.export_id&&a.has_zip)el("exStatus",'<span class="spin"></span> Downloading zip…',"run"),await n2(a.export_id,a.zip_name||"campaign-export.zip");else throw Error("The zip could not be built. Try Export pack again.");el("exStatus","<b>".concat(r(o),"</b> exported —\n      ").concat(a.creatives," creatives, ").concat(a.files," files, grouped by ").concat(r(a.group||oP()),". The zip is downloading.\n      ").concat(n?'<div class="tiny" style="margin-top:4px">'.concat(r(n),"</div>"):"","\n      ").concat(i?'<div class="tiny" style="margin-top:2px">'.concat(r(i),"</div>"):""),"okk"),await oU()}catch(e){el("exStatus","Export failed: "+r(e.message),"err")}eV(a,!1)}async function oU(){let e=i("exHistory");if(e&&nY.ok&&ot)try{let t=(await nV("export.list",null,{campaign_id:ot.id})).exports||[];e.innerHTML=t.length?'<table class="tbl">\n      <tr><th>When</th><th>Platform</th><th>Language</th><th>Scope</th><th>Files</th><th>Zip</th></tr>\n      '.concat(t.map(e=>'<tr><td class="tiny mono">'.concat(r(String(e.created_at||"").slice(0,16)),"</td>\n        <td>").concat(r(H[e.platform]||e.platform),'</td>\n        <td class="mono tiny">').concat(r(e.lang&&"all"!==e.lang?a4(e.lang):"all"),'</td><td class="mono">').concat(r(e.scope),'</td>\n        <td class="mono">').concat(e.files||0,"</td>\n        <td>").concat(e.has_zip?'<button type="button" class="btn small" data-exdl="'.concat(r(e.id),'" data-exname="').concat(r(e.zip_name||"campaign-export.zip"),'">⬇ Download zip</button>'):"—","</td></tr>")).join(""),"</table>"):'<div class="tiny">No exports yet.</div>',e.querySelectorAll("[data-exdl]").forEach(e=>e.onclick=async()=>{try{eV(e,!0,"Downloading…"),await n2(e.dataset.exdl,e.dataset.exname)}catch(e){el("exStatus","Download failed: "+r(e.message),"err")}finally{eV(e,!1)}})}catch(e){}}function oD(e){return(e.hashtags||[]).join(" ")}function oH(e,t,a){if(!t)return[];let n=(null==a?void 0:a.url)||"",o=(e,t,a,n)=>Object.assign({label:e,value:String(null!=t?t:""),hint:a||""},n||{}),i=(t.short_headlines||[]).length?t.short_headlines:[t.short_headline||t.headline||""],r=(t.descriptions||[]).length?t.descriptions:[t.description||""];switch(e){case"meta":return[o("Primary text",t.fb_primary_text||t.primary_text,"the feed body — Meta shows ~125 characters before “See more”",{max:125}),o("Headline",nC(t,"headline"),"truncates around 27 characters on mobile",{max:27}),o("Description",r[0],"the link description under the headline",{max:27}),o("Call to action",t.cta_label,"must be picked from Meta's own CTA list"),o("Website URL",n,"UTM-tagged, ready for the URL field",{mono:!0}),o("Display link",(et.product.spec.destination_url||"").replace(/^https?:\/\//,"").split("/")[0],"the clean domain Meta shows",{mono:!0}),o("Alt text",t.alt_text,"a real Meta field, and an accessibility requirement",{max:200})];case"instagram":return[o("Caption",function(e){let t=(e.ig_caption||e.fb_primary_text||"").trim(),a=oD(e);return a?t+"\n\n"+a:t}(t),"hook, substance, soft CTA — with the hashtag block appended",{max:2200,big:!0}),o("Hashtags only",oD(t),"".concat((t.hashtags||[]).length," tags — paste as a first comment if you keep the caption clean"),{tags:!0}),o("Headline",nC(t,"headline"),"used where Instagram shows one"),o("Call to action",t.cta_label,""),o("Website URL",n,"",{mono:!0}),o("Alt text",t.alt_text,"Instagram's own alt-text field",{max:200})];case"google_display":return[o("Short headlines",i.join("\n"),"".concat(i.length," of 5 — Google rotates all of them, ≤30 characters each"),{list:!0,max:30}),o("Long headline",t.long_headline,"≤90 characters, used where there is room",{max:90}),o("Descriptions",r.join("\n"),"".concat(r.length," of 5 — ≤90 characters each"),{list:!0,max:90}),o("Business name",et.brand.brand_name,"required on responsive display ads"),o("Call to action",t.cta_label,""),o("Final URL",n,"",{mono:!0})];case"youtube":return[o("Headline",nC(t,"headline"),""),o("Description",t.yt_description||r[0],"",{max:150}),o("Call to action",t.cta_label,""),o("Final URL",n,"",{mono:!0})];default:return[o("Banner line",nC(t,"banner_line"),"the line printed into the creative"),o("Call to action",t.cta_label,""),o("Click-through URL",n,"",{mono:!0}),o("Alt text",t.alt_text,"",{max:200})]}}function oG(e,t,a){let n=oH(e,t,a),o="".concat((H[e]||e).toUpperCase()," — ").concat(et.brand.brand_name||"brand"," \xb7 ").concat(et.camp.campaign||"campaign");return[o,"=".repeat(o.length),""].concat(n.map(e=>"".concat(e.label.toUpperCase()).concat(e.hint?"  ("+e.hint+")":"","\n").concat(e.value||"—","\n"))).join("\n")}function oW(){let e=i("deployPack");if(!e)return;let t=i("dpPlatform"),a=i("dpLang"),n=i("dpDevice"),o=[...new Set(a0().map(e=>e.platform))];if(t&&t.options.length!==o.length){let e=t.value;t.innerHTML=o.map(e=>'<option value="'.concat(e,'">').concat(r(H[e]||e),"</option>")).join(""),o.includes(e)&&(t.value=e)}if(a){let e=a.value,t="function"==typeof ii?ii():"",n=t?[t]:et.plan.langs;a.innerHTML=n.map(e=>'<option value="'.concat(e,'">').concat(r(a4(e)),"</option>")).join(""),a.value=n.includes(e)?e:n[0],a.disabled=n.length<2}let l=t5();if(n){let e=n.value,t=[];
1l.forEach(e=>{for(let a=1;a<=et.plan.variations;a++)t.push(["".concat(e,"|").concat(a),"".concat(ee[e].label," \xb7 v").concat(a)])}),n.innerHTML=t.map(e=>{let[t,a]=e;return'<option value="'.concat(t,'">').concat(r(a),"</option>")}).join(""),t.some(t=>t[0]===e)&&(n.value=e)}let s=(null==t?void 0:t.value)||o[0],c=(null==a?void 0:a.value)||et.plan.langs[0],[d,p]=((null==n?void 0:n.value)||"").split("|"),u=et.copy["".concat(d,"|").concat(p||1,"|").concat(c)];if(!u){e.innerHTML='<div class="tiny">No copy for that combination yet — run the copy engine in step 5.</div>';return}let h=et.creatives.find(e=>{var t;return e.deviceKey===d&&String(e.vn)===String(p||1)&&e.lang===c&&(null==(t=G[e.fmtId])?void 0:t.platform)===s}),m=h?h.dest_url||im(h):"",f=oH(s,u,{url:m});e.innerHTML=f.map((e,t)=>{let a=q(e.value),n=e.max&&a>e.max,o=e.tags?(u.hashtags||[]).map(e=>'<span class="tag">'.concat(r(e),"</span>")).join(""):'<div class="dpv '.concat(e.mono?"mono":"",'">').concat(r(e.value||"—"),"</div>");return'<div class="dp '.concat(n?"over":"",'">\n      <div class="dph"><b>').concat(r(e.label),"</b>\n        ").concat(e.max?'<span class="ct" style="'.concat(n?"color:var(--warn)":"",'">').concat(a,"/").concat(e.max,"</span>"):'<span class="ct"></span>','\n        <button class="cpy" data-cp="').concat(t,'">⧉ copy</button></div>\n      ').concat(o,"\n      ").concat(e.hint?'<div class="tiny" style="margin-top:5px">'.concat(r(e.hint),"</div>"):"","\n      ").concat(n?'<div class="tiny" style="color:var(--warn);margin-top:3px">Over the platform limit — it will be cut off. Edit it in the copy table.</div>':"","\n    </div>")}).join(""),e.querySelectorAll("[data-cp]").forEach(e=>e.onclick=async()=>{let t=f[+e.dataset.cp];try{await navigator.clipboard.writeText(t.value),e.textContent="✓ copied",setTimeout(()=>e.textContent="⧉ copy",1400)}catch(e){el("dpStatus","Clipboard blocked — select the text and copy it by hand.","err")}}),i("btnDpCopyAll").onclick=async()=>{try{await navigator.clipboard.writeText(oG(s,u,{url:m})),el("dpStatus","The whole ".concat(r(H[s]||s)," pack is on your clipboard — every field, labelled."),"okk")}catch(e){el("dpStatus","Clipboard blocked by the browser.","err")}}}function oK(){let e=i("copyTable");if(!e)return;let t=t5(),a="function"==typeof ii?ii():"",n=a?et.plan.langs.filter(e=>e===a):et.plan.langs;if(!t.length){e.innerHTML='<div class="tiny">No devices selected.</div>';return}if(!n.length){e.innerHTML='<div class="tiny">No language selected — pick at least one in step 3.</div>';return}let o=[];t.forEach(e=>{for(let t=1;t<=et.plan.variations;t++)o.push([e,t])});let l=0,s=0,c=0,d=0,p=o.length*n.length;e.innerHTML='<table class="ctbl">\n    <tr><th>Device</th>'.concat(n.map(e=>"<th>".concat(r(a4(e)),"</th>")).join(""),"</tr>\n    ").concat(o.map(e=>{let[t,a]=e;return'<tr><td class="dev">'.concat(r(ee[t].emoji)," ").concat(r(ee[t].label)).concat(et.plan.variations>1?" \xb7 v"+a:"","</td>\n      ").concat(n.map(e=>"<td>".concat(((e,t,a)=>{let n="".concat(e,"|").concat(t,"|").concat(a),o=et.copy[n];if(!o)return'<div class="cell none" data-ce="'.concat(n,'">not written yet<span>click to write or edit by hand</span></div>');if(o._failed)return s++,'<div class="cell bad" data-ce="'.concat(n,'">\n        <b style="color:var(--bad)">generation failed</b>\n        <span>').concat(r((o._err||"unknown error").slice(0,110)),'</span>\n        <span><button class="btn small ghost" data-retry="').concat(n,'">↻ Retry this cell').concat(r(eX(eQ())),"</button></span></div>");l++,o._edited&&d++;let i=(o._issues||[]).filter(e=>"fail"===e.level).length;i&&c++;let p=nC(o,"headline")||"—",u=[];return(o.short_headlines||[]).length&&u.push("".concat(o.short_headlines.length," short")),(o.hashtags||[]).length&&u.push("".concat(o.hashtags.length," tags")),o.ig_caption&&u.push("IG caption"),o.cta_label&&u.push(o.cta_label),'<div class="cell '.concat(i?"warn":""," ").concat(o._edited?"edited":"",'" data-ce="').concat(n,'">\n      <b>').concat(r(p),"</b>
1\n      <span>").concat(u.join(" \xb7 ")).concat(i?' \xb7 <b style="color:var(--bad)">'.concat(i," blocking</b>"):"").concat(o._edited?" \xb7 edited":"","</span></div>")})(t,a,e),"</td>")).join(""),"</tr>")}).join(""),"</table>"),e.querySelectorAll("[data-retry]").forEach(e=>e.onclick=async t=>{t.stopPropagation();let a=e.dataset.retry,n=await e8("copy:"+a,e,"Writing…",async()=>(await ni([a]),await oj(),!0));n&&n.skipped&&el("s5status","That cell is already being written.","warn")}),e.querySelectorAll("[data-ce]").forEach(e=>e.onclick=()=>{let t=et.copy[e.dataset.ce];t&&t._failed||oN(e.dataset.ce)});let u=i("copyTblChip");if(u){let e=["".concat(l,"/").concat(p," written")];c&&e.push("".concat(c," flagged")),s&&e.push("".concat(s," failed")),d&&e.push("".concat(d," edited")),u.textContent=e.join(" \xb7 "),u.className="chip "+(s?"bad":c?"warn":l===p&&p?"ok":l?"warn":"bad")}let h=i("btnCopyRetry");h&&(h.disabled=!(s||c||l<p),h.dataset.acsRetryLabel="↻ Retry ".concat(s+c+(p-l-s)," unfinished")),e2(),a&&u&&(u.textContent+=" \xb7 ".concat(a4(a)," only"))}let oJ={logo_missing:["No logo on file","Step 1 → ✦ Fetch logo & colours from the site, or upload one. Then Redraw — no plate is regenerated, so it costs nothing."],logo_small:["The logo zone is too small for this file","Upload a wider, simpler wordmark (SVG or transparent PNG), or lower the minimum logo height in step 1."],logo_lozenge:["The logo is an opaque image","It was set on a light plate so it stays legible. A transparent PNG or SVG removes the plate."],fit_fail:["The text will not fit at a legible size","Shorten that field in the copy table — the limit is per format, and the shortest format governs."],legibility:["Type is sitting at this placement's legibility floor","Shorten the line, or drop this format if the message needs more room."],safe_area:["A zone overlaps the platform's reserved UI area","This is a template problem, not a copy problem — tell me and I'll move the zone."],weight:["Over the format's file-size cap","The exporter already walked the quality ladder. A busier plate costs more bytes — try a calmer one."],claim:["Copy states something the lockbox cannot source","Edit the copy, or add the real evidence in step 1. This one is deliberate: it is what stops invented proof reaching a client's paid media."],number:["A number with no registered source","Check it, then either add it to the spec's features/proof points or take it out."],banned:["A banned word from the brand voice","Edit the copy, or remove the word from the brand's banned list."],cta_enum:["The CTA is not one the platform offers","Pick one from the platform's list in the copy editor."],emoji_in_art:["An emoji was written into text that gets printed into the image","Already stripped. Emoji belong in the caption fields, which are pasted, not typeset."],copy_missing:["No copy for this device and language","Run the copy engine in step 5."],font_missing:["A script font did not load","Check the network, then Redraw. Without it that language renders as boxes."],plate_missing:["The plate image is missing","Regenerate that plate in step 4."],plate_review:["The plate itself was flagged by vision QC","Open step 4, read the QC note, and regenerate that plate if it is wrong."],plate_fail:["The plate failed vision QC","Regenerate it in step 4 — this one does cost a generation."],draw:["The compositor threw an error","Tell me the message and I'll fix it."]};function oY(e){return e.checks.length?e.checks.map(e=>{let t=e.k.replace(/^len_/,"len_").replace(/^trunc_.*/,"trunc").replace(/^plate_(review|fail)$/,"plate_$1"),a=oJ[e.k]||oJ[t]||(e.k.startsWith("len_")||e.k.startsWith("trunc")?["Over the platform's character limit","Shorten it in the copy table — the deploy pack shows the exact count."]:["Flagged",""]);return'<div class="fixrow"><span class="lv '.concat(e.level,'">').concat(r(e.level),'</span>\n      <span class="fx"><b>').concat(r(a[0]),"</b><span>").concat(r(e.msg)).concat(a[1]?" — "+a[1]:"","</span></span></div>")}).join(""):'<div class="tiny" style="color:var(--ok)">Clean — nothing flagged.</div>'}async function oV(e,t){let a={bytes:e.bytes||0,fails:e.checks.filter(e=>"fail"===e.level).length,warns:e.checks.filter(e=>"warn"===e.level).length};if("approved"===e.state)return t||el("s6status","That creative is approved and locked. Un-approve it first if you want it re-drawn.","warn"),{locked:!0};await iu(e);let n={bytes:e.bytes||0,fails:e.checks.filter(e=>"fail"===e.level).length,warns:e.checks.filter(e=>"warn"===e.level).length},o=a.fails-n.fails+(a.warns-n.warns);return t||el("s6status",0===n.fails&&0===n.warns?"Re-drawn clean — ".concat((n.bytes/1024).toFixed(0),"KB. Nothing flagged."):o>0?"Re-drawn: ".concat(o," issue").concat(1===o?"":"s"," cleared, ").
1concat(n.fails," blocking and ").concat(n.warns," warning").concat(1===n.warns?"":"s"," left."):"Re-drawn, but <b>nothing changed</b> — the cause is still there. ".concat(n.fails+n.warns," issue").concat(n.fails+n.warns===1?"":"s"," remain; the panel on the card names the fix for each."),n.fails?"err":n.warns?"warn":"okk"),{cleared:o,after:n}}async function o$(){if(e6("redrawAll"))return el("s6status","Already re-drawing.","warn");let e=et.creatives.filter(e=>"approved"!==e.state&&"PASS"!==e.verdict);if(!e.length)return el("s6status","Nothing flagged to re-draw.","okk");e5.add("redrawAll"),e7("s6status"),eV("btnRedrawFlagged",!0,"Re-drawing ".concat(e.length,"…")),await ti(tr());let t=0,a=0;for(let n of e){let e=await oV(n,!0);e.after&&0===e.after.fails&&0===e.after.warns?t++:a++}await oA(),ip(),eV("btnRedrawFlagged",!1),e5.delete("redrawAll"),el("s6status",t?"Re-drew ".concat(e.length,": ").concat(t," came back clean, ").concat(a," still flagged."):"Re-drew ".concat(e.length," and none came back clean — the underlying cause has not been fixed yet. Open a card and read the fix panel."),a?"warn":"okk")}let oX={open:!1,cr:null,fmt:null,plate:null,copy:null,zones:[],sel:null,scale:1,snap:!0,guides:!0,before:null,dirty:!1,t:null,drag:null};function oQ(e){let t=et.creatives.find(t=>t.id===e);if(t){if("approved"===t.state)return el("s6status","That creative is approved and locked. Un-approve it to edit.","warn");oX.cr=t,oX.fmt=G[t.fmtId],oX.plate=nG(t.deviceKey,t.vn,oX.fmt),oX.copy=et.copy["".concat(t.deviceKey,"|").concat(t.vn,"|").concat(t.lang)]||{},oX.sel=null,oX.dirty=!1,oX.before=JSON.parse(JSON.stringify(nI()["cr:"+t.id]||null)),oX.open=!0,i("edWrap").classList.add("show"),document.body.style.overflow="hidden",i("edTitle").textContent="".concat(ee[t.deviceKey].label," \xb7 ").concat(a4(t.lang)),i("edSub").textContent="".concat(oX.fmt.placement," \xb7 ").concat(oX.fmt.w,"\xd7").concat(oX.fmt.h),o2(!0)}}function oZ(e){if(e){let e=nI();oX.before?e["cr:"+oX.cr.id]=JSON.parse(JSON.stringify(oX.before)):delete e["cr:"+oX.cr.id]}oX.open=!1,oX.sel=null,i("edWrap").classList.remove("show"),document.body.style.overflow=""}function o0(){let e=nI(),t="cr:"+oX.cr.id;return e[t]||(e[t]={}),e[t].zones||(e[t].zones={}),e[t].text||(e[t].text={}),e[t]}function o1(e){let t=o0();return t.zones[e]||(t.zones[e]={}
1),t.zones[e]}async function o2(e){if(!oX.open)return;clearTimeout(oX.t);let t=async()=>{let e;if(!oX.open)return;await ti(tr());try{e=await nz(oX.fmt,oX.plate,oX.copy,oX.cr.lang,nO(oX.cr.lang,oX.cr))}catch(e){el("edStatus","Could not draw: "+r(e.message),"err");return}oX.zones=e.editable||[];let t=i("edStage").getBoundingClientRect();oX.scale=Math.min((t.width-40)/oX.fmt.w,(t.height-40)/oX.fmt.h,1);let a=Math.round(oX.fmt.w*oX.scale),n=Math.round(oX.fmt.h*oX.scale),o=i("edFrame");o.style.width=a+"px",o.style.height=n+"px";let l=i("edCanvas"),s=Math.min(2,window.devicePixelRatio||1);l.width=Math.round(a*s),l.height=Math.round(n*s);let c=l.getContext("2d");c.imageSmoothingQuality="high",c.clearRect(0,0,l.width,l.height),c.drawImage(e.canvas,0,0,l.width,l.height),oX.drag?o8():o4(),function(e){let t=(e||[]).filter(e=>"info"!==e.level);i("edFlags").innerHTML=t.length?t.slice(0,3).map(e=>'<span class="qc '.concat("fail"===e.level?"f":"w",'" title="').concat(r(e.msg),'">').concat(r(e.k),"</span>")).join(""):'<span class="qc p">clean</span>'}(e.checks),i("edZoom").textContent=Math.round(100*oX.scale)+"%"};e?await t():oX.t=setTimeout(t,90)}function o4(){let e=i("edFrame");e.querySelectorAll(".edz").forEach(e=>e.remove()),oX.zones.forEach(t=>{let a=document.createElement("div");a.className="edz"+(oX.sel===t.key?" sel":"")+(t.drawn?"":" ghost"),a.dataset.k=t.key,a.style.left=100*t.x+"%",a.style.top=100*t.y+"%",a.style.width=100*t.w+"%",a.style.height=100*t.h+"%",a.innerHTML='<span class="edz-tag">'.concat(r(t.label),"</span>")+(oX.sel===t.key?["nw","ne","sw","se","n","s","w","e"].map(e=>'<i class="edh '.concat(e,'" data-h="').concat(e,'"></i>')).join(""):""),e.appendChild(a)}),function(){let e=i("edInsp"),t=oX.zones.find(e=>e.key===oX.sel);if(!t){e.innerHTML='<div class="ed-empty">\n      <b>Click any element</b>
1\n      <span>Drag it to move. Pull a corner to resize. Retype the words. Arrow keys nudge; hold shift for bigger steps.</span></div>';return}let a="text"===t.role||"cta"===t.role,n=(o0().text||{})[t.field],o=a?"string"==typeof n&&n.trim()?n:nC(oX.copy,t.field)||"":"";if(e.innerHTML='\n    <div class="ed-head"><b>'.concat(r(t.label),'</b>\n      <button class="btn small ghost" id="edZReset">↺ Reset</button></div>\n    ').concat(a?'<label class="ed-l">Words</label>\n      <textarea id="edText" class="ed-ta" rows="'.concat("cta"===t.role?1:3,'">').concat(r(o),'</textarea>\n      <div class="tiny" id="edLen"></div>'):'\n      <div class="tiny" style="margin-bottom:8px">The logo is drawn from the brand\'s own file. Move and size it here; it is never redrawn or recoloured beyond the knockout.</div>',"\n    ").concat(a?'<label class="ed-l">Ink <span class="hint" id="edInkNow"></span></label>\n      <div class="ed-ink" id="edInkSw">\n        <button class="ink-sw auto '.concat(t.ink?"":"on",'" data-ink="" title="Automatic — the compositor samples the plate and sets the type light or dark to clear the contrast target">Auto</button>\n        ').concat(o6().map(e=>'<button class="ink-sw '.concat(t.ink&&t.ink.toLowerCase()===e.hex.toLowerCase()?"on":"",'"\n            data-ink="').concat(r(e.hex),'" title="').concat(r(e.label)," \xb7 ").concat(r(e.hex),'"\n            style="background:').concat(r(e.hex),'"></button>')).join(""),'\n      </div>\n      <div class="ed-hexrow">\n        <input type="color" id="edInkPick" class="ed-pick" value="').concat(r(t.ink||t.contrast&&t.contrast.hex||"#ffffff"),'" title="Pick any colour">\n        <input type="text" id="edInkHex" class="ed-hex" maxlength="7" spellcheck="false"\n               placeholder="auto" value="').concat(r(t.ink||""),'">\n      </div>\n      <div class="tiny ed-contrast" id="edContrast"></div>\n      <label class="ed-l">Alignment</label>\n      <div class="seg ed-seg" id="edAlign">\n        ').concat(["start","center","end"].map(e=>'<span class="opt '.concat(t.align===e?"sel":"",'" data-a="').concat(e,'">').concat("start"===e?"⇤ Start":"center"===e?"⇔ Centre":"⇥ End","</span>")).join(""),'\n      </div>\n      <label class="ed-l">Type size <span class="mono" id="edSzV">').concat(Math.round(100*t.size),'%</span></label>\n      <input type="range" id="edSz" min="60" max="160" step="2" value="').concat(Math.round(100*t.size),'">\n      <label class="ed-l">Line height <span class="mono" id="edLdV">').concat(Math.round(100*t.leading),'%</span></label>\n      <input type="range" id="edLd" min="80" max="180" step="2" value="').concat(Math.round(100*t.leading),'">'):"",'\n    <label class="ed-l">Position &amp; size</label>\n    <div class="ed-grid">\n      ').concat([["x","X"],["y","Y"],["w","W"],["h","H"]].map(e=>{let[a,n]=e;return"<div><span>".concat(n,'</span><input type="number" class="ed-num" data-n="').concat(a,'" step="0.5"\n           value="').concat((100*t[a]).toFixed(1),'"></div>')}).join(""),'\n    </div>\n    <div class="tiny" style="margin-top:6px">Percentages of the frame. ').concat(r(oX.fmt.w),"\xd7").concat(r(oX.fmt.h),"px.</div>"),a){var l;let e=i("edText"),a=null==(l=(oX.fmt.text_fields||[]).find(e=>e.name===t.field))?void 0:l.max_chars,n=()=>{let t=q(e.value);i("edLen").innerHTML=a?"".concat(t,"/").concat(a," characters").concat(t>a?' <b style="color:var(--warn)">— will truncate on '.concat(r(oX.fmt.platform),"</b>"):""):"".concat(t," characters")};n(),e.oninput=()=>{n(),o0().text[t.field]=e.value,oX.dirty=!0,o2()},document.querySelectorAll("#edAlign .opt").forEach(e=>e.onclick=()=>{o1(t.key).align=e.dataset.a,oX.dirty=!0,o2()}),i("edSz").oninput=e=>{i("edSzV").textContent=e.target.value+"%",o1(t.key).size=e.target.value/100,oX.dirty=!0,o2()},i("edLd").oninput=e=>{i("edLdV").textContent=e.target.value+"%",o1(t.key).leading=e.target.value/100,oX.dirty=!0,o2()};let o=e=>{let a=String(e||"").trim(),n=o1(t.key);if(a)if(!/^#[0-9a-f]{6}$/i.test(a))return!1;else n.ink=a.toLowerCase();else delete n.ink;return oX.dirty=!0,o2(),!0};
1document.querySelectorAll("#edInkSw .ink-sw").forEach(e=>e.onclick=()=>o(e.dataset.ink)),i("edInkPick").oninput=e=>o(e.target.value),i("edInkHex").oninput=e=>{let t=e.target.value.trim(),a=""===t||/^#[0-9a-f]{6}$/i.test(t);e.target.classList.toggle("bad",!a),a&&o(t)},function(e){let t=i("edContrast"),a=i("edInkNow");if(!t)return;let n=e.contrast;if(a&&(a.textContent=e.ink?e.ink:n&&n.hex?"auto \xb7 "+n.hex:"auto"),!n){t.innerHTML="";return}let o=n.pass;t.innerHTML='<span class="chip '.concat(o?"ok":"bad",'">').concat(n.ratio.toFixed(1),":1</span>\n    against a ").concat(n.target,":1 requirement, measured on the ").concat(Math.round(100*n.ground),"% luminance behind the words.\n    ").concat(o?"":'<b style="color:var(--warn)">Below the standard — this will flag on the review card.</b>',"\n    ").concat(e.ink?'<br>Chosen by hand. <button class="btn small ghost" id="edInkAuto">Back to automatic</button>':"");let r=i("edInkAuto");r&&(r.onclick=()=>{let t=o1(e.key);delete t.ink,oX.dirty=!0,o2()})}(t)}e.querySelectorAll(".ed-num").forEach(e=>e.onchange=()=>{let a=Math.max(-20,Math.min(120,+e.value))/100;o1(t.key)[e.dataset.n]=a,oX.dirty=!0,o2()}),i("edZReset").onclick=()=>{let e=o0();delete e.zones[t.key],t.field&&e.text&&delete e.text[t.field],oX.dirty=!0,o2(!0)}}()}function o5(e){oX.sel=e===oX.sel?null:e,o4()}function o6(){let e=et.brand.palette||{},t=[],a=new Set;return[["on_ink","Text on ink"],["on_paper","Text on paper"],["paper","Paper"],["ink","Ink"],["brand","Brand"],["brand_alt","Brand alt"],["accent","Accent"],["on_brand","Text on brand"]].forEach(n=>{let[o,i]=n,r=String(e[o]||"").trim();/^#[0-9a-f]{6}$/i.test(r)&&(a.has(r.toLowerCase())||(a.add(r.toLowerCase()),t.push({hex:r,label:i})))}),[["#ffffff","White"],["#000000","Black"]].forEach(e=>{let[n,o]=e;a.has(n)||(a.add(n),t.push({hex:n,label:o}))}),t}function o8(){let e=oX.zones.find(e=>e.key===oX.sel);e&&document.querySelectorAll("#edInsp .ed-num").forEach(t=>{document.activeElement!==t&&(t.value=(100*e[t.dataset.n]).toFixed(1))})}async function o3(){let e=oX.cr;await e8("edsave:"+e.id,i("edSave"),"Saving…",async()=>{let t=nI(),a="cr:"+e.id,n=t[a];return n&&(n.zones&&!Object.keys(n.zones).length&&delete n.zones,n.text&&(Object.keys(n.text).forEach(e=>{String(n.text[e]||"").trim()||delete n.text[e]}),Object.keys(n.text).length||delete n.text),Object.keys(n).length||delete t[a]),await ti(tr()),await iu(e),await oA(),await oc(),ip(),!0}),oZ(!1),el("s6status","".concat(r(ee[e.deviceKey].label)," \xb7 ").concat(r(a4(e.lang))," \xb7 ").concat(r(e.fmtId)," saved and re-drawn — free, no plate touched."),"okk")}let o7=null;function o9(){if(!o7)return;clearTimeout(o7),o7=null;let e=i("edCancel");e&&(e.textContent="Cancel",e.classList.remove("armed")),e7("edStatus")}function ie(){if(!oX.dirty)return oZ(!0);let e=i("edCancel");if(!o7){e.textContent="Discard changes?",e.classList.add("armed"),el("edStatus","Press again to throw away the edits on this creative.","warn"),o7=setTimeout(o9,9e3);return}o9(),e7("edStatus"),oZ(!0)}async function it(){delete nI()["cr:"+oX.cr.id],oX.dirty=!0,oX.sel=null,await o2(!0),el("edStatus","Back to the template layout for this creative.","okk")}async function ia(){let e=oX.cr,t=nI()["cr:"+e.id];if(!t)return el("edStatus","Nothing to apply — this creative is on the template layout.","warn");let a=et.creatives.filter(t=>t.lang===e.lang&&t.fmtId===e.fmtId&&t.id!==e.id&&"approved"!==t.state),n=et.creatives.filter(t=>t.lang===e.lang&&t.fmtId!==e.fmtId&&"approved"!==t.state);await e8("edapply:"+e.id,i("edApply"),"Applying…",async()=>{let e=nI();for(let o of(a.forEach(a=>{e["cr:"+a.id]=JSON.parse(JSON.stringify(t))}),t.zones&&n.forEach(a=>{let n={};Object.entries(t.zones).forEach(e=>{let[t,a]=e,o={};["align","size","leading"].forEach(e=>{void 0!==a[e]&&(o[e]=a[e])}),Object.keys(o).length&&(n[t]=o)}),Object.keys(n).length&&(e["cr:"+a.id]=Object.assign({},e["cr:"+a.id]||{},{zones:n}))}),await ti(tr()),a.concat(n)))await iu(o);return await oA(),await oc(),ip(),!0}),el("edStatus","Applied to ".concat(a.length," more ").concat(r(a4(e.lang))," creative").concat(1===a.length?"":"s"," in this format")+(n.length?", and the type settings only to ".concat(n.length," in other formats — a position from a ").concat(oX.fmt.w,"\xd7").concat(oX.fmt.h," frame would land in the wrong place on them."):"."),"okk")}function io(){let e=i("revFmt");if(!e)return;let t=e.value;e.innerHTML='<option value="">All formats</option>'+et.plan.formats.map(e=>{var t;return'<option value="'.concat(e,'">').concat(r((null==(t=G[e])?void 0:t.placement)||e),"</option>")}).join(""),t&&(e.value=t)}function ii(){var e;let t=(null==(e=i("langScope"))?void 0:e.value)||"";return et.plan.langs.includes(t)?t:""}function ir(e){var t;let a=et.revFilter||"all",n=(null==(t=i("revFmt"))?void 0:t.value)||"",o=ii();return(!o||e.lang===o)&&(!n||e.fmtId===n)&&("flag"===a?"PASS"!==e.verdict:"ok"===a?"PASS"===e.verdict:"locked"!==a||"approved"===e.state)}function il(){return et.plan.langs.map(e=>{let t=et.creatives.filter(t=>t.lang===e),a=t5().flatMap(t=>{let a=[];for(let n=1;n<=et.plan.variations;n++)a.push("".concat(t,"|").concat(n,"|").concat(e));return a}),n=a.filter(e=>et.copy[e]&&!et.copy[e]._failed).length;return{lang:e,label:a4(e),locale:O(e),total:t.length,pass:t.filter(e=>"PASS"===e.verdict).length,review:t.filter(e=>"REVIEW"===e.verdict).length,fail:t.filter(e=>"FAIL"===e.verdict).length,approved:t.filter(e=>"approved"===e.state).length,rejected:t.filter(e=>"rejected"===e.state).length,copyWritten:n,copyTotal:a.length,typo:nP(nO(e))?"":"custom"}})}function is(){let e=i("langMatrix");if(!e)return;let t=il(),a=ii();if(!t.length){e.innerHTML='<div class="tiny">No languages in the plan.</div>';return}e.innerHTML='<table class="tbl">\n    <tr><th>Language</th><th>Locale</th><th>Copy</th><th>Creatives</th><th>Clean</th><th>Flagged</th>\n        <th>Blocked</th><th>Approved</th><th>Ready</th><th></th></tr>\n    '.concat(t.map(e=>{let t=e.total?Math.round(e.approved/e.total*100):0;
1return'<tr data-lg="'.concat(e.lang,'" class="').concat(a===e.lang?"sel":"",'">\n        <td><b>').concat(r(e.label),"</b>").concat(e.typo?' <span class="chip" style="padding:1px 5px">type '.concat(e.typo,"</span>"):"",'</td>\n        <td class="mono tiny">').concat(r(e.locale),'</td>\n        <td class="mono">').concat(e.copyWritten,"/").concat(e.copyTotal,'</td>\n        <td class="mono">').concat(e.total,'</td>\n        <td class="mono" style="color:var(--ok)">').concat(e.pass,'</td>\n        <td class="mono" style="color:var(--warn)">').concat(e.review,'</td>\n        <td class="mono" style="color:var(--bad)">').concat(e.fail,'</td>\n        <td class="mono">').concat(e.approved,'</td>\n        <td><span class="lgbar"><i style="width:').concat(t,'%"></i></span> <span class="tiny mono">').concat(t,'%</span></td>\n        <td><button class="btn small ghost" data-lgap="').concat(e.lang,'">✓ Approve clean</button></td></tr>')}).join(""),"</table>"),e.querySelectorAll("[data-lg]").forEach(e=>e.onclick=t=>{if(t.target.closest("[data-lgap]"))return;let a=i("langScope");a&&(a.value=a.value===e.dataset.lg?"":e.dataset.lg,id())}),e.querySelectorAll("[data-lgap]").forEach(e=>e.onclick=async t=>{t.stopPropagation();let a=e.dataset.lgap;await e8("lgap:"+a,e,"Approving…",async()=>{let e=[],t=0;return et.creatives.forEach(n=>{n.lang===a&&"PASS"===n.verdict&&"rejected"!==n.state&&"approved"!==n.state&&(n.state="approved",t++,e.push({id:n.id,state:"approved",comment:n.comment||""}))}),ip(),e.length&&await oC(e,"approved"),el("s6status","".concat(t," clean ").concat(a4(a)," creatives approved and locked. ").concat(a4(a)," can be exported on its own."),"okk"),!0})})}function ic(){let e=i("langScope");if(!e)return;let t=e.value;e.innerHTML='<option value="">All languages</option>'+et.plan.langs.map(e=>'<option value="'.concat(e,'">').concat(r(a4(e)),"</option>")).join(""),et.plan.langs.includes(t)&&(e.value=t);let a=i("langScopeWrap");a&&(a.style.display=et.plan.langs.length>1?"":"none",a.classList.toggle("narrowed",!!e.value))}function id(){oK(),ip(),oW();let e=i("lgMatrix");e&&"none"!==e.style.display&&oB()}function ip(){let e=i("board");if(!e)return;let t=et.creatives.filter(ir),a=et.creatives.filter(e=>"PASS"===e.verdict).length,n=et.creatives.filter(e=>"REVIEW"===e.verdict).length,o=et.creatives.filter(e=>"FAIL"===e.verdict).length,l=et.creatives.filter(e=>"approved"===e.state).length;i("qcSummary").innerHTML='\n    <div class="sm"><b>'.concat(et.creatives.length,'</b><span>creatives</span></div>\n    <div class="sm" style="color:var(--ok)"><b>').concat(a,'</b><span>clean</span></div>\n    <div class="sm" style="color:var(--warn)"><b>').concat(n,'</b><span>review</span></div>\n    <div class="sm" style="color:var(--bad)"><b>').concat(o,'</b><span>fail</span></div>\n    <div class="sm acc"><b>').concat(l,"</b><span>approved &amp; locked</span></div>"),i("revChip").textContent="".concat(et.creatives.length," creatives \xb7 ").concat(l," approved"),i("exChip").textContent="".concat(l," approved \xb7 ").concat(a," clean"),ic(),is();let s=i("revCount");s&&(s.textContent=t.length===et.creatives.length?"".concat(t.length," shown"):"".concat(t.length," of ").concat(et.creatives.length," shown")),e.innerHTML=t.map((e,t)=>{let a=G[e.fmtId],n=et.copy["".concat(e.deviceKey,"|").concat(e.vn,"|").concat(e.lang)]||{},o=e.verdict,i=e.state,l=e.checks.slice(0,4).map(e=>'<span class="qc '.concat("fail"===e.level?"f":"w",'" title="').concat(r(e.msg),'">').concat(r(e.k),"</span>")).join("")||'<span class="qc p">clean</span>';return'<div class="bd '.concat("approved"===i?"locked":"rejected"===i?"rej":"PASS"===o?"ok":"",'">\n      <div class="bd-thumb" data-z="').concat(e.id,'">').concat(e.dataUrl?'<img src="'.concat(e.dataUrl,'" alt="">'):'<span class="tiny">not composed</span>','</div>\n      <div class="bd-info">\n        <div class="bd-t">').concat(r(ee[e.deviceKey].label)," \xb7 v").concat(e.vn," \xb7 ").concat(r(a4(e.lang)),'</div>\n        <div class="bd-m">').concat(r((null==a?void 0:a.placement)||e.fmtId)," \xb7 ").concat(null==a?void 0:a.w,"\xd7").concat(null==a?void 0:a.h," \xb7 ").concat(e.bytes?(e.bytes/1024).toFixed(0)+"KB q"+e.quality:"—",'</div>\n        <div class="qcs">').concat(l).concat("approved"===i?'<span class="qc p">\uD83D\uDD12 approved</span>':"","</div>\n        ").concat(e.checks.length?'<details style="margin-bottom:7px"><summary class="tiny" style="cursor:pointer;color:var(--acc2)">\n          '.concat(e.checks.filter(e=>"fail"===e.level).length?"Why it's blocked":"Why it's flagged",' — and how to fix it</summary>\n          <div style="margin-top:7px">').concat(oY(e),"</div></details>"):"",'\n        <div class="copybox"><span class="cl">headline</span><div class="cv">').concat(r(nC(n,"headline")||"—"),"</div></div>\n        ").concat(nC(n,"primary_text")?'<div class="copybox"><span class="cl">primary text</span><div class="cv">'.concat(r(nC(n,"primary_text")),"</div></div>"):"","\n        ").concat(n.ig_caption?'<div class="copybox"><span class="cl">ig caption</span><div class="cv">'.concat(r(n.ig_caption),"</div></div>"):"","\n        ").concat((n.hashtags||[]).length?'<div class="copybox"><span class="cl">hashtags</span><div class="cv">'.concat(r((n.hashtags||[]).join(" ")),"</div></div>"):"","\n        ").concat(n.cta_label?'<div class="copybox"><span class="cl">cta</span><div class="cv">'.concat(r(n.cta_label),"</div></div>"):"",'\n        <input class="rej-note" data-note="').concat(e.id,'" placeholder="Note for the client / what to change" value="').concat(r(e.comment||""),'">\n        <div class="cat-actions" style="margin-top:8px">\n          <button class="btn small ').concat("approved"===i?"ok":"",'" data-ap="').concat(e.id,'">
1').concat("approved"===i?"\uD83D\uDD12 Approved":"✓ Approve",'</button>\n          <button class="btn small ghost" data-rj="').concat(e.id,'">✕ Reject</button>\n          <button class="btn small ghost" data-ed="').concat(e.id,'">✎ Edit</button>\n          <button class="btn small ghost" data-ce="').concat(e.id,'">✎ Copy</button>\n          <button class="btn small ghost" data-rd="').concat(e.id,'">↻ Re-draw</button>\n          ').concat(e.dataUrl?'<button class="btn small ghost" data-dl="'.concat(e.id,'">⬇</button>'):"","\n        </div>\n      </div></div>")}).join("")||'<div class="tiny">Nothing to show — compose in step 5, or change the filter.</div>';let c=e=>et.creatives.find(t=>t.id===e);e.querySelectorAll("[data-z]").forEach(e=>e.onclick=()=>{let t=c(e.dataset.z);(null==t?void 0:t.dataUrl)&&e9(t.dataUrl)}),e.querySelectorAll("[data-ap]").forEach(e=>e.onclick=()=>{let t=c(e.dataset.ap);t.state="approved"===t.state?"new":"approved",ip(),oC([{id:t.id,state:t.state,comment:t.comment||""}],t.state)}),e.querySelectorAll("[data-note]").forEach(e=>e.oninput=()=>{let t=c(e.dataset.note);t&&(t.comment=e.value)}),e.querySelectorAll("[data-rj]").forEach(t=>t.onclick=()=>{let a=c(t.dataset.rj),n=e.querySelector('[data-note="'.concat(a.id,'"]'));n&&(a.comment=n.value),a.state="rejected"===a.state?"new":"rejected",oC([{id:a.id,state:a.state,comment:a.comment||""}],a.state),ip(),el("s6status","rejected"===a.state?"Rejected".concat(a.comment?" — “"+r(a.comment)+"” recorded against it":". Add a note in the field on the card so the record says why."):"Rejection cleared.","warn")}),e.querySelectorAll("[data-ce]").forEach(e=>e.onclick=()=>{let t=c(e.dataset.ce);oN("".concat(t.deviceKey,"|").concat(t.vn,"|").concat(t.lang))}),e.querySelectorAll("[data-dl]").forEach(e=>e.onclick=()=>{let t=c(e.dataset.dl);te(t.dataUrl,ig(t)+".jpg")}),e.querySelectorAll("[data-rd]").forEach(e=>e.onclick=async()=>{let t=c(e.dataset.rd);if(!t)return;let a=await e8("draw:"+t.id,e,"Re-drawing…",async()=>(e7("s6status"),await ti(tr()),await oV(t),await oA(),!0));if(a&&a.skipped)return el("s6status","That creative is already being re-drawn.","warn");ip()}),e.querySelectorAll("[data-ed]").forEach(e=>e.onclick=()=>oQ(e.dataset.ed))}async function iu(e){if("approved"===e.state)return;let t=G[e.fmtId],a=et.copy["".concat(e.deviceKey,"|").concat(e.vn,"|").concat(e.lang)],n=nG(e.deviceKey,e.vn,t);await ti(tr());try{let{canvas:o,checks:i}=await nz(t,n,a,e.lang,nO(e.lang,e));e.checks=i.concat(nH(a,t)),(null==a?void 0:a._issues)&&e.checks.push(...a._issues.filter(e=>"fail"===e.level));let r=await nF(o,t);e.dataUrl=r.blob?await nU(r.blob):"",e.bytes=r.bytes,e.quality=r.quality,e.scale=r.scale,r.over&&e.checks.push({k:"weight",level:"fail",msg:"over the ".concat(t.max_bytes/1024|0,"KB cap at the quality floor")})}catch(t){e.checks=[{k:"draw",level:"fail",msg:t.message}]}aZ(),e.verdict=nD(e.checks)}function ih(){let e=0,t=[];et.creatives.forEach(a=>{"PASS"===a.verdict&&"rejected"!==a.state&&(a.state="approved",e++,t.push({id:a.id,state:"approved",comment:a.comment||""}))}),ip(),oC(t,"approved"),el("s6status","".concat(e," clean creatives approved and locked — a re-compose will not touch them, and only approved creatives are in the default export scope."),"okk")}function im(e){let t=et.product.spec,a=(t.destination_url||"").trim();if(!a)return"";let[n,o]=(i("ex_utm").value||"meta / paid_social").split("/").map(e=>e.trim()),r=new URLSearchParams({utm_source:n||"meta",utm_medium:o||"paid_social",utm_campaign:et.camp.campaign||y(t.product_name||"campaign"),utm_content:"".concat(e.deviceKey,"_v").concat(e.vn,"_").concat(e.lang),utm_term:e.fmtId});return a+(a.includes("?")?"&":"?")+r.toString()}function ig(e){let t=y(et.brand.brand_name||"brand"),a=y(et.camp.campaign||"campaign"),n=ee[e.deviceKey];return"".concat(t,"_").concat(a,"_").concat(n.appeal,"_").concat(e.deviceKey,"_").concat(e.fmtId,"_").concat(e.lang,"_v").concat(e.vn)}function ib(){let e=et.plan.scope||"locked";return"all"===e?et.creatives.slice():"clean"===e?et.creatives.filter(e=>"approved"===e.state||"PASS"===e.verdict&&"rejected"!==e.state):et.creatives.filter(e=>"approved"===e.state)}function iy(e){return{module:"ad-image-studio v1",generated_at:new Date().toISOString(),registry_version:D.registry_version,registry_verified_on:D.verified_on,models:{images:"draft"===et.plan.tier?_.model:v.model,text:ex,qc:k},ai_generated:!0,ai_disclosure:"Creatives in this export contain AI-generated imagery. Meta requires advertiser self-disclosure of AI-created or AI-edited media for ads about social issues, elections or politics, and applies automated 'AI info' labels more broadly; colour correction, cropping and resizing are exempt. Confirm the current requirement against Meta's Transparency Center before launch.",brand:{name:et.brand.brand_name,slug:et.brand.brand_slug,palette:et.brand.palette,type:et.brand.type,legal:et.brand.legal,evidence:(et.brand.evidence||[]).map(e=>({kind:e.kind,claim_text:e.claim_text,source:e.source}))},campaign:et.camp,plan:{variations:et.plan.variations,tier:et.plan.tier,langs:et.plan.langs,formats:et.plan.formats},claims:et.claims,plates:et.plates.filter(e=>"done"===e.status).map(e=>({id:e.id,device:e.deviceKey,variation:e.vn,family:e.family,aspect:e.aspect,seeded:!!e.seeded,qc:e.qc?{verdict:e.qc.verdict,notes:e.qc.notes,scores:e.qc.scores}:null,prompt:e.prompt})),creatives:e.map(e=>{var t,a;return{id:e.id,file:ig(e)+".jpg",device:e.deviceKey,appeal:ee[e.deviceKey].appeal,variation:e.vn,language:e.lang,format:e.fmtId,w:null==(t=G[e.fmtId])?void 0:t.w,h:null==(a=G[e.fmtId])?void 0:a.h,bytes:e.bytes,quality:e.quality,scale:e.scale,verdict:e.verdict,state:e.state,checks:e.checks,plate:e.plateId,destination_url:im(e),copy:et.copy["".concat(e.deviceKey,"|").concat(e.vn,"|").concat(e.lang)]||null}})}}function iv(e){let t=String(null==e?"":e);return/[",\n]/.test(t)?'"'+t.replace(/"/g,'""')+'"':t}function i_(e){return e.map(e=>e.map(iv).join(",")).join("\r\n")}function iw(e){let t=[["Ad Name","Ad Set Name","Campaign Name","Image File Name","Primary Text","Headline","Description","Call to Action","
1Link","Display Link","Alt Text","Language","Placement","Optimized Image Size","AI Generated"]];return e.filter(e=>{var t;return["meta","instagram"].includes(null==(t=G[e.fmtId])?void 0:t.platform)}).forEach(e=>{let a=et.copy["".concat(e.deviceKey,"|").concat(e.vn,"|").concat(e.lang)]||{},n=G[e.fmtId];t.push([ig(e),"".concat(ee[e.deviceKey].appeal,"_").concat(e.deviceKey),et.camp.campaign||"campaign",ig(e)+".jpg",a.primary_text||"",nC(a,"headline"),a.description||"",a.cta_label||"",im(e),(et.product.spec.destination_url||"").replace(/^https?:\/\//,"").split("/")[0],a.alt_text||"",a4(e.lang),n.placement,"".concat(n.w,"x").concat(n.h),"yes"])}),i_(t)}function ix(e){let t=[["Campaign","Ad group","Ad type","Image file","Short headline","Long headline","Description","Business name","Final URL","Width","Height","File size (KB)","Language","AI generated"]];return e.filter(e=>{var t;return["google_display","youtube","iab"].includes(null==(t=G[e.fmtId])?void 0:t.platform)}).forEach(e=>{let a=et.copy["".concat(e.deviceKey,"|").concat(e.vn,"|").concat(e.lang)]||{},n=G[e.fmtId];t.push([et.camp.campaign||"campaign","".concat(ee[e.deviceKey].appeal,"_").concat(e.deviceKey),n.id.startsWith("gdn_responsive")?"Responsive display ad":"Image ad",ig(e)+".jpg",a.short_headline||nC(a,"banner_line"),a.long_headline||"",a.description||"",et.brand.brand_name||"",im(e),n.w,n.h,(e.bytes/1024).toFixed(1),a4(e.lang),"yes"])}),i_(t)}async function ik(){let e=ib();for(let t of e)t.dataUrl&&(te(t.dataUrl,ig(t)+".jpg"),await g(140));el("exStatus","".concat(e.length," downloads triggered."),"okk")}function iS(){let e=ib().length?ib():et.creatives,t=e.map(e=>{var t,a,n;let o=et.copy["".concat(e.deviceKey,"|").concat(e.vn,"|").concat(e.lang)]||{};return'<div class="c"><img src="'.concat(e.dataUrl||"",'" alt="">\n      <div class="m"><b>').concat(r(ee[e.deviceKey].label)," \xb7 v").concat(e.vn," \xb7 ").concat(r(a4(e.lang)),'</b>\n      <div class="s">').concat(r((null==(t=G[e.fmtId])?void 0:t.placement)||e.fmtId)," \xb7 ").concat(null==(a=G[e.fmtId])?void 0:a.w,"\xd7").concat(null==(n=G[e.fmtId])?void 0:n.h,'</div>\n      <div class="h">').concat(r(nC(o,"headline")||""),"</div>\n      ").concat(o.primary_text?'<div class="p">'.concat(r(o.primary_text),"</div>"):"",'\n      <div class="v" data-id="').concat(r(e.id),'"><button onclick="mark(this,\'yes\')">✓ Approve</button><button onclick="mark(this,\'no\')">✕ Changes</button><input placeholder="comment"></div>\n      </div></div>')}).join("");tt('<!DOCTYPE html><html><head><meta charset="utf-8"><title>'.concat(r(et.brand.brand_name||"Brand")," — creative review</title>\n<style>body{background:#0b0b14;color:#eceafb;font-family:Inter,system-ui,sans-serif;margin:0;padding:26px}\nh1{font-size:22px}.g{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:16px;margin-top:18px}\n.c{background:#15152e;border:1px solid #26264e;border-radius:14px;overflow:hidden}\n.c img{width:100%;display:block;background:#000}.m{padding:11px}.s{font-size:11px;color:#8c8cb4;font-family:monospace}\n.h{font-size:13px;margin-top:6px}.p{font-size:11.5px;color:#9494bd;margin-top:4px}\n.v{display:flex;gap:6px;margin-top:9px;flex-wrap:wrap}.v button{font-size:11px;padding:5px 9px;border-radius:8px;border:1px solid #32326a;background:#1b1b3e;color:#eceafb;cursor:pointer}\n.v button.on{background:#7c5dfa;border-color:#7c5dfa}.v input{flex:1;min-width:110px;font-size:11px;padding:5px 7px;border-radius:8px;border:1px solid #26264e;background:#0d0d20;color:#eceafb}\n#out{position:fixed;bottom:0;left:0;right:0;background:#12122a;border-top:1px solid #32326a;padding:11px 18px;display:flex;gap:12px;align-items:center}\n#out button{background:#7c5dfa;border:none;color:#fff;padding:8px 14px;border-radius:9px;font-weight:700;cursor:pointer}\ntextarea{width:100%;height:60px;background:#0d0d20;color:#eceafb;border:1px solid #26264e;border-radius:8px;font-size:11px}</style></head>\n<body><h1>").concat(r(et.brand.brand_name||"Brand"),' — creative review</h1>\n<div style="color:#9494bd;font-size:13px">').concat(e.length," creatives \xb7 ").concat(r(et.camp.campaign||"campaign"),' \xb7 imagery is AI-generated</div>\n<div class="g">').concat(t,"</div><div style=\"height:90px\"></div>\n<div id=\"out\"><span style=\"font-size:12px;color:#9494bd\" id=\"msg\">Approve or request changes, then Copy feedback and send it back.</span>\n<button onclick=\"dump()\">⧉ Copy feedback</button></div>\n<script>const R={};function mark(b,v){const w=b.parentNode;w.querySelectorAll('button').forEach(x=>x.classList.remove('on'));b.classList.add('on');R[w.dataset.id]=R[w.dataset.id]||{};R[w.dataset.id].verdict=v;}\nfunction dump(){document.querySelectorAll('.v').forEach(w=>{const i=w.querySelector('input');if(i.value){R[w.dataset.id]=R[w.dataset.id]||{};R[w.dataset.id].comment=i.value;}});\nnavigator.clipboard.writeText(JSON.stringify(R,null,2)).then(()=>{\n  document.getElementById('msg').textContent='Feedback copied to your clipboard — paste it back to the team.';\n  document.getElementById('msg').style.color='#6ee7b7';\n},()=>{document.getElementById('msg').textContent='Your browser blocked the clipboard — select the page and copy manually.';\n  document.getElementById('msg').style.color='#fca5a5';});}<\/script>\n</body></html>"),"".concat(y(et.brand.brand_name||"brand"),"-review.html"),"text/html"),el("exStatus","Client review page downloaded — self-contained, opens with no login, returns feedback as JSON.","okk")}function iE(e){return"ais-brand-"+e}function ij(){let e=i("brandProfiles");e&&(e.innerHTML='<option value="">— saved brands —</option>'+(function(){let e=[];try{for(let t=0;t<localStorage.length;t++){let a=localStorage.key(t);a&&a.startsWith("ais-brand-")&&e.push(a.slice(10))}}catch(e){}return e.sort()})().map(e=>'<option value="'.concat(r(e),'">').concat(r(e),"</option>")).join(""))}function iA(e,t){document.querySelectorAll("#".concat(e," .opt")).forEac
1h(a=>a.onclick=()=>{document.querySelectorAll("#".concat(e," .opt")).forEach(e=>e.classList.remove("sel")),a.classList.add("sel"),t(a.dataset.v),oh()})}let iC=document.getElementById("edWrap");if(iC&&(iC.classList.remove("show"),iC.style.display="none",iC.setAttribute("hidden","")),og(e),S.forEach(e=>{et.brand.palette[e.k]||(et.brand.palette[e.k]=e.def)}),et.brand.type={display_family:"Poppins",text_family:"Inter",case:"sentence"},et.brand.logo={clear_space_multiple:.5,min_height_px:24},et.brand.legal={category:"general",mandatory_lines:[]},et.brand.voice={register:"",banned_words:[]},et.plan.formats=["meta_feed_4x5","meta_story_9x16","gdn_300x250","gdn_728x90"],et.plan.scope="locked",et.gates={},et.brand._dirty=!1,ea.gemini=l("af3-key-gemini")||"server",i("key_gemini")&&(i("key_gemini").value=ea.gemini),ta(),ty(),tH(),tU(),t6(),tP(),tW(),aB(),ij(),tI(),i("btnDiag").onclick=async()=>{let e=i("diagPanel");if(e.classList.toggle("hidden"),!e.classList.contains("hidden")){e.innerHTML="<span class='tiny'><span class='spin'></span> checking…</span>";let t=await n4();e.innerHTML=t?n5(t):"<div class='tiny'>Backend unreachable — see the message above.</div>"}},i("campState").onchange=oo,i("btnNewCamp").onclick=async()=>{let e=i("campListCard");e&&(e.style.display="none"),i("newCampCard").style.display="block";try{let e=await nV("brand.list");i("nc_brand").innerHTML='<option value="">— new brand, set up in step 1 —</option>'+(e.brands||[]).map(e=>'<option value="'.concat(e.id,'">').concat(r(e.name),"</option>")).join("")}catch(e){}i("nc_name").focus();try{i("newCampCard").scrollIntoView({block:"start",behavior:"smooth"})}catch(e){}},i("btnNewCampClose").onclick=()=>{i("newCampCard").style.display="none";let e=i("campListCard");e&&(e.style.display=""),n6().has("new")&&(eY(0),oo(),o_())},i("btnCreateCamp").onclick=async()=>{let e=i("nc_name").value.trim();if(!e)return el("s0status","Give the campaign a name.","err");if(!nY.ok)return el("s0status","Creating a campaign needs the backend — start WAMP (Apache + MySQL) and reload.","err");if(ot){eV("btnCreateCamp",!0,"Saving…");try{t1(),os();let e=i("nc_brand").value;if(e&&e!==et.brand._id)try{let t=await nV("brand.get",null,{id:e});t.brand&&(et.brand=Object.assign({brand_slug:t.brand.slug,brand_name:t.brand.name},n0(t.brand.tokens)),et.brand.palette=n0(et.brand.palette),et.brand.type=n0(et.brand.type),et.brand.logo=n0(et.brand.logo),et.brand.voice=n0(et.brand.voice),et.brand.legal=n0(et.brand.legal),et.brand.evidence=n1(t.brand.evidence),et.brand._dirty=!1,et.brand._id=t.brand.id,S.forEach(e=>{var t;(null==(t=et.brand.palette)?void 0:t[e.k])||(et.brand.palette=et.brand.palette||{},et.brand.palette[e.k]=e.def)}),ty(),tH(),tU(),i("brandChip").className="chip ok",i("brandChip").textContent=(et.brand.brand_name||"brand")+" loaded")}catch(e){}await oc(!1),eY(1)}catch(e){el("s0status",r(e.message),"err")}eV("btnCreateCamp",!1);return}eV("btnCreateCamp",!0,"Creating…");try{let t=await nV("camp.create",{name:e,brand_id:i("nc_brand").value,objective:i("nc_objective").value,audience:i("nc_audie
1nce").value.trim(),market:i("nc_market").value});i("nc_brand").value||(eo(),ty(),tH(),tU()),i("newCampCard").style.display="none",i("nc_name").value="",i("nc_audience").value="",await or(t.id),n8(t.id)}catch(e){el("s0status",r(e.message),"err")}eV("btnCreateCamp",!1)},i("btnSaveCamp").onclick=()=>oc(!1),i("btnCeClose").onclick=()=>i("copyModal").classList.remove("show"),i("btnCeSave").onclick=oO,i("btnCeRevert").onclick=()=>{oM&&(Object.assign(et.copy[oT],oM),oN(oT),el("ceStatus","Reverted to the generated copy — save to persist that.","okk"))},i("btnOvClose").onclick=i("btnOvCancel").onclick=()=>{i("ovModal").classList.remove("show"),ox=null},i("btnOvConfirm").onclick=()=>{let e=i("ov_reason").value.trim();if(e.length<8)return el("ovStatus","Give a real reason — it goes into the campaign's audit trail and stays with the creative.","err");i("ovModal").classList.remove("show");let t=null==ox?void 0:ox.onConfirm;ox=null,t&&t(e)},i("btnLogClose").onclick=()=>i("logModal").classList.remove("show"),i("btnLogRefresh").onclick=ok,i("btnPfRefresh").onclick=()=>{oq(),oB(),oU()},i("btnKeys").onclick=()=>i("keysModal").classList.add("show"),i("btnKeysClose").onclick=()=>i("keysModal").classList.remove("show"),i("btnKeysSave").onclick=function(){ea.gemini=(i("key_gemini").value||"").trim(),s("af3-key-gemini",i("rem_gemini").checked&&ea.gemini?ea.gemini:null),ta(),i("keysModal").classList.remove("show")},i("devmodeToggle").onchange=t=>{let a=!!t.target.checked;document.body.classList.toggle("devmode-on",a),e.classList.toggle("devmode-on",a)},i("btnExportProj").onclick=()=>tt(JSON.stringify({v:1,brand:et.brand,product:et.product,claims:et.claims,camp:et.camp,fit:et.fit,sel:et.sel,plan:et.plan,copy:et.copy,plates:et.plates.map(e=>({...e})),creatives:et.creatives.map(e=>({...e}))},null,2),"".concat(y(et.brand.brand_name||"brand"),"-adstudio.json")),i("btnImportProj").onclick=()=>i("importFile").click(),i("importFile").onchange=async e=>{let t=e.target.files[0];if(t)try{var a=await t.text();let e=JSON.parse(a);Object.assign(et,{brand:e.brand||et.brand,product:e.product||et.product,claims:e.claims||[],camp:e.camp||et.camp,fit:e.fit||{},sel:e.sel||{},plan:e.plan||et.plan,copy:e.copy||{},plates:e.plates||[],creatives:e.creatives||[]}),et.brand.evidence=et.brand.evidence||[],tP(),ty(),tH(),tU(),tW(),i("c_objective").value=et.camp.objective,i("c_audience").value=et.camp.audience||"",i("c_market").value=et.camp.market,i("c_competitor").value=et.camp.competitor||"",i("c_campaign").value=et.camp.campaign||"",document.querySelectorAll("#varSeg .opt").forEach(e=>e.classList.toggle("sel",+e.dataset.v===et.plan.variations)),document.querySelectorAll("#tierSeg .opt").forEach(e=>e.classList.toggle("sel",e.dataset.v===et.plan.tier)),t6(),t4(),t8(),t7(),aU(),io(),ip(),el("s1status","Project loaded.","okk")}catch(e){el("s1status","Bad project file: "+r(e.message),"err")}},e.querySelectorAll(".step-tab").forEach(e=>e.onclick=()=>eY(+e.dataset.step)),document.querySelectorAll("#intakeSeg .intake-tab").forEach(e=>e.onclick=()=>{var t;et.intakeMode=t=e.dataset.m,document.querySelectorAll("#intakeSeg .intake-tab").forEach(e=>e.classList.toggle("active",e.dataset.m===t)),i("mode_url").classList.toggle("hidden","url"!==t),i("mode_kit").classList.toggle("hidden","kit"!==t),tI()}),["in_url","in_notes","in_kitnotes"].forEach(e=>i(e).addEventListener("input",tI)),i("btnIntel").onclick=()=>n$("intel",tB,()=>oc(!0),"s1status"),i("btnKitIntel").onclick=()=>n$("kit_intel",tF,()=>oc(!0),"s1status"),i("btnReadRefs").onclick=aO,i("btnRefsClear").onclick=()=>{aN().read=null,aN().applied={},e7("refsStatus"),aB()},i("btnCopyIntel").onclick=()=>{var e;null==(e=navigator.clipboard)||e.writeText(i("pre_intel").textContent),el("s1status","Prompt copied.","okk")},i("btnApplyIntel").onclick=()=>{try{tO(JSON.parse(ed(i("man_intel").value))),el("s1status","Pasted JSON applied.","okk")}catch(e){el("s1status","That isn't valid JSON: "+r(e.message),"err")}},i("upLogo").onclick=()=>i("fileLogo").click(),i("fileLogo").onchange=async e=>{let t=[...e.target.files],a=["primary","mono_dark","mono_light"];for(let e=0;e<t.length&&e<3;e++)et.brand.logo[a[e]]=await eg(t[e]);et.brand._dirty=!0,tv(),oh(),el("s1status","Logo assets loaded — the compositor draws these, never a generated 
1wordmark.","okk")},[["logo_primary","primary"],["logo_mono_dark","mono_dark"],["logo_mono_light","mono_light"]].forEach(e=>{let[t,a]=e;i(t).onclick=()=>{let e=document.createElement("input");e.type="file",e.accept="image/*",e.onchange=async()=>{e.files[0]&&(et.brand.logo[a]=await eg(e.files[0]),et.brand._dirty=!0,tv(),oh())},e.click()}}),i("upKit").onclick=()=>i("fileKit").click(),i("fileKit").onchange=e=>tD([...e.target.files],{kit:!0}),["upLogo","upKit"].forEach(e=>{let t=i(e);t.ondragover=e=>{e.preventDefault(),t.classList.add("drag")},t.ondragleave=()=>t.classList.remove("drag"),t.ondrop=async a=>{if(a.preventDefault(),t.classList.remove("drag"),"upLogo"===e){let e=[...a.dataTransfer.files],t=["primary","mono_dark","mono_light"];for(let a=0;a<e.length&&a<3;a++)et.brand.logo[t[a]]=await eg(e[a]);tv()}else tD([...a.dataTransfer.files],{kit:!0})}}),i("btnAddImgFile").onclick=()=>i("fileImgs").click(),i("fileImgs").onchange=e=>tD([...e.target.files]),i("btnAddImgUrl").onclick=()=>{i("imgUrlRow").classList.remove("hidden"),i("in_imgUrl").focus()},i("btnAddImgUrlCancel").onclick=()=>{i("imgUrlRow").classList.add("hidden"),i("in_imgUrl").value=""},i("btnAddImgUrlGo").onclick=async()=>{let e=(i("in_imgUrl").value||"").trim();if(!e)return el("s1status","Paste an image URL first.","err");eV("btnAddImgUrlGo",!0,"Checking…");let t=ej(e),a=await eN(ev(t));if(a||(a=await eN(t)),eV("btnAddImgUrlGo",!1),!a)return el("s1status","That URL did not load as an image of at least 300px. Check the link, or upload the file instead.","err");et.product.images.push({src:t,w:a.w,h:a.h,sel:!0}),i("in_imgUrl").value="",i("imgUrlRow").classList.add("hidden"),tU(),oc(!0),el("s1status","Image added and verified.","okk")},i("in_imgUrl").addEventListener("keydown",e=>{"Enter"===e.key&&i("btnAddImgUrlGo").click()}),i("btnDupCancel").onclick=()=>{i("dupRow").classList.add("hidden"),oy=null},i("btnDupGo").onclick=ov,i("in_dupName").addEventListener("keydown",e=>{"Enter"===e.key&&ov()}),i("btnRescan").onclick=async()=>{let e=i("in_url").value.trim()||et.product.url;if(!e)return el("s1status","No page URL to re-scan.","err");eV("btnRescan",!0,"Scanning…");let t=await eR(e,[],!1);t.images.forEach(e=>{et.product.images.some(t=>t.src===e.src)||et.product.images.push({src:e.src,w:e.w,h:e.h})}),tU(),eV("btnRescan",!1),oc(!0),el("s1status","".concat(t.images.length," verified images on the page."),"okk")},i("btnRescan")&&!i("btnClearImgs")){let e=document.createElement("button");e.type="button",e.id="btnClearImgs",e.className="btn small ghost",e.textContent="✕ Remove all images",e.onclick=function(){et.product.images.length&&confirm("Remove all reference images from this campaign?")&&(et.product.images=[],tU(),oc(!0))},i("btnRescan").after(e)}i("btnContrast").onclick=tE,["t_display","t_text","t_case","t_clear","t_minlogo","t_cat","t_register","t_banned","t_legal","t_knock"].forEach(e=>i(e)&&i(e).addEventListener("change",()=>{et.brand._dirty=!0,t_(),tW()})),Object.values(tq).forEach(e=>{var t;return null==(t=i(e))?void 0:t.addEventListener("change",()=>{tz(),tW(),tZ()})}),["f_features","f_objections","f_proof"].forEach(e=>i(e).addEventListener("change",()=>{tz(),tW(),tZ()})),i("btnBrandSave").onclick=function(){if(t_(),!et.brand.brand_name)return el("s1status","Give the brand a name first.","err");s(iE(et.brand.brand_slug),JSON.stringify(et.brand)),ij(),el("s1status",'Brand profile "'.concat(r(et.brand.brand_name),'" saved in this browser — reusable across campaigns.'),"okk")},i("btnBrandLoad").onclick=function(){let e=i("brandProfiles").value;if(e)try{et.brand=JSON.parse(l(iE(e))),et.brand.evidence=et.brand.evidence||[],ty(),tH(),tW(),i("f_brand").value=et.brand.brand_name||"",i("brandChip").className="chip ok",i("brandChip").textContent=et.brand.brand_name+" loaded",el("s1status","Brand profile loaded.","okk")}catch(e){el("s1status","Could not read that profile.","err")}},i("btnAddEv").onclick=()=>i("evModal").classList.add("show"),i("btnEvClose").onclick=()=>i("evModal").classList.remove("show"),i("btnEvSave").onclick=async()=>{let e=i("ev_text").value.trim();if(!e)return;let t=i("ev_file").files[0];et.brand.evidence.push({kind:i("ev_kind").value,claim_text:e,source:i("ev_src").value.trim(),asset_dataUrl:t?await eg(t):"",verified_by:"client"}),i("ev_text").value="",i("ev_src").value="",i("ev_file").value="",et.brand._dirty=!0,i("evModal").classList.remove("show"),tH(),tW(),tZ()},i("btnTo2").onclick=()=>{tz(),t_(),tW(),tZ(),eY(2)},["c_objective","c_audience","c_market","c_competitor"].forEach(e
1=>i(e).addEventListener("change",()=>{t2(),tZ()})),i("btnFit").onclick=tZ,i("btnBrief").onclick=aj;let iL=async()=>{let e=(i("in_url").value||"").trim();e&&/^https?:\/\//i.test(e)&&ei!==e&&(et.brand.logoCandidates&&et.brand.logoCandidates.length&&tj(et.product.url||"")===tj(e)||(ei=e,et.product.url=e,await tA()))};i("in_url").addEventListener("input",()=>{et.product.url=(i("in_url").value||"").trim()}),i("in_url").addEventListener("blur",()=>{er(),ot&&of(!0),iL()}),i("in_url").addEventListener("keydown",e=>{"Enter"===e.key&&(e.preventDefault(),iL())}),i("in_url").addEventListener("paste",()=>setTimeout(iL,120)),i("btnSelBest").onclick=()=>{Q.forEach(e=>{var t;let a=Z.filter(t=>t.appeal===e.k).sort((e,t)=>{var a,n;return((null==(a=et.fit[t.key])?void 0:a.score)||0)-((null==(n=et.fit[e.key])?void 0:n.score)||0)})[0];a&&((null==(t=et.fit[a.key])?void 0:t.score)||0)>=4&&(et.sel[a.key]=et.sel[a.key]||{brief:""},et.sel[a.key].on=!0)}),t4(),oh()},i("btnSelNone").onclick=()=>{Object.values(et.sel).forEach(e=>e.on=!1),t4(),oh()},i("btnCopyFit").onclick=()=>{var e;null==(e=navigator.clipboard)||e.writeText(function(){var e;let t=et.product.spec,a=Z.map(e=>({key:e.key,appeal:e.appeal,label:e.label,what:e.what,evidence_required:e.evidence_required||null}));return"You are a chief strategy officer scoring persuasion devices for ONE image-ad campaign.\n\nPRODUCT: ".concat(t.product_name||"—"," by ").concat(t.brand_name||et.brand.brand_name||"—","\nCATEGORY: ").concat(t.category||"—","   PRICE: ").concat(t.price_display||"—","   OFFER: ").concat(t.offer_display||"—","\nFEATURES: ").concat((t.key_features||[]).join(" | ")||"—","\nBUYER OBJECTIONS: ").concat((t.objections||[]).join(" | ")||"—","\nOBJECTIVE: ").concat(et.camp.objective,"    AUDIENCE: ").concat(et.camp.audience||"—","    MARKET: ").concat(et.camp.market,"\n").concat(et.camp.competitor?"COMPETITOR CONTEXT: "+et.camp.competitor+"\n":"","REGULATED CATEGORY: ").concat((null==(e=et.brand.legal)?void 0:e.category)||"general","\nEVIDENCE ON FILE: ").concat((et.brand.evidence||[]).map(e=>e.kind+" ("+e.claim_text+")").join(" | ")||"NONE","\n\nScore every device 0-10 on four things together:\n 1. category trust norms — would this category's buyers accept this kind of argument\n 2. can the device be EXPRESSED IN A SINGLE STILL IMAGE (no video, no motion, no sequence)\n 3. how this specific audience responds at this objective\n 4. regulatory and brand-perception risk in this market\n\nScore 0-3 for a category error (a nostalgia ad for a trading app, a spec-hero for a fragrance),\nand 0-3 for any ethos device whose required evidence is NOT on file above. Be blunt; a low score\nwith an honest reason is more useful than a generous one.\n\nDEVICES:\n").concat(JSON.stringify(a),'\n\nReturn ONLY: {"scores":[{"key":"","score":0,"why":"one short clause, max 14 words"}]}')}()),el("s2status","Prompt copied.","okk")},i("btnTo3").onclick=()=>eY(3),iA("varSeg",e=>{et.plan.variations=+e,t7()}),iA("tierSeg",e=>{et.plan.tier=e,t7()}),i("c_campaign").addEventListener("input",()=>t2()),i("btnFmtSocial").onclick=()=>{et.plan.formats=D.formats.filter(e=>"social"===e.surface).map(e=>e.id),t8(),t7(),oh()},i("btnFmtWeb").onclick=()=>{et.plan.formats=D.formats.filter(e=>"web"===e.surface).map(e=>e.id),t8(),t7(),oh()},i("btnFmtAll").onclick=()=>{et.plan.formats=D.formats.map(e=>e.id),t8(),t7(),oh()},i("btnFmtNone").onclick=()=>{et.plan.formats=[],t8(),t7(),oh()},i("btnTo4").onclick=()=>{let e=et.plates.filter(e=>"done"===e.status);et.plates=e.length?et.plates:[],eY(4)},i("btnGenPlates").onclick=()=>n$("plates",aY,oE,"s4status"),i("btnStopPlates").onclick=()=>{nX("s4status")},i("btnSeedPlates").onclick=async()=>{aV(),await oE()},i("btnTo5").onclick=()=>eY(5),i("btnCopyGen").onclick=()=>n$("copy",ni,oj,"s5status"),i("btnCompose").onclick=()=>n$("compose",nW,oA,"s5status"),i("btnStopComp").onclick=()=>{nX("s5status")},i("btnPrevDraw").onclick=nJ,i("prevDevice").onchange=nJ,i("prevLang").onchange=nJ,i("btnCopyCopy").onclick=()=>{var e;null==(e=navigator.clipboard)||e.writeText(i("pre_copy").textContent),el("s5status","Prompt copied.","okk")},i("btnTo6").onclick=()=>eY(6),i("c_style").innerHTML=V.map(e=>'<option value="'.concat(e.k,'">').concat(r(e.label),"</option>")).join(""),i("c_style").value=et.plan.style||"open";let iT=()=>{let e=V.find(e=>
1e.k===et.plan.style)||V[0];i("styleNote").textContent=e.note};return iT(),i("c_style").onchange=()=>{et.plan.style=i("c_style").value,iT(),t7(),oh()},i("btnLangIndic").onclick=()=>{et.plan.langs=L.map(e=>e.k),t6(),t7(),oh()},i("btnLangNone").onclick=()=>{et.plan.langs=["en"],t6(),t7(),oh()},i("btnCopyTblRefresh").onclick=()=>{na(),oK(),oW()},i("langScope").onchange=id,!function(){let e=i("edFrame"),t=null,a=null,n=()=>{if(t=null,!a||!oX.drag)return;let{key:n,z:o}=a,i=e.querySelector('.edz[data-k="'.concat(n,'"]'));i&&(i.style.left=100*o.x+"%",i.style.top=100*o.y+"%",i.style.width=100*o.w+"%",i.style.height=100*o.h+"%");let r=oX.zones.find(e=>e.key===n);r&&Object.assign(r,{x:o.x,y:o.y,w:o.w,h:o.h}),Object.assign(o1(n),{x:o.x,y:o.y,w:o.w,h:o.h}),o8()};e.addEventListener("pointerdown",t=>{let a=t.target.closest(".edh"),n=t.target.closest(".edz");if(!n)return;let o=n.dataset.k;if(oX.sel!==o&&(o5(o),!a))return;let i=oX.zones.find(e=>e.key===o);i&&(oX.drag={key:o,mode:a?a.dataset.h:"move",sx:t.clientX,sy:t.clientY,z0:{x:i.x,y:i.y,w:i.w,h:i.h}},e.classList.add("dragging"),e.setPointerCapture(t.pointerId),t.preventDefault())}),e.addEventListener("pointermove",e=>{let o=oX.drag;if(!o)return;let i=(e.clientX-o.sx)/(oX.fmt.w*oX.scale),r=(e.clientY-o.sy)/(oX.fmt.h*oX.scale),l={...o.z0},s=o.mode;"move"===s?(l.x+=i,l.y+=r):(s.includes("w")&&(l.x+=i,l.w-=i),s.includes("e")&&(l.w+=i),s.includes("n")&&(l.y+=r,l.h-=r),s.includes("s")&&(l.h+=r)),l.w=Math.max(.02,l.w),l.h=Math.max(.01,l.h),l.x=Math.max(-.1,Math.min(1.05,l.x)),l.y=Math.max(-.1,Math.min(1.05,l.y)),oX.snap&&!e.altKey&&["x","y","w","h"].forEach(e=>l[e]=.005*Math.round(l[e]/.005)),a={key:o.key,z:l},t||(t=requestAnimationFrame(n)),oX.dirty=!0},{passive:!0});let o=()=>{oX.drag&&(t&&(cancelAnimationFrame(t),t=null),n(),oX.drag=null,a=null,e.classList.remove("dragging"),o2(!0))};e.addEventListener("pointerup",o),e.addEventListener("pointercancel",o),e.addEventListener("lostpointercapture",o),e.addEventListener("click",t=>{(t.target===e||t.target===i("edCanvas"))&&o5(null)})}(),document.addEventListener("keydown",function(e){var t;if(!oX.open)return;if("Escape"===e.key)return oX.sel?void o5(null):void ie();if(!oX.sel)return;let a=((null==(t=document.activeElement)?void 0:t.tagName)||"").toLowerCase();if("textarea"===a||"input"===a)return;let n=e.shiftKey?.01:.002,o={ArrowLeft:["x",-1],ArrowRight:["x",1],ArrowUp:["y",-1],ArrowDown:["y",1]};if(o[e.key]){let[t,a]=o[e.key],i=oX.zones.find(e=>e.key===oX.sel);if(!i)return;o1(oX.sel)[t]=i[t]+a*n,i[t]=i[t]+a*n,oX.dirty=!0,o2(),e.preventDefault()}}),i("edSave").onclick=o3,i("edStage").addEventListener("pointerdown",o9),i("edCancel").onclick=ie,i("edReset").onclick=it,i("edApply").onclick=ia,i("edGuides").onclick=()=>{oX.guides=!oX.guides,i("edFrame").classList.toggle("guides",oX.guides),i("edGuides").classList.toggle("on",oX.guides)},i("edSnap").onclick=()=>{oX.snap=!oX.snap,i("edSnap").classList.toggle("on",oX.snap)},i("edSnap").classList.add("on"),window.addEventListener("resize",()=>{oX.open&&o2(!0)}),document.querySelectorAll("#groupSeg .opt").forEach(e=>e.onclick=()=>{document.querySelectorAll("#groupSeg .opt").forEach(e=>e.classList.remove("sel")),e.classList.add("sel"),et.plan.group=e.dataset.v}),document.querySelectorAll("#exAxis .opt").forEach(e=>e.onclick=()=>{document.querySelectorAll("#exAxis .opt").forEach(e=>e.classList.remove("sel")),e.classList.add("sel");let t="language"===e.dataset.v;i("pfMatrix").style.display=t?"none":"",i("lgMatrix").style.display=t?"":"none",t?oB():oq()}),i("btnCopyRetry").onclick=nr,i("btnQcPlates").onclick=()=>n$("qc",aQ,oE,"s6status"),i("btnRedoBands").onclick=async()=>{await aK(),await oE()},i("btnRedrawFlagged").onclick=o$,["dpPlatform","dpLang","dpDevice"].forEach(e=>{i(e)&&(i(e).onchange=oW)}),i("btnBrandAssets").onclick=tA,i("btnPalette").onclick=tT,i("btnApproveAll").onclick=ih,i("revFmt").onchange=ip,document.querySelectorAll("#revSeg .opt").forEach(e=>e.onclick=()=>{document.querySelectorAll("#revSeg .opt").forEach(e=>e.classList.remove("sel")),e.classList.add("sel"),et.revFilter=e.dataset.v,ip()}),iA("scopeSeg",e=>{et.plan.scope=e,ip()}),i("btnDlManifest").onclick=()=>tt(JSON.stringify(iy(ib()),null,2),"manifest.json"),i("btnDlMetaCsv").onclick=()=>tt(iw(ib()),"meta_bulk_import.csv","text/csv"),i("btnDlGoogleCsv").onclick=()=>tt(ix(ib()),"google_ads_editor.csv","text/csv"),i("btnDlImgs").onclick=ik,i("btnShareBoard").onclick=iS,oz(),e4(),n4().then(async e=>{let t=n6(),n=(t.get("id")||"").trim(),o=t.has("new");if(e&&e.db){if(n)try{await or(n),n8(n),a.autoRun&&await nQ();return}catch(e){el("s1status","Could not open campaign: "+r(e.message),"err"),a.autoRun&&(window.__ACS_JOB_ERROR=String(e.message||e),window.__ACS_JOB_DONE=!0)}if(a.autoRun&&!n){window.__ACS_JOB_ERROR="Campaign id missing for background job",window.__ACS_JOB_DONE=!0;return}if(o){oe(),o_();return}eY(0),o_();return}if(a.autoRun){window.__ACS_JOB_ERROR="Campaign Builder API is not available",window.__ACS_JOB_DONE=!0;return}eY(0),o_()}),window.AIS={S:et,REGISTRY:D,TEMPLATES:Y,DEVICES:Z,FMT:G,drawCreative:nz,exportCanvas:nF,fitText:np,wrapLines:nd,scanClaims:tY,setSpec:e=>{Object.assign(et.product.spec,e),tP(),tW()},setCamp:e=>{Object.assign(et.camp,e),t0()},applySpecFields:tP,applyCampFields:t0,redrawOne:iu,runCopy:ni,runAllPlates:aY,qcPlates:aQ,runIntel:tB,runKitIntel:tF,enqueueOrRun:n$,requestCancel:nX,approveAllPassing:ih,exportScope:ib,shareBoard:iS,seedPlates:aV,composeAll:nW,plateJobs:t3,buildPlatePrompt:aF,buildCopyPrompt:a8,overlapsSafe:nf,ensureFonts:ti,heuristicDeviceFit:tQ,buildClaims:tW,readSpec:tz,readBrand:t_,readCamp:t2,verdictOf:nD,truncChecks:nH,tok:tw,contrastRatio:tS,metaCsv:iw,googleCsv:ix,buildManifest:iy,exportName:ig,renderBoard:ip,goStep:eY,utmUrl:im,templateFor:X,LAYOUT_STYLES:V,TEMPLATE_SET:$,scriptMetrics:C,lumStats:ng,requiredScrimAlpha:nx,renderCopyTable:oK,renderDeployPack:oW,deployFields:oH,packAsText:oG,redrawAndReport:oV,redrawAllFlagged:o$,fixPanel:oY,templateOverlaps:nm,runBrandAssets:tA,extractPalette:tL,harvestLogos:eP,dominantColors:eW,cssColors:eK,buildPalette:eJ,normaliseCopy:a3,platformsInPlan:a5,proxied:ev,apiCall:nV,ping:n4,openCampaign:or,saveCampaign:oc,loadCampaigns:oo,persistPlates:oE,persist
1Copy:oj,persistCreatives:oA,persistStates:oC,openCopyEditor:oN,setBrand:function(e){et.brand=Object.assign(et.brand,e),et.brand._dirty=!0,["palette","type","logo","voice","legal"].forEach(e=>et.brand[e]=n0(et.brand[e])),et.brand.evidence=n1(et.brand.evidence),ty(),tH(),i("f_brand")&&et.brand.brand_name&&(i("f_brand").value=et.brand.brand_name),tW()},objOf:n0,arrOf:n1,ceRead:oR,ceCheck:oI,ceSave:oO,markStage:ow,renderGates:o_,runExport:oF,loadPlatformMatrix:oq,loadExportHistory:oU,selectedDeviceKeys:t5,fieldLimits:a1,ctaEnum:a2,validateCopy:nt,withBusy:e8,isBusy:e6,INFLIGHT:e5,clearStatus:e7,applyPlateQcToCreatives:aZ,langStats:il,renderLangMatrix:is,renderLangPickers:ic,localeTag:O,marketRegion:I,langDir:N,langOf:M,glen:q,typoFor:nO,setTypo:function(e,t,a){let n=nI(),o=a||nM,i=Object.assign({},n[e]||{},t||{});return Object.keys(i).forEach(e=>{if(!(e in nM))return void delete i[e];i[e]===o[e]&&delete i[e]}),Object.keys(i).length?n[e]=i:delete n[e],nO(null,{id:String(e).replace(/^cr:/,"")})&&i},typoStore:nI,typoIsDefault:nP,resolveAlign:nq,TYPO_BASE:nM,zoneKey:nN,effectiveZone:nR,langScope:ii,scopedLangs:function(){let e=ii();return e?[e]:et.plan.langs},onLangScope:id,edOpen:oQ,edClose:oZ,edSave:o3,edCancel:ie,edResetAll:it,edApplyToLanguage:ia,resetBrandAndProduct:eo,blankBrand:en,TOKEN_ROLES:S,FONT_CHOICES:E,harvestPage:th,harvestSite:tf,mergeHarvest:tm,parseHtml:tl,normHex:td,orgFromJsonLd:tu,bestFromSrcset:tc,absUrl:ts,verifyLogoCandidates:tg,parseJsonLoose:ef,firstJsonObject:eu,stripCitations:eh,urlishTokens:eI,harvestLogoUrls:eO,extractImageUrls:eM,escapeControlsInStrings:em,brief:ae,runBrief:aj,renderBrief:aA,buildBriefPrompt:aE,buildResearchPrompt:aS,evidenceDeviceFit:av,deviceScore:ay,deviceVeto:ab,horizonMonths:an,inMarketShare:ao,hasVerifiedProof:ai,hasPurposeProof:al,isSeasonalCep:as,occasionIsAvailable:ac,nonProductPage:ap,aOrAn:au,ctaTier:aw,ctaAllowed:ax,ctaRationale:ak,CTA_TIERS:a_,BRIEF_BASE:t9,num:at,qcPlausible:a$,ED:oX,ZONE_LABEL:T,loadLanguageMatrix:oB,exportGroup:oP,bandScan:aD,scanPlateBands:aH,rescanPlateBands:aG,bandedPlates:aW,redoBandedPlates:aK,scrimShape:nk,scrimReversed:nS,scrimAnchor:nE,drawShapedScrim:nj,SCRIM_MAX:.76,contrastOnGround:n_,contrastTargetFor:nw,hexAlpha:eH,toHexish:eD,inkLum:nb,groundWithHalo:nv,readReferences:aO,renderRefs:aB,refApply:aP,mergeRefReadings:aq,buildRefPrompt:aI,refCandidates:aR,refBriefBlock:az,refsState:aN,REF_KINDS:aC,refCopyBlock:a6,listOf:aL,nameOf:aT,textOf:aM,alphaForTarget:ny,relLum:tk,INK_SWATCHES:o6,runCopyCell:no,retryCopyIssues:nr,copyJobs:nn,pruneStaleCopy:na,FAMILIES:J,applyCase:nc,get CAMP(){return ot},get API(){return nY}},{goStep:eY,openCampaign:or,showNewCampaignForm:oe,destroy(){oa=!0,on=!1,od=!1,clearTimeout(op),op=null,ot=null}}}}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.