diff --git "a/studio/assets/main-v2TpNvwt.js" "b/studio/assets/main-v2TpNvwt.js" deleted file mode 100644--- "a/studio/assets/main-v2TpNvwt.js" +++ /dev/null @@ -1,17 +0,0 @@ -var Eo=Object.defineProperty;var Lo=(e,t,s)=>t in e?Eo(e,t,{enumerable:!0,configurable:!0,writable:!0,value:s}):e[t]=s;var W=(e,t,s)=>Lo(e,typeof t!="symbol"?t+"":t,s);(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const a of document.querySelectorAll('link[rel="modulepreload"]'))o(a);new MutationObserver(a=>{for(const i of a)if(i.type==="childList")for(const r of i.addedNodes)r.tagName==="LINK"&&r.rel==="modulepreload"&&o(r)}).observe(document,{childList:!0,subtree:!0});function s(a){const i={};return a.integrity&&(i.integrity=a.integrity),a.referrerPolicy&&(i.referrerPolicy=a.referrerPolicy),a.crossOrigin==="use-credentials"?i.credentials="include":a.crossOrigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function o(a){if(a.ep)return;a.ep=!0;const i=s(a);fetch(a.href,i)}})();async function Lt(e,t={},s){return window.__TAURI_INTERNALS__.invoke(e,t,s)}function zo(){return!!(globalThis||window).isTauri}var Rn;(function(e){e.WINDOW_RESIZED="tauri://resize",e.WINDOW_MOVED="tauri://move",e.WINDOW_CLOSE_REQUESTED="tauri://close-requested",e.WINDOW_DESTROYED="tauri://destroyed",e.WINDOW_FOCUS="tauri://focus",e.WINDOW_BLUR="tauri://blur",e.WINDOW_SCALE_FACTOR_CHANGED="tauri://scale-change",e.WINDOW_THEME_CHANGED="tauri://theme-changed",e.WINDOW_CREATED="tauri://window-created",e.WINDOW_SUSPENDED="tauri://suspended",e.WINDOW_RESUMED="tauri://resumed",e.WEBVIEW_CREATED="tauri://webview-created",e.DRAG_ENTER="tauri://drag-enter",e.DRAG_OVER="tauri://drag-over",e.DRAG_DROP="tauri://drag-drop",e.DRAG_LEAVE="tauri://drag-leave"})(Rn||(Rn={}));var Gn;(function(e){e[e.Critical=1]="Critical",e[e.Informational=2]="Informational"})(Gn||(Gn={}));var Wn;(function(e){e.None="none",e.Normal="normal",e.Indeterminate="indeterminate",e.Paused="paused",e.Error="error"})(Wn||(Wn={}));var qn;(function(e){e.Disabled="disabled",e.Throttle="throttle",e.Suspend="suspend"})(qn||(qn={}));var Hn;(function(e){e.Default="default",e.FluentOverlay="fluentOverlay"})(Hn||(Hn={}));var Un;(function(e){e.AppearanceBased="appearanceBased",e.Light="light",e.Dark="dark",e.MediumLight="mediumLight",e.UltraDark="ultraDark",e.Titlebar="titlebar",e.Selection="selection",e.Menu="menu",e.Popover="popover",e.Sidebar="sidebar",e.HeaderView="headerView",e.Sheet="sheet",e.WindowBackground="windowBackground",e.HudWindow="hudWindow",e.FullScreenUI="fullScreenUI",e.Tooltip="tooltip",e.ContentBackground="contentBackground",e.UnderWindowBackground="underWindowBackground",e.UnderPageBackground="underPageBackground",e.Mica="mica",e.Blur="blur",e.Acrylic="acrylic",e.Tabbed="tabbed",e.TabbedDark="tabbedDark",e.TabbedLight="tabbedLight"})(Un||(Un={}));var jn;(function(e){e.FollowsWindowActiveState="followsWindowActiveState",e.Active="active",e.Inactive="inactive"})(jn||(jn={}));const Po=` - -`;function n(e,t=null,...s){const[o,...a]=e.split("."),i=document.createElement(o||"div");return a.length&&(i.className=a.join(" ")),Do(i,t),pn(i,s),i}function xe(e,t=null,...s){const[o,...a]=e.split("."),i=document.createElementNS("http://www.w3.org/2000/svg",o||"g");a.length&&i.setAttribute("class",a.join(" "));for(const[r,l]of Object.entries(t??{}))l==null||l===!1||(r.startsWith("on")&&typeof l=="function"?i.addEventListener(r.slice(2),l):i.setAttribute(r,String(l)));return pn(i,s),i}function Do(e,t){for(const[s,o]of Object.entries(t??{}))o==null||o===!1||(s==="class"?e.className=e.className?`${e.className} ${o}`:String(o):s==="style"&&typeof o=="object"?Object.assign(e.style,o):s.startsWith("on")&&typeof o=="function"?e.addEventListener(s.slice(2),o):s==="value"&&e instanceof HTMLInputElement?e.value=String(o):o===!0?e.setAttribute(s,""):e.setAttribute(s,String(o)))}function pn(e,t){for(const s of t.flat(8))s==null||s===!1||e.append(s instanceof Node?s:document.createTextNode(String(s)))}function F(e,...t){e.replaceChildren(),pn(e,t)}const Vn={image:"M3 3h18v18H3zM3 15l5-5 4 4 3-3 6 6M8.5 8.5h.01",type:"M4 7V4h16v3M9 20h6M12 4v16",droplet:"M12 2.7 6.7 8a7.5 7.5 0 1 0 10.6 0Z",wand:"m3 21 9-9M15 4V2M15 16v-2M8 9h2M20 9h2M17.8 11.8 19 13M15 9h.01M17.8 6.2 19 5M12.8 6.2 11.6 5",layers:"m12 2 9 5-9 5-9-5 9-5M3 12l9 5 9-5M3 17l9 5 9-5",palette:"M12 21a9 9 0 1 1 0-18c4.97 0 9 3.58 9 8 0 1.66-1.34 3-3 3h-2a2 2 0 0 0-1.6 3.2 2 2 0 0 1-1.6 3.2ZM7.5 10.5h.01M10.5 7.5h.01M14 7.5h.01M16.5 10.5h.01",check:"m20 6-11 11-5-5",checkCircle:"M22 11.1V12a10 10 0 1 1-5.9-9.1M22 4 12 14.01l-3-3",x:"M18 6 6 18M6 6l12 12",alert:"M12 9v4M12 17h.01M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0Z",info:"M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20ZM12 16v-4M12 8h.01",clock:"M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20ZM12 6v6l4 2",download:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3",upload:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M17 8l-5-5-5 5M12 3v12",globe:"M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20ZM2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10Z",copy:"M20 9H11a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2-2v-9a2 2 0 0 0-2-2ZM5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1",folder:"M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z",settings:"M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6ZM19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.6 1.65 1.65 0 0 0 10 3.09V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1Z",chevronDown:"m6 9 6 6 6-6",chevronRight:"m9 18 6-6-6-6",minus:"M5 12h14",pen:"M21.17 6.81a2.82 2.82 0 0 0-3.99-3.99L3.84 16.17a2 2 0 0 0-.5.83l-1.32 4.35a.5.5 0 0 0 .62.62l4.35-1.32a2 2 0 0 0 .83-.5Z",square:"M19 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2Z",crosshair:"M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20ZM22 12h-4M6 12H2M12 6V2M12 22v-4",zap:"M4 14h7l-3 8 12-12h-7l3-8Z",play:"m6 3 14 9-14 9V3Z",pause:"M14 4h4v16h-4zM6 4h4v16H6z",rotate:"M3 12a9 9 0 1 0 3-6.7L3 8M3 3v5h5",share:"M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8M16 6l-4-4-4 4M12 2v13",file:"M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7ZM14 2v6h6",maximize:"M8 3H5a2 2 0 0 0-2 2v3M21 8V5a2 2 0 0 0-2-2h-3M3 16v3a2 2 0 0 0 2 2h3M16 21h3a2 2 0 0 0 2-2v-3",external:"M15 3h6v6M10 14 21 3M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"};function Z(e,t=16){const s=Vn[e]??Vn.info;return xe("svg",{width:t,height:t,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":2,"stroke-linecap":"round","stroke-linejoin":"round","aria-hidden":"true",focusable:"false"},xe("path",{d:s}))}function As(e=18){const t=Fo.cloneNode(!0);t.removeAttribute("color");const[,,s,o]=(t.getAttribute("viewBox")??"0 0 1 1").split(/\s+/).map(Number);return t.setAttribute("height",String(e)),t.setAttribute("width",String(Math.round(e*s/o))),t.setAttribute("aria-hidden","true"),t.setAttribute("focusable","false"),t}const Fo=(()=>{const e=new DOMParser().parseFromString(Po,"image/svg+xml").documentElement;return document.importNode(e,!0)})();function No(e,t){let s=0;const o=i=>[...i.dataTransfer?.types??[]].includes("Files"),a=()=>{s=0,e.set({dragging:!1}),document.body.classList.remove("dragging")};window.addEventListener("dragenter",i=>{o(i)&&(s++,e.set({dragging:!0}),document.body.classList.add("dragging"))}),window.addEventListener("dragover",i=>{o(i)&&(i.preventDefault(),i.dataTransfer&&(i.dataTransfer.dropEffect="copy"))}),window.addEventListener("dragleave",i=>{o(i)&&(s=Math.max(0,s-1),s===0&&a())}),window.addEventListener("drop",i=>{if(!o(i))return;i.preventDefault(),a();const r=[...i.dataTransfer?.files??[]];r.length&&t(r)}),Oo(t)}function Oo(e){window.addEventListener("paste",t=>{if(t.target?.closest?.("input, textarea, [contenteditable]"))return;let o=[...t.clipboardData?.files??[]];o.length||(o=[...t.clipboardData?.items??[]].filter(a=>a.kind==="file").map(a=>a.getAsFile()).filter(a=>a!==null)),o.length&&(t.preventDefault(),e(o))})}const Io=760;function _o(e){const t=window.matchMedia(`(max-width: ${Io}px)`).matches,s=window.matchMedia("(pointer: coarse)").matches;if(!(t&&s))return;const o=n("div.phonenote",{role:"dialog","aria-label":"Best on a larger screen"},n("div.phonecard",null,n("span.serif",{style:{fontSize:"22px"}},"Best on a larger screen"),n("p",null,"Inkvec Studio Lite is a full editor: a viewer, a rail of controls and a palette side by side. On a phone they do not fit. Open this page on a laptop or desktop to use it; everything runs in your browser and nothing is uploaded."),n("button.btn",{onclick:()=>o.remove()},"Continue anyway")));e.append(o)}async function Bo(){const e=new URLSearchParams(window.location.search),t=e.get("sample"),s=e.get("open")==="handoff";return!t&&!s?null:(window.history.replaceState(null,"",window.location.pathname),t?{sample:t}:new Promise(o=>{let a;try{a=indexedDB.open("inkvec-handoff",1)}catch{o(null);return}a.onupgradeneeded=()=>a.result.createObjectStore("files"),a.onerror=()=>o(null),a.onsuccess=()=>{const i=a.result.transaction("files","readwrite").objectStore("files"),r=i.get("open");r.onsuccess=()=>{i.delete("open");const l=r.result;o(l?.bytes?{name:l.name??"image",bytes:l.bytes}:null)},r.onerror=()=>o(null)}}))}let wt=0,St=null,sn=0,tt=!1,Es=performance.now();const Wt=e=>(e/(1024*1024)).toFixed(1);function Ro(){sn=0;const e=St;if(St=null,!e||tt)return;wt=e.fraction;const t=document.getElementById("boot-bar"),s=document.getElementById("boot-fill"),o=document.getElementById("boot-status"),a=document.getElementById("boot-pct"),i=document.getElementById("boot-detail"),r=Math.round(wt*100);s&&(s.style.transform=`scaleX(${wt})`),t?.setAttribute("aria-valuenow",String(r)),o&&(o.textContent=e.text),a&&(a.textContent=`${r}%`),i&&(i.textContent=e.detail)}function ot(e,t,s=""){tt||(Es=performance.now(),St={text:e,fraction:Math.max(wt,St?.fraction??0,Math.min(1,t)),detail:s},sn||(sn=requestAnimationFrame(Ro)))}function Go(e,t){const s=t>0?Math.min(1,e/t):0;ot("Downloading the engine",.04+.8*s,t>0?`${Wt(e)} of ${Wt(t)} MB`:`${Wt(e)} MB`)}function Wo(){ot("Starting the engine",.88,"Compiling, and starting a thread per core")}function qo(e,t){ot(e,.9+.1*Math.min(1,Math.max(0,t)))}function Ho(){if(tt)return;const e=document.getElementById("boot-fill");e&&(e.style.transitionDuration="0.12s"),ot("Ready",1),window.setTimeout(()=>{tt=!0;const t=document.getElementById("boot");t&&(t.classList.add("leaving"),window.setTimeout(()=>t.remove(),260))},130)}{window.__inkvecBoot=!0,ot("Loading the Studio",.03);const e=window.setInterval(()=>{if(tt){window.clearInterval(e);return}if(performance.now()-Es>2e4){const t=document.getElementById("boot-detail");t&&(t.textContent="Still waiting for the network. On a slow connection the first visit can take a minute; later ones start from the browser's cache.")}},2e3)}function Uo(){let e;try{e=window.self!==window.top}catch{e=!0}const t=window.location.ancestorOrigins;if(t)for(let a=0;as.classList.toggle("fullscreen",!!document.fullscreenElement);document.addEventListener("fullscreenchange",o),o()}function Be(e,t,s="application/octet-stream"){const o=t instanceof Blob?t:new Blob([t],{type:s}),a=URL.createObjectURL(o),i=document.createElement("a");i.href=a,i.download=e,i.style.display="none",document.body.append(i),i.click(),i.remove(),window.setTimeout(()=>URL.revokeObjectURL(a),6e4)}const Ls="inkvec-studio-lite:prefs";function jo(e=()=>localStorage){try{const t=e().getItem(Ls);return t?JSON.parse(t):null}catch{return null}}function Vo(e,t=()=>localStorage){try{return t().setItem(Ls,JSON.stringify(e)),!0}catch{return!1}}var Yo={pkg:5906132,"pkg-threads":5938888};const Yn="inkvec-denoiser-v1",Zo="inkvec-ort-",Jo=3e4,Xo=700,Zn="Not available in the browser. Inkvec Studio, the desktop app, has it.",Jn=[["flat-logo.png","Flat logo"],["icon-64.png","Icon, 64 px"],["crest-filigree.png","Crest, filigree"],["signature-bw.png","Signature, B&W"]];class Ko{constructor(){W(this,"worker",null);W(this,"ready",null);W(this,"queue",[]);W(this,"running",null);W(this,"nextId",1);W(this,"listeners",new Map);W(this,"generation",0);W(this,"previewGeneration",0);W(this,"shared",null);W(this,"source",null);W(this,"prefs",null);W(this,"prefsTimer",0);W(this,"denoiser",null);W(this,"denoiserPort",null);W(this,"downloading",!1);W(this,"fetchState",{phase:"idle",got:0,total:null});W(this,"wanted",!1);W(this,"cancelled",!1);W(this,"retried",!1);W(this,"info",null);W(this,"restartMs",null);W(this,"stopped",[]);W(this,"stopTimer",0);W(this,"denoiserRuns",0);W(this,"last",null)}base(){return new URL("./",document.baseURI).href}start(){const t=typeof crossOriginIsolated<"u"&&crossOriginIsolated;t&&!this.shared&&(this.shared=new Int32Array(new SharedArrayBuffer(4)));const s=new Worker(new URL(""+new URL("engine.worker-wbMABDxy.js",import.meta.url).href,import.meta.url),{type:"module",name:"inkvec-engine"});this.worker=s;let o=null;if(t){const i=new MessageChannel;o=i.port1,this.denoiser?this.denoiser.postMessage({type:"port",port:i.port2},[i.port2]):this.denoiserPort=i.port2}this.ready=new Promise((i,r)=>{s.onmessage=l=>{const c=l.data;c.type==="loading"?(Go(c.got,c.total),c.done&&Wo()):c.type==="instantiated"?t&&(this.startDenoiser(c.denoiserUrl,c.denoiserSha256),this.prefetchDenoiser()):c.type==="ready"?(this.info={threads:c.threads,isolated:c.isolated,version:c.version},c.poolError&&console.warn("inkvec: thread pool did not start:",c.poolError),t&&this.startDenoiser(c.denoiserUrl,c.denoiserSha256),i(c)):c.type==="loadfail"?r(new Error(`The engine could not load: ${c.error}`)):c.type==="progress"?c.generation===this.generation&&this.emit("trace:progress",{generation:c.generation,...JSON.parse(c.json)}):c.type==="reply"&&this.finish(c)},s.onerror=l=>{r(new Error(`The engine stopped: ${l.message||"unknown error"}`)),this.crashed("The engine stopped unexpectedly.")}});const a=o?[o]:[];return s.postMessage({type:"init",base:this.base(),token:"d5c590fbfb80",generations:this.shared?this.shared.buffer:null,denoiserPort:o,wasmBytes:Yo},a),this.ready}whenReady(){return this.ready??this.start()}crashed(t){const s=this.running;this.running=null,window.clearTimeout(this.stopTimer),this.worker?.terminate(),this.worker=null,this.ready=null,s&&s.reject(new Error(t)),s?.op==="open_bytes"&&(this.source=null);const o=this.source,a=performance.now();this.whenReady().then(async()=>{o&&await this.enqueue("open_bytes",-1,{bytes:o.bytes.slice(),name:o.name}).catch(()=>{}),this.restartMs=performance.now()-a,this.pump()})}supersede(t,s){window.clearTimeout(this.stopTimer);const o=this.running;if(o?.op!=="trace"||o.payload.generation===this.generation||!t&&s&&Qo(o.payload.request,s))return;const a=performance.now(),i=()=>{this.running!==o||o.payload.generation===this.generation||(this.stopped.push({generation:o.payload.generation,asked:a,killed:performance.now()}),this.crashed("superseded by a newer request"))},r=a-(o.started??a),l=t?0:Math.max(0,(this.restartMs??Xo)-r);l===0?i():this.stopTimer=window.setTimeout(i,l)}finish(t){const s=this.running;if(!(!s||s.id!==t.id)){if(this.last={op:s.op,ms:t.ms??0,mem:t.mem??null},this.running=null,window.clearTimeout(this.stopTimer),t.ok)s.resolve(t.value);else if(t.crashed){this.crashed(t.error??"The engine stopped."),s.reject(new Error(t.error??"The engine stopped."));return}else s.reject(new Error(t.error??"failed"));this.pump()}}enqueue(t,s,o,a){return new Promise((i,r)=>{if(a){for(const l of this.queue.filter(c=>c.kind===a))l.reject(new Error("superseded by a newer request"));this.queue=this.queue.filter(l=>l.kind!==a)}this.queue.push({id:this.nextId++,op:t,priority:s,kind:a,payload:o,resolve:i,reject:r}),this.queue.sort((l,c)=>l.priority-c.priority||l.id-c.id),this.whenReady().then(()=>this.pump(),l=>{for(const c of this.queue)c.reject(l);this.queue=[]})})}pump(){if(this.running||!this.worker||!this.info)return;const t=this.queue.shift();if(!t)return;if(t.op==="trace"&&t.payload.generation!==this.generation)return t.resolve(null),this.pump();if(t.op==="preview"&&t.payload.generation!==this.previewGeneration)return t.resolve(null),this.pump();this.running=t,t.started=performance.now();const s=[];t.op==="open_bytes"&&s.push(t.payload.bytes.buffer),this.worker.postMessage({type:"job",id:t.id,op:t.op,...t.payload},s)}emit(t,s){for(const o of this.listeners.get(t)??[])o(s)}async listen(t,s){const o=i=>s({payload:i});let a=this.listeners.get(t);return a||this.listeners.set(t,a=new Set),a.add(o),()=>void a?.delete(o)}async invoke(t,s={}){return await this.dispatch(t,s)}async dispatch(t,s){switch(t){case"capabilities":{const o=await this.denoiserStatus();return this.enqueue("capabilities",0,{supported:o.supported,installed:o.installed,bytes:o.bytes})}case"startup_progress":return qo(String(s.text??""),Number(s.progress??0)),null;case"app_ready":return Ho(),null;case"open_path":throw new Error("A browser opens files you choose or drop, not paths.");case"open_bytes":{const o=s.bytes,a=o instanceof Uint8Array?o:new Uint8Array(o);return this.open(a,s.name??null)}case"open_sample":{const o=String(s.name);if(!Jn.some(([i])=>i===o))throw new Error(`${o} is not one of the bundled samples`);const a=await fetch(new URL(`samples/${o}`,this.base()));if(!a.ok)throw new Error(`cannot fetch the sample ${o}: HTTP ${a.status}`);return this.open(new Uint8Array(await a.arrayBuffer()),o)}case"list_samples":return Jn.map(([o,a])=>({file:o,label:a,preview:new URL(`samples/${o}`,this.base()).href}));case"launch_path":return null;case"start_trace":{const o=s.request,a=this.bump();this.rememberTrace(o.settings);const i=this.withoutUnreadyDenoiser(o);this.supersede(!1,i);const r=await this.loadPrefs();return this.enqueue("trace",1,{request:i,draftPx:r.draftPx??512,draftSeconds:r.draftSeconds??.4,generation:a},"trace").then(l=>{l&&a===this.generation&&this.emit("trace:done",{generation:a,outcome:l})},l=>{a===this.generation&&!/superseded/.test(l.message)&&this.emit("trace:done",{generation:a,outcome:{state:"failed",message:l.message}})}),a}case"cancel_trace":return this.bump(),this.queue=this.queue.filter(o=>o.kind==="trace"?(o.resolve(null),!1):!0),this.supersede(!0),null;case"start_preview":{const o=this.withoutUnreadyDenoiser(s.request),a=++this.previewGeneration,i=await this.loadPrefs();return this.enqueue("preview",2,{request:o,draftPx:i.draftPx??512,draftSeconds:i.draftSeconds??.4,generation:a},"preview").then(r=>{r&&a===this.previewGeneration&&this.emit("preview:done",{generation:a,outcome:r})},r=>{a===this.previewGeneration&&!/superseded/.test(r.message)&&this.emit("preview:done",{generation:a,outcome:{state:"failed",message:r.message}})}),a}case"cancel_previews":{this.previewGeneration++,this.queue=this.queue.filter(a=>a.kind==="preview"?(a.resolve(null),!1):!0);const o=this.running;return o?.op==="preview"&&o.started!==void 0&&performance.now()-o.started>500&&this.crashed("superseded by a newer request"),null}case"source_facts":case"trace_bands":case"snap_inks":case"match_palette":case"plan_export":case"minify_svg":case"fab_analyze":return this.enqueue(t,0,{args:s});case"fab_prepare":return this.enqueue(t,0,{args:s},"fab");case"write_export":{const o=s.request,a=await this.enqueue("export",0,{request:o,zip:!0}),i=(this.source?.name??"image").replace(/\.[^.]+$/,"")||"image";if(a.length===1&&a[0].name)return Be(a[0].name,a[0].data),[a[0].name];const r=`${i}-inkvec.zip`;return a[0]&&Be(r,a[0].data,"application/zip"),[r]}case"read_text_file":throw new Error("A browser reads files you choose or drop, not paths.");case"save_bytes":{const o=String(s.path);return Be(o.split(/[\\/]/).pop()||"download",new Uint8Array(s.bytes)),null}case"batch_scan":case"batch_start":case"batch_stats_csv":throw new Error(Zn);case"batch_pause":case"batch_cancel":return null;case"denoiser_status":return this.denoiserStatus();case"denoiser_download":return this.downloadDenoiser();case"denoiser_cancel":return this.fetchState.phase==="downloading"&&(this.cancelled=!0,this.wanted=!1,this.denoiser?.postMessage({type:"abort"})),this.downloading&&(this.downloading=!1,this.emit("denoiser:done",{ok:!1,message:"cancelled",status:await this.denoiserStatus()})),null;case"denoiser_remove":try{await caches.delete(Yn);for(const o of await caches.keys())o.startsWith(Zo)&&await caches.delete(o)}catch{}return this.denoiser?.postMessage({type:"forget"}),this.fetchState.phase!=="ready"&&this.setFetch({phase:"idle",got:0,total:null}),this.denoiserStatus();case"load_prefs":return this.loadPrefs();case"save_prefs":{this.prefs={...this.prefs??{},...s.prefs},this.storePrefs();const o=await this.enqueue("sanitise_prefs",0,{args:{prefs:s.prefs}});return this.prefs=o,this.storePrefs(),o}case"reset_prefs":{const o=await this.loadPrefs();return this.prefs=await this.enqueue("reset_prefs",0,{args:{prefs:o}}),this.storePrefs(),this.prefs}case"third_party_notices":{const a=(await Promise.all(["STUDIO_THIRD_PARTY.md","THIRD_PARTY.md"].map(i=>fetch(new URL(i,this.base())).then(r=>r.ok?r.text():null,()=>null)))).filter(i=>!!i);if(!a.length)throw new Error("the notices could not be fetched");return a.join(` - - -`)}case"check_update":return{latest:null,newer:!1,url:"https://github.com/logolabs/inkvec/releases",offline:"a web page is always the version it is served as"};case"cli_status":case"context_menu_status":case"install_cli":case"remove_cli":case"install_context_menu":case"remove_context_menu":return{available:!1,installed:!1,path:null,note:Zn}}throw new Error(`${t} is not something the browser build does`)}bump(){return this.generation++,this.shared&&Atomics.store(this.shared,0,this.generation),this.generation}async open(t,s){return this.source={bytes:t.slice(),name:s},this.bump(),this.queue=this.queue.filter(o=>o.kind?(o.resolve(null),!1):!0),this.supersede(!0),this.enqueue("open_bytes",0,{bytes:t,name:s})}async loadPrefs(){if(this.prefs)return this.prefs;const t=jo();return this.prefs=await(t?this.enqueue("sanitise_prefs",0,{args:{prefs:t}}):this.enqueue("default_prefs",0,{args:{}})),this.prefs}rememberTrace(t){if(!this.prefs)return;const s={...t};delete s.colourGroups,this.prefs={...this.prefs,trace:s},window.clearTimeout(this.prefsTimer),this.prefsTimer=window.setTimeout(()=>this.storePrefs(),1e3)}storePrefs(){Vo(this.prefs)}startDenoiser(t,s){if(this.denoiser||!this.denoiserPort)return;const o=new Worker(new URL(""+new URL("denoiser.worker-DjPuHhk4.js",import.meta.url).href,import.meta.url),{type:"module",name:"inkvec-denoiser"});this.denoiser=o,o.onmessage=a=>void this.denoiserMessage(a.data),o.postMessage({type:"init",base:this.base(),token:"d5c590fbfb80",url:t,sha256:s,port:this.denoiserPort},[this.denoiserPort])}setFetch(t){this.fetchState={...this.fetchState,retrace:void 0,...t},this.emit("denoiser:fetch",this.fetchState)}get denoiserFetch(){return this.fetchState}async denoiserMessage(t){const s=this.fetchState.phase;switch(t.type){case"progress":if(s==="preparing"||s==="ready")return;this.setFetch({phase:"downloading",got:t.got??0,total:t.total??null,message:void 0,saveData:void 0}),this.downloading&&this.emit("denoiser:progress",{got:t.got,total:t.total});return;case"stored":{if(s==="preparing"||s==="ready")return;const o=this.fetchState.total??this.fetchState.got;this.setFetch({phase:"stored",got:o,total:o,message:void 0}),this.downloading&&(this.downloading=!1,this.emit("denoiser:done",{ok:!0,status:await this.denoiserStatus()}));return}case"preparing":this.setFetch({phase:"preparing",message:void 0});return;case"ran":this.denoiserRuns++;return;case"loaded":{const o=this.wanted;this.wanted=!1,this.setFetch({phase:"ready",message:void 0,retrace:o});return}case"failed":{const o=t.message??"the download did not finish";if(this.cancelled||/abort/i.test(o)){this.cancelled=!1,this.setFetch({phase:"idle",message:void 0});return}this.setFetch({phase:"failed",message:o}),this.downloading&&(this.downloading=!1,this.emit("denoiser:done",{ok:!1,message:o,status:await this.denoiserStatus()})),t.during==="download"&&!this.retried&&(this.retried=!0,window.setTimeout(()=>{this.fetchState.phase==="failed"&&this.denoiser?.postMessage({type:this.wanted?"prepare":"prefetch"})},Jo));return}}}prefetchDenoiser(){if(navigator.connection?.saveData){this.setFetch({phase:"idle",saveData:!0});return}this.denoiser?.postMessage({type:"prefetch"})}withoutUnreadyDenoiser(t){const s=t.settings?.cleanUpDamage;return!s||s==="off"||!this.denoiser||this.fetchState.phase==="ready"?t:(this.wanted=!0,this.cancelled=!1,this.denoiser.postMessage({type:"prepare"}),{...t,settings:{...t.settings,cleanUpDamage:"off"}})}async denoiserStatus(){const t=await this.whenReady();let s=!1,o=null;try{const a=await(await caches.open(Yn)).match(t.denoiserUrl);a&&(s=!0,o=Number(a.headers.get("content-length"))||(await a.blob()).size)}catch{}return{supported:t.isolated,installed:s,path:null,bytes:o,repo:t.denoiserRepo,sha256:t.denoiserSha256}}async downloadDenoiser(){if(await this.whenReady(),!this.denoiser)throw new Error("The denoiser needs a cross-origin isolated page.");return this.downloading=!0,this.cancelled=!1,this.denoiser.postMessage({type:this.wanted?"prepare":"download"}),null}}function Qo(e,t){const s=o=>{const a={...o?.settings??{}};return delete a.minify,delete a.margin,JSON.stringify(Object.keys(a).sort().map(i=>[i,a[i]]))};return s(e)===s(t)}let ut=null;function zs(){return ut||(ut=new Ko,globalThis.__inkvecStudioLite=ut),ut}function R(e,t){return zs().invoke(e,t)}function ve(e,t){return zs().listen(e,t)}const B={capabilities:()=>R("capabilities"),startupProgress:(e,t)=>R("startup_progress",{text:e,progress:t}),appReady:()=>R("app_ready"),openPath:e=>R("open_path",{path:e}),openBytes:(e,t)=>R("open_bytes",{bytes:e,name:t??null}),openSample:e=>R("open_sample",{name:e}),listSamples:()=>R("list_samples"),startTrace:(e,t)=>R("start_trace",{request:{settings:e,tier:t}}),cancelTrace:()=>R("cancel_trace"),startPreview:e=>R("start_preview",{request:{settings:e,tier:"draft"}}),cancelPreviews:()=>R("cancel_previews"),sourceFacts:e=>R("source_facts",{maxDim:e}),launchPath:()=>R("launch_path"),traceBands:e=>R("trace_bands",{generation:e}),snapInks:(e,t,s,o)=>R("snap_inks",{svg:e,snaps:t,width:s,height:o}),matchPalette:(e,t)=>R("match_palette",{traced:e,pasted:t}),planExport:e=>R("plan_export",{request:e}),writeExport:(e,t)=>R("write_export",{request:e,destination:t}),minify:(e,t)=>R("minify_svg",{svg:e,settings:t}),fabAnalyze:e=>R("fab_analyze",{svg:e}),fabPrepare:(e,t)=>R("fab_prepare",{svg:e,options:t}),readTextFile:e=>R("read_text_file",{path:e}),saveBytes:(e,t)=>R("save_bytes",{path:e,bytes:t}),batchScan:e=>R("batch_scan",{folder:e}),batchStart:e=>R("batch_start",{plan:e}),batchPause:e=>R("batch_pause",{paused:e}),batchCancel:()=>R("batch_cancel"),batchStatsCsv:()=>R("batch_stats_csv"),denoiserStatus:()=>R("denoiser_status"),denoiserDownload:()=>R("denoiser_download"),denoiserCancel:()=>R("denoiser_cancel"),denoiserRemove:()=>R("denoiser_remove"),loadPrefs:()=>R("load_prefs"),savePrefs:e=>R("save_prefs",{prefs:e}),resetPrefs:()=>R("reset_prefs"),thirdPartyNotices:()=>R("third_party_notices"),checkUpdate:()=>R("check_update"),cliStatus:()=>R("cli_status"),installCli:()=>R("install_cli"),removeCli:()=>R("remove_cli"),contextMenuStatus:()=>R("context_menu_status"),installContextMenu:()=>R("install_context_menu"),removeContextMenu:()=>R("remove_context_menu")},ke={traceProgress:e=>ve("trace:progress",t=>e(t.payload)),traceDone:e=>ve("trace:done",t=>e(t.payload)),previewDone:e=>ve("preview:done",t=>e(t.payload)),openPath:e=>ve("open:path",t=>e(t.payload)),batchRow:e=>ve("batch:row",t=>e(t.payload)),batchTotals:e=>ve("batch:totals",t=>e(t.payload)),batchFinished:e=>ve("batch:finished",t=>e(t.payload)),denoiserProgress:e=>ve("denoiser:progress",t=>e(t.payload)),denoiserDone:e=>ve("denoiser:done",t=>e(t.payload)),denoiserFetch:e=>ve("denoiser:fetch",t=>e(t.payload))};function ea(e,t){return{caps:null,prefs:null,tab:"vectorize",screen:null,source:null,settings:e,preset:"logo",generation:0,tracing:!1,tracingTier:"final",liveStages:[],traceStarted:0,traceEnded:0,liveNow:null,traceLog:[],logOpen:!0,result:null,resultGeneration:0,bandsMissing:!1,previous:null,svg:null,palette:[],snaps:[],colourGroups:[],groupSuggestions:null,resultGroups:[],dismissedGroups:[],simplifyTo:null,paletteSelection:[],hoverFill:null,losses:[],report:null,worstCorner:null,stageState:{kind:"empty"},justSwapped:!1,view:"side",show:{fill:!0,wireframe:!1,anchors:!1,handles:!1,certainty:!1},zoom:1,pan:{x:0,y:0},fitted:!0,flicked:!1,wipe:.5,detail:!1,railTab:"result",groupsOpen:{Detail:!0,Colour:!0,Shape:!0,Output:!1},exportOpen:!1,dragging:!1,minify:{name:null,before:null,settings:t,result:null,error:null},fab:{name:null,svg:null,analysis:null,options:Ps(),plan:null,error:null,shown:-1,unit:"mm",showProblems:!0,preset:null,dxf:!1,gcode:!1},batch:{folder:null,outputDir:null,preset:"logo",skipExisting:!0,rows:[],totals:null,running:!1,paused:!1,failuresFirst:!1},update:null,denoiserFetch:null,auto:null,facts:null,chooser:!1,wizard:null,compare:null,autoNoteHidden:!1}}function Ps(){return{mode:"singleColour",widthMm:100,include:[],order:[],bleedMm:.8,minFeatureMm:.8,removeThin:!1,stickerMarginMm:3,bridgeMm:1.5,stencilMarginMm:10,maxLineMm:0,penMm:.4,registration:!0,weedBorderMm:0,mirror:!1,cutStyle:"filled",sizeCheckMm:0,dogboneMm:0,gcodeFeedMmMin:1e3,gcodePower:1e3,gcodePasses:1,fileUnits:"mm",kerfMm:0,toleranceMm:.05,mergeDeltaE:3}}class ta{constructor(t){W(this,"listeners",new Set);this.state=t}set(t){const s=new Set;for(const[o,a]of Object.entries(t))this.state[o]!==a&&(this.state[o]=a,s.add(o));s.size&&this.emit(s)}touch(...t){this.emit(new Set(t))}on(t,s){const o=new Set(t),a=i=>{for(const r of i)if(o.has(r)){s();return}};return this.listeners.add(a),()=>this.listeners.delete(a)}emit(t){for(const s of[...this.listeners])s(t)}}function oe(e){return e.toLocaleString("en-US")}function ie(e){return e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KB`:`${(e/(1024*1024)).toFixed(1)} MB`}function re(e){return e==null?"—":e.toFixed(2)}function nt(e){return e<1?`${Math.round(e*1e3)} ms`:`${e.toFixed(2)} s`}function hn(e,t){if(!e.source)return null;const s=Math.max(e.source.width,e.source.height),o=t==="draft"?Math.min(e.prefs?.draftPx??512,e.settings.traceSize):e.settings.traceSize;return Math.min(o,s)}function at(e,t){const s=t.mode==="fast"&&!t.blackAndWhite&&!t.lineArt;return e.modes==="qualityOnly"?!s:e.modes==="fastOnly"?s:!0}function $e(e,t=0){return`${(e*100).toFixed(t)}%`}function na(e){const t=Math.max(0,Math.round(e));return t<60?`${t} s`:`${Math.floor(t/60)} min ${t%60} s`}function Ds(e){return e==="macos"?"⌘":"Ctrl"}function sa(){return{preset:"logo",tab:"vectorize",railTab:"result",groupsOpen:{},view:"side",wipe:.5,show:{fill:!0,wireframe:!1,anchors:!1,handles:!1,certainty:!1},detail:!1,export:null,minify:null,minifyBackdrop:"auto",minifyView:"side",fab:null,fabUnit:"mm",fabShowProblems:!0,fabPreset:null,fabDxf:!1,fabGcode:!1,batchPreset:null,batchSkipExisting:!0,batchFailuresFirst:!1}}const Ce={export:null,minifyBackdrop:"auto",minifyView:"side"};let on=()=>{};function oa(){return{...Ce}}function an(e){Object.assign(Ce,e),on()}function aa(e){const t=Ce.export;return t?{...e,...t,pngSizes:[...t.pngSizes??e.pngSizes]}:e}function rn(e){const t={...e.fab.options};return delete t.include,delete t.order,{preset:e.preset,tab:e.tab,railTab:e.railTab,groupsOpen:{...e.groupsOpen},view:e.view,wipe:e.wipe,show:{...e.show},detail:e.detail,export:Ce.export,minify:{...e.minify.settings},minifyBackdrop:Ce.minifyBackdrop,minifyView:Ce.minifyView,fab:t,fabUnit:e.fab.unit,fabShowProblems:e.fab.showProblems,fabPreset:e.fab.preset,fabDxf:e.fab.dxf,fabGcode:e.fab.gcode,batchPreset:e.batch.preset,batchSkipExisting:e.batch.skipExisting,batchFailuresFirst:e.batch.failuresFirst}}function ln(e){const t={...e};return delete t.colourGroups,t}function ia(e,t,s,o){const a=e.state,i={...sa(),...t.ui??{}};Ce.export=i.export,Ce.minifyBackdrop=i.minifyBackdrop,Ce.minifyView=i.minifyView,e.set({preset:i.preset&&o(i.preset)?i.preset:null,tab:s.includes(i.tab)?i.tab:"vectorize",railTab:i.railTab,groupsOpen:{...a.groupsOpen,...i.groupsOpen},view:i.view,wipe:i.wipe,show:{...a.show,...i.show},detail:i.detail,minify:{...a.minify,settings:{...a.minify.settings,...i.minify??{}}},fab:{...a.fab,options:{...Ps(),...i.fab??{},include:[],order:[]},unit:i.fabUnit,showProblems:i.fabShowProblems,preset:i.fabPreset,dxf:i.fabDxf,gcode:i.fabGcode},batch:{...a.batch,preset:i.batchPreset&&o(i.batchPreset)?i.batchPreset:a.batch.preset,skipExisting:i.batchSkipExisting,failuresFirst:i.batchFailuresFirst}})}const ra=["settings","preset","tab","railTab","groupsOpen","view","wipe","show","detail","minify","fab","batch"];function la(e,t,s=500){let o=0,a=JSON.stringify({ui:rn(e.state),trace:ln(e.state.settings)});const i=()=>{if(window.clearTimeout(o),o=0,!e.state.prefs)return;const c={ui:rn(e.state),trace:ln(e.state.settings)},d=JSON.stringify(c);d!==a&&(a=d,t(c))};on=()=>{window.clearTimeout(o),o=window.setTimeout(i,s)};const r=e.on(ra,on),l=()=>{document.visibilityState==="hidden"&&o&&i()};return window.addEventListener("pagehide",i),document.addEventListener("visibilitychange",l),()=>{r(),window.removeEventListener("pagehide",i),document.removeEventListener("visibilitychange",l)}}const ca="modulepreload",ua=function(e,t){return new URL(e,t).href},Xn={},da=function(t,s,o){let a=Promise.resolve();if(s&&s.length>0){let r=function(p){return Promise.all(p.map(h=>Promise.resolve(h).then(g=>({status:"fulfilled",value:g}),g=>({status:"rejected",reason:g}))))};const l=document.getElementsByTagName("link"),c=document.querySelector("meta[property=csp-nonce]"),d=c?.nonce||c?.getAttribute("nonce");a=r(s.map(p=>{if(p=ua(p,o),p in Xn)return;Xn[p]=!0;const h=p.endsWith(".css"),g=h?'[rel="stylesheet"]':"";if(!!o)for(let b=l.length-1;b>=0;b--){const x=l[b];if(x.href===p&&(!h||x.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${p}"]${g}`))return;const f=document.createElement("link");if(f.rel=h?"stylesheet":ca,h||(f.as="script"),f.crossOrigin="",f.href=p,d&&f.setAttribute("nonce",d),document.head.appendChild(f),h)return new Promise((b,x)=>{f.addEventListener("load",b),f.addEventListener("error",()=>x(new Error(`Unable to preload CSS for ${p}`)))})}))}function i(r){const l=new Event("vite:preloadError",{cancelable:!0});if(l.payload=r,window.dispatchEvent(l),!l.defaultPrevented)throw r}return a.then(r=>{for(const l of r||[])l.status==="rejected"&&i(l.reason);return t().catch(i)})};async function pa(e={}){return typeof e=="object"&&Object.freeze(e),await Lt("plugin:dialog|open",{options:e})}async function ha(e={}){return typeof e=="object"&&Object.freeze(e),await Lt("plugin:dialog|save",{options:e})}async function fa(e,t){await Lt("plugin:opener|open_url",{url:e,with:t})}async function ga(e){return Lt("plugin:opener|reveal_item_in_dir",{paths:typeof e=="string"?[e]:e})}const zt="Inkvec Studio Lite",Fs="https://github.com/logolabs/inkvec/releases",ma="Inkvec Studio for Windows, macOS and Linux: the native engine is faster, it traces whole folders in one batch, has no browser memory limit (traces up to 16384 px), and works offline.";function Ns(e){return{name:e.name||"pasted image",path:null,file:e}}async function fn(e,t=!1){return new Promise(s=>{const o=document.createElement("input");o.type="file",o.multiple=t,o.accept=e.flatMap(r=>r.extensions.map(l=>`.${l}`)).join(","),o.style.display="none";let a=!1;const i=r=>{a||(a=!0,o.remove(),s(r))};o.addEventListener("change",()=>i([...o.files??[]].map(Ns))),o.addEventListener("cancel",()=>i([])),document.body.append(o),o.click()})}async function $t(e){if(e.file)return e.file.text();if(e.path)return B.readTextFile(e.path);throw new Error("nothing to read")}const Kn=!1;async function Os(e,t,s){const o=typeof t=="string"?new TextEncoder().encode(t):t;return Be(e,o),{path:null,name:e}}async function ba(e,t,s){const o=new TextEncoder,a=i=>typeof i=="string"?o.encode(i):i;{if(e.length===1)Be(e[0].name,a(e[0].data));else{const{zipStore:i}=await da(async()=>{const{zipStore:r}=await import("./zip-DlX9-WSC.js");return{zipStore:r}},[],import.meta.url);Be(t,i(e.map(r=>({name:r.name,data:a(r.data)}))))}return{folder:null,first:null}}}const ya=!1;async function Pt(e){await navigator.clipboard.writeText(e)}async function Dt(e){{window.open(e,"_blank","noopener,noreferrer");return}}function Ft(e){return e&&ya?{label:"Show in folder",run:()=>void 0}:void 0}const Is={mode:"quality",precision:.1,speckleFloor:2,traceSize:2048,timeLimit:0,maxColours:64,colourMerging:.035,flatFills:!1,blackAndWhite:!1,monochrome:!1,cleanUpDamage:"off",matchRepeatedShapes:!0,matchThreshold:.92,fewerPaths:!1,lineArt:!1,repairRings:!0,editability:!1,bezierCost:6,cornerAngle:10,minify:!1,transparentBackground:!1,margin:0,holesAsCutouts:!1,traceTransparency:!0};function wa(e,t,s){const o=e?.presets.find(i=>i.id===s);if(o)return{settings:o.settings,wantsDenoiser:o.wantsDenoiser};const a=t?.saved.find(i=>i.id===s);return a?{settings:a.settings,wantsDenoiser:!1}:null}function va(e){return/^[0-9]$/.test(e)?(Number(e)+9)%10:null}const xa=8;class ka{constructor(t){W(this,"watching",0);W(this,"newest",0);W(this,"early",new Map);W(this,"groupsSent",new Map);W(this,"settleTimer");this.host=t}async trace(t){if(!this.host.hasSource())return;const s=performance.now();try{const o=this.host.groups(),a=await this.host.start(t);if(athis.newest){this.hold(t.generation).progress.push(t);return}this.progressed(t)}onDone(t,s){if(t>this.newest){this.hold(t).done=s;return}const o=this.host.shown();t!==o.generation||!o.tracing||this.host.finished(s,t)}controlChanged(){this.clearSettle(),this.host.hasSource()&&(this.trace("draft"),this.settleTimer=setTimeout(()=>void this.trace("final"),this.host.settleMs()))}cancel(){this.clearSettle(),this.host.cancel()}clearSettle(){clearTimeout(this.settleTimer),this.settleTimer=void 0}detach(){this.clearSettle(),this.watching=0}groupsFor(t){return this.groupsSent.get(t)}progressed(t){t.generation===this.watching&&this.host.progressed(t)}hold(t){const s=this.early.get(t)??{progress:[]};return this.early.set(t,s),s}}class Sa{constructor(){W(this,"last",0)}take(){return++this.last}isNewest(t){return t===this.last}}function Pe(e){const t={...e,cleanUpDamage:"off"};return JSON.stringify(Object.keys(t).sort().map(s=>[s,t[s]]))}class $a{constructor(){W(this,"results",new Map);W(this,"queue",[]);W(this,"running",null);W(this,"epoch",0);W(this,"early",new Map);W(this,"listeners",new Set);ke.previewDone(({generation:t,outcome:s})=>this.done(t,s))}get(t){return this.results.get(t)}on(t){return this.listeners.add(t),()=>this.listeners.delete(t)}want(t){const s=new Set,o=t.filter(a=>{if(s.has(a.key))return!1;s.add(a.key);const i=this.results.get(a.key)?.state;return i!=="done"&&i!=="running"});if(o.map(a=>a.key).join(` -`)!==this.queue.map(a=>a.key).join(` -`)){for(const a of this.queue)this.results.get(a.key)?.state==="queued"&&this.results.delete(a.key);this.queue=o;for(const a of this.queue)this.results.set(a.key,{state:"queued"});this.emit(),this.next()}}pause(){this.want([])}reset(){this.epoch++;for(const t of this.results.values())t.state==="done"&&URL.revokeObjectURL(t.url);this.results.clear(),this.queue=[],this.early.clear(),this.running&&B.cancelPreviews().catch(()=>{}),this.running=null,this.emit()}pending(){return this.queue.length+(this.running?1:0)}next(){if(this.running)return;const t=this.queue.shift();if(!t)return;const s=this.epoch;this.running={key:t.key,generation:null,epoch:s},this.results.set(t.key,{state:"running"}),this.emit(),B.startPreview(t.settings).then(o=>{if(this.running?.epoch!==s||this.running.key!==t.key)return;this.running.generation=o;const a=this.early.get(o);a&&(this.early.delete(o),this.done(o,a))},o=>{this.running?.epoch===s&&this.finish(t.key,{state:"failed",message:String(o)})})}done(t,s){const o=this.running;if(o){if(o.generation===null){this.early.set(t,s);return}if(o.generation===t)if(s.state==="traced"){const a=URL.createObjectURL(new Blob([s.svg],{type:"image/svg+xml"}));this.finish(o.key,{state:"done",svg:s.svg,url:a,report:s.report})}else{const a="message"in s?s.message:s.state==="flat"?"one flat colour":s.state==="cancelled"?"stopped":"out of memory";this.finish(o.key,{state:"failed",message:a})}}}finish(t,s){this.results.set(t,s),this.running=null,this.emit(),this.next()}emit(){for(const t of[...this.listeners])t()}}const Qn=400;function Ma(e,t,s){let o=e.liveStages,a=e.liveNow;const i=[...e.traceLog],r=c=>{for(let d=i.length-1;d>=0;d--){const p=i[d];if(p.kind==="stage")return p.what===c&&p.ms===void 0?d:-1}return-1};for(const c of t.entries)if(c.kind==="begin")a={stage:c.stage,what:c.what,since:s,step:null},i.push({at:c.at,kind:"stage",stage:c.stage,what:c.what});else if(c.kind==="end"){if(c.stage!=="measure"){const p=o.findIndex(h=>h.name===c.stage);o=p>=0?o.map((h,g)=>g===p?{...h,ms:h.ms+c.ms}:h):[...o,{name:c.stage,ms:c.ms}]}const d=r(c.what);d>=0?i[d]={...i[d],ms:c.ms,step:i[d].step??null}:i.push({at:Math.max(0,c.at-c.ms),kind:"stage",stage:c.stage,what:c.what,ms:c.ms}),a&&a.what===c.what&&(a=null)}else i.push({at:c.at,kind:"note",stage:c.stage,what:c.what,text:c.text});const l=t.step;if(l){!a||a.what!==l.what?a={stage:l.stage,what:l.what,since:s,step:l}:a={...a,step:l};const c=r(l.what);c>=0&&(i[c]={...i[c],step:l})}return{liveStages:o,liveNow:a,traceLog:i.length>Qn?i.slice(i.length-Qn):i}}function gn(e){return e.total>0?`${e.done} / ${e.total} ${e.unit}`:`${e.unit} ${e.done}`}function Re(e){const t=Math.max(0,e)/1e3;return t<60?`${t.toFixed(1)} s`:`${Math.floor(t/60)} min ${String(Math.floor(t%60)).padStart(2,"0")} s`}function es(e){return`${(Math.max(0,e)/1e3).toFixed(2)}`}function Ca(e,t,s){let o=0;const a=()=>{const r=performance.now(),l=t(),c=s();for(const d of document.querySelectorAll('[data-live="elapsed"]'))d.textContent=Re(r-l);for(const d of document.querySelectorAll('[data-live="stage"]'))d.textContent=c===null?"":Re(r-c)},i=()=>{e()&&!o?(a(),o=window.setInterval(a,100)):!e()&&o&&(window.clearInterval(o),o=0)};return()=>{i(),o&&a()}}function Nt(e){const t=e.liveNow;if(t){const s=t.stage==="measure"?"measuring":t.stage;return t.step?`${s} · ${gn(t.step)}`:`${s} · ${t.what}`}return e.liveStages[e.liveStages.length-1]?.name??"starting"}function Ot(e){return Re(performance.now()-e.traceStarted)}const mn=()=>document.getElementById("overlays"),Ta=()=>document.getElementById("toasts");function q(e,t={}){const s=t.kind??"plain",o=s==="good"?"checkCircle":s==="bad"?"alert":"info",a=n("div.toast",{role:"status"},n(`span.glyph.${s}`,null,Z(o,16)),n("span.text",null,e),t.action&&n("button",{class:"reset",onclick:()=>{t.action?.run(),a.remove()}},t.action.label));Ta().append(a),window.setTimeout(()=>a.remove(),6e3)}function ue(){F(mn()),document.removeEventListener("keydown",bn,!0)}function bn(e){e.key==="Escape"&&(e.stopPropagation(),ue())}function Ye(e){const t=n("div.scrim",{onmousedown:s=>{s.target===t&&ue()}});e.setAttribute("role","dialog"),e.setAttribute("aria-modal","true"),t.append(e),F(mn(),t),document.addEventListener("keydown",bn,!0),(e.querySelector("button, input, [tabindex]")??e).focus()}function It(e,t,s=[]){return n("div.modal",{tabindex:"-1"},n("h2",null,e),...t.filter(Boolean).map(o=>typeof o=="string"?n("p",null,o):o),s.length?n("div.actions",null,...s):null)}function yn(e,t){const s=n("div.popover",null,t),o=n("div",{style:{position:"fixed",inset:"0",zIndex:"44"},onmousedown:ue});F(mn(),o,s),document.addEventListener("keydown",bn,!0);const a=e.getBoundingClientRect(),i=s.getBoundingClientRect(),r=8,l=Math.min(Math.max(r,a.left),window.innerWidth-i.width-r),c=a.bottom+6,d=c+i.height+r>window.innerHeight?a.top-i.height-6:c;s.style.left=`${Math.round(l)}px`,s.style.top=`${Math.round(Math.max(r,d))}px`,(s.querySelector("button, input, [tabindex]")??s).focus?.()}let Ie=null,wn=0;function st(){window.cancelAnimationFrame(wn),Ie?.node.remove(),Ie=null}function _s(){const e=Ie;if(!e)return;if(!(e.el.isConnected&&(e.el.matches(":hover")||e.el.matches(":focus-visible")))){st();return}wn=window.requestAnimationFrame(_s)}function Fe(e,t){let s=0;const o=()=>{if(!e.isConnected||Ie?.el===e)return;st();const i=n("div.tooltip",{role:"tooltip"},t);document.body.append(i);const r=e.getBoundingClientRect(),l=i.getBoundingClientRect(),c=Math.min(Math.max(8,r.left),window.innerWidth-l.width-8),d=r.top-l.height-8;i.style.left=`${Math.round(c)}px`,i.style.top=`${Math.round(d<8?r.bottom+8:d)}px`,Ie={node:i,el:e},wn=window.requestAnimationFrame(_s)},a=()=>{window.clearTimeout(s),Ie?.el===e&&st()};return e.addEventListener("pointerenter",()=>{window.clearTimeout(s),s=window.setTimeout(o,350)}),e.addEventListener("pointerleave",a),e.addEventListener("pointerdown",a),e.addEventListener("focus",()=>{e.matches(":focus-visible")&&o()}),e.addEventListener("blur",a),e}document.addEventListener("keydown",e=>{e.key==="Escape"&&Ie&&st()},!0);window.addEventListener("blur",st);function Aa(e,t,s,o,a=!1){Ye(It(e,[t],[n("button.btn",{onclick:ue},"Cancel"),n(`button.btn.primary${a?".danger":""}`,{onclick:()=>{ue(),o()}},s)]))}function Bs(e){const t=e.trim().replace(/^#/,""),s=t.length===3?[...t].map(o=>o+o).join(""):t;return/^[0-9a-fA-F]{6}$/.test(s)?[0,2,4].map(o=>parseInt(s.slice(o,o+2),16)/255):null}function Rs([e,t,s]){const o=f=>f<=.04045?f/12.92:((f+.055)/1.055)**2.4,[a,i,r]=[o(e),o(t),o(s)],l=(.4124564*a+.3575761*i+.1804375*r)/.95047,c=.2126729*a+.7151522*i+.072175*r,d=(.0193339*a+.119192*i+.9503041*r)/1.08883,p=f=>f>216/24389?Math.cbrt(f):(24389/27*f+16)/116,[h,g,u]=[p(l),p(c),p(d)];return[116*g-16,500*(h-g),200*(g-u)]}function Ea(e,t){const[s,o,a]=e,[i,r,l]=t,c=Math.PI/180,d=Math.hypot(o,a),p=Math.hypot(r,l),h=((d+p)/2)**7,g=.5*(1-Math.sqrt(h/(h+25**7))),u=(1+g)*o,f=(1+g)*r,b=Math.hypot(u,a),x=Math.hypot(f,l),M=(pe,Te)=>{if(pe===0&&Te===0)return 0;const fe=Math.atan2(Te,pe)/c;return fe<0?fe+360:fe},v=M(u,a),S=M(f,l),y=i-s,$=x-b;let C=0;b*x!==0&&(C=S-v,C>180?C-=360:C<-180&&(C+=360));const D=2*Math.sqrt(b*x)*Math.sin(C*c/2),L=(s+i)/2,I=(b+x)/2;let A=v+S;b*x!==0&&(A=Math.abs(v-S)<=180?(v+S)/2:v+S<360?(v+S+360)/2:(v+S-360)/2);const P=1-.17*Math.cos((A-30)*c)+.24*Math.cos(2*A*c)+.32*Math.cos((3*A+6)*c)-.2*Math.cos((4*A-63)*c),J=30*Math.exp(-(((A-275)/25)**2)),U=I**7,X=2*Math.sqrt(U/(U+25**7)),se=1+.015*(L-50)**2/Math.sqrt(20+(L-50)**2),ee=1+.045*I,Q=1+.015*I*P,V=-Math.sin(2*J*c)*X,ne=y/se,ae=$/ee,te=D/Q;return Math.sqrt(ne*ne+ae*ae+te*te+V*ae*te)}function Mt(e){return e.kind==="gradient"&&e.stops.length>1?e.stops.map(t=>t.toLowerCase()).join(">"):e.traced.toLowerCase()}function We(e){return e.split(">").map(t=>t.trim().toLowerCase())}function it(e){return e.includes(">")}function vn(e,t=!1){const s=We(e);return s.length<2?s[0]??"transparent":t?`radial-gradient(circle, ${s.join(", ")})`:`linear-gradient(90deg, ${s.join(", ")})`}const dt=new Map;function ts(e){const t=dt.get(e);if(t)return t;const s=Bs(e);if(!s)return null;const o=Rs(s);return dt.size>4096&&dt.clear(),dt.set(e,o),o}function xn(e,t){const s=We(e).map(ts),o=We(t).map(ts);if(s.some(l=>!l)||o.some(l=>!l)||!s.length||!o.length)return 1/0;const a=s,i=o,r=(l,c)=>l.reduce((d,p)=>d+Math.min(...c.map(h=>Ea(p,h))),0)/l.length;return Math.max(r(a,i),r(i,a))}const La=2;function _t(e,t){const s=it(e),o=t.find(r=>Mt(r)===e);if(o)return o;let a=null,i=La;for(const r of t){if(r.kind==="gradient"!==s)continue;const l=xn(e,Mt(r));l<=i&&(i=l,a=r)}return a}function Se(e,t){return e===t||xn(e,t)<.5}const za=2,Pa=240;function Da(e,t={}){const s=t.skip??[],o=e.filter(p=>p.share>0),a=[...o].sort((p,h)=>h.share-p.share).slice(0,Pa).map(p=>({member:Mt(p),share:p.share})).filter(p=>!s.some(h=>Se(h,p.member))),i=a.length;if(i<2)return[];const r=a.map((p,h)=>a.map((g,u)=>h===u?0:xn(p.member,g.member))),l=a.map((p,h)=>[h]);let c=i;const d=t.count?Math.max(1,Math.round(t.count)-(o.length-i)):null;for(;!(d!==null&&c<=d);){let p=1/0,h=-1,g=-1;for(let u=0;uza)break;l[h]=[...l[h],...l[g]],l[g]=null,c--;for(let u=0;up.length>1).map(p=>p.map(h=>a[h]).sort((h,g)=>g.share-h.share)).sort((p,h)=>h.reduce((g,u)=>g+u.share,0)-p.reduce((g,u)=>g+u.share,0)).map(p=>({members:p.map(h=>h.member),target:null}))}function kn(e){return[...e.members].sort().join(",")}function Fa(e){return e.filter(t=>t.trimStart().startsWith("merge colors")).map(t=>{const s=t.trim().replace(/^merge colors\s+/,""),o=/;\s*(?:not found in the trace:|no ink near)\s*(.*)$/.exec(s),a=(o?.[1]??"").split(",").map(i=>i.trim().toLowerCase()).filter(i=>/^#[0-9a-f]{6}(>#[0-9a-f]{6})*$/.test(i));return{line:o?s.slice(0,o.index).trim():s,unmatched:a,leftAlone:s.startsWith("group left alone")}})}function Na(e,t){return e?e.unmatched.some(s=>s===t||We(t).includes(s)||Se(s,t)):!1}function Gs(e){const t=(e.groupSuggestions??[]).filter(a=>a.edited),s=[...e.colourGroups.flatMap(a=>a.members),...t.flatMap(a=>a.members)],o=Da(e.palette,{count:e.simplifyTo,skip:s}).filter(a=>!e.dismissedGroups.includes(kn(a)));return[...t,...o]}function Oa(e,t){const s=e.members.filter(a=>_t(a,t));if(s.length<2)return null;let o=e.target;if(o?.startsWith("@")){const a=e.members[Number(o.slice(1))-1],i=a?s.indexOf(a):-1;o=i>=0?`@${i+1}`:null}return{...e,members:s,target:o}}function Sn(e){return(e.groupSuggestions??[]).map((t,s)=>({g:Oa(t,e.palette),index:s})).filter(t=>t.g!==null)}function Ws(e,t){let s=e.target;if(s?.startsWith("@")){const o=Number(s.slice(1))-1;s=o===t?null:o>t?`@${o}`:s}return{...e,members:e.members.filter((o,a)=>a!==t),target:s}}function be(e,t,s=-1,o=!1){return e.map((a,i)=>{if(i===s)return a;const r=a.members.findIndex(l=>Se(l,t));return r<0?a:{...Ws(a,r),...o?{edited:!0}:{}}})}const qe=e=>e.filter(t=>t.members.length>1);function Ia(e,t,s,o,a){const i=e.state;let r=i.colourGroups.map(g=>({...g,members:[...g.members]})),l=(i.groupSuggestions??[]).map(g=>({...g,members:[...g.members]}));const c=a?.closest("[data-drop]")??null,d=c?.dataset.drop??null,p=Number(c?.dataset.index??-1);if((d==="group"||d==="sugg")&&o.kind===d&&o.index===p||d==="ink"&&Se(c?.dataset.member??"",s))return;if(d==="group")r=be(r,s,p),l=be(l,s,-1,!0),r[p].members.some(g=>Se(g,s))||r[p].members.push(s);else if(d==="sugg")r=be(r,s),l=be(l,s,p,!0),l[p].members.some(g=>Se(g,s))||(l[p]={...l[p],members:[...l[p].members,s],edited:!0});else if(d==="ink"){const g=c?.dataset.member??"";r=be(r,s),l=be(l,s,-1,!0);const u=r.findIndex(f=>f.members.some(b=>Se(b,g)));u>=0?r[u].members.push(s):(l=be(l,g,-1,!0),r.push({members:[g,s],target:null}))}else{if(o.kind==="palette")return;o.kind==="group"?r=be(r,s):l=be(l,s,-1,!0)}const h=qe(r);e.set({groupSuggestions:qe(l),paletteSelection:[]}),JSON.stringify(h)!==JSON.stringify(i.colourGroups)&&t.setColourGroups(h)}function _a(e,t){const s=e.state,o=s.paletteSelection;if(o.length<2)return;let a=s.colourGroups.map(l=>({...l,members:[...l.members]}));const i=a.findIndex(l=>l.members.some(c=>o.some(d=>Se(d,c))));let r=s.groupSuggestions??[];for(const l of o)a=be(a,l,i),r=be(r,l,-1,!0);if(i>=0)for(const l of o)a[i].members.some(c=>Se(c,l))||a[i].members.push(l);else a.push({members:[...o],target:null});e.set({groupSuggestions:qe(r),paletteSelection:[]}),t.setColourGroups(qe(a))}const Ba=4;function qs(e,t,s,o,a,i){e.addEventListener("pointerdown",r=>{if(r.button!==0)return;const l=r.clientX,c=r.clientY;let d=null,p=null;const h=f=>{f!==p&&(p?.classList.remove("dropping"),p=f,p?.classList.add("dropping"))},g=f=>{if(!d){if(Math.hypot(f.clientX-l,f.clientY-c){if(window.removeEventListener("pointermove",g),window.removeEventListener("pointerup",u),window.removeEventListener("pointercancel",u),!d)return;d.remove(),document.body.classList.remove("dragging-ink"),h(null);const b=x=>x.stopPropagation();window.addEventListener("click",b,!0),window.setTimeout(()=>window.removeEventListener("click",b,!0),0),f.type!=="pointercancel"&&Ia(t,s,o,a,document.elementFromPoint(f.clientX,f.clientY))};window.addEventListener("pointermove",g),window.addEventListener("pointerup",u),window.addEventListener("pointercancel",u)})}function pt(e,t){return _t(e,t)?.keys??[]}function Hs(e,t){let s=0;const o=()=>{let i=[...e.querySelectorAll("[data-fill]:hover")].pop()??null;if(!i){const l=document.activeElement;l instanceof HTMLElement&&e.contains(l)&&l.matches(":focus-visible")&&(i=l.closest("[data-fill]"))}const r=i?JSON.parse(i.dataset.fill??"[]"):[];return r.length?r:null},a=()=>{const i=o(),r=t.state.hoverFill;(i??[]).join(` -`)!==(r??[]).join(` -`)&&t.set({hoverFill:i}),window.cancelAnimationFrame(s),i&&(s=window.requestAnimationFrame(a))};for(const i of["pointerover","pointerout","focusin","focusout"])e.addEventListener(i,()=>window.requestAnimationFrame(a))}function Us(e,t){const s=e.state,o=Sn(s),a=s.palette.filter(r=>r.kind!=="gradient"),i=s.paletteSelection;return n("div.card.palettecard",null,n("div.cardhead",null,n("span.eyebrow",{style:{display:"flex",alignItems:"center",gap:"6px"}},Z("palette",12),`Palette · ${s.palette.length} ink${s.palette.length===1?"":"s"}`),n("button.reset",{disabled:!a.length,onclick:()=>Ya(e,t)},"Paste brand palette")),s.colourGroups.length?Ra(e,t):null,s.palette.length?qa(e,t,o):null,n("div.inklist",{"data-drop":"list","aria-label":"Inks"},...s.palette.map(r=>Ha(e,t,r))),i.length?n("div.mergebar",null,n("span.muted",null,`${i.length} ticked`),n("button.btn.compact",{"data-ctl":"merge-ticked",disabled:i.length<2,title:"Draw the ticked inks as one: a new group, or the group one of them is already in",onclick:()=>_a(e,t)},"Merge"),n("button.reset",{onclick:()=>e.set({paletteSelection:[]})},"Clear")):null,s.palette.length?n("div",{style:{display:"flex",alignItems:"baseline",justifyContent:"space-between",gap:"10px"}},n("span.muted",{style:{fontSize:"11px",lineHeight:"1.45"}},"Snapping rewrites fills only — no re-trace. Grouping re-traces, so the shapes join."),n("button.reset",{style:{fontSize:"11px",flex:"none"},title:"Custom properties, one per flat ink, in canvas-share order",onclick:()=>void Va(a)},"Copy all as CSS")):n("span.muted",{style:{fontSize:"11px"}},"Trace an image to see its inks."))}function ns(e,t=!1){return n(`span.swatch.mini${it(e)?".ramp":""}`,null,n("i",{style:{background:vn(e,t)}}))}function qt(e){const t=We(e);return t.length>1?`${t.length} stops`:t[0].toUpperCase()}function Ra(e,t){const s=e.state,a=JSON.stringify(s.resultGroups)===JSON.stringify(s.colourGroups)&&s.result?Fa(s.result.engineLog):[];return n("div.cgroups",null,n("div.cgroupshead",null,n("span.eyebrow",null,`Colour groups · ${s.colourGroups.length}`),n("button.reset",{title:"Trace every ink on its own again",onclick:()=>t.setColourGroups([])},"Clear groups")),...s.colourGroups.map((i,r)=>js(e,t,i,r,"group",a[r]??null)))}function Ga(e,t){if(e.target?.startsWith("#"))return{member:e.target.toLowerCase(),label:"custom colour"};if(e.target?.startsWith("@")){const a=e.members[Number(e.target.slice(1))-1]??e.members[0];return{member:a,label:it(a)?"extends this gradient":"becomes this"}}let s=e.members[0],o=-1;for(const a of e.members){const i=_t(a,t)?.share??0;i>o&&(o=i,s=a)}return{member:s,label:"most-used"}}function js(e,t,s,o,a,i){const r=e.state,l=r.palette,c=x=>_t(x,l)?.gradient==="radial",d=Ga(s,l),p=[...new Set([...s.members,d.member].flatMap(x=>pt(x,l)))],h=x=>{if(a==="group"){const M=r.colourGroups.map((v,S)=>S===o?x:v);t.setColourGroups(qe(M))}else{const M=(r.groupSuggestions??[]).map((v,S)=>S===o?{...x,edited:!0}:v);e.set({groupSuggestions:qe(M)})}},g=x=>Na(i,x),u=x=>{const M=pt(x,l);return M.length||a==="sugg"?M:pt(d.member,l)},f=s.members.map((x,M)=>{const v=s.target===`@${M+1}`,S=n(`div.cchip${v?".target":""}${g(x)?".unmatched":""}`,{"data-fill":JSON.stringify(u(x))},n("button.cchipmain",{"data-ctl":`${a}${o}:${x}`,"aria-pressed":String(v),title:v?"The group becomes this. Press again for the most-used colour.":it(x)?"Make the group this gradient: it is extended over the other members":"Make the group this colour",onclick:()=>h({...s,target:v?null:`@${M+1}`})},ns(x,c(x)),n("span.num",null,qt(x))),n("button.cchipx",{"data-ctl":`${a}${o}:${x}:x`,"aria-label":`Take ${qt(x)} out of the group`,title:"Take it out of the group",onclick:()=>h(Ws(s,M))},Z("x",11)));return qs(S,e,t,x,{kind:a,index:o},c(x)),S}),b=n("button.reset",{title:"Paint the whole group one colour of your choosing",onclick:x=>Ua(x.currentTarget,d.member,M=>h({...s,target:M}))},"Custom…");return n(`div.cgroup${a==="sugg"?".suggested":""}`,{"data-drop":a,"data-index":String(o),"data-fill":JSON.stringify(p)},n("div.cchips",null,...f),n("div.cresult",null,n("span.arrow",{"aria-hidden":"true"},"→"),n("span.cresultchip",{"data-fill":JSON.stringify(pt(d.member,l))},ns(d.member,c(d.member)),n("span.num",null,qt(d.member))),n("span.muted",null,d.label),n("span",{style:{flex:"1"}}),b),a==="sugg"?n("div.cactions",null,n("button.btn.compact.primary",{onclick:()=>{const x=(r.groupSuggestions??[]).filter((M,v)=>v!==o);e.set({groupSuggestions:x}),t.setColourGroups([...r.colourGroups,{members:s.members,target:s.target}])}},"Accept"),n("button.btn.compact",{onclick:()=>e.set({groupSuggestions:(r.groupSuggestions??[]).filter((x,M)=>M!==o),dismissedGroups:[...r.dismissedGroups,kn(s)]})},"Dismiss")):[Wa(i),n("div.cactions",null,n("span",{style:{flex:"1"}}),s.target?n("button.reset",{title:"Become the member covering the most of the image",onclick:()=>h({...s,target:null})},"Most-used"):null,n("button.reset",{title:"Trace these inks on their own again",onclick:()=>t.setColourGroups(r.colourGroups.filter((x,M)=>M!==o))},"Ungroup"))])}function Wa(e){if(!e)return n("span.muted.creport",null,"Merged on the next trace.");if(e.leftAlone)return n("span.creport.warn",{title:e.line},Z("alert",12),"Left alone: fewer than two of these colours are in the image now.");if(e.unmatched.length){const s=e.unmatched.map(o=>it(o)?"a gradient":o.toUpperCase()).join(", ");return n("span.creport.warn",{title:`${e.line}; not found: ${e.unmatched.join(", ")}`},Z("alert",12),`${s} ${e.unmatched.length===1?"is":"are"} no longer in the image; the rest merged.`)}const t=e.line.replace(/ -> /," → ").replace(/#[0-9a-f]{6}/gi,s=>s.toUpperCase());return n("span.muted.creport",{title:e.line},t)}function qa(e,t,s){const o=e.state,a=o.palette.length,i=l=>{const c=l===null?null:Math.max(1,Math.min(a,Math.round(l))),d=(o.groupSuggestions??[]).filter(p=>p.edited);e.set({simplifyTo:c,groupSuggestions:Gs({...o,simplifyTo:c,groupSuggestions:d})})},r=n("input.numberfield.simplify",{type:"text",inputmode:"numeric",value:String(o.simplifyTo??a),"aria-label":"Simplify the palette to this many colours",onchange:l=>{const c=Number(l.target.value);Number.isFinite(c)&&c>=1&&i(c>=a?null:c)}});return n("div.cgroups.suggestions",null,n("div.cgroupshead",null,n("span.eyebrow",null,s.length?`Suggested · ${s.length}`:"Suggested"),s.length>1?n("button.reset",{title:"Accept every suggestion below",onclick:()=>{e.set({groupSuggestions:[]}),t.setColourGroups([...o.colourGroups,...s.map(({g:l})=>({members:l.members,target:l.target}))])}},"Accept all"):null),n("div.simplifyrow",null,n("span.muted",null,"Colours"),n("button.btn.compact.icon",{"aria-label":"One colour fewer",disabled:(o.simplifyTo??a)<=1,onclick:()=>i((o.simplifyTo??a)-1)},Z("minus",12)),r,n("button.btn.compact.icon",{"aria-label":"One colour more",disabled:o.simplifyTo===null,onclick:()=>i((o.simplifyTo??a)+1>=a?null:(o.simplifyTo??a)+1)},"+"),n("span.muted",{style:{fontSize:"11px"}},o.simplifyTo===null?`of ${a} · near duplicates`:`of ${a} · merging down`)),...s.length?s.map(({g:l,index:c})=>js(e,t,l,c,"sugg",null)):[n("span.muted",{style:{fontSize:"11px"}},o.simplifyTo===null?"No near-duplicate inks: nothing to propose.":"Nothing more to propose at this count.")])}function Ha(e,t,s){const o=e.state,a=Mt(s),i=s.kind==="gradient",r=s.snappedDe00!==null,l=o.paletteSelection.includes(a),c=o.colourGroups.some(g=>g.members.some(u=>Se(u,a))),d=n("button.inkmain",{"data-ctl":`ink:${a}`,title:i?`${s.gradient??"linear"} gradient · ${s.stops.map(g=>g.toUpperCase()).join(" → ")}`:"Snap this ink to a colour of your choosing","aria-disabled":i?"true":null,onclick:g=>{i||ja(g.currentTarget,s,t)}},n(`span.swatch${r?".snapped":""}${i?".ramp":""}`,null,n("i",{style:{background:i?vn(a,s.gradient==="radial"):s.hex}})),n("span.hex",null,i?s.gradient==="radial"?"radial":"linear":s.hex.toUpperCase()),n("span.share",null,$e(s.share)),n(`span.snap${r?".on":""}`,null,i?`${s.stops.length} stops`:r?`snapped · ΔE ${re(s.snappedDe00)}`:"snap to…")),p=()=>e.set({paletteSelection:l?o.paletteSelection.filter(g=>g!==a):[...o.paletteSelection,a]}),h=n(`div.ink${c?".grouped":""}`,{"data-drop":"ink","data-member":a,"data-fill":JSON.stringify(s.keys)},n("button.inkpick",{"data-ctl":`pick:${a}`,role:"checkbox","aria-checked":String(l),"aria-label":`Tick ${i?"this gradient":s.hex.toUpperCase()} to merge it`,title:"Tick inks, then Merge to draw them as one. Or drag one onto another.",onclick:p,onkeydown:g=>{g.key===" "&&(g.preventDefault(),g.stopPropagation(),g.repeat||p())}},l?Z("check",11):null),d);return qs(h,e,t,a,{kind:"palette"},s.gradient==="radial"),h}function Ua(e,t,s){const o=We(t)[0]??"#000000",a=n("input.numberfield",{type:"text",value:o.toUpperCase(),spellcheck:"false",style:{width:"100%",textAlign:"left",height:"30px",padding:"0 9px"}}),i=n("input",{type:"color",value:o,style:{width:"100%",height:"60px",border:"none",background:"none",padding:"0"},oninput:()=>{a.value=i.value.toUpperCase()}}),r=()=>{const l=a.value.trim().toLowerCase(),c=l.startsWith("#")?l:`#${l}`;if(!/^#[0-9a-f]{6}$/.test(c)){q(`${a.value} is not a colour; write it as #RRGGBB.`,{kind:"bad"});return}s(c),ue()};yn(e,n("div",{style:{width:"260px",padding:"12px",display:"flex",flexDirection:"column",gap:"11px"}},i,n("span.muted",{style:{fontSize:"11px"}},"The group becomes"),n("div",{style:{display:"flex",gap:"6px"}},a,n("button.btn.compact",{style:{background:"var(--accent)",borderColor:"var(--accent)",color:"var(--accent-ink)",fontWeight:"600"},onclick:r},"Use")))),a.addEventListener("keydown",l=>{l.key==="Enter"&&r()})}function ja(e,t,s){const o=n("input.numberfield",{type:"text",value:t.hex.toUpperCase(),spellcheck:"false",style:{width:"100%",textAlign:"left",height:"30px",padding:"0 9px"}}),a=n("input",{type:"color",value:t.hex,style:{width:"100%",height:"60px",border:"none",background:"none",padding:"0"},oninput:()=>{o.value=a.value.toUpperCase()}}),i=n("span",{style:{fontSize:"11.5px",color:"var(--gold)"}},"You are overriding a measurement.");yn(e,n("div",{style:{width:"300px",padding:"12px",display:"flex",flexDirection:"column",gap:"11px"}},a,n("div",{style:{display:"flex",gap:"8px",alignItems:"center"}},n("span.swatch",null,n("i",{style:{background:t.traced}})),n("div",{style:{display:"flex",flexDirection:"column"}},n("span.muted",{style:{fontSize:"11px"}},"traced"),n("span.num",{style:{fontSize:"12.5px"}},`${t.traced.toUpperCase()} · ${$e(t.share)} of canvas`))),n("span.muted",{style:{fontSize:"11px"}},"Snap to"),n("div",{style:{display:"flex",gap:"6px"}},o,n("button.btn.compact",{style:{background:"var(--accent)",borderColor:"var(--accent)",color:"var(--accent-ink)",fontWeight:"600"},onclick:()=>{s.snap([{from:t.traced,to:o.value.trim().toLowerCase()}]),ue()}},"Snap")),i))}async function Va(e){const t=e.map((s,o)=>` --ink-${o+1}: ${s.hex.toLowerCase()}; /* ${$e(s.share)} of canvas */`).join(` -`);try{await Pt(`:root { -${t} -} -`),q(`${e.length} ink${e.length===1?"":"s"} copied as CSS.`,{kind:"good"})}catch(s){q(String(s),{kind:"bad"})}}function Ya(e,t){const s=e.state.palette.filter(l=>l.kind!=="gradient"),o=n("textarea",{rows:"5",spellcheck:"false",placeholder:`#12443E -#E9B24C -rgb(207, 198, 180)`,style:{width:"100%",border:"1px solid var(--rule2)",borderRadius:"var(--radius)",background:"var(--paper)",color:"var(--ink)",padding:"10px 12px",font:"12px/1.8 var(--font-mono)",resize:"vertical"}}),a=n("div",{style:{display:"flex",flexDirection:"column",gap:"7px"}});let i=[];const r=async()=>{i=await B.matchPalette(s.map(l=>l.traced),o.value),F(a,...i.map(l=>n("div",{style:{display:"flex",alignItems:"center",gap:"9px",fontSize:"12px"},class:"num"},n("span.swatch",{style:{width:"20px",height:"20px"}},n("i",{style:{background:l.from}})),n("span.muted",null,l.from.toUpperCase()),n("span.muted",null,"→"),n("span.swatch",{style:{width:"20px",height:"20px"}},n("i",{style:{background:l.to}})),n("span.dim",null,l.to.toUpperCase()),n("span",{style:{marginLeft:"auto",color:l.de00<.5?"var(--good)":"var(--gold)"}},l.de00<.05?"exact":`${re(l.de00)} dE00`))))};o.addEventListener("input",()=>void r()),Ye(It("Paste a brand palette",[o,n("span.muted",{style:{fontSize:"11.5px"}},"Hex, RGB or CSS variables, one per line or separated by commas. We match each to the nearest traced ink."),a],[n("button.btn",{onclick:ue},"Cancel"),n("button.btn.primary",{onclick:()=>{t.snap(i.map(l=>({from:l.from,to:l.to}))),ue()}},`Snap ${s.length} inks`)]))}const Vs=2.5,Za=256;function Ys(e,t){return!!e?.lossy||t!==null&&t.noiseLevels>=Vs}function Ja(e){return e.filter(t=>t.share>=.005)}function Xa(e){if(e.length!==2||e.some(l=>l.kind==="gradient"))return!1;const t=e.map(l=>Bs(l.hex)).map(l=>l?Rs(l):null);if(t.some(l=>l===null))return!1;const[s,o]=t,a=l=>Math.hypot(l[1],l[2])<12,[i,r]=s[0]65}function Ka(e){const{source:t,report:s,palette:o,losses:a,facts:i,settings:r}=e;if(!t||!s)return[];const l=[];if(Ys(t,i)&&r.cleanUpDamage==="off"){const g=[t.lossy?t.container:null,i?`noise ${i.noiseLevels.toFixed(1)} levels`:null].filter(Boolean);l.push({id:"photo",preset:"photo-or-scan",title:"Looks like a photo, a scan or a screenshot",evidence:g.join(" · "),action:"Photo or scan, with the denoiser"})}const c=Ja(o);!r.blackAndWhite&&!r.monochrome&&Xa(c)&&l.push({id:"bw",preset:"black-and-white",title:"Two colours, black and white",evidence:`${c.length} inks cover the canvas`,action:"Two-tone scan"}),!r.lineArt&&a.some(g=>g.kind==="strokes")&&!r.blackAndWhite&&l.push({id:"lineart",preset:"line-art",title:"Drawn with strokes",evidence:"line work came back as filled outlines",action:"Line art, as strokes"});const d=Math.max(t.width,t.height),p=o.some(g=>g.kind==="gradient");return!(r.speckleFloor<=1&&r.maxColours<=16)&&d<=Za&&!p&&c.length<=16&&l.length===0&&l.push({id:"icon",preset:"icon",title:"A small flat mark",evidence:`${d} px, ${c.length} colour${c.length===1?"":"s"}`,action:"Icon, keeping one-pixel details"}),l.slice(0,2)}function De(e,t,s){return(e?.controls??[]).filter(o=>t[o.key]!==s[o.key]).map(o=>o.key)}function Zs(e,t,s){const o=e?.presets.find(a=>a.id===s);if(o){const a=De(e,t,o.settings).length,i=o.id==="logo"?"the Logo preset, the default":`the ${o.name} preset`;return a?`${i}, with ${a} control${a===1?"":"s"} changed last time`:i}return"your settings from last time"}function $n(e){const t=e.state,s=t.auto;return s?.report?Ka({source:t.source,report:s.report,palette:s.palette,losses:s.losses,facts:t.facts,settings:s.settings,preset:s.preset}):[]}function Js(e){const t=e.state;return t.auto?t.preset!==t.auto.preset||De(t.caps,t.settings,t.auto.settings).length>0:!1}function Qa(e){const t=e.state;return t.auto?.report?Js(e)||Sn(t).length>0||$n(e).length>0:!1}function Xs(e,t,s=!1){const o=e.state,a=o.auto;if(!a?.report)return null;const i=$n(e),r=Sn(o),l=Js(e),c=o.caps?.presets.find(p=>p.id===o.preset),d=i.map(p=>{const h=o.preset===p.preset;return n("div.autorow",null,n("div.autotext",null,n("span.autotitle",null,p.title),n("span.muted.num",null,p.evidence)),h?n("span.autotaken",null,"chosen"):n("button.btn.compact",{"data-ctl":`auto-try:${p.id}`,title:`Switch to ${p.action}. An ordinary preset: Back to Auto undoes it.`,onclick:()=>t.setPreset(p.preset)},`Try ${p.action}`))});if(r.length){const p=r.reduce((h,{g})=>h+g.members.length,0);d.push(n("div.autorow",null,n("div.autotext",null,n("span.autotitle",null,`${r.length} colour group${r.length===1?"":"s"} suggested`),n("span.muted.num",null,`${p} inks that look like the same colour measured twice`)),n("div.autobtns",null,n("button.btn.compact",{"data-ctl":"auto-groups-accept",title:"Draw each suggested group as one colour; the trace runs again so the shapes join",onclick:()=>{e.set({groupSuggestions:[]}),t.setColourGroups([...o.colourGroups,...r.map(({g:h})=>({members:h.members,target:h.target}))])}},"Accept"),n("button.reset",{"data-ctl":"auto-groups-dismiss",onclick:()=>e.set({groupSuggestions:[],dismissedGroups:[...o.dismissedGroups,...r.map(({g:h})=>kn(h))]})},"Dismiss"))))}return n("div.autochose",null,s?null:n("span.autolead",null,`Auto traced with ${Zs(o.caps,a.settings,a.preset)}.`),...d,!d.length&&!s?n("span.muted",null,"Nothing in its report suggests another preset."):null,l?n("div.autorow.moved",null,n("div.autotext",null,n("span.autotitle",null,c?`Now: ${c.name}`:"Now: your own settings"),n("span.muted",null,"An ordinary setting, shown under Tune")),n("button.reset",{"data-ctl":"auto-back",onclick:t.backToAuto},"Back to Auto")):null)}const Ht=e=>`${Math.round(e/(1024*1024))}`;function Mn(e){return e.phase==="stored"||e.phase==="preparing"||e.phase==="ready"?100:e.phase!=="downloading"||!e.total?null:Math.min(100,Math.floor(e.got/e.total*100))}function Ct(e,t){if(!e)return t?"Starting the denoiser…":null;switch(e.phase){case"downloading":{const s=Mn(e);return e.total?`Downloading the denoiser, ${Ht(e.got)} of ${Ht(e.total)} MB, ${s}%`:`Downloading the denoiser, ${Ht(e.got)} MB`}case"stored":case"preparing":return t?"Preparing the denoiser…":null;case"failed":return"The denoiser did not download";case"idle":return t?"Starting the denoiser download…":null;case"ready":return null}}function Cn(e){return/[….]$/.test(e)?e:`${e}.`}function Tt(e){if(!e||e.phase!=="downloading"&&e.phase!=="preparing"&&e.phase!=="stored")return null;const t=Mn(e)??0;return n("div.progress.fetchbar",{role:"progressbar","aria-label":"Denoiser download","aria-valuemin":"0","aria-valuemax":"100","aria-valuenow":String(t)},n("i",{style:{width:`${t}%`}}))}function Ks(e,t,s,o){const a=e.state.settings[t.key],i=a!==o[t.key],r=(...u)=>n(i?"div.control.changed":"div.control",null,...u);if(t.kind==="switch"){const u=n("button.switch",{role:"switch","aria-checked":String(!!a),"aria-label":t.label,"data-ctl":`${t.key}:switch`,onclick:()=>s.changeSetting(t.key,!a)});return r(n("div.controlhead",null,Fe(n("span.label",{tabindex:"0"},t.label),t.help),u))}if(t.kind==="tri"){const u=[["off","Off"],["auto","Auto"],["on","On"]];return r(n("div.controlhead",null,Fe(n("span.label",{tabindex:"0"},t.label),t.help),n("div.seg",null,...u.map(([f,b])=>n("button",{"aria-pressed":String(a===f),"data-ctl":`${t.key}:${f}`,onclick:()=>s.changeSetting(t.key,f)},b)))))}if(t.kind==="choice"){const u=t.stops.map(f=>[f.label.toLowerCase(),f.label]);return r(n("div.controlhead",null,Fe(n("span.label",{tabindex:"0"},t.label),t.help),n("div.seg",null,...u.map(([f,b])=>n("button",{"aria-pressed":String(a===f),"data-ctl":`${t.key}:${f}`,onclick:()=>s.changeSetting(t.key,f)},b)))))}const l=u=>Math.pow((Number(u)-t.min)/(t.max-t.min||1),1/t.curve)*1e3,c=u=>t.min+Math.pow(u/1e3,t.curve)*(t.max-t.min),d=u=>t.decimals===0?String(Math.round(u)):u.toFixed(t.decimals),p=n("input.numberfield",{type:"text",value:d(Number(a)),inputmode:"decimal","aria-label":`${t.label}${t.unit?` in ${t.unit}`:""}`,"data-ctl":`${t.key}:field`,onchange:u=>{const f=Number(u.target.value);Number.isFinite(f)&&s.changeSetting(t.key,Math.min(t.max,Math.max(t.min,f)))}}),h=u=>{const f=c(Number(u.value));return t.decimals===0?Math.round(f):Number(f.toFixed(t.decimals))},g=n("input.slider",{type:"range",min:"0",max:"1000",step:"1",value:String(Math.round(l(Number(a)))),"aria-label":t.label,"aria-valuetext":`${d(Number(a))} ${t.unit}`.trim(),"data-ctl":`${t.key}:slider`,oninput:u=>{const f=u.target,b=h(f);p.value=d(b),f.setAttribute("aria-valuetext",`${d(b)} ${t.unit}`.trim())},onchange:u=>s.changeSetting(t.key,h(u.target))});return r(n("div.controlhead",null,Fe(n("span.label",{tabindex:"0"},t.label),t.help),t.unit?n("span.unit",null,t.unit):null,p),g,t.stops.length?n("div.stops",null,...t.stops.map(u=>n("span",null,u.label))):null)}const Ut={axisHandles:.34,smoothJoins:.89,alignedNodes:.86};function ei(){return n("div.card.emptycard",null,n("span.eyebrow",null,"Quality report"),n("p",null,"Open an image and its report lands here: the measured colour difference, what the drawing cost in coordinates, how editable it is, what could not be recovered, and its palette."),n("span.faint",null,"The controls are under Tune; they apply to whatever you open next."))}function ti(){return n("div.benchmark",null,n("span.eyebrow.label",null,"Benchmark"),n("p",null,"Across our published 21-case set, VTracer's defaults average 4.4× the coordinates at 10× the colour error. ",n("span.dim",null,"Not a measurement of this file.")))}function ni(e){const t=e.state,s=t.caps?.stages??[],o=new Map(t.liveStages.map(i=>[i.name,i.ms])),a=t.liveNow;return n("div.card",null,n("div.cardhead",null,n("span",{style:{fontSize:"12px",fontWeight:"500"}},`Tracing at ${hn(t,t.tracingTier)??"—"} px`),n("span.muted.num",{style:{fontSize:"11px"},"data-live":"elapsed"},Ot(t))),n("div.sweep",null,n("i")),n("div.stagelist",null,...s.map(i=>{const r=o.get(i);return a?.stage===i?n("div.stagerow.running",null,n("span.mark",null,"›"),n("span.name",null,i,n("span.sub",null,a.step?gn(a.step):a.what)),n("span.num",{"data-live":"stage"})):n(`div.stagerow${r===void 0?"":".done"}`,null,n("span.mark",null,r===void 0?"·":"✓"),n("span.name",null,i),n("span.muted",null,r===void 0?"—":`${r.toFixed(0)} ms`))})))}function si(e,t){const s=e.state,o=s.report,a=o?s.result?.tier==="draft"?`draft · ${o.tracedPx} px`:`full trace · ${o.tracedPx} px`:"—",i=r=>`${(Math.sqrt(Math.min(r,5)/5)*100).toFixed(1)}%`;return n("div.card",{style:{opacity:s.tracing?"0.45":"1",transition:"opacity var(--d-fast)"}},n("div.cardhead",null,n("span.eyebrow",null,"Quality report"),n("span.muted",{style:{fontSize:"11px"}},a)),n("div.headline",null,n("span.figure",null,re(o?.meanDe00)),n("div.of",null,n("span.dim",{style:{fontSize:"11.5px"}},"mean colour difference"),n("span.muted.num",{style:{fontSize:"11px"}},`median ${re(o?.medianDe00)} · dE00`))),n("div",{style:{display:"flex",flexDirection:"column",gap:"5px"}},n("div.meter",{role:"img","aria-label":`Colour difference ${re(o?.meanDe00)} of a 0 to 5 scale`},n("span.threshold",{style:{left:i(1)}}),o?.meanDe00!=null?n("span.needle",{style:{left:i(o.meanDe00)}}):null),n("div.meterscale",null,n("span",null,"0 invisible"),n("span",null,"1.0 just visible"),n("span",null,"5.0"))),n("div.stats",null,...oi(e).map(([r,l])=>n("div",null,n("span.k",null,r),n("span.v",null,l)))),o&&s.worstCorner?n("button.reset",{style:{textAlign:"left"},onclick:t.jumpToWorst},`Find the worst corner · ${re(s.worstCorner.de00)} dE00 →`):null)}function oi(e){const t=e.state.report;return t?[["coordinates",oe(t.coordinates)],["paths",oe(t.paths)],["colours found",oe(t.colours)],["segments",oe(t.segments)],["file size",ie(t.bytes)],["minified",t.minifiedBytes===null?"already":ie(t.minifiedBytes)],["traced at",`${t.tracedPx} px`],["time taken",nt(t.seconds)]]:[["coordinates","—"],["paths","—"],["colours found","—"],["segments","—"],["file size","—"],["minified","—"]]}function ai(e,t){const s=e.state,o=s.report?.structure;if(s.report&&!o)return null;const a=s.settings.editability,r=(o?[{label:"Handles on an axis",help:"Curve handles that point exactly along x or y, as an artist places them with the keyboard. Counted over every cubic handle in the drawing.",part:o.axisHandles,whole:o.handles,artist:Ut.axisHandles},{label:"Smooth joins",help:"Places where two curves meet with their tangents within a degree of each other, so the outline has no kink for a hand to trip on.",part:o.smoothJoins,whole:o.joins,artist:Ut.smoothJoins},{label:"Nodes sharing a coordinate",help:"Nodes with the same x or the same y as another node, so a group of them can be selected and aligned in one move.",part:o.alignedNodes,whole:o.nodes,artist:Ut.alignedNodes}]:[]).filter(c=>c.whole>0),l=o!==void 0&&o.cubics===0;return n("div.card.structure",{style:{opacity:s.tracing?"0.45":"1",transition:"opacity var(--d-fast)"}},n("div.cardhead",null,n("span.eyebrow",null,"Editability"),n("button.reset",{title:a?"Stop moving nodes and handles onto what an artist would draw":"Move nodes and handles onto what an artist would draw. Costs about 0.04 dE00 on icons, and the report above shows the price on this image.",onclick:()=>t.changeSetting("editability",!a)},a?"Editable structure on · turn off":"Make it editable")),...r.map(c=>{const d=c.part/c.whole;return n("div.srow",{title:c.help},n("span.k",null,c.label),n("span.v",null,$e(d),n("span.of",null,`${c.part} of ${c.whole}`)),n("div.sbar",{role:"img","aria-label":`${c.label}: ${$e(d)}, ${c.part} of ${c.whole}; hand-drawn files ${$e(c.artist)}`},n("i",{style:{width:`${(d*100).toFixed(1)}%`}}),n("b",{style:{left:`${(c.artist*100).toFixed(1)}%`}})))}),l?n("span.muted",{style:{fontSize:"11px",lineHeight:"1.45"}},"This drawing is lines and arcs, so there are no curve handles to tidy; only its nodes can line up."):null,n("span.muted",{style:{fontSize:"11px",lineHeight:"1.45"}},"The tick is where hand-drawn files sit (median of 1,544 artist SVGs). ",a?"Nothing moves further than the trace's own tolerance.":"A trace fitted for pixels alone starts near zero on all three."))}function ii(e){const t=e.state,s=o=>({lettering:"type",lossy:"image",strokes:"wand",ramp:"droplet",detail:"layers"})[o]??"info";return n("div.card",null,n("span.eyebrow",null,"What this trace could not recover"),...t.losses.map(o=>ri(o,s(o.kind))),n("div.nothinglost",null,n("span.glyph",null,Z("checkCircle",15)),n("span",null,t.losses.length?"Nothing else we can detect was lost.":"Nothing we can detect was lost.")))}function ri(e,t){const s=n("div.why",{hidden:!0},e.why),o=n("button.expand",{"aria-expanded":"false",onclick:()=>{const a=s.hasAttribute("hidden");s.toggleAttribute("hidden",!a),o.setAttribute("aria-expanded",String(a)),o.textContent=a?"Hide":"Why"}},"Why");return n("div.loss",null,n("span.glyph",null,Z(t,15)),n("div",{style:{display:"flex",flexDirection:"column",gap:"3px",minWidth:"0"}},n("span.text",null,e.text),n("div.meta",null,o,e.link?n("a",{href:e.link.href,"data-external":e.link.href.startsWith("http")?"1":null},e.link.label):null),s))}const Tn=new Set(["mode","cleanUpDamage","editability"]);function li(e,t){const s=n("div.railtabs"),o=n("div.railmodes"),a=n("div.railscroll"),i=n("div.railfoot"),r=n("aside.rail",{"aria-label":"Controls"},s,o,a,i),l={result:0,tune:0};let c=e.state.railTab;const d=()=>{const u=e.state,f=document.activeElement instanceof HTMLElement&&a.contains(document.activeElement)?document.activeElement.getAttribute("data-ctl"):null;l[c]=a.scrollTop,F(a,...u.railTab==="result"?[u.tracing?ni(e):null,...mi(e,t)]:pi(e,t)),c=u.railTab,a.scrollTop=l[c]??0,f&&a.querySelector(`[data-ctl="${f}"]`)?.focus({preventScroll:!0})},p=()=>{const u=e.state,f=i.querySelector(".readout.busy");if(u.tracing&&f){f.querySelector(".stage").textContent=Nt(u);return}F(i,...wi(e,t))},h=()=>{const u=document.activeElement instanceof HTMLElement&&o.contains(document.activeElement)?document.activeElement.getAttribute("data-ctl"):null;F(o,...ci(e,t)),u&&o.querySelector(`[data-ctl="${u}"]`)?.focus({preventScroll:!0})},g=()=>{F(s,...ui(e,t)),h(),d(),p()};return e.on(["caps","prefs","preset","settings","railTab","groupsOpen"],g),e.on(["denoiserFetch"],h),e.on(["tracing","liveStages","liveNow","result","report","palette","losses","source","worstCorner","previous","colourGroups","groupSuggestions","resultGroups","paletteSelection","simplifyTo","auto","facts","chooser","wizard","autoNoteHidden"],()=>{e.state.railTab==="result"&&d(),p();const u=s.querySelector('[data-note="result"]');u&&(u.textContent=eo(e))}),Hs(a,e),g(),r}function ci(e,t){const s=e.state,o=x=>s.caps?.controls.find(M=>M.key===x)?.help??"",a=s.settings.mode??"quality",i=n("div.mode.mode-engine",null,n("div.modehead",null,Fe(n("span.modetitle",{tabindex:"0"},"Engine"),o("mode")||"Quality places every edge to a fraction of a pixel and fits the fewest curves that match the image: the closest trace, and the default. Fast traces each shape in a single pass, many times quicker, for previews, batches and very large images."),n("span.modestate",null,a==="fast"?"one pass":"closest fit")),n("div.seg.big",{role:"group","aria-label":"Tracing Engine"},n("button",{"aria-pressed":String(a==="quality"),"data-ctl":"mode:quality",title:"Quality: the closest trace, edges placed to a fraction of a pixel",onclick:()=>t.changeSetting("mode","quality")},"Quality"),n("button",{"aria-pressed":String(a==="fast"),"data-ctl":"mode:fast",title:"Fast: each shape traced in one pass, many times quicker, a little less exact",onclick:()=>t.changeSetting("mode","fast")},"Fast"))),r=s.caps?.denoiser,l=s.settings.cleanUpDamage,c=r?.supported??!1,d=c&&l!=="off"&&s.denoiserFetch?.phase!=="ready"?s.denoiserFetch:void 0,p=d!==void 0,h=!p&&c&&r!==void 0&&!r.installed&&l!=="off",g={off:"pixels as they are",auto:"only if damaged",on:"always"},u=n("div.mode",null,n("div.modehead",null,Fe(n("span.modetitle",{tabindex:"0"},"Denoiser"),o("cleanUpDamage")),p&&d?.phase==="failed"?n("button.reset",{"data-ctl":"denoiser-retry",onclick:t.retryDenoiser},"Retry"):h?n("button.reset",{onclick:t.openDenoiser,title:"It runs on this computer; nothing is uploaded"},"Download it"):n("span.modestate",null,c?g[l]:"needs its own tab")),n("div.seg.big",{role:"group","aria-label":"Denoiser"},...["off","auto","on"].map(x=>n("button",{"aria-pressed":String(l===x),disabled:!c,"data-ctl":`cleanUpDamage:${x}`,onclick:()=>t.changeSetting("cleanUpDamage",x)},x==="off"?"Off":x==="auto"?"Auto":"On"))),p?n("div.fetchnote",{"data-ctl":"denoiser-progress",role:"status"},Tt(d??null),n("span.fetchline",null,d?.phase==="failed"?`${Ct(d,!0)}: ${d.message??"the connection dropped"}.`:Cn(Ct(d??null,!0)??"")),n("span.muted",null,d?.phase==="failed"?"The trace shown is without it.":"Shown without it until then; it traces again by itself.")):null),f=s.settings.editability,b=n("div.mode",null,n("div.modehead",null,Fe(n("span.modetitle",{tabindex:"0"},"Editable"),o("editability"))),n("button.bigswitch",{role:"switch","aria-checked":String(f),"aria-label":"Editable structure","data-ctl":"editability:switch",onclick:()=>t.changeSetting("editability",!f)},n("span.knob"),n("span.state",null,f?"On":"Off")));return[i,n("div.railmodes-row",null,u,b)]}function Qs(e,t){const s=t.baseSettings(),o=e.state;return(o.caps?.controls??[]).filter(a=>at(a,o.settings)&&o.settings[a.key]!==s[a.key]).length}function eo(e){const t=e.state.report;return t?`${re(t.meanDe00)} dE00`:""}function ui(e,t){const s=e.state,o=Qs(e,t),a=(i,r,l,c)=>n("button",{role:"tab","aria-selected":String(s.railTab===i),title:c,onclick:()=>e.set({railTab:i})},n("span.tablabel",null,r),n("span.tabnote",{"data-note":i},l));return[n("div.railseg",{role:"tablist","aria-label":"What the rail shows"},a("result","Result",eo(e),"What this trace produced"),a("tune","Tune",o?`${o} changed`:"","The settings that make the next trace")),n("p.tabcaption",null,s.railTab==="result"?"What this trace produced.":"The settings for the next trace.")]}function di(e,t){const s=e.state;if(s.settings.mode!=="fast")return null;const o=(s.caps?.controls??[]).filter(a=>!Tn.has(a.key)&&!at(a,s.settings)).length;return o?n("div.guideline",{"data-note":"quality-only"},n("span.faint",null,`${o} more control${o===1?" is":"s are"} for Quality: switch to see ${o===1?"it":"them"}.`),n("button.reset",{"data-ctl":"show-quality",title:"Switch the engine to Quality",onclick:()=>t.changeSetting("mode","quality")},"Quality")):null}function pi(e,t){return[hi(e,t),di(e,t),fi(e,t),...yi(e,t)]}function hi(e,t){return e.state.source?n("div.guideline",null,n("span.faint",null,"Not sure which controls matter here?"),n("button.reset",{"data-ctl":"open-wizard",title:"The main choices one step at a time, each explained, with the result beside Auto's",onclick:t.openWizard},"Walk me through it")):null}function fi(e,t){const s=e.state,o=s.caps?.presets??[],a=s.prefs?.saved??[],i=Ds(s.caps?.platform),r=(p,h,g,u,f)=>n(f?"button.preset.saved":"button.preset",{"aria-pressed":String(s.preset===p),title:`${h} — ${g}${u!==null?` (${i}+${u})`:""}`,onclick:()=>t.setPreset(p)},n("span.name",null,h),f?n("span.forget",{role:"button",tabindex:"0","aria-label":`Forget ${h}`,title:`Forget ${h}`,onclick:b=>{b.stopPropagation(),f()}}):null),l=o.find(p=>p.id===s.preset),c=l?`${l.subtitle}.`:a.find(p=>p.id===s.preset)?"One of your saved presets.":"Controls moved from the preset they started at.",d=Qs(e,t);return n("div.presetblock",null,n("div.cardhead",null,n("span.eyebrow",null,"Preset"),n("button.reset",{disabled:!s.caps||!d,title:"Put every control back to where the preset had it",onclick:()=>s.preset&&t.setPreset(s.preset)},d?`Reset ${d} changed`:"Nothing changed")),n("div.presets",{role:"group","aria-label":"Presets"},...o.map((p,h)=>r(p.id,p.name,p.subtitle,h<10?(h+1)%10:null,null)),...a.map(p=>r(p.id,p.name,"Saved preset",null,()=>t.deletePreset(p.id)))),n("div.traybar",null,n("span.faint",null,c),n("button.reset",{disabled:!s.caps,title:`Remember all ${s.caps?.controls.length??""} controls exactly as they stand`,onclick:()=>gi(e,t)},"Save as preset")))}function gi(e,t){const s=e.state.caps?.presets.find(i=>i.id===e.state.preset),o=n("input.numberfield",{type:"text",maxlength:"40",spellcheck:"false",placeholder:s?`${s.name}, adjusted`:"Northwind, flat",style:{width:"100%",textAlign:"left",height:"32px",padding:"0 10px"}}),a=()=>{t.savePreset((o.value.trim()||o.placeholder).slice(0,40)),ue()};Ye(It("Save current as preset",[o,n("span.muted",{style:{fontSize:"11.5px",lineHeight:"1.5"}},`All ${e.state.caps?.controls.length??""} controls, as they stand. A saved preset is a snapshot rather than a set of differences from the defaults, so it will not drift when those move.`)],[n("button.btn",{onclick:ue},"Cancel"),n("button.btn.primary",{onclick:a},"Save")])),o.focus(),o.addEventListener("keydown",i=>{i.key==="Enter"&&a()})}function mi(e,t){return e.state.source?[bi(e,t),si(e,t),ai(e,t),ii(e),Us(e,t),ti()]:[ei()]}function bi(e,t){const s=e.state;if(s.chooser||s.wizard||s.autoNoteHidden||!Qa(e))return null;const o=Xs(e,t,!0);return o?n("div.card.autocard",null,n("div.cardhead",null,n("span.eyebrow",null,"Auto chose"),n("div",{style:{display:"flex",gap:"10px"}},n("button.reset",{"data-ctl":"auto-customise",onclick:t.openWizard},"Customise"),n("button.reset",{"data-ctl":"auto-hide",title:"Hide this for the image",onclick:t.hideAutoNote},"Hide"))),o):null}function yi(e,t){const s=e.state,o=(s.caps?.controls??[]).filter(r=>!Tn.has(r.key)&&at(r,s.settings)),a=t.baseSettings();return[...new Set(o.map(r=>r.group))].map(r=>{const l=o.filter(p=>p.group===r),c=s.groupsOpen[r]!==!1,d=l.filter(p=>s.settings[p.key]!==a[p.key]).length;return n("section.group",{class:c?"open":void 0},n("div.grouphead",null,n("button.groupbtn",{"aria-expanded":String(c),"data-ctl":`group:${r}`,onclick:()=>{s.groupsOpen[r]=!c,e.touch("groupsOpen")}},Z(c?"chevronDown":"chevronRight",13),n("span.eyebrow",null,r),d?n("span.changed",null,`${d} changed`):null),d?n("button.reset",{onclick:()=>t.resetGroup(r)},"Reset"):null),c?n("div.groupbody",null,...l.map(p=>Ks(e,p,t,a))):null)})}function wi(e,t){const s=e.state,o=!!s.svg&&!s.tracing;return[vi(e,t),n("div.row",null,n("button.btn.primary",{style:{flex:"1"},disabled:!o,onclick:t.openExport},"Export"),n("button.btn",{disabled:!o,onclick:t.copySvg,title:"Paste straight into Figma or Illustrator"},"Copy SVG"),n("button.btn.icon",{disabled:!o,onclick:t.saveCard,"aria-label":"Save comparison card",title:"Save comparison card"},Z("share",16))),n("div.note",null,n("span",null,"Export runs a fresh full trace."),n("button.reset",{disabled:!s.source||s.tracing,onclick:t.traceNow},"Trace again"))]}function vi(e,t){const s=e.state,o=s.report;if(s.tracing)return n("div.readout.busy",null,n("span.pulse"),n("span.stage",null,Nt(s)),n("span.muted.num.elapsed",{"data-live":"elapsed"},Ot(s)),n("button.reset",{onclick:t.cancel,title:"Esc"},"Cancel"));if(!o)return n("div.readout.idle",null,n("span.faint",null,s.source?"Nothing traced yet.":"Open an image to see what a setting does."));const a=s.result?.tier==="final"?s.previous:null,i=(c,d,p)=>n("div.cell",null,n("span.v.num",null,c),n("span.k",null,d),p),r=(s.settings.mode??"quality")==="fast",l=n("div.readout-badge-row",null,n("button.engine-pill"+(r?".fast":".quality"),{type:"button",title:r?"Fast vectorizer active. Click to switch to Quality mode.":"Quality vectorizer active. Click to switch to Fast mode.",onclick:()=>t.changeSetting("mode",r?"quality":"fast")},n("span.engine-name",null,r?"Fast":"Quality")),o.seconds!=null?n("span.time-pill.num",{title:`Traced in ${nt(o.seconds)}`},nt(o.seconds)):null);return n("div.readout",{"aria-live":"polite"},l,n("div.readout-grid",null,i(re(o.meanDe00),"dE00",a?jt(o.meanDe00,a.meanDe00,c=>c.toFixed(2),.005,!0):null),i(oe(o.coordinates),"coordinates",a?jt(o.coordinates,a.coordinates,oe,0,!1):null),i(ie(o.bytes),"file",a?jt(o.bytes,a.bytes,ie,16,!1):null)))}function jt(e,t,s,o,a){if(e==null||t==null)return null;const i=e-t;if(Math.abs(i)<=o)return n("span.delta.same",null,"no change");const r=i<0?"better":a?"worse":"same";return n(`span.delta.${r}`,null,`${i<0?"−":"+"}${s(Math.abs(i))}`)}const xi="https://logolabs.github.io/inkvec/";function ki(e,t,s){const o=n("div.chooser",{role:"region","aria-label":"How to trace this image"});let a=!1;const i=d=>{const p=e.state;a&&t.setOnOpen(d),p.prefs?.seenFirstRun||t.markSeen(),e.set({chooser:!1}),d==="custom"&&s()},r=()=>{const d=e.state;if(d.tracing&&d.generation===d.auto?.generation)return`Tracing now · ${Nt(d)} · ${Ot(d)}`;const p=d.auto?.report;return p?`Done · ${re(p.meanDe00)} dE00 · ${oe(p.coordinates)} coordinates · ${ie(p.bytes)}`:d.stageState.kind!=="drawing"&&d.stageState.kind!=="decoding"?"Stopped; the stage says why":"Starting…"},l=()=>{const d=e.state,p=d.chooser&&!!d.source&&d.tab==="vectorize"&&!d.wizard&&!d.screen;if(o.hidden=!p,!p){F(o);return}const h=!d.prefs?.seenFirstRun,g=Xs(e,t);F(o,n("div.choosehead",null,n("span.eyebrow",null,`Just opened · ${d.source?.name??""}`),n("button.btn.icon.compact.ghost",{"aria-label":"Close; Auto stands",title:"Close. Auto stands (Esc)",onclick:()=>i("auto")},Z("x",14))),h?n("p.chooseintro",null,"New: every image is traced the moment it opens. Keep Auto, or customise it a step at a time with the result beside each choice. Ignore this card and Auto stands."):null,n("div.choices",null,n("div.choice",null,n("span.choicename",null,Z("zap",14),"Auto"),n("span.choicebody.num",{"data-status":""},r()),n("button.btn.primary.compact",{"data-ctl":"chooser-auto",onclick:()=>i("auto")},"Keep Auto")),n("div.choice",null,n("span.choicename",null,Z("settings",14),"Custom"),n("span.choicebody",null,"What it is, colours, detail, shape, output: each explained, beside Auto's result."),n("button.btn.compact",{"data-ctl":"chooser-custom",onclick:()=>i("custom")},"Customise…"))),g?n("div.choosenews",null,n("span.eyebrow",null,"Auto chose"),g):null,n("label.remember",null,n("input",{type:"checkbox",checked:a,onchange:u=>{a=u.target.checked}}),n("span",null,"Remember my choice"),n("span.muted",null,"· change it in Settings")))},c=()=>{const d=o.querySelector("[data-status]");d&&(d.textContent=r())};return e.on(["chooser","source","tab","wizard","screen","auto","facts","prefs","preset","settings","groupSuggestions","palette","stageState"],l),e.on(["tracing","liveStages","liveNow","generation"],c),l(),o}const ht={kind:{id:"kind",title:"What is it?",does:"Picks the preset that suits the kind of image: a handful of settings chosen together. Each tile is a small draft of this image under that preset.",when:"When Auto's trace is not the kind of drawing you wanted: strokes that should be lines, a scan that should be two inks.",costs:"Nothing on its own. Photo or scan adds the denoiser to the full trace, about 0.6 s.",learn:["How a trace is made","algorithm/plain/00-overview.html"]},cleanup:{id:"cleanup",title:"Clean-up",does:"Repairs JPEG and screenshot damage before tracing, so the colours are the ones the artwork was drawn with rather than the ones the compression left.",when:"The source is a JPEG, a screenshot or a photo of a mark, and its flat colours come back speckled or with extra shades.",costs:"About 0.6 s per full trace, and a one-time download. Drafts skip it.",learn:["Intake and noise","algorithm/plain/01-intake.html"]},colours:{id:"colours",title:"Colours",does:"Shows every ink the trace found. Suggested groups are inks that are the same colour measured twice; grouping re-traces so the shapes between them join.",when:"The palette has near-duplicates, or more colours than the design has.",costs:"Fewer colours can merge a detail into its neighbour; the colour difference says when.",learn:["How the palette is found","algorithm/plain/03-palette.html"]},detail:{id:"detail",title:"Detail",does:"How closely curves follow the pixels, and how small a speck can be before it is dropped.",when:"Tighter for crests and filigree; looser for a smaller file with fewer nodes.",costs:"Tighter precision means more nodes and a bigger file; the readout above shows the trade.",learn:["Curve fitting","algorithm/plain/11-fitting.html"]},shape:{id:"shape",title:"Shape",does:"How the outlines are built: nodes an artist would place, fewer paths, one shape reused where it repeats, and what a curve or a corner costs the fit.",when:"The SVG will be edited by hand, or has to be as small as possible.",costs:"Editable nodes cost a little colour accuracy (about 0.04 dE00 on icons); the readout shows it here.",learn:["Symmetry and repeated shapes","algorithm/plain/10-symmetry.html"]},output:{id:"output",title:"Output",does:"How the file is written: the background, a margin around the drawing, and whether it is minified.",when:"Placing the SVG on a coloured page, or shipping it on the web.",costs:"None of these re-trace: they rewrite the same drawing.",learn:["Writing the SVG","algorithm/plain/13-emit.html"]}},ss={detail:["precision","speckleFloor"],shape:["editability","fewerPaths","matchRepeatedShapes","bezierCost","cornerAngle"],output:["transparentBackground","margin","minify"]},Si=[{preset:"logo",name:"Logo",why:"Flat colours and crisp shapes. The default."},{preset:"icon",name:"Icon",why:"A small flat mark: keeps one-pixel details, at most 16 colours."},{preset:"fine-detail",name:"Illustration or emoji",why:"Gradients and fine detail: curves fitted twice as closely."},{preset:"line-art",name:"Line art",why:"Even-width lines come back as strokes you can re-weight."},{preset:"monochrome",name:"One-colour logo",why:"Every shape drawn black on white, light colours included."},{preset:"monochrome-transparent",name:"Black, no background",why:"Black artwork on a transparent background, ready to place on any design."},{preset:"black-and-white",name:"Two-tone scan",why:"Split by lightness, like a photocopy: stamps, signatures."},{preset:"photo-or-scan",name:"Photo or scan",why:"JPEGs and screenshots: the denoiser cleans compression damage first.",note:"The draft is shown without the denoiser, which runs on the full trace."}];function $i(e){const t=e.state,s=!!t.caps?.denoiser.supported&&Ys(t.source,t.facts);return["kind","cleanup","colours","detail","shape","output"].filter(o=>o!=="cleanup"||s)}function Mi(e,t,s,o){let a=null,i=[],r=null,l=e.state.view,c="auto",d=null,p=null;const h=n("div.wizhead"),g=n("div.wizintro"),u=n("div.wizcontrols"),f=n("div.wizlive"),b=n("div.wizguide"),x=n("div.wizscroll",null,g,u,f,b),M=n("div.wiznav");Hs(f,e);const v=()=>$i(e),S=()=>e.state.wizard?.step??"kind",y=k=>{k!==S()&&(S()==="kind"&&o.pause(),e.set({wizard:{step:k},hoverFill:null}),x.scrollTop=0)},$=()=>{const k=e.state.auto?.svg;return c!=="auto"||!k?null:(d?.svg!==k&&(d={svg:k,label:"Auto"}),d)},C=()=>e.set({compare:$()}),D=()=>{const k=e.state.auto?.svg;return k?(p?.svg!==k&&(p&&URL.revokeObjectURL(p.url),p={svg:k,url:URL.createObjectURL(new Blob([k],{type:"image/svg+xml"}))}),p.url):null},L=()=>{const k=v(),N=Math.max(0,k.indexOf(S())),E=ht[S()],Y=r!==null&&(e.state.preset!==r.preset||De(e.state.caps,e.state.settings,r.settings).length>0||JSON.stringify(e.state.colourGroups)!==JSON.stringify(r.colourGroups));F(h,n("div.cardhead",null,n("span.eyebrow",null,`Custom · step ${N+1} of ${k.length}`),n("div",{style:{display:"flex",gap:"10px",alignItems:"baseline"}},Y?n("button.reset",{"data-ctl":"wiz-undo",title:"Put every control and colour group back where they were when the wizard opened",onclick:()=>r&&s.restore(r)},"Undo my changes"):null,n("button.reset",{"data-ctl":"wiz-close",title:"Keep what is chosen so far (Esc)",onclick:()=>Ae(!0)},"Close"))),n("h2.wiztitle.serif",null,E.title),n("div.wizdots",{role:"tablist","aria-label":"Steps"},...k.map((K,O)=>n("button",{role:"tab","aria-selected":String(K===S()),"aria-label":`Step ${O+1}: ${ht[K].title}`,title:ht[K].title,class:Oy(K)}))),n("div.wizcompare",null,n("span.muted",null,"Left pane"),n("div.seg",{role:"group","aria-label":"What the left pane shows"},n("button",{"aria-pressed":String(c==="auto"&&!!e.state.auto?.svg),disabled:!e.state.auto?.svg,title:e.state.auto?.svg?"Auto's trace, to compare yours with":"Auto's trace has not landed yet",onclick:()=>{c="auto",C(),L()}},"Auto's trace"),n("button",{"aria-pressed":String(c==="source"||!e.state.auto?.svg),onclick:()=>{c="source",C(),L()}},"Source"))))},I=()=>{const k=ht[S()];F(g,n("p.wizlead",null,k.does)),F(b,n("dl.wizexplain",null,n("dt",null,"Change it when"),n("dd",null,k.when),n("dt",null,"What it costs"),n("dd",null,k.costs)),n("a.wizlearn",{href:xi+k.learn[1]},`Learn more: ${k.learn[0]} →`))},A=()=>{const k=v(),N=k.indexOf(S()),E=N===k.length-1;F(M,n("button.btn",{"data-ctl":"wiz-back",disabled:N<=0,onclick:()=>y(k[N-1])},"Back"),E?null:n("button.reset",{"data-ctl":"wiz-skip",title:"Keep what is chosen so far and go to the last step",onclick:()=>y(k[k.length-1])},"Skip to finish"),n("span",{style:{flex:"1"}}),E?n("button.btn",{"data-ctl":"wiz-export",disabled:!e.state.svg,onclick:()=>Ae(!0,!0)},"Export now"):null,E?n("button.btn.primary",{"data-ctl":"wiz-finish",onclick:()=>Ae(!0)},"Finish"):n("button.btn.primary",{"data-ctl":"wiz-next",onclick:()=>y(k[N+1])},"Next"))},P=()=>{if(!a)return;const k=v();if(!k.includes(S())){e.set({wizard:{step:k[Math.min(k.length-1,1)]}});return}L(),I(),A(),X(),V()},J=k=>e.state.caps?.controls.find(N=>N.key===k),U=k=>{const N=s.baseSettings();return k.map(E=>J(E)).filter(E=>!!E&&at(E,e.state.settings)).map(E=>Ks(e,E,s,N))},X=()=>{const k=document.activeElement instanceof HTMLElement&&u.contains(document.activeElement)?document.activeElement.getAttribute("data-ctl"):null,N=e.state,E=S();let Y=[];E==="cleanup"?Y=ee():E==="colours"?Y=U(N.facts?.hasAlpha?["maxColours","traceTransparency"]:["maxColours"]):E==="detail"?Y=[se(),...U(ss.detail??[])]:(E==="shape"||E==="output")&&(Y=U(ss[E]??[])),F(u,...Y),u.hidden=Y.length===0,k&&u.querySelector(`[data-ctl="${k}"]`)?.focus({preventScroll:!0}),a&&L()},se=()=>n("div.wizrow",null,n("span.label",null,"Compare as"),n("div.seg",{role:"group","aria-label":"How the two panes are arranged"},...["side","wipe"].map(k=>n("button",{"aria-pressed":String(e.state.view===k),"data-ctl":`wiz-view:${k}`,onclick:()=>e.set({view:k})},k==="side"?"Side by side":"Wipe")))),ee=()=>{const k=e.state,N=k.caps?.denoiser,E=k.settings.cleanUpDamage,Y=[k.source?.lossy?`the file is a ${k.source.container}, which stores colour approximately`:null,k.facts&&k.facts.noiseLevels>=Vs?`its pixels measure ${k.facts.noiseLevels.toFixed(1)} levels of noise (a clean file reads about 0.5)`:null].filter(Boolean),K=(O,j,w)=>n("button.wizchoice",{"aria-pressed":String(E===O||O==="auto"&&E==="on"),"data-ctl":`wiz-cleanup:${O}`,onclick:()=>s.changeSetting("cleanUpDamage",O)},n("span.name",null,j),n("span.muted",null,w));return[Y.length?n("p.wizwhy",null,`Offered because ${Y.join(", and ")}.`):null,n("div.wizchoices",null,K("off","Off","Trace the pixels as they are, damage and all."),K("auto","Auto","Clean the damage first, where it is found.")),N?.supported&&E!=="off"&&k.denoiserFetch?.phase!=="ready"?n("div.wiznote",{"data-ctl":"wiz-denoiser-progress"},Tt(k.denoiserFetch),n("span",null,`${Cn(Ct(k.denoiserFetch,!0)??"Starting the denoiser…")} It comes from ${N.repo}, is checked against its SHA-256 on arrival and runs in this tab: the model comes down, your image never goes up. Until it is ready the trace runs without it, then again with it.`)):null,n("p.muted.wizsmall",null,"Left: Auto's trace, without it. Right: yours. It runs on the full trace only, so the right pane changes when the full trace lands, a moment after the draft.")].filter(O=>O!==null)};let Q=null;const V=()=>{const k=S();if(k==="kind"){ze();return}Q=null;const N=e.state;k==="colours"?F(f,N.palette.length?Us(e,s):n("p.muted.wizsmall",null,"The palette appears when a trace lands."),n("p.muted.wizsmall",null,"Hover an ink or a group to single it out in the drawing on the right.")):k==="detail"||k==="shape"?F(f,ne()):k==="output"?F(f,ae(),ne()):F(f)},ne=()=>{const k=e.state,N=k.auto?.report??null,E=k.report,Y=k.result?.tier==="draft",K=j=>j?j.structure?.nodes||Math.round(j.coordinates/2):null,O=(j,w,z,T,_=!0,H=0)=>{const G=w!==null&&z!==null&&!Y&&!k.tracing?z-w:null,le=G===null||Math.abs(G)<=H?"same":G<0===_?"better":"worse";return n("tr",null,n("th",null,j),n("td.num",null,w===null?"—":T(w)),n("td.num",null,z===null?"—":T(z)),n(`td.num.delta.${le}`,null,G===null?"":Math.abs(G)<=H?"same":`${G<0?"−":"+"}${T(Math.abs(G))}`))};return n("div.card.wizreadout",{"aria-live":"polite"},n("div.cardhead",null,n("span.eyebrow",null,"Auto · yours"),n("span.muted",{style:{fontSize:"11px"}},k.tracing?"tracing…":Y?"draft; the full trace follows":"full traces")),n("table",null,n("thead",null,n("tr",null,n("th"),n("th",null,"Auto"),n("th",null,"Yours"),n("th"))),n("tbody",null,O("colour difference",N?.meanDe00??null,E?.meanDe00??null,j=>j.toFixed(2),!0,.005),O("nodes",K(N),K(E),oe),O("paths",N?.paths??null,E?.paths??null,oe),O("file size",N?.bytes??null,E?.bytes??null,ie,!0,16))),n("span.muted.wizsmall",null,"Colour difference in dE00: under 1 is invisible side by side."))},ae=()=>{const k=e.state,N=k.auto,E=N?De(k.caps,N.settings,k.settings):[],Y=(O,j)=>typeof j=="boolean"?j?"on":"off":typeof j=="number"?`${O.decimals?j.toFixed(O.decimals):Math.round(j)}${O.unit?` ${O.unit}`:""}`:String(j),K=k.caps?.presets.find(O=>O.id===k.preset);return n("div.card.wizchanges",null,n("span.eyebrow",null,"What you changed from Auto"),K&&N&&K.id!==N.preset?n("div.wizchange",null,n("span",null,"Preset"),n("span.num",null,K.name)):null,...E.map(O=>{const j=J(O);return!j||!N?null:n("div.wizchange",null,n("span",null,j.label),n("span.num",null,`${Y(j,N.settings[O])} → ${Y(j,k.settings[O])}`))}),k.colourGroups.length?n("div.wizchange",null,n("span",null,"Colour groups"),n("span.num",null,String(k.colourGroups.length))):null,!E.length&&!k.colourGroups.length&&!(K&&N&&K.id!==N.preset)?n("span.muted",null,"Nothing yet: this is Auto's trace."):n("span.muted.wizsmall",null,"Finish keeps these as ordinary settings, under Tune."))},te=()=>{const k=e.state,N=k.auto,E=k.caps,Y=new Set($n(e).map(T=>T.preset)),K=k.colourGroups.length?{colourGroups:k.colourGroups}:{},O=N?Pe({...N.settings,...K}):null,j=[];let w=null;for(const T of Si){const _=E?.presets.find(G=>G.id===T.preset);if(!_)continue;const H={..._.settings,...K};if(O&&Pe(H)===O&&T.preset!=="photo-or-scan"&&w===null){w=T.name;continue}j.push({id:T.preset,name:T.name,why:T.why,note:T.note,settings:H,preset:T.preset,suggested:Y.has(T.preset)})}return[{id:"auto",name:w?`${w} · Auto`:"As Auto traced",why:N?`Auto used ${Zs(E,N.settings,N.preset)}.`:"Auto's trace.",settings:null,preset:null,suggested:!1},...j.filter(T=>T.suggested),...j.filter(T=>!T.suggested)]},pe=k=>{const N=e.state;if(k.id==="auto"){const E=N.auto;return!!E&&N.preset===E?.preset&&De(N.caps,N.settings,E.settings).length===0}return N.preset===k.preset},Te=k=>{k.id==="auto"?s.backToAuto():k.preset&&s.setPreset(k.preset)},fe=k=>{if(!k){C();return}const N=e.state;if(k.id==="auto"||Me(k)){N.auto?.svg&&e.set({compare:$()??{svg:N.auto.svg,label:"Auto"}});return}const E=k.settings?o.get(Pe(k.settings)):void 0;E?.state==="done"&&e.set({compare:{svg:E.svg,label:`Preview · ${k.name} (draft)`}})},Me=k=>{const N=e.state.auto;if(!N||!k.settings)return!1;const E=e.state.colourGroups.length?{colourGroups:e.state.colourGroups}:{};return Pe(k.settings)===Pe({...N.settings,...E})},Le=k=>{const N=e.state;if(k.id==="auto"||Me(k)){const Y=N.auto?.report;return Y?{url:D(),text:`${re(Y.meanDe00)} dE00 · ${oe(Y.paths)} paths · ${ie(Y.bytes)}`,busy:!1}:N.tracing?{url:null,text:"tracing…",busy:!0}:{url:null,text:"no trace yet",busy:!1}}const E=k.settings?o.get(Pe(k.settings)):void 0;return!E||E.state==="queued"?{url:null,text:N.auto?.report?"queued":"after Auto",busy:!0}:E.state==="running"?{url:null,text:"tracing a draft…",busy:!0}:E.state==="failed"?{url:null,text:"no preview",busy:!1}:{url:E.url,text:`${re(E.report.meanDe00)} dE00 · ${oe(E.report.paths)} paths · ${ie(E.report.bytes)}`,busy:!1}},ze=()=>{const k=te(),N=k.map(O=>O.id).join(",");if(!Q||Q.dataset.signature!==N){Q=n("div.kinds",{"data-signature":N,role:"radiogroup","aria-label":"What is it?"});for(const O of k){const j=n("button.kind",{role:"radio","data-key":O.id,"data-ctl":`wiz-kind:${O.id}`,onclick:()=>Te(O),onpointerenter:()=>fe(O),onpointerleave:()=>fe(null),onfocus:()=>fe(O),onblur:()=>fe(null)},n("span.kthumb",null,n("img",{alt:"",draggable:"false"}),n("span.kspin")),n("span.kname",null,O.name,O.suggested?n("span.ksugg",null,"suggested"):null),n("span.kwhy",null,O.why),O.note?n("span.knote",null,O.note):null,n("span.kstat.num"));Q.append(j)}F(f,Q,n("p.muted.wizsmall.kqueue"))}const E=e.state;E.auto&&!E.auto.report&&E.tracing||o.want(k.filter(O=>O.settings&&!Me(O)).map(O=>({key:Pe(O.settings),settings:O.settings})));for(const O of k){const j=Q.querySelector(`[data-key="${O.id}"]`);if(!j)continue;const w=Le(O),z=j.querySelector("img");w.url&&z.getAttribute("src")!==w.url&&(z.src=w.url),z.hidden=!w.url,j.classList.toggle("busy",w.busy),j.setAttribute("aria-checked",String(pe(O))),j.querySelector(".kstat").textContent=w.text}const Y=o.pending(),K=f.querySelector(".kqueue");K&&(K.textContent=Y?`Previews are small drafts of this image, traced one at a time behind the main trace · ${Y} to go.`:"Previews are small drafts of this image. Hover one to see it large on the left; choose one and the full trace follows on the right.")};function de(){if(a)return;if(!e.state.auto&&e.state.svg&&e.state.report&&!e.state.tracing){const E=e.state;e.set({auto:{generation:E.resultGeneration,settings:{...E.settings},preset:E.preset,svg:E.svg,report:E.report,palette:E.palette,losses:E.losses}})}const k=e.state;r={settings:{...k.settings},preset:k.preset,colourGroups:[...k.colourGroups]},l=k.view,c="auto",Q=null;const N=n("div.sheetbody.wizbody",null,h,x,M);a=n("div.sheet.wizsheet",null,N),t.classList.add("wizarding"),t.append(a),e.set({wizard:{step:v()[0]},chooser:!1,view:k.view==="ab"?"side":k.view,compare:$()}),k.prefs?.seenFirstRun||s.markSeen(),i=[e.on(["wizard","facts","caps","prefs"],P),e.on(["denoiserFetch"],()=>{a&&S()==="cleanup"&&X()}),e.on(["settings","preset","colourGroups","view"],()=>{X(),(S()==="kind"||S()==="output")&&V()}),e.on(["report","result","tracing","palette","groupSuggestions","resultGroups","paletteSelection","simplifyTo","losses","auto","svg"],()=>{V(),(e.state.compare===null||e.state.compare===d||e.state.compare?.label==="Auto")&&C(),A()}),o.on(()=>{S()==="kind"&&V()}),e.on(["auto"],L)],P(),(M.querySelector("[data-ctl=wiz-next]")??N).focus?.()}function Ae(k=!1,N=!1){if(!a)return;for(const O of i)O();i=[],o.pause(),a.remove(),a=null,Q=null,t.classList.remove("wizarding"),p&&URL.revokeObjectURL(p.url),p=null,d=null;const E=e.state,Y=r!==null&&(E.preset!==r.preset||De(E.caps,E.settings,r.settings).length>0||JSON.stringify(E.colourGroups)!==JSON.stringify(r.colourGroups)),K=E.auto?De(E.caps,E.auto.settings,E.settings).length:0;e.set({wizard:null,compare:null,hoverFill:null,view:l,railTab:Y?"tune":E.railTab}),r=null,k&&Y&&q(K?`Custom settings kept: ${K} control${K===1?"":"s"} changed from Auto, all of them under Tune.`:"Custom settings kept. Everything is under Tune.",{kind:"good"}),N&&s.openExport()}return{open:de,close:Ae,isOpen:()=>a!==null}}const os=4,as=24;function Ci(e){const t={size:"1600x900",theme:"dark",fileName:!1,detail:!0,numbers:!0,zoom:12},s=n("img",{alt:"Preview of the comparison card",draggable:"false"}),o=n("div.cardstage",null,s,n("div.cardbusy",{"aria-hidden":"true"},n("span.cardspin"))),a=n("span.faint",{style:{fontSize:"11.5px"}});let i=0;const r=async()=>{const y=++i;o.classList.add("busy");try{const $=await rs(e,t);if(y!==i)return;s.src=$.toDataURL("image/png")}catch{y===i&&q("The card could not be drawn.",{kind:"bad"})}finally{y===i&&o.classList.remove("busy")}},l=()=>{const[y,$]=to(t.size);o.style.setProperty("--ar",String(y/$)),a.textContent=`${y} × ${$} px · PNG · ${t.theme}`},c=[],d=()=>{c.forEach(y=>y()),l(),r()},p=(y,$,C)=>{const D=C.map(L=>n("button",{type:"button",role:"radio",onclick:()=>{t[y]=L.value,d()}},n("span",null,L.label),L.sub?n("small",null,L.sub):null));return c.push(()=>D.forEach((L,I)=>L.setAttribute("aria-checked",String(t[y]===C[I].value)))),is($,n("div.cardseg",{role:"radiogroup","aria-label":$},...D))},h=(y,$,C)=>{const D=n("span.switch"),L=n("button.optrow",{type:"button",role:"switch",onclick:()=>{t[y]=!t[y],d()}},n("span.optext",null,n("span.optlabel",null,$),n("span.optnote",null,C)),D);return c.push(()=>{L.setAttribute("aria-checked",String(t[y])),D.setAttribute("aria-checked",String(t[y]))}),L},g=n("span.num",null,`${t.zoom}×`),u=n("input.slider",{type:"range",min:String(os),max:String(as),step:"1",value:String(t.zoom),"aria-label":"Detail crop magnification",oninput:y=>{g.textContent=`${y.target.value}×`},onchange:y=>{t.zoom=Number(y.target.value),d()}}),f=n("div.optslider",null,n("div.optsliderhead",null,n("span",null,"Detail magnification"),g),u,n("div.stops",null,n("span",null,`${os}×`),n("span",null,`${as}×`)));c.push(()=>{u.disabled=!t.detail,f.classList.toggle("off",!t.detail)});const b=(y,$,C)=>async()=>{const D=y.lastChild?.textContent??"";y.disabled=!0,y.lastChild&&(y.lastChild.textContent=$);try{await C()}finally{y.disabled=!1,y.lastChild&&(y.lastChild.textContent=D)}},x=async()=>{const y=await rs(e,t);return new Promise($=>y.toBlob($,"image/png"))},M=n("button.btn",{type:"button"},Z("copy",15),n("span",null,"Copy image"));M.onclick=b(M,"Copying…",async()=>{const y=await x();if(y)try{await navigator.clipboard.write([new ClipboardItem({"image/png":y})]),q("Card copied.",{kind:"good"})}catch{q("This system would not take an image on the clipboard; save it instead.",{kind:"bad"})}});const v=n("button.btn.primary",{type:"button"},Z("download",15),n("span",null,"Download PNG"));v.onclick=b(v,"Saving…",async()=>{const y=e.state.source?.name.replace(/\.[^.]+$/,"")??"inkvec",$=await x();if(!$)return;const C=await Os(`${y}-card.png`,new Uint8Array(await $.arrayBuffer()),[{name:"PNG",extensions:["png"]}]);C&&(ue(),q(C.path?`Card saved to ${C.path}`:`Downloaded ${C.name}`,{kind:"good",action:Ft(C.path)}))});const S=n("div.modal.composer",{tabindex:"-1"},n("div.composerhead",null,n("div",null,n("h2",null,"Save comparison card"),n("p",null,"The before and after on one poster, with the measurements behind it.")),n("button.btn.icon.ghost",{type:"button","aria-label":"Close",onclick:ue},Z("x",16))),n("div.composerbody",null,n("div.composerpreview",null,o,a),n("div.composerside",null,p("size","Format",[{value:"1600x900",label:"Landscape",sub:"1600 × 900"},{value:"1080x1080",label:"Square",sub:"1080 × 1080"}]),p("theme","Appearance",[{value:"dark",label:"Dark"},{value:"light",label:"Light"}]),is("On the card",n("div.optlist",null,h("numbers","The numbers","Colour difference, coordinates and file size."),h("detail","A zoomed detail","The vector edge cutting through the source's pixels."),f,h("fileName","The file name","Off by default, so unreleased work stays private."))),n("div.composeractions",null,v,M))));Ye(S),d()}function is(e,t){return n("div.optgroup",null,n("span.eyebrow",null,e),t)}function to(e){return e==="1600x900"?[1600,900]:[1080,1080]}async function rs(e,t){const[s,o]=to(t.size),a=document.createElement("canvas");a.width=s,a.height=o;const i=a.getContext("2d");if(!i)return a;const r=t.theme==="dark",l=r?"#1a1816":"#faf8f5",c=r?"#faf8f5":"#1a1816",d=r?"rgba(250,248,245,.4)":"rgba(26,24,22,.46)",p=r?"rgba(250,248,245,.55)":"rgba(26,24,22,.58)",h=r?"#c9754a":"#9c5730",g=r?"#26231f":"#ffffff",u=r?"#211f1c":"#e8e3db";await Promise.all([document.fonts.load('500 20px "Playfair Studio"'),document.fonts.load('400 20px "Inter Studio"'),document.fonts.load('500 20px "Inter Studio"')]).catch(()=>{}),i.fillStyle=l,i.fillRect(0,0,s,o);const f=i.createRadialGradient(s*.2,-o*.1,0,s*.2,-o*.1,s*.6);f.addColorStop(0,"rgba(201,117,74,.16)"),f.addColorStop(1,"rgba(201,117,74,0)"),i.fillStyle=f,i.fillRect(0,0,s,o);const b=Math.round(s*.042),x=t.size==="1080x1080",M=x?s-b*2:Math.round(s*.46);i.fillStyle=d,i.font=`500 ${Math.round(s*.0105)}px "Inter Studio", Inter, sans-serif`,i.letterSpacing="3px",i.fillText("PNG TRACED TO SVG",b,b+14),i.letterSpacing="0px",i.fillStyle=c;const v=Math.round(s*(x?.05:.039));i.font=`500 ${v}px "Playfair Studio", Georgia, serif`,i.fillText("A logo, measured",b,b+v+26),i.fillText("rather than guessed.",b,b+v*2+30);let S=b+v*2+96;t.fileName&&e.state.source&&(i.fillStyle=p,i.font=`400 ${Math.round(s*.013)}px "Inter Studio", Inter, sans-serif`,i.fillText(e.state.source.name,b,S),S+=40);const y=e.state.report;if(t.numbers&&y){const se=[[re(y.meanDe00),"mean dE00"],[oe(y.coordinates),"coordinates"],[`${(y.bytes/1024).toFixed(1)} KB`,"SVG"]];let ee=b;for(const[Q,V]of se.slice(0,x?2:3)){i.fillStyle=Q===re(y.meanDe00)?h:c;const ne=Math.round(s*.026);i.font=`500 ${ne}px "Playfair Studio", Georgia, serif`,i.fillText(Q,ee,S+ne),i.fillStyle=d,i.font=`400 ${Math.round(s*.0085)}px "Inter Studio", Inter, sans-serif`,i.fillText(V,ee,S+ne+20),ee+=Math.max(i.measureText(V).width,120)+Math.round(s*.05)}}i.fillStyle=p,i.font=`400 ${Math.round(s*.0095)}px "Inter Studio", Inter, sans-serif`,i.fillText("traced with Inkvec Studio · by LogoLabs",b,o-b);const $=x?b:b+M,C=s-$-b,D=x?Math.round(o*.42):b,L=Math.round(x?o*.3:o*.44),I=(se,ee,Q,V)=>{for(let ae=0;ae*14{i.save(),i.beginPath(),i.rect(ee,D,A,L),i.clip(),I(ee,D,A,L);const ne=await Vt(se),ae=Math.min((A-40)/ne.width,(L-40)/ne.height),te=ne.width*ae,pe=ne.height*ae;i.drawImage(ne,ee+(A-te)/2,D+(L-pe)/2,te,pe),i.restore(),i.fillStyle=V,i.font=`500 ${Math.round(s*.0085)}px "Inter Studio", Inter, sans-serif`,i.letterSpacing="2px",i.fillText(Q,ee+12,D+22),i.letterSpacing="0px"};if(P&&await X(P.preview,$,P.container.toUpperCase(),d),U&&await X(U.url,$+A+12,"SVG",h),t.detail&&P&&U){const se=D+L+14,ee=o-se-b-40;if(ee>60){const Q=e.state.worstCorner,V=t.zoom,ne=Q?.x??U.w/2,ae=Q?.y??U.h/2,te=$+C/2-ne*V,pe=se+ee/2-ae*V;i.save(),i.beginPath(),i.rect($,se,C,ee),i.clip(),i.fillStyle=r?"#141210":"#f0ece6",i.fillRect($,se,C,ee);const Te=await Vt(P.preview);i.imageSmoothingEnabled=!1,i.drawImage(Te,te,pe,U.w*V,U.h*V),i.imageSmoothingEnabled=!0,i.strokeStyle=r?"rgba(250,248,245,.09)":"rgba(26,24,22,.12)",i.lineWidth=1;const fe=(te%V+V)%V,Me=(pe%V+V)%V;for(let de=$+fe;de<$+C;de+=V)i.beginPath(),i.moveTo(Math.round(de)+.5,se),i.lineTo(Math.round(de)+.5,se+ee),i.stroke();for(let de=se+Me;de0&&i[3]>0&&(t=i[2],s=i[3]),a.setAttribute("width",String(t)),a.setAttribute("height",String(s)),o=new XMLSerializer().serializeToString(a)}return{url:`data:image/svg+xml;charset=utf-8,${encodeURIComponent(o)}`,w:t,h:s}}function Vt(e){return new Promise((t,s)=>{const o=new Image;o.onload=()=>t(o),o.onerror=s,o.src=e})}const ls=[512,1024,2048];function cs(e,t){const s=e.state;return!s.svg||!s.report?null:{svg:s.svg,report:{meanDe00:s.report.meanDe00,coordinates:s.report.coordinates,paths:s.report.paths,tracedPx:s.report.tracedPx},palette:s.palette.map(o=>o.kind==="gradient"?{hex:o.hex,traced:o.traced,share:o.share,stops:o.stops}:{hex:o.hex,traced:o.traced,share:o.share}),losses:s.losses.map(o=>({text:o.text})),formats:t}}function no(e,t,s){const o=aa({svg:!0,svgMinified:!0,pngSizes:[...ls],favicon:!1,assetPack:!0});let a=[],i=e.state.prefs?.outputFolder??null;const r=n("div.sheetbody"),l=n("div.sheet",{onmousedown:u=>{u.target===l&&c()}});l.append(r);const c=()=>{l.remove(),e.set({exportOpen:!1})},d=u=>a.filter(f=>f.group===u).reduce((f,b)=>f+b.bytes,0),p=async()=>{an({export:{...o,pngSizes:[...o.pngSizes]}});const u=cs(e,o);if(u){try{a=await B.planExport(u)}catch(f){a=[],q(String(f),{kind:"bad"})}g()}},h=(u,f,b,x)=>n("button.format",{onclick:x,"aria-pressed":String(b)},n("span.checkbox",{"aria-checked":String(b)},Z("check",11)),n("span",null,u),n("span.size",null,b?ie(d(f)):"—"));function g(){const u=a.reduce((f,b)=>f+b.bytes,0);F(r,n("div.cardhead",null,n("span.serif",{style:{fontSize:"19px"}},"Export"),n("button.reset",{onclick:c},"Close")),h("SVG","svg",o.svg,()=>{o.svg=!o.svg,p()}),h("SVG (minified)","svgMinified",o.svgMinified,()=>{o.svgMinified=!o.svgMinified,p()}),h("PNG · 512 / 1024 / 2048","png",o.pngSizes.length>0,()=>{o.pngSizes=o.pngSizes.length?[]:[...ls],p()}),h("ICO and favicon set","favicon",o.favicon,()=>{o.favicon=!o.favicon,p()}),h("Asset pack (.zip)","assetPack",o.assetPack,()=>{o.assetPack=!o.assetPack,p()}),n("div.format",{style:{cursor:"default"}},n("span.muted",{style:{fontSize:"11px"}},"To"),n("span",{style:{flex:"1"}},a.length>1?"Your downloads, as one .zip":"Your downloads")),n("button.btn.primary",{disabled:!a.length,onclick:async()=>{let f=i;f="",c(),await s();const b=cs(e,o);if(b)try{const x=await B.writeExport(b,f??"");q(Kn?`${x.length} file${x.length===1?"":"s"} written to ${f}`:`Downloaded ${x[0]??"the export"}`,{kind:"good",action:Ft(Kn?x[0]??null:null)})}catch(x){q(String(x),{kind:"bad"})}}},a.length?`Download ${a.length} file${a.length===1?"":"s"} · ${ie(u)}`:"Nothing selected"))}e.set({exportOpen:!0}),t.append(l),g(),p()}function rt(){return Ei()}function Ai(){try{return window.self!==window.top}catch{return!0}}function Ei(){const e=n("button.btn.ghost.compact.fullscreen",{"data-ctl":"fullscreen",title:"Fill the screen (Esc to leave)",onclick:()=>{document.fullscreenElement?document.exitFullscreen():document.documentElement.requestFullscreen().catch(()=>o())}},Z("maximize",14),n("span",null,"Full screen")),t=e.querySelector("span"),s=()=>{t&&(t.textContent=document.fullscreenElement?"Exit full screen":"Full screen")};document.addEventListener("fullscreenchange",s);const o=()=>window.open(window.location.href,"_blank","noopener"),a=Ai()?n("button.btn.ghost.compact",{"data-ctl":"own-tab",title:"The same app at its own address, with the whole window",onclick:o},Z("external",14),n("span",null,"Open in its own tab")):null,i=n("a.btn.ghost.compact",{"data-ctl":"home",href:"../index.html",title:"The Inkvec front page: what's new, before and after, results"},Z("globe",14),n("span",null,"Inkvec home")),r=n("a.btn.ghost.compact.desktopapp",{"data-ctl":"desktop-app",href:Fs,target:"_blank",rel:"noopener noreferrer",title:ma},Z("download",14),n("span",null,"Desktop app"));return n("div.webcontrols",null,i,r,document.fullscreenEnabled?e:null,a)}const us="https://logolabs.github.io/inkvec/studio/",Yt="./guide/";function so(e){if(e.screen==="settings")return"settings.html";switch(e.tab){case"minify":return"minify.html";case"fabricate":return"fabricate.html";case"batch":return"batch.html";default:return e.source?e.wizard?"getting-started.html#the-custom-wizard":e.railTab==="tune"?"tune.html":"result.html":"getting-started.html"}}function Zt(e){zo()?fa(e).catch(()=>q("Could not open the link in your browser.",{kind:"bad"})):window.open(e,"_blank","noopener")}let Ne=null;function An(e="index.html"){if(Ne){Ne.querySelector("iframe")?.setAttribute("src",Yt+e);return}const t=n("iframe",{title:"Inkvec Studio user guide",src:Yt+e,style:{flex:"1",width:"100%",border:"0",background:"var(--stage)"}}),s=n("div.screenbody",{style:{display:"flex",overflow:"hidden"}},t),o=()=>{try{const a=t.contentWindow?.location,i=a?a.pathname.indexOf("/guide/"):-1;if(a&&i>=0)return a.pathname.slice(i+7)+a.hash}catch{}return""};Ne=n("div.screen",{role:"dialog","aria-label":"User guide"},n("div.screenbar",{"data-tauri-drag-region":""},n("span",{style:{fontSize:"12.5px",fontWeight:"600"}},"User guide"),n("div.spacer"),n("button.btn.compact.ghost",{onclick:()=>t.setAttribute("src",Yt+"index.html")},"Contents"),n("button.btn.compact.ghost",{title:"The same page on logolabs.github.io, in your browser",onclick:()=>Zt(us+o())},"Open in browser"),n("button.btn.compact",{onclick:cn},"Done"),n("div.sep"),rt()),s),t.addEventListener("load",()=>{const a=t.contentDocument;if(!a||!a.querySelector('meta[name="inkvec-guide"]')){F(s,n("div.firstrun",null,n("span.serif",{style:{fontSize:"22px"}},"The guide is not bundled with this build"),n("span.faint",{style:{maxWidth:"46ch",textAlign:"center"}},"The same guide is published online."),n("button.btn.primary",{onclick:()=>Zt(us)},"Open the online guide")));return}a.addEventListener("keydown",i=>{i.key==="Escape"&&cn(),i.key==="F1"&&i.preventDefault()}),a.addEventListener("click",i=>{const l=i.target?.closest?.("a[href]")?.getAttribute("href")??"";/^(https?:|mailto:)/i.test(l)&&(i.preventDefault(),Zt(l))})}),window.addEventListener("keydown",oo,!0),(document.getElementById("app")??document.body).append(Ne)}function cn(){window.removeEventListener("keydown",oo,!0),Ne?.remove(),Ne=null}function oo(e){e.stopImmediatePropagation(),(e.key==="Escape"||e.key==="F1")&&(e.preventDefault(),e.key==="Escape"&&cn())}function Li(e){window.addEventListener("keydown",t=>{t.key!=="F1"||Ne||(t.preventDefault(),An(so(e.state)))})}function zi(e,t,s){const o=t.state;F(e,n("span.brand",null,As(18),zt),o.tab==="vectorize"&&o.source?n("div.filechip",null,n("span.name",null,o.source.name),n("span.muted.num",null,`${o.source.width} × ${o.source.height} · ${o.source.container}`)):null,n("div.seg",{style:{marginLeft:"auto"}},...[["vectorize","Vectorize"],["minify","Minify SVG"],["fabricate","Fabricate"]].map(([a,i])=>n("button",{"aria-pressed":String(o.tab===a),onclick:()=>t.set({tab:a})},i))),n("div",{style:{marginLeft:"auto",display:"flex",alignItems:"center",gap:"2px"}},n("button.btn.ghost.compact",{onclick:()=>s.openFile()},"Open…"),null,n("button.btn.ghost.compact",{onclick:()=>t.set({screen:"settings"})},"Settings"),n("button.btn.ghost.compact",{onclick:()=>t.set({screen:"about"})},"About"),n("button.btn.ghost.compact",{title:"Before and after on real logos, and how Inkvec compares",onclick:()=>t.set({screen:"showcase"})},"Showcase"),n("button.btn.ghost.compact",{title:"The user guide (F1)",onclick:()=>An(so(t.state))},"Help"),n("div.sep"),rt())),e.setAttribute("data-tauri-drag-region","")}const ft=30,ds=8;function Pi(e){const t=n("div.batchbar"),s=n("div.batchhead.eyebrow"),o=n("div.batchviewport"),a=n("div.batchfoot"),i=n("section.stage",{style:{background:"var(--paper)"}},t,s,o,a),r=n("div"),l=n("div"),c=n("div");o.append(r,l,c);const d=()=>{const v=e.state.batch;return v.failuresFirst?[...v.rows].sort((S,y)=>+(y.state==="failed")-+(S.state==="failed")):v.rows},p=()=>n("div.firstrun",{style:{minHeight:"100%",background:"transparent"}},n("div.drop",null,n("span.glyph",null,Z("folder",32)),n("div",{style:{display:"flex",flexDirection:"column",gap:"6px"}},n("span.headline",null,"Trace a whole folder"),n("span.faint",{style:{fontSize:"13px",maxWidth:"46ch"}},"Every PNG, JPEG and WebP in it becomes an SVG in the output folder. A row that fails stays listed after the run.")),n("button.btn.primary",{onclick:()=>void u()},"Choose a folder…"))),h=()=>{const v=d();if(s.hidden=v.length===0,!v.length){r.style.height="0px",c.style.height="0px",F(l,p());return}const S=o.scrollTop,y=Math.ceil(o.clientHeight/ft)+ds*2,$=Math.max(0,Math.floor(S/ft)-ds),C=v.slice($,$+y);r.style.height=`${$*ft}px`,c.style.height=`${Math.max(0,(v.length-$-C.length)*ft)}px`,F(l,...C.map(D=>Di(D,e,()=>e.touch("batch"))))};o.addEventListener("scroll",h,{passive:!0});const g=async()=>{const v=await pa({directory:!0,multiple:!1});return typeof v=="string"?v:null},u=async()=>{const v=await g();if(v)try{const S=await B.batchScan(v),y=e.state.batch;y.folder=v,y.outputDir??=`${v}${v.includes("\\")?"\\":"/"}svg`,y.rows=S.map(($,C)=>({id:C,path:$,file:$.split(/[\\/]/).pop()??$,preset:y.preset,state:"queued",de00:null,coordinates:null,outBytes:null,destination:"",message:null,seconds:null})),y.totals=null,e.touch("batch")}catch(S){q(String(S),{kind:"bad"})}},f=async()=>{const v=e.state.batch;if(!v.folder||!v.outputDir){q("Choose a folder of images and an output folder first.");return}v.running=!0,v.paused=!1,e.touch("batch");try{await B.batchStart({files:v.rows.map(S=>S.path),preset:v.preset,overrides:v.rows.filter(S=>S.preset!==v.preset).map(S=>[S.id,S.preset]),outputDir:v.outputDir,skipExisting:v.skipExisting})}catch(S){v.running=!1,e.touch("batch"),q(String(S),{kind:"bad"})}},b=()=>{const v=e.state.batch,S=e.state.caps?.presets??[],y=($,C,D)=>n("button.btn.compact",{onclick:C,title:null},$);F(t,n("select",{style:{height:"28px",padding:"0 8px",borderRadius:"7px",border:"1px solid var(--rule2)",background:"var(--card)",color:"var(--ink)",font:"400 12px var(--font-body)"},"aria-label":"Preset for all",onchange:$=>{const C=$.target.value;v.preset=C;for(const D of v.rows)D.state==="queued"&&(D.preset=C);e.touch("batch")}},...S.map($=>n("option",{value:$.id,selected:v.preset===$.id?"":null},`Preset for all · ${$.name}`))),y(v.folder?`Input · ${v.folder}`:"Choose a folder…",()=>void u()),y(v.outputDir?`Output · ${v.outputDir}`:"Output folder…",async()=>{const $=await g();$&&(v.outputDir=$,e.touch("batch"))}),n("button.btn.compact",{"aria-pressed":String(v.skipExisting),onclick:()=>{v.skipExisting=!v.skipExisting,e.touch("batch")}},"Skip existing"),n("div.spacer"),n("button.btn.compact",{"aria-pressed":String(v.failuresFirst),disabled:!v.rows.some($=>$.state==="failed"),onclick:()=>{v.failuresFirst=!v.failuresFirst,e.touch("batch")}},"Failures first"),v.running?y(v.paused?"Resume":"Pause",()=>{v.paused=!v.paused,B.batchPause(v.paused),e.touch("batch")}):null,v.running?y("Cancel",()=>void B.batchCancel()):n("button.btn.primary.compact",{disabled:!v.rows.length,onclick:()=>void f()},"Start")),F(s,n("span"),n("span",null,"File"),n("span",null,"Preset"),n("span",null,"Status"),n("span.right",null,"dE00"),n("span.right",null,"Coordinates"),n("span.right",null,"Output"),n("span",null,"Destination"))},x=()=>{const v=e.state.batch,S=v.totals,y=S?.finished??0,$=S?.total??v.rows.length,C=S&&S.sourceBytes>0?1-S.bytesWritten/S.sourceBytes:null;F(a,n("div",{style:{flex:"1",maxWidth:"420px",display:"flex",flexDirection:"column",gap:"6px"}},n("div",{style:{display:"flex",justifyContent:"space-between",color:"var(--faint)"}},n("span",null,$?`${y} of ${$} done${S?.failed?` · ${S.failed} failed`:""}`:"Nothing queued"),n("span",null,S?.remaining!=null?`${na(S.remaining)} left`:"")),n("div.progress",null,n("i",{style:{width:$e($?y/$:0)}}))),S?n("span.faint",null,`mean dE00 ${re(S.meanDe00)} · ${ie(S.bytesWritten)} written${C!==null?` · ${$e(C)} smaller than source`:""}`):null,n("button.reset",{style:{marginLeft:"auto"},disabled:!v.rows.some(D=>D.state!=="queued"),onclick:async()=>{const D=await B.batchStatsCsv(),L=await ha({defaultPath:"stats.csv",filters:[{name:"CSV",extensions:["csv"]}]});L&&(await B.saveBytes(L,[...new TextEncoder().encode(D)]),q(`Stats written to ${L}`,{kind:"good",action:{label:"Show in folder",run:()=>void ga(L)}}))}},"Export stats.csv"))},M=()=>{b(),x(),h()};return e.on(["batch","caps"],M),M(),i}function Di(e,t,s){const o=e.state==="failed"?".failed":e.state==="running"?".running":"",a=e.state==="queued",i=n(`button.reset${a?"":".muted"}`,{style:{textAlign:"left",color:a?"var(--dim)":"var(--muted)"},disabled:!a,title:a?"Use a different preset for this file":"Already run",onclick:()=>Fi(i,e,t,s)},Ni(e.preset));return n(`div.batchrow${o}`,{title:e.message??e.path},n("span",{style:{width:"14px",height:"14px",borderRadius:"4px",background:"var(--surface)",display:"block"}}),n("span.trunc.dim",null,e.file),i,n(`span.state.${e.state}`,null,n("i"),e.state),n("span.right",{class:e.de00===null?"muted":"dim"},e.de00===null?"—":re(e.de00)),n("span.right.faint",null,e.coordinates===null?"—":oe(e.coordinates)),n("span.right.faint",null,e.outBytes===null?"—":ie(e.outBytes)),n("span.trunc.muted",null,e.message??e.destination))}function Fi(e,t,s,o){const a=s.state.caps?.presets??[];yn(e,n("div.menu",null,...a.map(i=>n("button.item",{onclick:()=>{const r=s.state.batch.rows.find(l=>l.id===t.id);r&&(r.preset=i.id),ue(),o()}},n("span",{style:{flex:"1"}},i.name),n("span.when",null,i.id===t.preset?"current":i.subtitle)))))}function Ni(e){return e.split("-").map((t,s)=>s===0?t[0].toUpperCase()+t.slice(1):t).join(" ")}const Oi=.05,Ii=20,_i=.97,Bi=.3,Ri=.8;function Gi(e){const t=e.length/4;let s=0,o=0;for(let i=0;i+3_i*t)return null;const a=o/s;return aRi?"dark":null}function Wi(e){const t=n("div.pane",null,n("span.eyebrow.panelabel",null,"Before")),s=n("div.pane",null,n("span.eyebrow.panelabel",null,"After")),o=n("div.wipehandle",{role:"separator","aria-label":"Wipe"}),a=n("div.panes",null,t,s,o),i=n("div.viewertools"),r=n("section.stage",null,i,a);let l="side",c=.5,d=!1,p="auto",h=null,g=null,u=!1;const f=()=>{u||!e.state.prefs||(u=!0,{minifyView:l,minifyBackdrop:p}=oa())},b=()=>{f();const A=p==="auto"?h:p;a.classList.toggle("onlight",A==="light"),a.classList.toggle("ondark",A==="dark")},x=()=>{if(f(),a.classList.toggle("stacked",l!=="side"),o.style.display=l==="wipe"?"":"none",l==="side")t.classList.remove("hidden"),s.classList.remove("hidden"),t.style.clipPath="",s.style.clipPath="";else if(l==="wipe"){t.classList.remove("hidden"),s.classList.remove("hidden");const A=`${(c*100).toFixed(2)}%`;o.style.left=A,t.style.clipPath=`inset(0 ${(100-c*100).toFixed(2)}% 0 0)`,s.style.clipPath=`inset(0 0 0 ${A})`}else t.style.clipPath="",s.style.clipPath="",t.classList.toggle("hidden",!d),s.classList.toggle("hidden",d)};o.addEventListener("pointerdown",A=>{o.setPointerCapture(A.pointerId);const P=U=>{const X=a.getBoundingClientRect();c=Math.min(.98,Math.max(.02,(U.clientX-X.left)/X.width)),x()},J=()=>{o.removeEventListener("pointermove",P),o.removeEventListener("pointerup",J)};o.addEventListener("pointermove",P),o.addEventListener("pointerup",J)}),window.addEventListener("keydown",A=>{e.state.tab!=="minify"||e.state.screen||A.key!==" "||A.repeat||A.target instanceof HTMLInputElement||A.target instanceof HTMLTextAreaElement||(A.preventDefault(),d=!0,x())}),window.addEventListener("keyup",A=>{A.key===" "&&d&&(d=!1,x())});const M=()=>{f();const A=e.state.minify,P=A.result;F(i,n("div.seg",null,...["side","wipe","ab"].map(J=>n("button",{"aria-pressed":String(l===J),disabled:!A.before,onclick:()=>{l=J,an({minifyView:J}),x(),M()}},J==="side"?"Side by side":J==="wipe"?"Wipe":"A/B"))),n("span.muted",{style:{fontSize:"11.5px"}},"hold ",n("kbd",null,"Space")," to flick"),n("div",{style:{display:"flex",alignItems:"center",gap:"8px",marginLeft:"12px"}},n("span.muted",{style:{fontSize:"11.5px"}},"Backdrop"),n("div.seg",{role:"group","aria-label":"Backdrop"},...["auto","light","dark"].map(J=>n("button",{"aria-pressed":String(p===J),disabled:!A.before,onclick:()=>{p=J,an({minifyBackdrop:J}),b(),M()}},J==="auto"?"Auto":J==="light"?"Light":"Dark")))),n("span.muted",{style:{marginLeft:"auto",fontSize:"11.5px"}},P?`Nothing moved more than ${A.settings.tolerancePx} px${P.differenceDe00===null?"":` · measured ${re(P.differenceDe00)} dE00`}`:""))},v=n("aside.minifyrail"),S=n("div",{style:{flex:"1",minHeight:"0",display:"flex"}},r,v);let y=0;const $=async()=>{const A=e.state.minify;if(!A.before)return;const P=++y;try{const J=await B.minify(A.before,A.settings);if(P!==y)return;e.state.minify.result=J,e.state.minify.error=null}catch(J){if(P!==y)return;e.state.minify.result=null,e.state.minify.error=String(J)}e.touch("minify")},C=async()=>{const[A]=await fn([{name:"SVG",extensions:["svg"]}]);if(A)try{const P=await $t(A);e.state.minify.name=A.name,e.state.minify.before=P,e.state.minify.result=null,e.state.minify.error=null,e.touch("minify"),await $()}catch(P){q(String(P),{kind:"bad"})}},D=async A=>{if(!A.name.toLowerCase().endsWith(".svg"))return!1;const P=await $t(A);return e.state.minify.name=A.name,e.state.minify.before=P,e.touch("minify"),await $(),!0};S.acceptDropped=D;const L=()=>n("div.drop.solid",null,n("span.glyph",null,Z("file",32)),n("div",{style:{display:"flex",flexDirection:"column",gap:"6px"}},n("span.headline",null,"Drop an SVG here"),n("span.faint",{style:{fontSize:"13px"}},"or choose one to rewrite it in the fewest segments")),n("button.btn.primary",{onclick:C},"Open an SVG")),I=()=>{const A=e.state.minify,P=A.result;if(M(),x(),F(t,n("span.eyebrow.panelabel",null,`Before${P?` · ${ie(P.bytesBefore)}`:""}`),A.before?_e(A.before):L()),F(s,n("span.eyebrow.panelabel",null,`After${P?` · ${ie(P.bytesAfter)}`:""}`),P?_e(P.svg):A.before?null:n("span.faint.panehint",null,"The rewritten drawing appears here, on the same ground, so nothing can hide.")),A.before!==g){const U=A.before;g=U,h=null,b(),U&&io(U).then(X=>{g===U&&(h=X,b())})}if(!A.before){F(v,n("div.railscroll",{style:{justifyContent:"center",alignItems:"center",textAlign:"center",gap:"16px"}},n("span.serif",{style:{fontSize:"20px"}},"Minify an SVG"),n("span.faint",{style:{fontSize:"12.5px",lineHeight:"1.6",maxWidth:"30ch"}},"From Illustrator, Figma, a stock site — anything. It gets rewritten with the fewest segments that draw the same picture, and the result appears as you open it.")));return}const J=P?1-P.bytesAfter/Math.max(1,P.bytesBefore):0;F(v,n("div.railscroll",{style:{gap:"20px",padding:"22px 20px"}},A.error?n("div.card",null,n("span.eyebrow",null,"This file would not parse"),n("span.faint",{style:{fontSize:"12.5px",lineHeight:"1.6"}},A.error)):n("div.minifyresult",null,n("span.eyebrow",null,P?`Result · ${nt((P.ms??0)/1e3)}`:"Working"),n("div",{style:{display:"flex",alignItems:"baseline",gap:"10px"}},n("span.figure",null,P?$e(J):"—"),n("span.dim",{style:{fontSize:"14px",paddingBottom:"6px"}},"smaller")),P?n("span.faint.num",{style:{fontSize:"13px"}},`${ie(P.bytesBefore)} → ${ie(P.bytesAfter)}`):null),P?n("div.stats",null,...[["numbers before",oe(P.numbersBefore)],["numbers after",oe(P.numbersAfter)],["paths",`${P.pathsBefore} → ${P.pathsAfter}`],["segments",`${oe(P.segmentsBefore)} → ${oe(P.segmentsAfter)}`],["difference",P.differenceDe00===null?"—":`${re(P.differenceDe00)} dE00`],["kept as drawn",`${P.guarded} run${P.guarded===1?"":"s"}`]].map(([U,X])=>n("div",null,n("span.k",null,U),n("span.v",null,X)))):null,qi(e,$),n("div.card",null,n("div",{style:{display:"flex",alignItems:"center",justifyContent:"space-between"}},n("span.dim",{style:{fontSize:"12.5px"}},"Corner threshold"),(()=>{const U=n("span.num.faint",{style:{fontSize:"12px",width:"34px"}},`${A.settings.cornerDegrees}°`);return n("div",{style:{display:"flex",alignItems:"center",gap:"8px"}},n("input.slider",{type:"range",min:"5",max:"90",step:"1",style:{width:"120px"},value:String(A.settings.cornerDegrees),"aria-label":"Corner threshold in degrees",oninput:X=>{U.textContent=`${X.target.value}°`},onchange:X=>{A.settings.cornerDegrees=Number(X.target.value),e.touch("minify"),$()}}),U)})()),n("div",{style:{display:"flex",alignItems:"center",justifyContent:"space-between"}},n("span.dim",{style:{fontSize:"12.5px"}},"Document cleanup"),n("button.switch",{role:"switch","aria-checked":String(A.settings.documentCleanup),"aria-label":"Document cleanup",onclick:()=>{A.settings.documentCleanup=!A.settings.documentCleanup,e.touch("minify"),$()}})),n("span.muted",{style:{fontSize:"11.5px",lineHeight:"1.5"}},"Removes ids, groups, editor metadata and trailing zeros. Same geometry.")),P&&P.removed.length?n("div",{style:{display:"flex",flexDirection:"column",gap:"7px"}},n("span.eyebrow",null,"Removed"),...P.removed.map(U=>n("div",{style:{display:"flex",alignItems:"baseline",gap:"10px",fontSize:"12px"}},n("span.faint",{style:{flex:"1"}},U.what),n("span.dim.num",null,U.amount)))):null),n("div.railfoot",null,A.before?n("button.btn.ghost",{style:{justifyContent:"flex-start",gap:"8px"},title:"Minifying keeps every path the file has. Re-tracing draws the file again from its render: stacked or translucent paths, broken outlines and thousands of nodes come back as a few clean shapes, and the report measures the result against the original.",onclick:()=>window.dispatchEvent(new CustomEvent("inkvec:retrace",{detail:{svg:A.before,name:A.name??"drawing.svg"}}))},Z("wand",14),"Rebuild it clean by re-tracing"):null,n("div.row",null,n("button.btn.primary",{style:{flex:"1"},disabled:!P,onclick:async()=>{if(!P)return;const U=(A.name??"drawing").replace(/\.svg$/i,""),X=await Os(`${U}.min.svg`,P.svg);X&&q(X.path?`Saved to ${X.path}`:`Downloaded ${X.name}`,{kind:"good",action:Ft(X.path)})}},"Download SVG"),n("button.btn",{disabled:!P,onclick:async()=>{P&&(await Pt(P.svg),q("SVG copied. Paste straight into Figma or Illustrator."))}},"Copy"),n("button.btn",{onclick:C},"Open…"))))};return e.on(["minify"],I),I(),S}function qi(e,t){const s=e.state.minify,o=(r,l,c)=>n("input",{class:`inlinefield${l?" accent":""}`,type:"text",inputmode:"decimal",value:String(r),onchange:d=>{const p=Number(d.target.value);Number.isFinite(p)&&p>0&&(c(p),e.touch("minify"),t())}}),a=o(s.settings.tolerancePx,!0,r=>s.settings.tolerancePx=r),i=r=>{const l=Number(r.value)/1e3;return Number((l*l).toFixed(3))};return n("div.card",null,n("span.sentence",null,"Nothing moves more than ",a," px when the drawing is ",o(s.settings.judgePx,!1,r=>s.settings.judgePx=r)," px wide."),n("input.slider",{type:"range",min:"0",max:"1000",step:"1",value:String(Math.round(Math.sqrt(Math.min(1,s.settings.tolerancePx))*1e3)),"aria-label":"Tolerance",oninput:r=>{a.value=String(i(r.target))},onchange:r=>{s.settings.tolerancePx=i(r.target),e.touch("minify"),t()}}),n("div.stops",null,n("span",null,"exact"),n("span",null,"gentle"),n("span",null,"aggressive")))}function _e(e){const t=n("div.art.centred"),s=ao(e);return s&&(s.setAttribute("width","100%"),s.setAttribute("height","100%"),t.append(s)),t}function Hi(e){const t=(e.getAttribute("viewBox")??"").trim().split(/[\s,]+/).map(Number);return t.length===4&&t.every(Number.isFinite)&&t[2]>0&&t[3]>0?[t[0],t[1],t[2],t[3]]:null}function ao(e){const t=new DOMParser().parseFromString(e,"image/svg+xml"),s=t.documentElement;if(!s||s.nodeName!=="svg"||t.querySelector("parsererror"))return null;const o=document.importNode(s,!0);if(Hi(o))return o;const a=o.getAttribute("width")??"",i=o.getAttribute("height")??"",r=parseFloat(a),l=parseFloat(i);if(r>0&&l>0&&!a.includes("%")&&!i.includes("%"))return o.setAttribute("viewBox",`0 0 ${r} ${l}`),o;const c=document.createElement("div");c.style.cssText="position:absolute;visibility:hidden;width:0;height:0;overflow:hidden",o.setAttribute("width","1"),o.setAttribute("height","1"),c.append(o),document.body.append(c);try{const d=o.getBBox();if(d.width>0&&d.height>0){const p=Math.max(d.width,d.height)*.02;o.setAttribute("viewBox",`${d.x-p} ${d.y-p} ${d.width+2*p} ${d.height+2*p}`)}else o.setAttribute("viewBox","0 0 100 100")}catch{o.setAttribute("viewBox","0 0 100 100")}finally{c.remove()}return o}async function io(e){const t=ao(e);if(!t)return null;const s=64;t.setAttribute("width",String(s)),t.setAttribute("height",String(s));const o=`data:image/svg+xml;charset=utf-8,${encodeURIComponent(new XMLSerializer().serializeToString(t))}`;try{const a=await new Promise((l,c)=>{const d=new Image;d.onload=()=>l(d),d.onerror=c,d.src=o}),i=document.createElement("canvas");i.width=s,i.height=s;const r=i.getContext("2d",{willReadFrequently:!0});return r?(r.drawImage(a,0,0,s,s),Gi(r.getImageData(0,0,s,s).data)):null}catch{return null}}const ye=25.4,ps=[["singleColour","One colour","Everything chosen, joined into one sheet of one material."],["layered","Layered","One sheet per colour. Each runs under the colours above it by the bleed, so the stack has no gaps; registration marks line the sheets up."],["inlay","Inlay","One sheet per colour, cut exactly, laid edge to edge. Thinner, but unforgiving to line up."],["sticker","Sticker","The artwork with a contour around it, to print and then cut."],["stencil","Stencil","A sheet with the artwork cut out. Counters that would fall out, like the middle of an O, are held by thin bridges."],["lines","Lines","For a pen, a scoring blade or a laser line: every line in the drawing is followed once, along its centre, instead of round both of its edges. Strokes in the file are used as drawn; lines inside filled shapes are found and checked against the shape."]],hs=[["mm","Millimetres","Exact by the SVG standard, and read correctly by most programs. If yours imports at the wrong size, choose the pixel rate it uses."],["px96","Pixels, 96/in","The CSS pixel: Inkscape, LightBurn, Carbide Create, Fusion and browsers read a bare pixel size at 96 per inch."],["px72","Pixels, 72/in","Cricut Design Space and Silhouette Studio read a bare pixel size at 72 per inch."]],fs=[{id:"vinyl",label:"Adhesive vinyl",glyph:"layers",note:"Cricut, Silhouette, Brother. Filled shapes, stacked by colour with a 0.8 mm overlap, marks to line up, nothing narrower than 0.8 mm.",apply:e=>({mode:e>1?"layered":"singleColour",cutStyle:"filled",mirror:!1,registration:!0,bleedMm:.8,minFeatureMm:.8,kerfMm:0})},{id:"htv",label:"Iron-on",glyph:"zap",note:"Heat-transfer vinyl is cut face down, so every sheet is mirrored. Layers overlap by 0.3 mm (0.25–0.38 mm is the press-safe range) and nothing is narrower than 1 mm. Glitter, holographic and puff go on top only.",apply:e=>({mode:e>1?"layered":"singleColour",cutStyle:"filled",mirror:!0,registration:!0,bleedMm:.3,minFeatureMm:1,kerfMm:0})},{id:"sticker",label:"Sticker",glyph:"square",note:"Print the artwork, then the machine cuts the contour around it.",apply:()=>({mode:"sticker",cutStyle:"filled",mirror:!1,stickerMarginMm:3,kerfMm:0})},{id:"stencil",label:"Stencil",glyph:"copy",note:"Mylar or card with the design cut out, for paint or etching cream. Loose centres are bridged.",apply:()=>({mode:"stencil",cutStyle:"filled",mirror:!1,bridgeMm:1.5,stencilMarginMm:10,minFeatureMm:1.5,kerfMm:0})},{id:"pen",label:"Pen or score",glyph:"pen",note:"A pen in the cutter, a scoring or foil tool, a plotter, or a laser engraving lines: each line is drawn once, down its middle. Set the tip width so the preview shows what the pen will leave.",apply:()=>({mode:"lines",penMm:.4,maxLineMm:0,mirror:!1})},{id:"laser",label:"Laser or sign cutter",glyph:"crosshair",note:"Hairlines in LightBurn's layer colours, inner shapes cut first, parts grown for a 0.15 mm kerf (CO₂ 0.1–0.2 mm, diode 0.15–0.3 mm; measure yours). A DXF is saved too.",apply:()=>({mode:"inlay",cutStyle:"hairline",mirror:!1,registration:!1,kerfMm:.15,minFeatureMm:.3})}];function Ui(e){const t=n("div.viewertools"),s=n("div.pane",null),o=n("div.panes.onlight",null,s),a=n("section.stage",null,t,o),i=n("aside.minifyrail"),r=n("div",{style:{flex:"1",minHeight:"0",display:"flex"}},a,i);let l=0;const c=async()=>{const b=e.state.fab;if(!b.svg)return;const x=++l;try{const M=await B.fabPrepare(b.svg,b.options);if(x!==l)return;b.plan=M,b.error=null,b.shown>=M.layers.length&&(b.shown=-1)}catch(M){if(x!==l)return;b.plan=null,b.error=String(M)}e.touch("fab")},d=async(b,x)=>{const M=e.state.fab;M.name=b,M.svg=x,M.plan=null,M.error=null,M.shown=-1;try{const v=await B.fabAnalyze(x);M.analysis=v;const S=v.colours.filter(y=>!y.background).map(y=>y.hex);M.options.include=S,M.options.order=[...S],S.length>1&&M.options.mode==="singleColour"&&(M.options.mode="layered")}catch(v){M.analysis=null,M.error=String(v)}e.touch("fab"),await c()},p=async()=>{const[b]=await fn([{name:"SVG",extensions:["svg"]}]);if(b)try{await d(b.name,await $t(b))}catch(x){q(String(x),{kind:"bad"})}},h=async()=>{const b=e.state;b.svg&&await d(b.source?.name?`${b.source.name.replace(/\.[^.]+$/,"")}.svg`:"trace.svg",b.svg)},g=async b=>b.name.toLowerCase().endsWith(".svg")?(await d(b.name,await $t(b)),!0):!1;r.acceptDropped=g;const u=(b,x=!1)=>{const M=e.state.fab;Object.assign(M.options,b),x||(M.preset=null),e.touch("fab"),c()},f=()=>{const b=e.state.fab;if(Zi(e,t,s),!b.svg){F(i,Xi(e,p,h));return}F(i,n("div.railscroll",{style:{gap:"16px",padding:"20px"}},Ki(e,p,h),b.error?n("div.card",null,n("span.eyebrow",null,"This file could not be prepared"),n("span.faint",null,b.error)):null,Qi(e,u),er(e,u),tr(e,u),nr(e,u),or(e,u),ar(e),ir(e)),rr(e))};return e.on(["fab"],f),e.on(["svg"],()=>{e.state.tab==="fabricate"&&f()}),f(),r}function ro(e,t,s=1){return e.state.fab.unit==="in"?`${(t/ye).toFixed(s+1)} in`:`${t.toFixed(s)} mm`}function ji(e,t){return e.state.fab.unit!=="in"?t:t.replace(/(\d+(?:\.\d+)?) mm²/g,(s,o)=>`${(Number(o)/(ye*ye)).toFixed(4)} in²`).replace(/(\d+(?:\.\d+)?) mm(?![²\w])/g,(s,o)=>`${(Number(o)/ye).toFixed(3)} in`)}function et(e,t){return e.state.fab.unit==="in"?`${(t[0]/ye).toFixed(2)} × ${(t[1]/ye).toFixed(2)} in`:`${t[0].toFixed(1)} × ${t[1].toFixed(1)} mm`}function Vi(e){return e?e.checks.filter(t=>t.code==="thin"||t.code==="gaps"||t.code==="specks").length:0}function Yi(){return n("div.drop.solid",null,n("span.glyph",null,Z("layers",32)),n("div",{style:{display:"flex",flexDirection:"column",gap:"6px"}},n("span.headline",null,"Drop an SVG here"),n("span.faint",{style:{fontSize:"13px"}},"or open one, or bring over a trace, from the panel on the right")))}function En(e,t=10){return n("span",{style:{display:"inline-block",width:`${t}px`,height:`${t}px`,borderRadius:"2px",background:e,border:"1px solid var(--rule2)",flex:"none"}})}function Zi(e,t,s){const o=e.state.fab,a=o.plan,i=Vi(a),r=h=>{o.shown=h,e.touch("fab")};F(t,n("div.seg",{role:"group","aria-label":"Sheet"},n("button",{"aria-pressed":String(o.shown===-1),disabled:!a,onclick:()=>r(-1)},"All sheets"),...(a?.layers??[]).map((h,g)=>n("button",{"aria-pressed":String(o.shown===g),title:`${h.name}: ${et(e,h.materialMm)} of material, ${h.parts} pieces, ${oe(h.nodes)} path segments`,onclick:()=>r(g)},En(h.hex),n("span",{style:{marginLeft:"6px"}},(a?.layers.length??0)>1?`${g+1}`:h.name)))),a&&i?n("button.btn.ghost.compact",{"aria-pressed":String(o.showProblems),style:{marginLeft:"12px",color:o.showProblems?"#e5484d":void 0},title:"Draw the parts too thin to weed (red), the waste gaps too narrow to weed (amber) and the loose specks (ringed)",onclick:()=>{o.showProblems=!o.showProblems,e.touch("fab")}},Z("alert",13),n("span",{style:{marginLeft:"6px"}},o.showProblems?"Showing problems":"Show problems")):null,n("span.muted",{style:{marginLeft:"auto",fontSize:"11.5px"}},a?`${et(e,a.sizeMm)}${o.options.mode==="lines"?"":o.options.registration&&a.layers.length>1&&(o.options.mode==="layered"||o.options.mode==="inlay")?", marks included":o.options.weedBorderMm>0?", border included":""}`:""));const l=o.options.mode==="lines",c=a?o.shown>=0?a.layers[o.shown]?.svg:l?a.combinedSvg:a.previewSvg:null,d=(h,g=!1)=>(h.style.gridArea="1 / 1",g&&(h.style.pointerEvents="none",h.style.opacity="0.85"),h),p=c?n("div",{style:{display:"grid",placeSelf:"stretch",minWidth:"0",minHeight:"0"}},l&&a&&o.shown<0?Ji(d(_e(a.previewSvg))):null,d(_e(c)),a&&i&&o.showProblems?d(_e(a.problemsSvg),!0):null):o.svg?_e(o.svg):Yi();F(s,n("span.eyebrow.panelabel",null,o.shown>=0&&a?`${l?"Pen":"Sheet"} ${o.shown+1} · ${a.layers[o.shown].name} · ${et(e,a.layers[o.shown].materialMm)}`:l?"What the pen draws, over the drawing":"All sheets, stacked"),p)}function vt(e,t){const s=e.state.fab.options.mode==="lines"?"pen drawing":"sheet";return t===1?s:`${s}s`}function Ji(e){return e.style.opacity="0.18",e.style.pointerEvents="none",e}function Xi(e,t,s){const o=!!e.state.svg;return n("div.railscroll",{style:{justifyContent:"center",alignItems:"center",textAlign:"center",gap:"16px"}},n("span.serif",{style:{fontSize:"20px"}},"Make it real"),n("span.faint",{style:{fontSize:"12.5px",lineHeight:"1.6",maxWidth:"32ch"}},"Prepare a drawing for a vinyl cutter, a sticker or a laser: real sizes, one sheet per colour, and a check of what will go wrong before anything is cut."),o?n("button.btn.primary",{onclick:s},"Use the current trace"):null,n(o?"button.btn":"button.btn.primary",{onclick:t},"Open an SVG"))}function Ki(e,t,s){const o=e.state.fab;return n("div",{style:{display:"flex",alignItems:"center",gap:"8px"}},Z("file",14),n("span.dim",{style:{flex:"1",fontSize:"12.5px",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"}},o.name??"drawing"),e.state.svg?n("button.btn.ghost.compact",{onclick:s,title:"Use what the Vectorize tab traced"},"Current trace"):null,n("button.btn.ghost.compact",{onclick:t},"Open…"))}function Qi(e,t){const s=e.state.fab,o=s.options.include.length,a=fs.find(i=>i.id===s.preset);return n("div.card",null,n("span.eyebrow",null,"Material"),n("div",{style:{display:"grid",gridTemplateColumns:"1fr 1fr",gap:"6px"}},...fs.map(i=>n("button.btn",{"aria-pressed":String(s.preset===i.id),style:{justifyContent:"flex-start",gap:"8px",borderColor:s.preset===i.id?"var(--accent)":void 0,background:s.preset===i.id?"var(--accent-soft)":void 0},onclick:()=>{s.preset=i.id,s.dxf=i.id==="laser"||i.id==="pen",t(i.apply(o),!0)}},Z(i.glyph,14),i.label))),n("span.muted",{style:{fontSize:"11.5px",lineHeight:"1.5"}},a?a.note:"Pick what goes in the machine, or set everything below by hand."))}function er(e,t){const s=e.state.fab.options,o=ps.find(a=>a[0]===s.mode)?.[2]??"";return n("div.card",null,n("span.eyebrow",null,"Making"),n("div.seg",{role:"group","aria-label":"What is being made",style:{display:"grid",gridTemplateColumns:"repeat(3, 1fr)"}},...ps.map(([a,i])=>n("button",{"aria-pressed":String(s.mode===a),onclick:()=>t({mode:a})},i))),n("span.muted",{style:{fontSize:"11.5px",lineHeight:"1.5"}},o))}function xt(e,t,s="58px",o=0){return n("input",{class:"inlinefield",type:"text",inputmode:"decimal",value:String(e),style:{width:s},onchange:a=>{const i=Number(a.target.value);Number.isFinite(i)&&i>=o&&t(i)}})}function he(e,t,s,o="58px"){const a=e.state.fab.unit==="in",i=Number(a?(t/ye).toFixed(3):t.toFixed(2));return xt(i,r=>s(a?r*ye:r),o)}function Ln(e){return e.state.fab.unit==="in"?"in":"mm"}function tr(e,t){const s=e.state.fab,o=s.analysis?.aspect??1,a=s.options.widthMm,i=Ln(e);return n("div.card",null,n("div.cardhead",null,n("span.eyebrow",null,"Size"),n("div.seg",{role:"group","aria-label":"Units"},...["mm","in"].map(r=>n("button",{"aria-pressed":String(s.unit===r),onclick:()=>{s.unit=r,e.touch("fab")}},r)))),n("span.sentence",null,"The design is ",he(e,a,r=>r>0&&t({widthMm:r})),` ${i} wide and `,he(e,a*o,r=>r>0&&t({widthMm:r/o})),` ${i} tall.`),Oe("Cut a size-check square",s.options.sizeCheckMm>0,()=>t({sizeCheckMm:s.options.sizeCheckMm>0?0:s.unit==="in"?ye:20}),s.options.sizeCheckMm>0?`A ${ro(e,s.options.sizeCheckMm,1)} square below the design. Measure it once cut: any other size means the program rescaled the file on import.`:"Wrong size on import is the commonest cutter complaint. A small square below the design shows it before a whole sheet is wasted."),n("div",{style:{display:"flex",flexDirection:"column",gap:"6px"}},n("span.dim",{style:{fontSize:"12.5px"}},"The files state their size in"),n("div.seg",{role:"group","aria-label":"Size written in",style:{display:"grid",gridTemplateColumns:"repeat(3, 1fr)"}},...hs.map(([r,l])=>n("button",{"aria-pressed":String(s.options.fileUnits===r),onclick:()=>t({fileUnits:r})},l))),n("span.muted",{style:{fontSize:"11px",lineHeight:"1.45"}},hs.find(r=>r[0]===s.options.fileUnits)?.[2]??"")))}function nr(e,t){const s=e.state.fab,o=s.analysis;if(!o)return null;const a=s.options,i=a.mode==="layered",r=i?[...a.order.filter(d=>a.include.includes(d)),...o.colours.map(d=>d.hex).filter(d=>!a.include.includes(d))]:o.colours.map(d=>d.hex),l=d=>o.colours.find(p=>p.hex===d),c=(d,p)=>{const h=a.order.filter(f=>a.include.includes(f)),g=h.indexOf(d),u=g+p;g<0||u<0||u>=h.length||([h[g],h[u]]=[h[u],h[g]],t({order:h}))};return n("div.card",null,n("div.cardhead",null,n("span.eyebrow",null,i?"Colours, bottom sheet first":"Colours"),n("span.muted",{style:{fontSize:"11px"}},`${a.include.length} of ${o.colours.length}`)),...r.map(d=>{const p=l(d);if(!p)return null;const h=a.include.includes(d),g=[p.background?"page":null,p.gradient?"gradient":null,p.translucent?"translucent":null].filter(Boolean);return n("div",{style:{display:"flex",alignItems:"center",gap:"8px",fontSize:"12.5px",opacity:h?"1":"0.55"}},n("button.switch",{role:"switch","aria-checked":String(h),"aria-label":`Cut ${d}`,onclick:()=>{const u=h?a.include.filter(b=>b!==d):[...a.include,d],f=h?a.order:[...a.order.filter(b=>b!==d),d];t({include:u,order:f})}}),En(d,16),n("span.num",{style:{flex:"1"}},d,g.length?n("span.muted",{style:{marginLeft:"6px",fontSize:"11px"}},g.join(" · ")):null),n("span.muted.num",{style:{fontSize:"11px"}},$e(p.coverage,1)),i&&h?n("span",{style:{display:"flex",gap:"2px"}},n("button.btn.ghost.compact",{title:"Lower in the stack",onclick:()=>c(d,-1)},"↓"),n("button.btn.ghost.compact",{title:"Higher in the stack",onclick:()=>c(d,1)},"↑")):null)}))}function Oe(e,t,s,o){return n("div",{style:{display:"flex",flexDirection:"column",gap:"3px"}},n("div",{style:{display:"flex",alignItems:"center",justifyContent:"space-between"}},n("span.dim",{style:{fontSize:"12.5px"}},e),n("button.switch",{role:"switch","aria-checked":String(t),"aria-label":e,onclick:s})),o?n("span.muted",{style:{fontSize:"11px",lineHeight:"1.45"}},o):null)}function sr(e,t){const s=e.state.fab.options,o=Ln(e);return n("div.card",null,n("span.eyebrow",null,"Drawing"),n("span.sentence",null,"The pen or tool draws ",he(e,s.penMm,a=>a>0&&t({penMm:a})),` ${o} wide.`),n("span.sentence",null,"Lines up to ",he(e,s.maxLineMm,a=>t({maxLineMm:a})),` ${o} wide are drawn once (0 for any width); wider parts are drawn round their outline.`),Oe("Also save a DXF",e.state.fab.dxf,()=>{e.state.fab.dxf=!e.state.fab.dxf,e.touch("fab")},"Lines as open polylines and outlines as closed ones, one layer per colour, in millimetres."),lo(e,t),n("span.sentence",null,"Lines stay within ",he(e,s.toleranceMm,a=>a>0&&t({toleranceMm:a})),` ${o} of the drawing.`))}function lo(e,t){const s=e.state.fab,o=s.options,a=s.unit==="in"?"in/min":"mm/min",i=s.unit==="in"?Number((o.gcodeFeedMmMin/ye).toFixed(1)):Math.round(o.gcodeFeedMmMin);return n("div",{style:{display:"flex",flexDirection:"column",gap:"8px"}},Oe("Also save G-code",s.gcode,()=>{s.gcode=!s.gcode,e.touch("fab")},"For GRBL lasers and plotters: curves as G2/G3 arcs, holes cut first, origin at the lower left of the job. GRBL's laser mode ($32=1) keeps the beam off between cuts."),s.gcode?n("span.sentence",null,"Cut at ",xt(i,r=>r>0&&t({gcodeFeedMmMin:s.unit==="in"?r*ye:r}),"64px"),` ${a}, power S`,xt(o.gcodePower,r=>t({gcodePower:r}),"58px"),", ",xt(o.gcodePasses,r=>r>=1&&t({gcodePasses:Math.round(r)}),"40px",1),o.gcodePasses===1?" pass.":" passes."):null)}function or(e,t){const s=e.state.fab.options;if(s.mode==="lines")return sr(e,t);const o=Ln(e),a=s.mode==="layered"||s.mode==="inlay";return n("div.card",null,n("span.eyebrow",null,"Cutting"),s.mode==="layered"?n("span.sentence",null,"Each colour runs ",he(e,s.bleedMm,i=>t({bleedMm:i})),` ${o} under the colours above it.`):null,s.mode==="sticker"?n("span.sentence",null,"The contour is ",he(e,s.stickerMarginMm,i=>t({stickerMarginMm:i})),` ${o} outside the artwork.`):null,s.mode==="stencil"?n("span.sentence",null,"Bridges are ",he(e,s.bridgeMm,i=>i>0&&t({bridgeMm:i})),` ${o} wide; the sheet reaches `,he(e,s.stencilMarginMm,i=>t({stencilMarginMm:i})),` ${o} past the artwork.`):null,n("span.sentence",null,"Parts narrower than ",he(e,s.minFeatureMm,i=>i>0&&t({minFeatureMm:i})),` ${o} are too thin to weed.`),Oe("Remove them",s.removeThin,()=>t({removeThin:!s.removeThin}),"Drops the slivers and necks narrower than that from the sheets."),a?Oe("Registration marks",s.registration,()=>t({registration:!s.registration})):null,n("span.sentence",null,"Weeding border ",he(e,s.weedBorderMm,i=>t({weedBorderMm:i})),` ${o} outside (0 for none).`),Oe("Also save a DXF",e.state.fab.dxf,()=>{e.state.fab.dxf=!e.state.fab.dxf,e.touch("fab")},"Every sheet as a layer of closed polylines in millimetres, curves as true arcs (which CAM turns into G2/G3 moves), for CAD, CNC and laser programs."),lo(e,t),s.mode==="sticker"||s.mode==="stencil"?null:Oe("Mirror",s.mirror,()=>t({mirror:!s.mirror}),"For heat-transfer vinyl, which is cut from the back."),n("div",{style:{display:"flex",flexDirection:"column",gap:"6px"}},n("span.dim",{style:{fontSize:"12.5px"}},"Cut lines as"),n("div.seg",{role:"group","aria-label":"Cut lines as"},n("button",{"aria-pressed":String(s.cutStyle==="filled"),onclick:()=>t({cutStyle:"filled"})},"Filled shapes"),n("button",{"aria-pressed":String(s.cutStyle==="hairline"),onclick:()=>t({cutStyle:"hairline"})},"Hairlines")),n("span.muted",{style:{fontSize:"11px",lineHeight:"1.45"}},s.cutStyle==="filled"?"What Cricut Design Space and Silhouette Studio expect: they cut around every shape.":"The laser and sign-cutter convention: only a hairline is a cut. Each sheet keeps its colour, so a laser program can give it its own operation.")),s.cutStyle==="hairline"?n("span.sentence",null,"The cut removes ",he(e,s.kerfMm,i=>t({kerfMm:i})),` ${o} (kerf); parts are grown by half of it so they come out the drawn size.`):null,n("span.sentence",null,"Router bit ",he(e,2*s.dogboneMm,i=>t({dogboneMm:i/2})),` ${o} across (0 for none): every inside corner gets a dogbone so parts seat square.`),n("span.sentence",null,"Cuts stay within ",he(e,s.toleranceMm,i=>i>0&&t({toleranceMm:i})),` ${o} of the drawing.`))}function ar(e){const t=e.state.fab.plan;if(!t)return null;const s=t.layers.reduce((i,r)=>i+r.nodes,0),o=t.layers.reduce((i,r)=>i+r.parts,0),a=t.checks.some(i=>i.level==="error")?"error":t.checks.some(i=>i.level==="warn")?"warn":"ok";return n("div.card",{style:a==="ok"?{}:{borderColor:a==="error"?"#e5484d":"var(--accent)"}},n("div.cardhead",null,n("span.eyebrow",null,"Before you cut"),n("span.muted",{style:{fontSize:"11px"}},a==="ok"?"ready":`${t.checks.length} finding${t.checks.length===1?"":"s"}`)),n("div.stats",null,...[[vt(e,2),String(t.layers.length)],["pieces",oe(o)],["path segments",oe(s)],["whole job",et(e,t.sizeMm)]].map(([i,r])=>n("div",null,n("span.k",null,i),n("span.v",null,r)))),t.checks.length?n("div",{style:{display:"flex",flexDirection:"column",gap:"8px"}},...t.checks.map(i=>n("div",{style:{display:"flex",gap:"8px",alignItems:"flex-start",fontSize:"12px",lineHeight:"1.5"}},n("span",{style:{color:i.level==="error"?"#e5484d":i.level==="warn"?"var(--accent-text)":"var(--muted)",flex:"none",paddingTop:"2px"}},Z(i.level==="info"?"info":"alert",14)),n("span.faint",null,ji(e,i.message))))):n("div",{style:{display:"flex",gap:"8px",alignItems:"center",fontSize:"12.5px"}},Z("check",14),n("span.dim",null,"Nothing here will trouble the cutter.")))}function ir(e){const t=e.state.fab.plan;if(!t||!t.layers.length)return null;const s=e.state.fab;return n("div.card",null,n("span.eyebrow",null,e.state.fab.options.mode==="lines"?"Pens, one per colour":"Cut list"),...t.layers.map((o,a)=>n("button",{style:{display:"flex",alignItems:"center",gap:"8px",fontSize:"12.5px",background:"none",border:"none",padding:"2px 0",color:"inherit",cursor:"pointer",textAlign:"left",fontWeight:s.shown===a?"600":"400"},title:"Show this sheet",onclick:()=>{s.shown=a,e.touch("fab")}},n("span.muted.num",{style:{width:"16px"}},String(a+1)),En(o.hex,14),n("span.num",{style:{flex:"1"}},o.name),n("span.dim.num",null,et(e,o.materialMm)),n("span.muted.num",{style:{width:"54px",textAlign:"right",fontSize:"11px"}},`${o.parts} pc`))))}function rr(e){const t=e.state.fab,s=t.plan,o=(t.name??"drawing").replace(/\.svg$/i,""),a=(i,r)=>{const l=i.layers[r].name.replace(/[^A-Za-z0-9_-]+/g,"");return i.layers.length>1?`${o}-${r+1}-${l}.svg`:`${o}-cut.svg`};return n("div.railfoot",null,n("div.row",null,n("button.btn.primary",{style:{flex:"1"},disabled:!s||s.layers.length===0,onclick:async()=>{if(!s)return;const i=s.layers.map((d,p)=>({name:a(s,p),data:d.svg}));s.layers.length>1&&i.push({name:`${o}-all.svg`,data:s.combinedSvg}),t.dxf&&i.push({name:`${o}.dxf`,data:s.dxf}),t.gcode&&i.push({name:`${o}.gcode`,data:s.gcode});const r=await ba(i,`${o}-sheets.zip`);if(!r)return;const l=s.layers.length>1,c=`${s.layers.length} ${vt(e,s.layers.length)}${l?" and one combined file":""}`;q(r.folder?`Saved ${c} to ${r.folder}`:`Downloaded ${c}${i.length>1?" as one .zip":""}`,{kind:"good",action:Ft(r.first)})}},`Download ${s&&s.layers.length>1?`${s.layers.length} ${vt(e,2)}`:`the ${vt(e,1)}`}`),n("button.btn",{disabled:!s,title:"Copies the shown sheet, or every sheet in one file",onclick:async()=>{s&&(await Pt(t.shown>=0?s.layers[t.shown].svg:s.combinedSvg),q("Copied. Paste it into Design Space, Silhouette Studio or LightBurn."))}},"Copy")),n("div.note",null,n("span",null,s?`Real size: ${ro(e,s.sizeMm[0])} wide`:"Real sizes"),n("span",null,"Nothing leaves this computer")))}const gt={inkvec:"Inkvec","vtracer-1.0-simplify":"VTracer 1.0, best flags","trazor-auto":"Trazor","vtracer-1.0-default":"VTracer 1.0","vtracer-default":"VTracer 0.6"},co=e=>fetch(new URL(e,document.baseURI)).then(t=>{if(!t.ok)throw new Error(`${e}: HTTP ${t.status}`);return t.json()});let gs=null;function lr(){return gs??=co("../showcase.json"),gs}function cr(e){return co(`../showcase/${e}.json`)}function ur(e){const t=n("div.screenbody",null,n("div.sc-loading.faint",null,"Loading the showcase…"));return lr().then(s=>F(t,dr(s)),s=>F(t,n("div.sc-loading.faint",null,`The showcase could not be loaded: ${String(s)}`))),n("div.screen",null,n("div.screenbar",{"data-tauri-drag-region":""},n("span",{style:{fontSize:"12.5px",fontWeight:"600"}},"Showcase"),n("div.spacer"),n("button.btn.compact",{onclick:e},"Done"),n("div.sep"),rt()),t)}function dr(e){let t=e.sets[0].cases[0],s="vtracer-1.0-simplify",o="fill",a=0;const i=pr(),r=n("div.sc-chips"),l=n("p.sc-caption"),c=n("div.sc-list"),d=new Map,p=e.gallery_engines.filter(y=>y!=="inkvec"),h=(y,$,C)=>{const D=n("div.seg"),L=()=>{for(const I of D.querySelectorAll("button"))I.setAttribute("aria-pressed",String(I.dataset.k===$()))};for(const[I,A]of y)D.append(n("button",{"data-k":I,onclick:()=>{C(I),L()}},A));return L(),D},g=async()=>{const y=t,$=++a;for(const[A,P]of d)P.setAttribute("aria-pressed",String(A===y.key));const C=y.what.includes("brand")||y.key.startsWith("brand");F(l,n("b",null,y.label),` · ${y.what}`,C?n("span.faint",{style:{fontSize:"11px",marginLeft:"6px"},title:"Nominative benchmark demonstration. All trademarks belong to their respective owners."},"(nominative benchmark)"):null);const D=y.m.inkvec,L=y.m[s];F(r,n("span.sc-legend.faint",null,n("i.sc-dot.a"),"anchor",n("i.sc-dot.hd"),"control point"),Ke(`Inkvec dE00 ${D.de1024.toFixed(3)}`,!0),Ke(`${gt[s]} dE00 ${L.de1024.toFixed(3)}`),Ke(`Inkvec ${D.paths} paths · ${D.coordinates} coordinates · ${ms(D.bytes)}`,!0),Ke(`${gt[s]} ${L.paths} paths · ${L.coordinates} coordinates · ${ms(L.bytes)}`),Ke(`The artist's file: ${y.artist.paths} paths · ${y.artist.coordinates} coordinates`));const I=await cr(y.key);$===a&&(i.set(I.input,I.svg.inkvec,I.svg[s],"Inkvec",gt[s]??s),i.layer(o))},u=[];new ResizeObserver(()=>{const y=i.el.getBoundingClientRect().height;y>200&&(c.style.height=`${y}px`)}).observe(i.el);const f=(y,$)=>{t=y,g();const C=d.get(y.key);if(!C)return;const D=c.getBoundingClientRect(),L=C.getBoundingClientRect(),I=34;L.topD.bottom&&(c.scrollTop+=L.bottom-D.bottom+6),$&&C.focus({preventScroll:!0})};c.addEventListener("keydown",y=>{const $=u.findIndex(L=>L.key===t.key),D={ArrowDown:3,ArrowUp:-3,ArrowRight:1,ArrowLeft:-1,Home:-$,End:u.length-1-$}[y.key];D!==void 0&&(y.preventDefault(),f(u[Math.min(u.length-1,Math.max(0,$+D))],!0))});for(const y of e.sets)c.append(n("div.sc-sethead",null,y.label,n("span",null,String(y.cases.length)))),c.append(n("div.sc-tiles",null,...y.cases.map($=>{const C=n("button.sc-case",{onclick:()=>f($,!1),title:`${$.label} · ${$.what}`},n("span.sc-thumb",null,n("img",{src:$.thumb,alt:"",loading:"lazy"})),n("span.sc-name",null,$.label));return d.set($.key,C),u.push($),C})));const b=n("div.sc-tools",null,n("span.eyebrow",null,"Compare with"),h(p.map(y=>[y,gt[y]??y]),()=>s,y=>{s=y,g()}),n("span.eyebrow",null,"Show"),h([["fill","Fill"],["wire","Wireframe"],["anchor","Anchors"],["handle","Handles"]],()=>o,y=>{o=y,i.layer(o)}),n("span.eyebrow",null,"Zoom"),...[1,4,12].map(y=>n("button.btn.compact.ghost",{onclick:()=>i.zoom(y)},`${y}×`)));g();const x=e.engines.find(y=>y.id==="inkvec"),M=e.best_de_wins.inkvec??0,v=e.brands.best_de_wins.inkvec??0,S=e.corpus.best_de_wins.inkvec??0;return n("div.sc-page",null,n("div.sc-head",null,n("h1.serif",null,"Before and after, with the geometry showing"),n("p.faint",null,`Real brand logos, the benchmark's cases and more icons and emoji, all in the list on the left, each traced by Inkvec (build ${e.inkvec.git}, ${e.traced}) and by the other engines. Left, the raster; middle, Inkvec's SVG; right, the engine you compare with. Scroll to zoom and drag to pan, all three together; Anchors shows every on-curve point, Handles every control point with its tangent.`)),n("div.sc-main",null,c,n("div.sc-stagecol",null,b,i.el,l,r)),n("div.sc-results",null,n("h2.serif",null,"Inkvec vs the field"),n("p.faint",null,`${e.cases} benchmark cases (real icons, emoji, synthetic probes and real brand logos; selection fixed from the seed ${e.seed}), each rendered from its source SVG at ${e.size} px, traced by every engine from that raster and scored against the render: CIEDE2000 colour difference, DISTS and DINOv3 perceptual similarity, and geometry against the artist's own file (1.00× is exactly the artist's number of parameters). Inkvec had the lowest colour difference on ${M} of ${e.cases}; in the other sets, on ${v} of ${e.brands.cases} brand logos and ${S} of ${e.corpus.cases} more icons and emoji.`),x?n("div.sc-bignums",null,mt("median colour difference",x.de_median.toFixed(3),"dE00; under 1 is invisible"),mt("geometry vs the artist",`${x.geometry_mean.toFixed(2)}×`,`mean; median ${x.geometry_median.toFixed(2)}×`),mt("DINOv3 similarity",x.dino.toFixed(4),"1 is identical"),mt("seconds per case",x.seconds_median.toFixed(2),"median, native command line")):null,Jt(`${e.cases} benchmark cases, re-run at ${e.size} px`,e.engines),Jt(`${e.brands.cases} brand logos`,e.brands.engines),Jt(`${e.corpus.cases} more icons and emoji`,e.corpus.engines),n("p.faint.sc-note",null,`Means over the cases except seconds (median). Every case is rendered at ${e.size} px on white, traced by every engine from that raster and scored at 1024 px: the pictures and the tables are the same traces. Inkvec was traced by the build named above; the other engines have not changed. The benchmark of record is the ${e.date} run at 512 px (out/${e.run}/results.json, bench/crosscompare_competitors.py); these tables re-run its cases at ${e.size} px, where a logo's fine detail survives. The brand logos beyond the first twenty, and the third set, were picked by the seed ${e.pick_seed} alone (hash order, a quota per kind), not by how any engine traces them. Computed by tools/showcase_data.py. `,n("a",{href:"#",onclick:y=>{y.preventDefault(),Dt("https://github.com/logolabs/inkvec#results")}},"Method and every case")),n("p.faint.sc-note",{style:{marginTop:"10px",borderTop:"1px solid var(--rule2)",paddingTop:"10px",lineHeight:"1.6"}},n("b",{style:{color:"var(--dim)"}},"Trademarks: "),"the logos and brand names shown here are the property of their owners, and appear only to compare how engines trace real artwork. This implies no affiliation with, sponsorship by or endorsement from them.")))}function Jt(e,t){return n("table.sc-table",null,n("caption",null,e),n("thead",null,n("tr",null,...["Engine","mean dE00","median dE00","DISTS","DINO","geometry","coordinates","seconds"].map(s=>n("th",null,s)))),n("tbody",null,...t.map(s=>n(`tr${s.id==="inkvec"?".win":""}`,null,n("td",null,s.name),n("td.num",null,s.de_mean.toFixed(3)),n("td.num",null,s.de_median.toFixed(3)),n("td.num",null,s.dists.toFixed(4)),n("td.num",null,s.dino.toFixed(4)),n("td.num",null,`${s.geometry_mean.toFixed(2)}×`),n("td.num",null,Math.round(s.coordinates).toString()),n("td.num",null,s.seconds_median.toFixed(2))))))}const ms=e=>`${(e/1024).toFixed(1)} KB`,Ke=(e,t=!1)=>n(`span.sc-chip${t?".win":""}`,null,e),mt=(e,t,s)=>n("div.sc-bignum",null,n("span.eyebrow",null,e),n("span.v.serif",null,t),n("span.faint",null,s));function pr(){const e=[0,1,2].map(()=>{const r=n("div.sc-cam"),l=n("span.sc-tag");return{pane:n("div.sc-pane",null,r,l),cam:r,tag:l}}),t=n("div.sc-viewer",null,...e.map(r=>r.pane)),s={z:1,x:0,y:0};let o=[];const a=()=>{for(const r of e)r.cam.style.transform=`translate(${s.x}px, ${s.y}px)`,r.cam.style.width=r.cam.style.height=`${s.z*100}%`};let i=null;for(const{pane:r}of e){r.addEventListener("wheel",c=>{c.preventDefault();const d=r.getBoundingClientRect(),p=c.clientX-d.left,h=c.clientY-d.top,g=Math.min(40,Math.max(1,s.z*Math.exp(-c.deltaY*.0015))),u=g/s.z;s.x=p-(p-s.x)*u,s.y=h-(h-s.y)*u,s.z=g,a()},{passive:!1}),r.addEventListener("pointerdown",c=>{i=[c.clientX-s.x,c.clientY-s.y],r.setPointerCapture(c.pointerId)}),r.addEventListener("pointermove",c=>{i&&(s.x=c.clientX-i[0],s.y=c.clientY-i[1],a())});const l=()=>i=null;r.addEventListener("pointerup",l),r.addEventListener("pointercancel",l),r.addEventListener("dblclick",()=>{s.z=1,s.x=s.y=0,a()})}return{el:t,set(r,l,c,d,p){F(e[0].cam,n("img.sc-raster",{src:r,alt:"",draggable:"false"})),o=[ws(l),ws(c)],F(e[1].cam,o[0].root),F(e[2].cam,o[1].root),e[0].tag.textContent="Raster",e[1].tag.textContent=`${d} · ${o[0].anchors} anchors`,e[2].tag.textContent=`${p} · ${o[1].anchors} anchors`,a()},layer(r){for(const l of o)l.set(r)},zoom(r){const l=e[0].pane.getBoundingClientRect();s.z=r,s.x=(l.width-l.width*r)/2,s.y=(l.height-l.height*r)/2,a()}}}const bs="http://www.w3.org/2000/svg";function ys(e){const s=new DOMParser().parseFromString(e,"image/svg+xml").documentElement;if(!(s instanceof SVGSVGElement))return null;if(!s.getAttribute("viewBox")){const o=parseFloat(s.getAttribute("width")??""),a=parseFloat(s.getAttribute("height")??"");o>0&&a>0&&s.setAttribute("viewBox",`0 0 ${o} ${a}`)}return s.removeAttribute("width"),s.removeAttribute("height"),s.setAttribute("preserveAspectRatio","xMidYMid meet"),document.importNode(s,!0)}function ws(e){const t=ys(e),s=ys(e),o=n("div.sc-layer");if(!t||!s)return{root:o,anchors:0,set:()=>{}};s.classList.add("sc-wire");const a=hr(t);o.append(t,s,a.svg);const i=r=>{const l=r==="anchor"||r==="handle";t.style.display=r==="wire"?"none":"",t.style.opacity=l?"0.35":"1",s.style.display=r==="fill"?"none":"",a.anchors.style.display=l?"":"none",a.handles.style.display=a.handleDots.style.display=r==="handle"?"":"none"};return i("fill"),{root:o,anchors:a.count,set:i}}function hr(e){const t=document.createElementNS(bs,"svg");t.setAttribute("viewBox",e.getAttribute("viewBox")??"0 0 100 100"),t.setAttribute("preserveAspectRatio","xMidYMid meet"),t.setAttribute("class","sc-ov");let s="",o="",a="",i=0;const r=h=>h.toFixed(2);for(const h of e.querySelectorAll("path")){const[g,u]=vs(h),f=fr(h.getAttribute("d")??"",g,u);i+=f.anchors.length;for(const[b,x]of f.anchors)s+=`M${r(b)} ${r(x)}h0.001`;for(const[[b,x],[M,v]]of f.handles)o+=`M${r(b)} ${r(x)}L${r(M)} ${r(v)}`,a+=`M${r(M)} ${r(v)}h0.001`}for(const h of e.querySelectorAll("circle, ellipse")){const[g,u]=vs(h);s+=`M${r(Number(h.getAttribute("cx")??0)+g)} ${r(Number(h.getAttribute("cy")??0)+u)}h0.001`,i++}const l=(h,g)=>{const u=document.createElementNS(bs,"path");return u.setAttribute("d",h||"M0 0"),u.setAttribute("class",g),t.append(u),u},c=l(o,"h"),d=l(a,"hd"),p=l(s,"a");return{svg:t,anchors:p,handles:c,handleDots:d,count:i}}function vs(e){let t=0,s=0;for(let o=e;o&&o.tagName.toLowerCase()!=="svg";o=o.parentElement){const a=(o.getAttribute("transform")??"").match(/translate\(\s*(-?[\d.e+-]+)[ ,]*(-?[\d.e+-]+)?\s*\)/i);a&&(t+=parseFloat(a[1]),s+=parseFloat(a[2]??"0"))}return[t,s]}function fr(e,t,s){const o=e.match(/[MLHVCSQTAZmlhvcsqtaz]|-?\d*\.?\d+(?:e[-+]?\d+)?/gi)??[],a=[],i=[];let r=0,l=0,c=0,d=0,p=0,h="",g=null,u=0,f=0;const b=()=>parseFloat(o[r++]),x=(v,S)=>a.push([v+t,S+s]),M=(v,S,y,$)=>i.push([[v+t,S+s],[y+t,$+s]]);for(;r{const a=e.state;if(!a.screen){F(s),s.style.display="none";return}s.style.display="",F(s,a.screen==="settings"?mr(e,t):a.screen==="showcase"?ur(t.close):yr(e,t))};return e.on(["screen","prefs","caps"],o),o(),s}function mr(e,t){const s=e.state.prefs,o=e.state.caps?.denoiser;return n("div.screen",null,n("div.screenbar",{"data-tauri-drag-region":""},n("span",{style:{fontSize:"12.5px",fontWeight:"600"}},"Settings"),n("div.spacer"),n("button.btn.compact",{onclick:t.close},"Done"),n("div.sep"),rt()),n("div.screenbody",null,n("div.settingsgrid",null,bt("General",[null,ge("Theme","The light theme matches the dark one token for token.",Xt([["system","System"],["dark","Dark"],["light","Light"]],s?.theme??"system",a=>t.applyPrefs({theme:a}))),ge("When an image is opened","Auto always starts tracing at once. Ask shows a small card offering the Custom wizard beside it; Custom opens the wizard straight away.",Xt([["ask","Ask"],["auto","Auto only"],["custom","Custom wizard"]],s?.onOpen??"ask",a=>t.applyPrefs({onOpen:a}))),ge("Language","English only in this release. Nothing in the app is machine-translated, which is why there is one.",Xt([["en","English"]],"en",()=>{}))]),bt("Performance",[ge("Threads",crossOriginIsolated?"The engine runs on a pool of workers, one per core the browser reports.":"This page is not cross-origin isolated, so the engine runs on one core. Open it in its own tab for all of them.",n("span.muted.num",{style:{fontSize:"12.5px"}},crossOriginIsolated?`${navigator.hardwareConcurrency||4}`:"1")),ge("Draft resolution","Used while you move a control.",Kt(s?.draftPx??512,128,2048,a=>t.applyPrefs({draftPx:a}),"px")),ge("Draft time limit","A draft that overruns is abandoned, not shown late.",Kt(s?.draftSeconds??.4,.05,10,a=>t.applyPrefs({draftSeconds:a}),"s",2)),ge("Settle before the full trace","How long the controls stay still before the full-resolution trace is queued.",Kt(s?.settleMs??800,100,5e3,a=>t.applyPrefs({settleMs:a}),"ms"))]),bt("Denoiser",br(e,o)),null,bt("Advanced",[null,null,ge("Reset settings","Puts every preference and the trace controls back to their defaults. Your saved presets and your files are untouched.",n("button.btn.compact.danger",{onclick:()=>Aa("Reset settings?","Every preference goes back to its default, and the recent list is cleared. Your saved presets are kept, and nothing you have traced or exported is affected.","Reset",async()=>{const a=await B.resetPrefs();e.set({prefs:a,settings:a.trace}),t.afterReset(a),q("Settings reset.")},!0)},"Reset"))]),n("div.privacycard",null,n("span.eyebrow",null,"Privacy"),n("span.line",null,"Your image never leaves this browser."),n("p",{style:{margin:"0",fontSize:"12.5px",lineHeight:"1.65",color:"var(--faint)"}},"Tracing runs in this tab, on your own processor, as WebAssembly. There is no account, no upload and no telemetry. The page fetches only itself and the denoiser (its weights from Hugging Face and ONNX Runtime Web from jsDelivr, once, in the background, and not at all while the browser asks to save data until you turn it on); your preferences are kept in this browser's storage.")))))}function br(e,t){return t?t.supported?[ge("Status","Optional. Improves traces from photographs and screenshots.",n("span",{style:{fontSize:"12.5px",color:t.installed?"var(--good)":"var(--muted)"}},t.installed?`Installed · ${ie(t.bytes??0)}`:e.state.denoiserFetch?.phase==="downloading"?"Downloading in the background":"Not installed")),ge(t.installed?"Remove":"Download",t.installed?t.path??"Kept in this browser's storage.":e.state.denoiserFetch?.saveData?"Not downloaded in the background, because this browser asks to save data. Turning the denoiser on downloads it (about 100 MB), once, into this browser's storage. The model comes down, your image never goes up.":"Downloaded in the background when the Studio opens, once, into this browser's storage (about 100 MB), and run in this tab on your GPU where it has one. The model comes down, your image never goes up.",t.installed?n("button.btn.compact.danger",{onclick:async()=>{const s=await B.denoiserRemove(),o=e.state.caps;o&&e.set({caps:{...o,denoiser:s}}),q("Denoiser removed.")}},"Remove"):n("button.btn.compact",{onclick:()=>zn(e)},"Download")),ge("Verified against",t.repo,n("span.mono.muted",{style:{fontSize:"11px"}},`${t.sha256.slice(0,16)}…`))]:[ge("Status","The denoiser needs a cross-origin isolated page, which this one is not. Open the app in its own tab to use it.",n("span.muted",{style:{fontSize:"12.5px"}},"Not in this build"))]:[ge("Status","",n("span.muted",null,"—"))]}function zn(e){const t=e.state.caps?.denoiser;if(!t)return;const s=n("div.progress",null,n("i",{style:{width:"0%"}})),o=n("span.faint.num",{style:{fontSize:"12.5px"}},""),a=n("div.modal",{tabindex:"-1"});let i=!1;const r=()=>{F(a,n("h2",null,i?"Downloading the denoiser":"Download the denoiser"),i?n("div",{style:{display:"flex",flexDirection:"column",gap:"10px"}},s,o):n("p",null,"Downloaded once (about 76 MB of weights and 26 MB of ONNX Runtime Web) and kept in this browser; the Studio starts it in the background when it opens, unless the browser asks to save data. It repairs JPEG and screenshot damage before tracing, which usually halves the colour error on photographed logos. It runs in this tab, on your GPU where there is one — the model comes down, your image never goes up."),n("div.stats",null,n("div",null,n("span.k",null,"from"),n("span.v",{style:{fontSize:"12px"}},t.repo)),n("div",null,n("span.k",null,"adds per trace"),n("span.v",null,"seconds"))),n("span.muted",{style:{fontSize:"11.5px",lineHeight:"1.5"}},`SHA-256 verified on arrival against ${t.sha256.slice(0,16)}… · removable in Settings.`),n("div.actions",null,n("button.btn",{onclick:()=>{i&&B.denoiserCancel(),ue()}},i?"Cancel download":"Not now"),i?null:n("button.btn.primary",{onclick:()=>{i=!0,r(),B.denoiserDownload()}},"Download")))};(async()=>{const l=await ke.denoiserProgress(({got:d,total:p})=>{const h=p?d/p*100:0;s.firstElementChild.style.width=`${h.toFixed(1)}%`,o.textContent=p?`${ie(d)} of ${ie(p)} · you can keep tracing`:`${ie(d)} · you can keep tracing`}),c=await ke.denoiserDone(({ok:d,message:p,status:h})=>{const g=e.state.caps;g&&e.set({caps:{...g,denoiser:h}}),l(),c(),ue(),d?q("Denoiser installed and verified.",{kind:"good"}):p!=="cancelled"&&q(p??"The download did not finish.",{kind:"bad"})})})(),Ye(a),r()}function yr(e,t){const s=e.state.caps,o=n("pre",null,"Loading the notices…");B.thirdPartyNotices().then(i=>{o.textContent=i}).catch(()=>{o.textContent="The bundled notices could not be read. They are also published at github.com/logolabs/inkvec in docs/THIRD_PARTY.md."});const a=(i,r)=>n("a",{href:"#",onclick:l=>{l.preventDefault(),Dt(r)}},i);return n("div.screen",null,n("div.screenbar",{"data-tauri-drag-region":""},n("span",{style:{fontSize:"12.5px",fontWeight:"600"}},"About"),n("div.spacer"),n("button.btn.compact",{onclick:t.close},"Done"),n("div.sep"),rt()),n("div.screenbody",null,n("div.about",null,n("div.aboutleft",null,n("span.mark",null,As(72)),n("div",{style:{display:"flex",flexDirection:"column",gap:"8px"}},n("span.serif",{style:{fontSize:"40px",lineHeight:"1"}},zt),n("span.faint.num",{style:{fontSize:"13px"}},`Version ${s?.version??"—"} · engine Inkvec ${s?.engineVersion??"—"} · Apache-2.0`),n("span.muted.mono",{style:{fontSize:"11px"}},`build target ${s?.buildTarget??"—"}`)),n("p",{style:{margin:"0",fontSize:"13.5px",lineHeight:"1.7",color:"var(--dim)",maxWidth:"380px"}},"Made by LogoLabs, an AI lab working on brand assets and logo creation. Tracing reconstructs a picture; drawing one is a different job, and that is the one we do."),n("p",{style:{margin:"0",fontSize:"13px",lineHeight:"1.7",color:"var(--faint)",maxWidth:"380px"}},"This is the browser edition. The desktop app, Inkvec Studio, is the same interface with folders of images traced in one go, the command line, the right-click menu and traces up to 16384 px."),n("button.btn.compact",{style:{alignSelf:"flex-start"},onclick:()=>e.set({screen:"showcase"})},"Before and after on real logos: the showcase"),n("div",{style:{display:"flex",gap:"18px",fontSize:"13px",flexWrap:"wrap"}},n("a",{href:"#",onclick:i=>{i.preventDefault(),An()}},"User guide"),a("GitHub","https://github.com/logolabs/inkvec"),a("Benchmark results","https://github.com/logolabs/inkvec#benchmark"),a("Inkvec Studio for the desktop","https://github.com/logolabs/inkvec/releases"),a("logolabs.org","https://logolabs.org"))),n("div.aboutright",null,n("div.credit",null,n("span.eyebrow",{style:{color:"var(--gold)"}},"Compute acknowledgement"),n("p",{style:{margin:"0",fontSize:"13px",lineHeight:"1.7",color:"var(--dim)"}},"The denoiser was trained on resources provided by EuroHPC JU and NAISS. Training ran on the Arrhenius GPU supercomputer at NAISS, Sweden, under EuroHPC project EHPC-AIF-2026PG01-907."),n("div",{style:{display:"flex",gap:"10px",marginTop:"4px"}},n("span.btn.compact",{style:{cursor:"default"}},"EuroHPC JU"),n("span.btn.compact",{style:{cursor:"default"}},"NAISS"))),n("div.notices",null,n("div.head",null,n("span",null,"Licence and third-party notices"),n("button.reset",{onclick:()=>wr(o.textContent??"")},"Open")),o),n("span.muted",{style:{fontSize:"12px"}},"Your image never leaves this browser."),n("p.muted",{style:{margin:"0",fontSize:"11px",lineHeight:"1.5",color:"var(--faint)"}},"Trademarks, brand marks, and logos depicted in showcase comparisons are property of their respective owners, used solely for nominative, educational benchmarking demonstration.")))))}function wr(e){Ye(It("Licence and third-party notices",[n("pre",{style:{margin:"0",maxHeight:"60vh",overflow:"auto",font:"11.5px/1.7 var(--font-mono)",color:"var(--faint)",whiteSpace:"pre-wrap",userSelect:"text"}},e)],[n("button.btn",{onclick:ue},"Close")]))}function bt(e,t){return n("div.settinggroup",null,n("div.head.eyebrow",null,e),...t)}function ge(e,t,s){return n("div.settingrow",null,n("div.about",null,n("span.label",null,e),t?n("span.help",null,t):null),n("div.control",null,s))}function Xt(e,t,s){return n("select",{onchange:o=>s(o.target.value)},...e.map(([o,a])=>n("option",{value:o,selected:o===t?"":null},a)))}function Kt(e,t,s,o,a="",i=0){return n("div",{style:{display:"flex",alignItems:"center",gap:"6px"}},n("input",{type:"number",value:String(i?e.toFixed(i):Math.round(e)),min:String(t),max:String(s),step:i?String(10**-i):"1",style:{width:"90px"},onchange:r=>{const l=Number(r.target.value);Number.isFinite(l)&&o(Math.min(s,Math.max(t,l)))}}),a?n("span.muted",{style:{fontSize:"11.5px"}},a):null)}const vr={M:2,L:2,T:2,H:1,V:1,C:6,S:4,Q:4,A:7,Z:0};function*xr(e){const t=e.match(/[a-zA-Z]|-?\d*\.?\d+(?:[eE][-+]?\d+)?/g)??[];let s=0,o="";for(;s="a"&&l<="z",p=l.toUpperCase(),h=s,g=o,u=b=>d?h+c[b]:c[b],f=b=>d?g+c[b]:c[b];switch(p){case"M":s=u(0),o=f(1),a=s,i=o,t.push({x:s,y:o}),r=null;break;case"L":s=u(0),o=f(1),t.push({x:s,y:o}),r=null;break;case"H":s=d?h+c[0]:c[0],t.push({x:s,y:o}),r=null;break;case"V":o=d?g+c[0]:c[0],t.push({x:s,y:o}),r=null;break;case"C":{const b={x:u(0),y:f(1)},x={x:u(2),y:f(3)};s=u(4),o=f(5),t.push({x:s,y:o,in:b,out:x}),r=x;break}case"S":{const b=r?{x:2*h-r.x,y:2*g-r.y}:{x:h,y:g},x={x:u(0),y:f(1)};s=u(2),o=f(3),t.push({x:s,y:o,in:b,out:x}),r=x;break}case"Q":{const b={x:u(0),y:f(1)};s=u(2),o=f(3),t.push({x:s,y:o,in:b,out:b}),r=b;break}case"T":{const b=r?{x:2*h-r.x,y:2*g-r.y}:{x:h,y:g};s=u(0),o=f(1),t.push({x:s,y:o,in:b,out:b}),r=b;break}case"A":s=u(5),o=f(6),t.push({x:s,y:o}),r=null;break;case"Z":s=a,o=i,r=null;break}}return t}const Sr="path, circle, ellipse, rect, line, polyline, polygon";function $r(e){const t=[];for(const s of e.querySelectorAll(Sr)){const o=Cr(s);o&&t.push(o)}return t}function ce(e,t,s=0){const o=e.getAttribute(t);if(o===null||o.trim()==="")return s;const a=Number(o);return Number.isFinite(a)?a:s}function Mr(e){const t=/rotate\(\s*(-?[\d.]+)/.exec(e.getAttribute("transform")??"");return t?Number(t[1]):0}function Cr(e){const t=s=>(Math.round(s*1e3)/1e3).toString();switch(e.nodeName.toLowerCase()){case"path":return e.getAttribute("d");case"circle":case"ellipse":{const s=ce(e,"cx"),o=ce(e,"cy"),a=e.nodeName.toLowerCase()==="circle",i=a?ce(e,"r"):ce(e,"rx"),r=a?ce(e,"r"):ce(e,"ry");if(!(i>0)||!(r>0))return null;const l=Mr(e),c=l*Math.PI/180,d=Math.cos(c),p=Math.sin(c),h=(u,f)=>`${t(s+u*d-f*p)},${t(o+u*p+f*d)}`,g=`A${t(i)},${t(r)} ${t(l)} 0 1 `;return`M${h(i,0)}${g}${h(0,r)}${g}${h(-i,0)}${g}${h(0,-r)}${g}${h(i,0)}Z`}case"rect":{const s=ce(e,"x"),o=ce(e,"y"),a=ce(e,"width"),i=ce(e,"height");if(!(a>0)||!(i>0))return null;const r=Math.min(ce(e,"rx",ce(e,"ry")),a/2),l=Math.min(ce(e,"ry",ce(e,"rx")),i/2);if(!(r>0)||!(l>0))return`M${t(s)},${t(o)}L${t(s+a)},${t(o)}L${t(s+a)},${t(o+i)}L${t(s)},${t(o+i)}Z`;const c=`A${t(r)},${t(l)} 0 0 1 `;return`M${t(s+r)},${t(o)}L${t(s+a-r)},${t(o)}${c}${t(s+a)},${t(o+l)}L${t(s+a)},${t(o+i-l)}${c}${t(s+a-r)},${t(o+i)}L${t(s+r)},${t(o+i)}${c}${t(s)},${t(o+i-l)}L${t(s)},${t(o+l)}${c}${t(s+r)},${t(o)}Z`}case"line":return`M${t(ce(e,"x1"))},${t(ce(e,"y1"))}L${t(ce(e,"x2"))},${t(ce(e,"y2"))}`;case"polyline":case"polygon":{const s=(e.getAttribute("points")??"").trim().split(/[\s,]+/).map(Number);if(s.length<4||s.some(a=>!Number.isFinite(a)))return null;const o=[`M${t(s[0])},${t(s[1])}`];for(let a=2;a+1Number.isFinite(i)))return{x:a[0],y:a[1],w:a[2],h:a[3]}}const s=/\swidth="([\d.]+)"/.exec(e),o=/\sheight="([\d.]+)"/.exec(e);return s&&o?{x:0,y:0,w:Number(s[1]),h:Number(o[1])}:null}function Tr(e){const t=Math.min(1,Math.max(0,(e-1)/11));return{r:1.2+t*1.2,opacity:.4+t*.6}}const ks=.25,Ss=64;function Ar(e){const t=n("div.art"),s=n("div.art.compare");s.style.display="none";const o=n("span.eyebrow.panelabel",null,"Source"),a=n("div.art"),i=xe("svg.overlay"),r=n("div.pane.source",null,o,t,s);a.append(i);const l=n("canvas.anchors"),c=n("span.eyebrow.panelabel",null,"Vector · SVG"),d=n("div.pane.vector",null,c,a,l),p=n("div.wipehandle",{role:"separator","aria-label":"Wipe"}),h=n("div.panes",null,r,d,p),g=h;let u={x:0,y:0,w:1,h:1},f=null,b,x,M,v=0,S=0,y=Number.NaN,$,C=null,D={x:0,y:0,w:1,h:1};const L={certainty:xe("g.certainty"),wireframe:xe("g.wireframe"),handles:xe("g.handles")},I={certainty:!1,wireframe:!1,handles:!1};for(const w of Object.values(L))w.style.display="none";l.style.display="none",i.append(L.certainty,L.wireframe,L.handles);let A=null,P=null;function J(){const w=e.state,z=w.svg,T=w.source!==x,_=z!==b;if((T||_)&&(u=(z?xs(z):null)??{x:0,y:0,w:w.source?.width??1,h:w.source?.height??1},y=Number.NaN),T&&(x=w.source,F(t,w.source?n("img",{src:w.source.preview,alt:"",draggable:"false"}):null)),_){if(b=z,f=null,z){const G=new DOMParser().parseFromString(z,"image/svg+xml").documentElement;G&&G.nodeName==="svg"&&!G.querySelector("parsererror")&&(G.setAttribute("width",String(u.w)),G.setAttribute("height",String(u.h)),f=document.importNode(G,!0))}F(a,f,i);const H=++v;(z?io(z):Promise.resolve(null)).then(G=>{H===v&&(h.classList.toggle("onlight",G==="light"),h.classList.toggle("ondark",G==="dark"))}),i.setAttribute("viewBox",`${u.x} ${u.y} ${u.w} ${u.h}`),A=null,P=null;for(const G of["wireframe","handles"])F(L[G]),I[G]=!1;te(!0)}(w.result!==M||_&&!z)&&se(),T||_?(Me(),Le()):w.show.certainty&&Me()}function U(){const w=e.state.compare;if(w===$)return;$=w,C&&URL.revokeObjectURL(C),C=null,w?(D=xs(w.svg)??u,C=URL.createObjectURL(new Blob([w.svg],{type:"image/svg+xml"})),F(s,n("img",{src:C,alt:"",draggable:"false",style:{imageRendering:"auto"}}))):F(s),s.style.display=w?"":"none",t.style.display=w?"none":"",o.textContent=w?w.label:"Source",c.textContent=w?"Yours":"Vector · SVG",r.classList.toggle("comparing",!!w);const z=`translate(${e.state.pan.x}px, ${e.state.pan.y}px)`;(w?s:t).style.transform=z,X()}function X(){const w=s.querySelector("img");if(!w)return;const z=e.state.zoom;w.style.position="absolute",w.style.left=`${(D.x-u.x)*z}px`,w.style.top=`${(D.y-u.y)*z}px`,w.style.width=`${D.w*z}px`,w.style.height=`${D.h*z}px`,w.style.maxWidth="none"}function se(){M=void 0,S++,F(L.certainty),I.certainty=!1}function ee(){return A??=f?$r(f):[],A}function Q(){if(P)return P;const w=[],z=[],T=[];for(const _ of ee()){const H=new Set;for(const G of kr(_)){const le=Math.round(G.x*1e3)*1e8+Math.round(G.y*1e3);H.has(le)||(H.add(le),w.push({x:G.x,y:G.y}));for(const me of[G.in,G.out])me&&(z.push(`M${G.x} ${G.y}L${me.x} ${me.y}`),T.push(`M${me.x} ${me.y}l.001 .001`))}}return P={lines:z.join(""),knobs:T.join(""),dots:w},P}function V(w){if(I[w]=!0,!f)return;const z=L[w];if(w==="wireframe"){const T=Er(ee());T&&z.append(xe("path",{d:T}))}else{const{lines:T,knobs:_}=Q();T&&z.append(xe("path.lines",{d:T}),xe("path.knobs",{d:_}))}}let ne="",ae=!1;function te(w=!1){w&&(ne=""),!ae&&(ae=!0,requestAnimationFrame(()=>{ae=!1,pe()}))}function pe(){const w=e.state,z=!!f&&w.show.anchors,T=Math.round(d.clientWidth),_=Math.round(d.clientHeight);if(z&&(!T||!_))return;const H=window.devicePixelRatio||1,G=z?`${w.zoom} ${w.pan.x} ${w.pan.y} ${T} ${_} ${H}`:"off";if(G===ne)return;ne=G;const le=l.getContext("2d");if(!le)return;if(!z){l.width=0,l.height=0;return}const me=Math.max(1,Math.round(T*H)),On=Math.max(1,Math.round(_*H));(l.width!==me||l.height!==On)&&(l.width=me,l.height=On),l.style.width=`${T}px`,l.style.height=`${_}px`,le.setTransform(H,0,0,H,0,0),le.clearRect(0,0,T,_);const{dots:In}=Q();if(!In.length)return;const _n=getComputedStyle(d),So=_n.getPropertyValue("--stage").trim()||"#808080",$o=_n.getPropertyValue("--anchor").trim()||"#ff5c00",{r:Bn,opacity:Mo}=Tr(w.zoom);l.style.opacity=String(Mo);const Co=w.pan.x-u.x*w.zoom,To=w.pan.y-u.y*w.zoom,Ze=Bn*1.15,Ao=Bn*.85,ct=[],Gt=[];for(const Je of In){const Xe=Co+Je.x*w.zoom,we=To+Je.y*w.zoom;Xe<-Ze||we<-Ze||Xe>T+Ze||we>_+Ze||(ct.push(Xe),Gt.push(we))}for(const[Je,Xe]of[[Ze,So],[Ao,$o]]){le.beginPath();for(let we=0;we{T===S&&e.set({bandsMissing:!0,show:{...e.state.show,certainty:!1}})};B.traceBands(w.resultGeneration).then(H=>{T===S&&(H?fe(H):_())},_)}function fe(w){F(L.certainty);for(const{cls:z,d:T}of Nr(Lr(w)))L.certainty.append(xe(`path.${z}`,{d:T}))}function Me(){const{show:w}=e.state,z=(T,_)=>{_&&!I[T]&&(T==="certainty"?Te():V(T)),L[T].style.display=_?"":"none"};z("wireframe",w.wireframe),z("handles",w.handles),z("certainty",w.certainty),l.style.display=w.anchors?"":"none",te(),f&&(f.style.opacity=w.fill?"1":"0.12")}function Le(){const w=e.state,z=`translate(${w.pan.x}px, ${w.pan.y}px)`;for(const T of $?[s,a]:[t,a])T.style.transform=z;if(w.zoom!==y){y=w.zoom;const T=u.w*w.zoom,_=u.h*w.zoom,H=t.querySelector("img");H&&(H.style.width=`${T}px`,H.style.height=`${_}px`),f&&(f.setAttribute("width",String(T)),f.setAttribute("height",String(_))),i.setAttribute("width",String(T)),i.setAttribute("height",String(_)),X()}w.show.anchors&&te(),ze()}function ze(){const w=e.state;if(h.classList.toggle("stacked",w.view!=="side"),p.style.display=w.view==="wipe"?"":"none",w.view==="side")r.classList.remove("hidden"),d.classList.remove("hidden"),r.style.clipPath="",d.style.clipPath="";else if(w.view==="wipe"){r.classList.remove("hidden"),d.classList.remove("hidden");const H=`${(w.wipe*100).toFixed(2)}%`;p.style.left=H,r.style.clipPath=`inset(0 ${(100-w.wipe*100).toFixed(2)}% 0 0)`,d.style.clipPath=`inset(0 0 0 ${H})`}else r.style.clipPath="",d.style.clipPath="",r.classList.toggle("hidden",!w.flicked),d.classList.toggle("hidden",w.flicked);const z=w.detail&&w.zoom>=6;let T=d.querySelector(".pixelgrid");z&&!T&&(T=n("div.pixelgrid"),d.append(T)),T&&(T.style.display=z?"":"none",T.style.backgroundSize=`${w.zoom}px ${w.zoom}px`,T.style.backgroundPosition=`${w.pan.x}px ${w.pan.y}px`);const _=t.querySelector("img");_&&(_.style.imageRendering=w.zoom>=4?"pixelated":"auto")}function de(){const w=d.getBoundingClientRect();return{w:w.width||1,h:w.height||1}}function Ae(){const{w,h:z}=de(),T=Math.min(w/u.w,z/u.h)*.88;e.set({zoom:T,pan:{x:(w-u.w*T)/2,y:(z-u.h*T)/2},fitted:!0})}function k(w,z,T){const{w:_,h:H}=de();e.set({zoom:T,pan:{x:_/2-w*T,y:H/2-z*T},fitted:!1})}function N(w){const{w:z,h:T}=de();E(z/2,T/2,Math.min(Ss,Math.max(ks,w))/e.state.zoom)}function E(w,z,T){const _=e.state,H=Math.min(Ss,Math.max(ks,_.zoom*T)),G=H/_.zoom;e.set({zoom:H,pan:{x:w-(w-_.pan.x)*G,y:z-(z-_.pan.y)*G},fitted:!1})}for(const w of[r,d])w.addEventListener("wheel",z=>{z.preventDefault();const T=w.getBoundingClientRect();E(z.clientX-T.left,z.clientY-T.top,Math.exp(-z.deltaY*.0016))},{passive:!1}),w.addEventListener("dblclick",Ae),w.addEventListener("pointerdown",z=>{if(z.button!==0)return;w.setPointerCapture(z.pointerId);let T=z.clientX,_=z.clientY;const H=le=>{const me=e.state;e.set({pan:{x:me.pan.x+(le.clientX-T),y:me.pan.y+(le.clientY-_)},fitted:!1}),T=le.clientX,_=le.clientY},G=()=>{w.removeEventListener("pointermove",H),w.removeEventListener("pointerup",G),w.removeEventListener("pointercancel",G)};w.addEventListener("pointermove",H),w.addEventListener("pointerup",G),w.addEventListener("pointercancel",G)});p.addEventListener("pointerdown",w=>{w.stopPropagation(),p.setPointerCapture(w.pointerId);const z=_=>{const H=h.getBoundingClientRect();e.set({wipe:Math.min(.98,Math.max(.02,(_.clientX-H.left)/H.width))})},T=()=>{p.removeEventListener("pointermove",z),p.removeEventListener("pointerup",T)};p.addEventListener("pointermove",z),p.addEventListener("pointerup",T)});const Y=n("style",{"data-owner":"viewer-isolate"});document.head.append(Y);function K(){const w=e.state.hoverFill;d.classList.toggle("isolating",!!w?.length),Y.textContent=w?.length?Or(w):""}e.on(["svg","source","result"],J),e.on(["compare"],U),e.on(["zoom","pan","view","wipe","flicked","detail"],Le),e.on(["show"],Me),e.on(["hoverFill"],K),new ResizeObserver(()=>{!e.state.svg&&!e.state.source||(ze(),e.state.show.anchors&&te(),e.state.fitted&&Ae())}).observe(h),new MutationObserver(()=>{e.state.show.anchors&&te(!0)}).observe(document.documentElement,{attributes:!0,attributeFilter:["data-theme"]});const j=()=>{window.matchMedia(`(resolution: ${window.devicePixelRatio||1}dppx)`).addEventListener("change",()=>{e.state.show.anchors&&te(),j()},{once:!0})};return j(),{el:g,redraw:J,transform:Le,fit:Ae,goTo:k,zoomTo:N}}function Er(e){let t="";for(const s of e){const o=s.trim();o&&(t+=o[0]==="M"?o:`M0 0${o}`)}return t}function Lr(e){const t=[],s=/]*)>/g;for(let o=s.exec(e);o;o=s.exec(e)){const a=o[1],i=/(?:^|\s)d="([^"]*)"/.exec(a)?.[1]?.trim();if(!i)continue;const r=Pr(i);if(!r)continue;const l=Number(/\sdata-sigma-mean="([^"]*)"/.exec(a)?.[1]??0);t.push({d:i[0]==="M"?i:`M0 0${i}`,cls:l<=.1?"sure":l<=.3?"soft":"unsure",x0:r.x0,y0:r.y0,x1:r.x1,y1:r.y1})}return t}const zr=[1,.1,.01,.001,1e-4,1e-5,1e-6,1e-7,1e-8,1e-9,1e-10,1e-11,1e-12,1e-13,1e-14,1e-15];function Pr(e){let t=1/0,s=1/0,o=-1/0,a=-1/0,i=!1;const r=e.length;let l=0;for(;l=48&&c<=57||c===46)){p||l++;continue}let h=0;for(;c>=48&&c<=57;)h=h*10+(c-48),c=e.charCodeAt(++l);if(c===46){let g=0,u=0;for(c=e.charCodeAt(++l);c>=48&&c<=57;)u<15&&(g=g*10+(c-48),u++),c=e.charCodeAt(++l);h+=g*zr[u]}if(c===101||c===69){c=e.charCodeAt(++l);const g=c===45;(g||c===43)&&(c=e.charCodeAt(++l));let u=0;for(;c>=48&&c<=57;)u=u*10+(c-48),c=e.charCodeAt(++l);h*=10**(g?-u:u)}d&&(h=-h),i?(ha&&(a=h)):(ho&&(o=h)),i=!i}return t<=o&&s<=a?{x0:t,y0:s,x1:o,y1:a}:null}const Dr=1,yt=16,Fr=256;function Nr(e,t=Fr){const s=new Map,o=new Int32Array(e.length),a=[],i={sure:[],soft:[],unsure:[]},r=[],l=Dr/2;for(let d=0;dh&&I.y0-lg&&(I.cls===p.cls?r[o[L]]=d+1:o[L]>S&&(S=o[L]))}}let y=-1;for(const $ of i[p.cls])if($>S&&r[$]!==d+1){y=$;break}y<0&&(y=a.length,a.push(p.cls),i[p.cls].push(y)),o[d]=y;for(let $=M;$<=v;$++)for(let C=b;C<=x;C++){const D=C*65536+$,L=s.get(D);L?L.push(d):s.set(D,[d])}}const c=new Map;for(let d=0;dd[0]-p[0]).map(d=>d[1])}const Qt=".pane.vector > .art > svg:not(.overlay)";function Or(e){const t=new Set;for(const o of e){const a=o.replace(/["\\\n\r]/g,"").trim();if(!a)continue;t.add(a);const i=/^#([0-9a-f])\1([0-9a-f])\2([0-9a-f])\3$/i.exec(a);i&&t.add(`#${i[1]}${i[2]}${i[3]}`)}if(!t.size)return"";const s=o=>[...t].map(a=>`[${o}="${a}" i]`).join(",");return[`${Qt} * { fill-opacity: 0.12 !important; stroke-opacity: 0.12 !important; }`,`${Qt} :is(${s("fill")}) { fill-opacity: 1 !important; stroke: var(--ink, #faf8f5) !important; stroke-opacity: 1 !important; stroke-width: 3px !important; vector-effect: non-scaling-stroke !important; paint-order: stroke !important; }`,`${Qt} :is(${s("stroke")}) { stroke-opacity: 1 !important; }`].join(` -`)}function Ir(e){const t=n("aside.activity",{"aria-label":"Engine activity","aria-live":"off"}),s=n("button.activityhead",{type:"button","data-ctl":"activity-toggle",onclick:()=>e.set({logOpen:!e.state.logOpen})}),o=n("div.activitybody",{role:"log"});t.append(s,o);const a=(r,l)=>{if(r.kind==="note")return n("div.activityline.note",null,n("span.t",null,es(r.at)),n("span.w",null,r.text??""));const c=l&&r.step?` · ${gn(r.step)}`:"";return n(`div.activityline${l?".running":".done"}`,null,n("span.t",null,es(r.at)),n("span.w",null,`${r.what}${c}`),l?n("span.r.num",{"data-live":"stage"}):n("span.r.num",null,r.ms===void 0?"":Re(r.ms)))},i=()=>{const r=e.state,l=r.tab==="vectorize"&&!!r.source&&!r.screen&&(r.tracing||r.traceLog.length>0);if(t.hidden=!l,!l)return;t.classList.toggle("open",r.logOpen),t.classList.toggle("busy",r.tracing);const c=r.traceEnded&&!r.tracing?Re(r.traceEnded-r.traceStarted):null;if(F(s,r.tracing?n("span.pulse"):null,n("span.title",null,"Engine log"),r.tracing?n("span.num.muted",{"data-live":"elapsed"},Re(performance.now()-r.traceStarted)):c?n("span.num.muted",null,c):null,n("span.chev",null,r.logOpen?"Hide":"Show")),!r.logOpen){F(o);return}const d=o.scrollHeight-o.scrollTop-o.clientHeight<24,p=r.traceLog.slice(-160);let h=-1;if(r.tracing)for(let g=p.length-1;g>=0&&h<0;g--)p[g].kind==="stage"&&p[g].ms===void 0&&(h=g);F(o,...p.map((g,u)=>a(g,u===h))),d&&(o.scrollTop=o.scrollHeight)};return e.on(["traceLog","tracing","logOpen","source","tab","screen","liveNow"],i),i(),t}function _r(e,t,s){const o=Ar(e),a=n("div.viewertools"),i=n("div",{style:{flex:"1",minHeight:"0",position:"relative",display:"flex"}}),r=n("div.statusstrip"),l=n("section.stage",null,a,i,r);let c="";const d=()=>{const u=e.state,f=!!u.source,b=[1,4,12].map(v=>!u.fitted&&Math.abs(u.zoom-v)<.01),x=JSON.stringify([f,u.view,u.show,u.fitted,b,u.detail,!!u.svg,u.bandsMissing,u.settings.mode]);if(x===c)return;c=x;const M=(v,S)=>n("button",{"aria-pressed":String(S==="fit"?u.fitted:!u.fitted&&Math.abs(u.zoom-S)<.01),disabled:!f,onclick:()=>S==="fit"?o.fit():o.zoomTo(S)},v);F(a,n("div.seg",null,...["side","wipe","ab"].map(v=>n("button",{"aria-pressed":String(u.view===v),disabled:!f,onclick:()=>e.set({view:v})},v==="side"?"Side by side":v==="wipe"?"Wipe":"A/B"))),n("span.muted",{style:{fontSize:"11.5px"}},"hold ",n("kbd",null,"Space")," to flick"),n("div.sep"),n("span.eyebrow",null,"Show"),n("div",{style:{display:"flex",gap:"6px"}},...[["fill","Fill"],["wireframe","Wireframe"],["anchors","Anchors"],["handles","Handles"],["certainty","Certainty"]].map(([v,S])=>n("button.toggle",{"aria-pressed":String(u.show[v]),disabled:!u.svg||v==="certainty"&&u.bandsMissing,title:v==="certainty"?"Where each boundary could be: a band two sigmas either side, measured from the pixels. Thin is certain; a wide band is a soft, blurred or compressed edge, and the curve there is a best guess.":void 0,onclick:()=>{u.show[v]=!u.show[v],e.touch("show")}},S))),n("div",{style:{marginLeft:"auto",display:"flex",alignItems:"center",gap:"12px"}},u.show.wireframe||u.show.anchors||u.show.handles||u.show.certainty?n("div.legend",null,u.show.anchors?n("span",null,n("i.anchor"),"anchor"):null,u.show.handles?n("span",null,n("i.handle"),"handle"):null,u.show.wireframe?n("span",null,n("i.wire"),"wireframe"):null,...u.show.certainty?[n("span",null,n("i.band.sure"),"sure"),n("span",null,n("i.band.soft"),"soft"),n("span",null,n("i.band.unsure"),"unsure")]:[]):null,n("button.toggle",{"aria-pressed":String(u.detail),disabled:!u.svg,title:"Show the source's pixel grid under the vector edge",onclick:()=>{const v=!u.detail;e.set({detail:v}),v&&u.zoom<12&&o.zoomTo(12)}},"Detail"),n("div.seg.num",null,M("Fit","fit"),M("1×",1),M("4×",4),M("12×",12))))},p=n("div",{style:{display:"contents"}});i.append(o.el,p,Ir(e));const h=()=>{const u=e.state;if(!u.source){o.el.style.display="none",F(p,Hr(e,t,s()));return}o.el.style.display="",F(p,Wr(u.stageState,u,t),u.svg?Rr(e):null,u.detail&&u.svg?Gr(e,t):null),u.zoom===1&&u.pan.x===0&&u.pan.y===0&&o.fit()},g=()=>{const u=e.state,f=u.report;let b="Ready",x="",M="var(--dim)",v=null;if(u.tracing)b=Nt(u),x=`· ${u.settings.mode==="fast"?"fast":"quality"} · ${hn(u,u.tracingTier)??"—"} px ·`,v=n("span",null,n("span.num",{"data-live":"elapsed"},Ot(u))," elapsed · Esc to cancel"),M="var(--state-stale)";else if(f&&u.result){const S=u.result.tier==="draft",y=u.settings.mode==="fast"?"Fast":"Quality";b=S?`${y} draft`:`${y} in ${nt(f.seconds)}`,x=S?`· ${f.tracedPx} px · full trace queued`:`· ${f.tracedPx} px · final · ${oe(f.segments)} segments`,M=S?"var(--state-draft)":"var(--state-final)"}else u.source&&(b="Opened",x=`· ${u.source.width} × ${u.source.height} · ${u.source.container}`);F(r,n("span.lead",{style:{color:M}},b),x?n("span",null,x):null,v,u.update?.newer?n("span",{style:{display:"flex",alignItems:"center",gap:"8px",marginLeft:"12px"}},n("span.updatechip",null,n("i"),`${u.update.latest} available`),n("button.reset",{onclick:t.showUpdate},"What changed")):null,Br(e,t),n("span.privacy",null,"Your image never leaves this computer."))};return e.on(["source","view","show","zoom","fitted","detail","svg","bandsMissing","settings"],d),e.on(["source","svg","stageState","result","detail","worstCorner","prefs"],h),e.on(["tracing","liveStages","liveNow","report","result","source","update","denoiserFetch","settings"],g),d(),h(),g(),window.addEventListener("keydown",u=>{if(e.state.screen||e.state.tab!=="vectorize"||u.target instanceof HTMLInputElement||u.target instanceof HTMLTextAreaElement)return;const f=e.state;u.key===" "&&!u.repeat?(u.preventDefault(),e.set({flicked:!0})):u.key==="0"?o.fit():u.key==="+"||u.key==="="?o.zoomTo(f.zoom*1.4):u.key==="-"&&o.zoomTo(f.zoom/1.4)}),window.addEventListener("keyup",u=>{u.key===" "&&e.set({flicked:!1})}),{el:l,viewer:o,mount:u=>i.append(u)}}function Br(e,t){const s=e.state,o=s.denoiserFetch;if(!o||!s.caps?.denoiser.supported)return null;const a=s.settings.cleanUpDamage!=="off",i=r=>Math.round(r/(1024*1024));if(o.phase==="downloading"){const r=Mn(o);return n("span.fetchchip",{"data-ctl":"denoiser-chip",title:"The denoiser downloads in the background, once; later visits read it from this browser"},Tt(o),o.total?`Denoiser ${i(o.got)} of ${i(o.total)} MB · ${r}%`:`Denoiser ${i(o.got)} MB`)}return(o.phase==="stored"||o.phase==="preparing")&&a?n("span.fetchchip",{"data-ctl":"denoiser-chip"},Tt(o),"Preparing the denoiser…"):o.phase==="failed"?n("span.fetchchip.bad",{"data-ctl":"denoiser-chip",title:o.message??""},"Denoiser download failed",n("button.reset",{"data-ctl":"denoiser-chip-retry",onclick:t.retryDenoiser},"Retry")):null}function Rr(e){const t=e.state,s=t.tracing?"stale":t.result?.tier==="draft"?"draft":"final",o=t.tracing?`re-tracing · ${hn(t,t.tracingTier)??"—"} px`:`${t.result?.tier??"final"} · ${t.report?.tracedPx??"—"} px`;return n(`span.chip.${s}${t.justSwapped?".swapped":""}`,{style:{position:"absolute",top:"46px",right:"20px"}},n("i"),o)}function Gr(e,t){const s=e.state;return n("div.callout",null,n("div.head",null,n("span.eyebrow",null,s.worstCorner?"Worst corner · 12×":"Detail"),s.worstCorner?n("button.reset",{onclick:t.jumpToWorst},"Jump to it"):null),n("span",null,Ur(e)))}function Wr(e,t,s){const o=(a,i,r,l,c)=>n("div.stagestate",null,n("span",{style:{color:i,display:"flex"}},Z(a,24)),n("span.title",null,r),n("span.body",null,l),c?n("button.action.reset",{onclick:c[1]},c[0]):null);switch(e.kind){case"decoding":return o("clock","var(--faint)",`Reading ${t.source?.name??"the image"}`,`${t.source?.width} × ${t.source?.height}. Tracing starts as soon as the pixels are in memory.`);case"flat":return o("info","var(--gold)","This image is one flat colour","There are no boundaries to trace. A single rectangle would be the whole output.",["Open another image",s.openFile]);case"undecodable":return o("alert","var(--bad)","This file will not decode",e.message,["Open another image",s.openFile]);case"outOfMemory":return o("alert","var(--bad)","The trace would run out of memory",`About ${e.neededGb.toFixed(1)} GB is needed at this trace size. Lowering it to ${e.suggestPx} px usually costs nothing visible.`,[`Retry at ${e.suggestPx} px`,()=>s.retryAt(e.suggestPx)]);case"failed":return o("alert","var(--bad)","The trace stopped",e.message,["Trace again",s.traceNow]);case"cancelled":return o("x","var(--faint)","Trace cancelled","The last full trace is still on screen and still exportable. Nothing was discarded.",["Trace again",s.traceNow]);case"denoiserMissing":return o("download","var(--faint)","This preset wants the denoiser","Photo or scan works without it, but compression damage stays in the colours. The download is explained before anything is fetched, and it runs locally like everything else.",["Download the denoiser",s.openDenoiser]);case"offline":return o("globe","var(--faint)","No network",`Tracing is unaffected — it never needed one. ${e.message}`);default:return null}}function qr(e,t){const s=e.state.prefs;if(!s||s.seenFirstRun)return null;const o=(a,i,r)=>n("li",null,n("span.introstep.num",null,a),n("div",null,n("span.introtitle",null,i),n("span.faint",null,r)));return n("div.intro",{role:"note"},n("span.eyebrow",null,"How opening an image works"),n("ol",null,o("1","Auto traces it at once.","The moment it opens, with nothing to choose first."),o("2","A small card offers Custom.","A few short steps, each explained, with the result beside Auto's as you go. Ignore it and Auto stands."),o("3","Everything is an ordinary setting.","What Custom chooses is under Tune afterwards, to change like any other control.")),n("div.introfoot",null,n("span.muted",null,"Settings has a switch to always use Auto, or always Custom."),n("button.btn.compact",{"data-ctl":"intro-seen",onclick:t.markSeen},"Got it")))}function Hr(e,t,s){const o=Ds(e.state.caps?.platform);return n("div.firstrun",null,qr(e,t),n("div.drop",null,n("span.glyph",null,Z("image",32)),n("div",{style:{display:"flex",flexDirection:"column",gap:"6px"}},n("span.headline.resting",null,"Drop a PNG, JPEG or WebP"),n("span.headline.release",null,"Release to trace"),n("span.faint.resting",{style:{fontSize:"13px"}},"or press ",n("kbd",null,`${o}+O`)," to choose a file")),n("button.btn.primary.resting",{onclick:t.openFile},"Open an image")),s.length?n("div.samples",null,n("span.eyebrow",null,"Or try one of these"),n("div.samplerow",null,...s.map(a=>n("button.sample",{onclick:()=>t.openSample(a.file)},n("span.thumb",null,n("img",{src:a.preview,alt:""})),n("span.label",null,a.label))))):null,n("p.desktopnote.faint",null,"For the fastest engine, whole folders in one batch, no browser limits and offline use, get ",n("a",{href:Fs,target:"_blank",rel:"noopener noreferrer"},"Inkvec Studio for the desktop"),"."))}function uo(e,t){const s=e.state.worstCorner;s&&(e.set({detail:!0}),t.goTo(s.x,s.y,12))}function Ur(e){const t=e.state.worstCorner;return t?`Worst disagreement: ${re(t.de00)} dE00, at ${t.x} × ${t.y} in the traced raster.`:"The trace and the source agree everywhere we can measure."}function po(e,t,s){e[t]=s}const m=new ta(ea(Is,{tolerancePx:.1,judgePx:1024,cornerDegrees:30,documentCleanup:!0}));let ho=[];const fo=new $a;function jr(){const e={...m.state.settings};return delete e.colourGroups,m.state.colourGroups.length&&(e.colourGroups=m.state.colourGroups),e}const Ee=new ka({hasSource:()=>!!m.state.source,groups:()=>m.state.colourGroups,start:e=>B.startTrace(jr(),e),settleMs:()=>m.state.prefs?.settleMs??800,shown:()=>({generation:m.state.generation,tracing:m.state.tracing}),began:(e,t,s)=>m.set({generation:e,tracing:!0,tracingTier:t,liveStages:[],liveNow:null,traceLog:[],traceStarted:s,traceEnded:0}),progressed:e=>m.set(Ma(m.state,e,performance.now())),finished:(e,t)=>Vr(e,t),failed:e=>m.set({tracing:!1,liveNow:null,traceEnded:performance.now(),stageState:{kind:"failed",message:String(e)}}),cancel:()=>{B.cancelTrace(),m.set({tracing:!1,liveNow:null,traceEnded:performance.now(),stageState:m.state.svg?{kind:"cancelled"}:{kind:"empty"}})}});function He(e){return Ee.trace(e)}function Pn(){Ee.cancel()}function Ge(){Ee.controlChanged()}function go(){return new Promise(e=>{if(!m.state.source){e();return}const t=m.on(["result","stageState"],()=>{m.state.tracing||(t(),e())});Ee.clearSettle(),He("final")})}const en=new Sa;function Vr(e,t=m.state.generation){const s=en.take();if(e.state!=="traced"||!m.state.snaps.length){tn(e,t);return}mo(e).then(o=>{en.isNewest(s)&&tn(e,t,o)},o=>{en.isNewest(s)&&(q(`The snapped colours could not be applied to this trace: ${o}`,{kind:"bad"}),tn(e,t))})}async function mo(e){for(;;){const t=m.state.snaps;if(!t.length)return{svg:e.svg,inks:e.palette};const s=e.report.tracedPx,o=await B.snapInks(e.svg,t,s,s);if(m.state.snaps===t)return o}}function tn(e,t,s=null){const o=t!==m.state.generation&&m.state.tracing;if(e.state!=="cancelled"){if(o||m.set({liveNow:null,traceEnded:performance.now()}),e.state==="traced"){const a=m.state.result?.tier==="draft",i=m.state.result?.tier==="final"?m.state.report:m.state.previous;m.set({result:e,resultGeneration:t,resultGroups:Ee.groupsFor(t)??m.state.resultGroups,bandsMissing:!1,previous:e.tier==="final"?i:m.state.previous,svg:s?.svg??e.svg,report:e.report,palette:s?.inks??e.palette,losses:e.losses,worstCorner:e.worstCorner,stageState:{kind:"drawing"},tracing:o,justSwapped:a&&e.tier==="final"}),m.state.justSwapped&&window.setTimeout(()=>m.set({justSwapped:!1}),600),e.tier==="final"&&m.set({groupSuggestions:Gs(m.state)});const r=m.state.auto;r&&t===r.generation&&e.tier==="final"&&!r.report&&(m.set({auto:{...r,svg:e.svg,report:e.report,palette:e.palette,losses:e.losses}}),B.sourceFacts(r.settings.traceSize).then(l=>{m.state.auto?.generation===t&&m.set({facts:l})},()=>{})),e.oversized&&e.tier==="final"&&q(`Traced at ${e.tracedPx} px; the SVG is still ${e.sourcePx[0]} × ${e.sourcePx[1]} and scales without limit. This is normal.`);return}if(!o)switch(m.set({tracing:!1}),e.state){case"flat":m.set({stageState:{kind:"flat"},svg:null,report:null,palette:[],losses:[]});break;case"undecodable":m.set({stageState:{kind:"undecodable",message:e.message}});break;case"outOfMemory":m.set({stageState:{kind:"outOfMemory",neededGb:e.neededGb,suggestPx:e.suggestPx}});break;case"failed":m.set({stageState:{kind:"failed",message:e.message}});break}}}async function Ue(e,t=!0){je.close(),Ee.detach(),m.set({tracing:!1,liveNow:null,liveStages:[],traceLog:[],stageState:{kind:"decoding"},svg:null,report:null,previous:null,palette:[],losses:[],result:null,snaps:[],colourGroups:[],resultGroups:[],groupSuggestions:null,dismissedGroups:[],simplifyTo:null,paletteSelection:[],hoverFill:null,auto:null,facts:null,chooser:!1,compare:null,autoNoteHidden:!1});try{await e(),m.set({zoom:1,pan:{x:0,y:0},stageState:{kind:"drawing"}}),await He("final"),fo.reset();const s=m.state;s.tracing&&m.set({auto:{generation:s.generation,settings:{...s.settings},preset:s.preset,svg:null,report:null,palette:[],losses:[]}}),t&&Yr()}catch(s){m.set({stageState:{kind:"undecodable",message:String(s)}})}}function Yr(){const e=m.state.prefs?.onOpen??"ask";e==="custom"?je.open():e==="ask"&&m.set({chooser:!0})}async function Dn(e){await Ue(async()=>{const t=await B.openPath(e);m.set({source:t});const s=await B.loadPrefs();m.set({prefs:s})})}async function kt(e){if(e.path)return Dn(e.path);const t=e.file;t&&await Ue(async()=>{const s=await B.openBytes(new Uint8Array(await t.arrayBuffer()),e.name);m.set({source:s})})}async function Fn(){const[e]=await fn([{name:"Images and SVG",extensions:["png","jpg","jpeg","webp","gif","bmp","tif","tiff","svg"]}]);e&&await kt(e)}async function Zr(e,t){m.set({tab:"vectorize"}),At(),await Ue(async()=>{const s=await B.openBytes([...new TextEncoder().encode(e)],t);m.set({source:s})},!1)}window.addEventListener("inkvec:retrace",e=>{const{svg:t,name:s}=e.detail;Zr(t,s)});const bo=n("header.appbar"),Qe=n("div.body"),Jr=gr(m,{applyPrefs:e=>void Ve(e),afterReset:e=>{Et(e.theme),xo(e)},close:()=>m.set({screen:null})});function yo(e,t){po(m.state.settings,e,t),m.touch("settings"),m.state.caps?.denoiser,e==="cleanUpDamage"&&t!=="off"&&ko(),Ge()}const Bt=_r(m,{openFile:()=>void Fn(),openSample:e=>void Ue(async()=>{const t=await B.openSample(e);m.set({source:t})}),traceNow:()=>void He("final"),cancel:Pn,retryAt:e=>{m.state.settings.traceSize=e,m.touch("settings"),He("final")},jumpToWorst:()=>uo(m,Bt.viewer),openDenoiser:()=>zn(m),retryDenoiser:()=>lt.retryDenoiser(),showUpdate:()=>{const e=m.state.update;e&&Dt(e.url)},markSeen:()=>void Ve({seenFirstRun:!0}),changeSetting:yo},()=>ho);function Nn(e){return wa(m.state.caps,m.state.prefs,e)}function $s(){const e=m.state.preset;return e&&Nn(e)?.settings||Is}function wo(e){m.set({settings:{...e.settings},preset:e.preset,colourGroups:[...e.colourGroups]}),Ge()}const lt={setPreset:e=>{const t=Nn(e);t&&(m.set({preset:e,settings:{...t.settings}}),t.settings.cleanUpDamage!=="off"&&ko(),Ge())},savePreset:e=>{const t=m.state.prefs;if(!t)return;const s=`saved:${Date.now().toString(36)}${Math.random().toString(36).slice(2,6)}`,o=[...t.saved,{id:s,name:e,settings:{...m.state.settings}}];Ve({saved:o}).then(()=>{const a=m.state.prefs?.saved.find(i=>i.id===s);a?(m.set({preset:s}),q(`Saved as "${a.name}".`,{kind:"good"})):q("The preset tray is full. Forget one and try again.",{kind:"bad"})})},deletePreset:e=>{const t=m.state.prefs;t&&(Ve({saved:t.saved.filter(s=>s.id!==e)}),m.state.preset===e&&m.set({preset:null}))},baseSettings:$s,changeSetting:yo,resetGroup:e=>{const t=$s(),s={...m.state.settings};for(const o of m.state.caps?.controls??[])o.group===e&&!Tn.has(o.key)&&at(o,s)&&po(m.state.settings,o.key,t[o.key]);m.touch("settings"),Ge()},traceNow:()=>void He("final"),cancel:Pn,snap:e=>void Xr(e),setColourGroups:e=>{m.set({colourGroups:e,paletteSelection:[]}),Ge()},openExport:()=>no(m,Rt,go),copySvg:async()=>{m.state.svg&&(await Pt(m.state.svg),q("SVG copied. Paste straight into Figma or Illustrator."))},saveCard:()=>Ci(m),openDenoiser:()=>zn(m),retryDenoiser:()=>void B.denoiserDownload().catch(e=>q(String(e),{kind:"bad"})),jumpToWorst:()=>uo(m,Bt.viewer),backToAuto:()=>{const e=m.state.auto;e&&wo({settings:e.settings,preset:e.preset,colourGroups:m.state.colourGroups})},hideAutoNote:()=>m.set({autoNoteHidden:!0}),openWizard:()=>je.open()},Rt=li(m,lt),vo={...lt,restore:wo,setOnOpen:e=>void Ve({onOpen:e}),markSeen:()=>void Ve({seenFirstRun:!0})},je=Mi(m,Rt,vo,fo);Bt.mount(ki(m,vo,()=>je.open()));async function Xr(e){const t=m.state,s=t.result;if(!s||!e.length)return;const o=t.snaps,a=new Set(e.map(r=>r.from.toLowerCase())),i=[...t.snaps.filter(r=>!a.has(r.from.toLowerCase())),...e.filter(r=>r.to.toLowerCase()!==r.from.toLowerCase())];m.set({snaps:i});try{const r=await mo(s);if(m.state.result!==s)return;m.set({svg:r.svg,palette:r.inks})}catch(r){m.state.snaps===i&&m.set({snaps:o}),q(String(r),{kind:"bad"})}}let un=null,dn=null,Ms=null;function At(){const e=m.state;e.tab==="vectorize"?F(Qe,Bt.el,Rt):e.tab==="minify"?(un??=Wi(m),F(Qe,un)):e.tab==="fabricate"?(dn??=Ui(m),F(Qe,dn)):(Ms??=Pi(m),F(Qe,Ms))}function Cs(){zi(bo,m,{openFile:()=>void Fn(),openPath:e=>void Dn(e)})}async function Ve(e){const t=m.state.prefs;if(!t)return;const s={...t,trace:ln(m.state.settings),ui:rn(m.state),...e},o=await B.savePrefs(s);m.set({prefs:o}),Et(o.theme)}async function Kr(e){const t=m.state.prefs;if(t)try{const s=await B.savePrefs({...t,...e});m.state.prefs===t&&(m.state.prefs=s)}catch{}}const Qr=["vectorize","minify","fabricate"];function xo(e){ia(m,e,Qr,t=>Nn(t)!==null)}function Et(e){const t=e==="dark"||e==="system"&&window.matchMedia("(prefers-color-scheme: dark)").matches;document.documentElement.dataset.theme=t?"dark":"light"}function el(e){const t=e.metaKey||e.ctrlKey,s=e.target instanceof HTMLInputElement||e.target instanceof HTMLTextAreaElement;if(t&&e.key.toLowerCase()==="o"){e.preventDefault(),Fn();return}if(t&&e.key.toLowerCase()==="e"&&m.state.svg){e.preventDefault(),no(m,Rt,go);return}if(t&&e.key===","){e.preventDefault(),m.set({screen:"settings"});return}if(s)return;const o=va(e.key);if(t&&o!==null){const a=m.state.caps?.presets[o];a&&(e.preventDefault(),m.set({preset:a.id,settings:{...a.settings}}),Ge());return}e.key==="Escape"&&(m.state.screen?m.set({screen:null}):je.isOpen()?je.close(!0):m.state.chooser?m.set({chooser:!1}):m.state.tracing&&Pn())}async function tl(){{No(m,e=>void nl(e.map(Ns)));return}}async function nl(e){if(!e.length)return;const t=e.filter(s=>s.name.toLowerCase().endsWith(".svg"));if(t.length===1&&e.length===1&&m.state.tab==="vectorize"){await kt(t[0]);return}if(t.length){const s=m.state.tab==="fabricate"?"fabricate":"minify";m.set({tab:s}),At(),await(s==="fabricate"?dn:un)?.acceptDropped?.(t[0]);return}if(e.length>1){q(`${e.length} files dropped. ${zt} opens one image at a time; the desktop app has Batch.`),await kt(e[0]);return}m.set({tab:"vectorize"}),await kt(e[0])}async function sl(){const e=document.getElementById("app");if(!e)return;e.append(bo,Qe,Jr),_o(e),Uo(),Cs(),At(),m.on(["tab","source","prefs","caps"],Cs),m.on(["tab"],At),window.addEventListener("keydown",el),Li(m),window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",()=>Et(m.state.prefs?.theme??"system")),document.addEventListener("click",i=>{const r=i.target?.closest?.("a[href]"),l=r?.getAttribute("href")??"";r&&/^(https?:|mailto:)/i.test(l)&&(i.preventDefault(),Dt(l).catch(()=>q("Could not open the link in your browser.",{kind:"bad"})))});const t=Ca(()=>m.state.tracing,()=>m.state.traceStarted,()=>m.state.liveNow?.since??null);m.on(["tracing","liveNow","traceLog","liveStages","tab","railTab"],t),await ke.traceProgress(i=>Ee.onProgress(i)),await ke.traceDone(({generation:i,outcome:r})=>Ee.onDone(i,r)),await ke.openPath(i=>void Ts(i)),await ke.batchRow(i=>{const r=m.state.batch,l=r.rows.findIndex(c=>c.id===i.id);l>=0&&(r.rows[l]=i),m.touch("batch")}),await ke.batchTotals(i=>{m.state.batch.totals=i,m.touch("batch")}),await ke.batchFinished(i=>{const r=m.state.batch;r.rows=i,r.running=!1,r.paused=!1,m.touch("batch");const l=i.filter(c=>c.state==="failed").length;q(l?`Batch finished · ${l} failed. Sort failures first to see them.`:`Batch finished · ${i.filter(c=>c.state==="done").length} written.`,{kind:l?"bad":"good"})}),await ke.denoiserFetch(al);const[s,o]=await Promise.all([B.capabilities(),B.loadPrefs()]);m.set({caps:s,prefs:o,settings:o.trace}),Et(o.theme),xo(o),la(m,i=>void Kr(i)),nn(`Engine ${s.engineVersion} · ${s.buildTarget}`,.55),ho=await B.listSamples(),m.touch("source"),nn("Preparing the workspace…",.85),await tl(),nn("Ready",1),B.appReady().catch(()=>{});const a=await B.launchPath().catch(()=>null);a&&Ts(a),Bo().then(ol),o.checkUpdates&&B.checkUpdate().then(i=>m.set({update:i})).catch(()=>{})}async function ol(e){e&&(m.set({tab:"vectorize",screen:null}),"sample"in e?await Ue(async()=>m.set({source:await B.openSample(e.sample)})):await Ue(async()=>m.set({source:await B.openBytes(e.bytes,e.name)})))}async function Ts(e){m.set({tab:"vectorize",screen:null}),await Dn(e)}function ko(){const e=m.state.denoiserFetch;if(!m.state.caps?.denoiser.supported||e?.phase==="ready")return;const t=Ct(e,!0);q(e?.phase==="failed"?"The denoiser did not download. The trace runs without it.":`${Cn(t??"Starting the denoiser…")} The trace shown is without it until it is ready, then it traces again by itself.`,e?.phase==="failed"?{kind:"bad",action:{label:"Retry",run:()=>lt.retryDenoiser()}}:{})}function al(e){const t=m.state.denoiserFetch?.phase;m.set({denoiserFetch:e});const s=m.state.caps;(e.phase==="stored"||e.phase==="ready")&&s&&!s.denoiser.installed&&B.denoiserStatus().then(a=>{const i=m.state.caps;i&&m.set({caps:{...i,denoiser:a}})});const o=m.state.settings.cleanUpDamage!=="off";e.retrace&&o&&m.state.source?(q("The denoiser is ready. Tracing again with it.",{kind:"good"}),He("final")):e.phase==="failed"&&t!=="failed"&&o&&q(`The denoiser did not download: ${e.message??"the connection dropped"}.`,{kind:"bad",action:{label:"Retry",run:()=>lt.retryDenoiser()}})}function nn(e,t){B.startupProgress(e,t).catch(()=>{})}sl().catch(e=>{B.appReady().catch(()=>{});const t=document.getElementById("app");t&&F(t,n("div.firstrun",null,n("span.serif",{style:{fontSize:"24px"}},`${zt} could not start`),n("span.faint",{style:{maxWidth:"50ch",textAlign:"center"}},String(e))))});