var la=Object.defineProperty;var ca=(e,t,s)=>t in e?la(e,t,{enumerable:!0,configurable:!0,writable:!0,value:s}):e[t]=s;var R=(e,t,s)=>ca(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 Kt(e,t={},s){return window.__TAURI_INTERNALS__.invoke(e,t,s)}function ua(){return!!(globalThis||window).isTauri}var cs;(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"})(cs||(cs={}));var us;(function(e){e[e.Critical=1]="Critical",e[e.Informational=2]="Informational"})(us||(us={}));var ds;(function(e){e.None="none",e.Normal="normal",e.Indeterminate="indeterminate",e.Paused="paused",e.Error="error"})(ds||(ds={}));var ps;(function(e){e.Disabled="disabled",e.Throttle="throttle",e.Suspend="suspend"})(ps||(ps={}));var hs;(function(e){e.Default="default",e.FluentOverlay="fluentOverlay"})(hs||(hs={}));var fs;(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"})(fs||(fs={}));var gs;(function(e){e.FollowsWindowActiveState="followsWindowActiveState",e.Active="active",e.Inactive="inactive"})(gs||(gs={}));const da=` `;function n(e,t=null,...s){const[o,...a]=e.split("."),i=document.createElement(o||"div");return a.length&&(i.className=a.join(" ")),pa(i,t),Nn(i,s),i}function Ee(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 Nn(i,s),i}function pa(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 Nn(e,t){for(const s of t.flat(8))s==null||s===!1||e.append(s instanceof Node?s:document.createTextNode(String(s)))}function D(e,...t){e.replaceChildren(),Nn(e,t)}const ms={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 V(e,t=16){const s=ms[e]??ms.info;return Ee("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"},Ee("path",{d:s}))}function ao(e=18){const t=ha.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 ha=(()=>{const e=new DOMParser().parseFromString(da,"image/svg+xml").documentElement;return document.importNode(e,!0)})();function fa(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)}),ga(t)}function ga(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 ma=760,bs="inkvec-phone-tip-dismissed";function ba(e){const t=window.matchMedia(`(max-width: ${ma}px)`).matches,s=window.matchMedia("(pointer: coarse)").matches;if(!(t&&s))return;try{if(sessionStorage.getItem(bs))return}catch{}const o=document.documentElement;let a=null;const i=()=>{a?.disconnect(),o.style.removeProperty("--phonetip-h"),r.remove()},r=n("aside.phonetip",{"aria-label":"Tip",style:{position:"fixed",left:"0",right:"0",bottom:"0",zIndex:"40",display:"flex",alignItems:"center",gap:"8px",padding:"6px 6px 6px 14px",background:"var(--card)",borderTop:"1px solid var(--rule)",color:"var(--dim)",fontSize:"13px",lineHeight:"1.4"}},n("span",{style:{flex:"1"}},"Works on a phone; a larger screen shows the drawing, the controls and the palette side by side."),n("button.btn.ghost.compact",{"aria-label":"Close this tip",style:{minWidth:"32px",minHeight:"32px",justifyContent:"center"},onclick:()=>{i();try{sessionStorage.setItem(bs,"1")}catch{}}},V("x",14)));e.append(r);const l=()=>o.style.setProperty("--phonetip-h",`${r.offsetHeight}px`);l(),typeof ResizeObserver<"u"&&(a=new ResizeObserver(l),a.observe(r))}async function ya(){const e=new URLSearchParams(window.location.search),t=e.get("sample"),s=e.get("open")==="handoff",o=wa(window.location.hash);return!t&&!s&&!o?null:(window.history.replaceState(null,"",window.location.pathname),o||(t?{sample:t}:new Promise(a=>{let i;try{i=indexedDB.open("inkvec-handoff",1)}catch{a(null);return}i.onupgradeneeded=()=>i.result.createObjectStore("files"),i.onerror=()=>a(null),i.onsuccess=()=>{const r=i.result.transaction("files","readwrite").objectStore("files"),l=r.get("open");l.onsuccess=()=>{r.delete("open");const c=l.result;a(c?.bytes?{name:c.name??"image",bytes:c.bytes}:null)},l.onerror=()=>a(null)}})))}function wa(e){if(!e.startsWith("#"))return null;const t=new URLSearchParams(e.slice(1)),s=t.get("open");if(!s)return null;try{const o=s.replace(/-/g,"+").replace(/_/g,"/"),a=atob(o+"=".repeat(4-(o.length&3)&3)),i=new Uint8Array(a.length);for(let r=0;rIn)return null;let s="";for(let a=0;a(e/(1024*1024)).toFixed(1);function xa(){$n=0;const e=Bt;if(Bt=null,!e||ft)return;Ft=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(Ft*100);s&&(s.style.transform=`scaleX(${Ft})`),t?.setAttribute("aria-valuenow",String(r)),o&&(o.textContent=e.text),a&&(a.textContent=`${r}%`),i&&(i.textContent=e.detail)}function wt(e,t,s=""){ft||(io=performance.now(),Bt={text:e,fraction:Math.max(Ft,Bt?.fraction??0,Math.min(1,t)),detail:s},$n||($n=requestAnimationFrame(xa)))}function ka(e,t){const s=t>0?Math.min(1,e/t):0;wt("Downloading the engine",.04+.8*s,t>0?`${ln(e)} of ${ln(t)} MB`:`${ln(e)} MB`)}function Sa(){wt("Starting the engine",.88,"Compiling")}function $a(e,t){wt(e,.9+.1*Math.min(1,Math.max(0,t)))}function Ma(){if(ft)return;const e=document.getElementById("boot-fill");e&&(e.style.transitionDuration="0.12s"),wt("Ready",1),window.setTimeout(()=>{ft=!0;const t=document.getElementById("boot");t&&(t.classList.add("leaving"),window.setTimeout(()=>t.remove(),260))},130)}{window.__inkvecBoot=!0,wt("Loading the Studio",.03);const e=window.setInterval(()=>{if(ft){window.clearInterval(e);return}if(performance.now()-io>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 Ea(){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 Ze(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 ro="inkvec-studio-lite:prefs";function Aa(e=()=>localStorage){try{const t=e().getItem(ro);return t?JSON.parse(t):null}catch{return null}}function Ca(e,t=()=>localStorage){try{return t().setItem(ro,JSON.stringify(e)),!0}catch{return!1}}var Ta={pkg:6425165,"pkg-threads":6475632};const ys="inkvec-denoiser-v1",Pa="inkvec-ort-",La=3e4,za=700,ws="Not available in the browser. Inkvec Studio, the desktop app, has it.",vs=[["flat-logo.png","Flat logo"],["icon-64.png","Icon, 64 px"],["crest-filigree.png","Crest, filigree"],["signature-bw.png","Signature, B&W"]];class Da{constructor(){R(this,"worker",null);R(this,"ready",null);R(this,"queue",[]);R(this,"running",null);R(this,"nextId",1);R(this,"listeners",new Map);R(this,"generation",0);R(this,"previewGeneration",0);R(this,"shared",null);R(this,"source",null);R(this,"prefs",null);R(this,"prefsTimer",0);R(this,"denoiser",null);R(this,"denoiserPort",null);R(this,"downloading",!1);R(this,"fetchState",{phase:"idle",got:0,total:null});R(this,"wanted",!1);R(this,"cancelled",!1);R(this,"retried",!1);R(this,"info",null);R(this,"restartMs",null);R(this,"stopped",[]);R(this,"stopTimer",0);R(this,"denoiserRuns",0);R(this,"last",null)}openImage(){return this.source}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-D2MRJiuD.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"?(ka(c.got,c.total),c.done&&Sa()):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,pool:c.pool,poolMs:null},t&&this.startDenoiser(c.denoiserUrl,c.denoiserSha256),i(c)):c.type==="pool"?(this.info&&(this.info={...this.info,threads:c.threads,pool:c.poolError?"failed":"running",poolMs:c.ms}),c.poolError&&console.warn("inkvec: thread pool did not start:",c.poolError)):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:"56098c26623d",generations:this.shared?this.shared.buffer:null,denoiserPort:o,wasmBytes:Ta},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&&Fa(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??za)-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 $a(String(s.text??""),Number(s.progress??0)),null;case"app_ready":return Ma(),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(!vs.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 vs.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();o.tier!=="draft"&&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 Ze(a[0].name,a[0].data),[a[0].name];const r=`${i}-inkvec.zip`;return a[0]&&Ze(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 Ze(o.split(/[\\/]/).pop()||"download",new Uint8Array(s.bytes)),null}case"batch_scan":case"batch_start":case"batch_stats_csv":throw new Error(ws);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(ys);for(const o of await caches.keys())o.startsWith(Pa)&&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:ws}}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=Aa();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(){Ca(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:"56098c26623d",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"})},La));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(ys)).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 Fa(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 Mt=null;function On(){return Mt||(Mt=new Da,globalThis.__inkvecStudioLite=Mt),Mt}function B(e,t){return On().invoke(e,t)}function Me(e,t){return On().listen(e,t)}const _={capabilities:()=>B("capabilities"),startupProgress:(e,t)=>B("startup_progress",{text:e,progress:t}),appReady:()=>B("app_ready"),openPath:e=>B("open_path",{path:e}),openBytes:(e,t)=>B("open_bytes",{bytes:e,name:t??null}),openSample:e=>B("open_sample",{name:e}),listSamples:()=>B("list_samples"),startTrace:(e,t)=>B("start_trace",{request:{settings:e,tier:t}}),cancelTrace:()=>B("cancel_trace"),startPreview:e=>B("start_preview",{request:{settings:e,tier:"draft"}}),cancelPreviews:()=>B("cancel_previews"),sourceFacts:e=>B("source_facts",{maxDim:e}),launchPath:()=>B("launch_path"),traceBands:e=>B("trace_bands",{generation:e}),snapInks:(e,t,s,o)=>B("snap_inks",{svg:e,snaps:t,width:s,height:o}),matchPalette:(e,t)=>B("match_palette",{traced:e,pasted:t}),planExport:e=>B("plan_export",{request:e}),writeExport:(e,t)=>B("write_export",{request:e,destination:t}),minify:(e,t)=>B("minify_svg",{svg:e,settings:t}),fabAnalyze:e=>B("fab_analyze",{svg:e}),fabPrepare:(e,t)=>B("fab_prepare",{svg:e,options:t}),readTextFile:e=>B("read_text_file",{path:e}),saveBytes:(e,t)=>B("save_bytes",{path:e,bytes:t}),batchScan:e=>B("batch_scan",{folder:e}),batchStart:e=>B("batch_start",{plan:e}),batchPause:e=>B("batch_pause",{paused:e}),batchCancel:()=>B("batch_cancel"),batchStatsCsv:()=>B("batch_stats_csv"),denoiserStatus:()=>B("denoiser_status"),denoiserDownload:()=>B("denoiser_download"),denoiserCancel:()=>B("denoiser_cancel"),denoiserRemove:()=>B("denoiser_remove"),loadPrefs:()=>B("load_prefs"),savePrefs:e=>B("save_prefs",{prefs:e}),resetPrefs:()=>B("reset_prefs"),thirdPartyNotices:()=>B("third_party_notices"),checkUpdate:()=>B("check_update"),cliStatus:()=>B("cli_status"),installCli:()=>B("install_cli"),removeCli:()=>B("remove_cli"),contextMenuStatus:()=>B("context_menu_status"),installContextMenu:()=>B("install_context_menu"),removeContextMenu:()=>B("remove_context_menu")},Ae={traceProgress:e=>Me("trace:progress",t=>e(t.payload)),traceDone:e=>Me("trace:done",t=>e(t.payload)),previewDone:e=>Me("preview:done",t=>e(t.payload)),openPath:e=>Me("open:path",t=>e(t.payload)),batchRow:e=>Me("batch:row",t=>e(t.payload)),batchTotals:e=>Me("batch:totals",t=>e(t.payload)),batchFinished:e=>Me("batch:finished",t=>e(t.payload)),denoiserProgress:e=>Me("denoiser:progress",t=>e(t.payload)),denoiserDone:e=>Me("denoiser:done",t=>e(t.payload)),denoiserFetch:e=>Me("denoiser:fetch",t=>e(t.payload))};function Na(e,t){return{caps:null,prefs:null,tab:"vectorize",screen:null,source:null,settings:e,preset:"logo",generation:0,tracing:!1,tracingTier:"final",tracingEngine:e.mode,liveStages:[],traceStarted:0,traceEnded:0,liveNow:null,traceLog:[],logOpen:!0,result:null,resultGeneration:0,resultEngine:e.mode,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:lo(),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 lo(){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 Ia{constructor(t){R(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 le(e){return e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KB`:`${(e/(1024*1024)).toFixed(1)} MB`}function ue(e){return e==null?"—":e.toFixed(2)}function gt(e){return e<1?`${Math.round(e*1e3)} ms`:`${e.toFixed(2)} s`}function mt(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 _n(e){return e==="fast"||e==="balanced"}function Mn(e){return e==="fast"?"Fast":e==="balanced"?"Balanced":"Quality"}function vt(e,t){const s=_n(t.mode)&&!t.blackAndWhite&&!t.lineArt;return e.modes==="qualityOnly"?!s:e.modes==="fastOnly"?s:!0}function Te(e,t=0){return`${(e*100).toFixed(t)}%`}function Oa(e){const t=Math.max(0,Math.round(e));return t<60?`${t} s`:`${Math.floor(t/60)} min ${t%60} s`}function co(e){return e==="macos"?"⌘":"Ctrl"}function _a(){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 ze={export:null,minifyBackdrop:"auto",minifyView:"side"};let En=()=>{};function Ba(){return{...ze}}function An(e){Object.assign(ze,e),En()}function Ra(e){const t=ze.export;return t?{...e,...t,pngSizes:[...t.pngSizes??e.pngSizes]}:e}function Cn(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:ze.export,minify:{...e.minify.settings},minifyBackdrop:ze.minifyBackdrop,minifyView:ze.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 Tn(e){const t={...e};return delete t.colourGroups,t}function Ga(e,t,s,o){const a=e.state,i={..._a(),...t.ui??{}};ze.export=i.export,ze.minifyBackdrop=i.minifyBackdrop,ze.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:{...lo(),...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 Wa=["settings","preset","tab","railTab","groupsOpen","view","wipe","show","detail","minify","fab","batch"];function qa(e,t,s=500){let o=0,a=JSON.stringify({ui:Cn(e.state),trace:Tn(e.state.settings)});const i=()=>{if(window.clearTimeout(o),o=0,!e.state.prefs)return;const c={ui:Cn(e.state),trace:Tn(e.state.settings)},u=JSON.stringify(c);u!==a&&(a=u,t(c))};En=()=>{window.clearTimeout(o),o=window.setTimeout(i,s)};const r=e.on(Wa,En),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 Ha="modulepreload",Ua=function(e,t){return new URL(e,t).href},xs={},ja=function(t,s,o){let a=Promise.resolve();if(s&&s.length>0){let r=function(d){return Promise.all(d.map(h=>Promise.resolve(h).then(f=>({status:"fulfilled",value:f}),f=>({status:"rejected",reason:f}))))};const l=document.getElementsByTagName("link"),c=document.querySelector("meta[property=csp-nonce]"),u=c?.nonce||c?.getAttribute("nonce");a=r(s.map(d=>{if(d=Ua(d,o),d in xs)return;xs[d]=!0;const h=d.endsWith(".css"),f=h?'[rel="stylesheet"]':"";if(!!o)for(let m=l.length-1;m>=0;m--){const k=l[m];if(k.href===d&&(!h||k.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${d}"]${f}`))return;const g=document.createElement("link");if(g.rel=h?"stylesheet":Ha,h||(g.as="script"),g.crossOrigin="",g.href=d,u&&g.setAttribute("nonce",u),document.head.appendChild(g),h)return new Promise((m,k)=>{g.addEventListener("load",m),g.addEventListener("error",()=>k(new Error(`Unable to preload CSS for ${d}`)))})}))}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 Va(e={}){return typeof e=="object"&&Object.freeze(e),await Kt("plugin:dialog|open",{options:e})}async function Ka(e={}){return typeof e=="object"&&Object.freeze(e),await Kt("plugin:dialog|save",{options:e})}async function Ya(e,t){await Kt("plugin:opener|open_url",{url:e,with:t})}async function Za(e){return Kt("plugin:opener|reveal_item_in_dir",{paths:typeof e=="string"?[e]:e})}const Yt="Inkvec Studio Lite",uo="https://github.com/logolabs/inkvec/releases",Ja="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 po(e){return{name:e.name||"pasted image",path:null,file:e}}async function Bn(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(po))),o.addEventListener("cancel",()=>i([])),document.body.append(o),o.click()})}async function Rt(e){if(e.file)return e.file.text();if(e.path)return _.readTextFile(e.path);throw new Error("nothing to read")}const ks=!1;async function ho(e,t,s){const o=typeof t=="string"?new TextEncoder().encode(t):t;return Ze(e,o),{path:null,name:e}}async function Xa(e,t,s){const o=new TextEncoder,a=i=>typeof i=="string"?o.encode(i):i;{if(e.length===1)Ze(e[0].name,a(e[0].data));else{const{zipStore:i}=await ja(async()=>{const{zipStore:r}=await import("./zip-DlX9-WSC.js");return{zipStore:r}},[],import.meta.url);Ze(t,i(e.map(r=>({name:r.name,data:a(r.data)}))))}return{folder:null,first:null}}}const Qa=!1;async function Zt(e){await navigator.clipboard.writeText(e)}async function Jt(e){{window.open(e,"_blank","noopener,noreferrer");return}}function Xt(e){return e&&Qa?{label:"Show in folder",run:()=>void 0}:void 0}const fo={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 ei(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 ti(e){return/^[0-9]$/.test(e)?(Number(e)+9)%10:null}const ni=8;class si{constructor(t){R(this,"watching",0);R(this,"newest",0);R(this,"early",new Map);R(this,"groupsSent",new Map);R(this,"settleTimer");R(this,"afterDraft",null);this.host=t}async trace(t){this.dropAfterDraft(),await this.begin(t,!1)}open(t){return this.dropAfterDraft(),t?new Promise(s=>{const o={draft:0,resolve:s};this.afterDraft=o,this.begin("draft",!0).then(a=>{a===null&&this.afterDraft===o&&this.dropAfterDraft()})}):this.begin("final",!1).then(()=>{})}async begin(t,s){if(!this.host.hasSource())return null;const o=performance.now();try{const a=this.host.groups(),i=await this.host.start(t,s);if(io.resolve()))}dropAfterDraft(){const t=this.afterDraft;this.afterDraft=null,t?.resolve()}onProgress(t){if(t.generation>this.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.landed(s,t)}controlChanged(){this.clearSettle(),this.host.hasSource()&&(this.trace("draft"),this.settleTimer=setTimeout(()=>void this.trace("final"),this.host.settleMs()))}get waitingForDraft(){return this.afterDraft!==null}cancel(){this.clearSettle(),this.host.cancel()}clearSettle(){clearTimeout(this.settleTimer),this.settleTimer=void 0,this.dropAfterDraft()}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 oi{constructor(){R(this,"last",0)}take(){return++this.last}isNewest(t){return t===this.last}}function Re(e){const t={...e,cleanUpDamage:"off"};return JSON.stringify(Object.keys(t).sort().map(s=>[s,t[s]]))}class ai{constructor(){R(this,"results",new Map);R(this,"queue",[]);R(this,"running",null);R(this,"epoch",0);R(this,"early",new Map);R(this,"listeners",new Set);Ae.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&&_.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(),_.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 Ss=400;function ii(e,t,s){let o=e.liveStages,a=e.liveNow;const i=[...e.traceLog],r=c=>{for(let u=i.length-1;u>=0;u--){const d=i[u];if(d.kind==="stage")return d.what===c&&d.ms===void 0?u:-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 d=o.findIndex(h=>h.name===c.stage);o=d>=0?o.map((h,f)=>f===d?{...h,ms:h.ms+c.ms}:h):[...o,{name:c.stage,ms:c.ms}]}const u=r(c.what);u>=0?i[u]={...i[u],ms:c.ms,step:i[u].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>Ss?i.slice(i.length-Ss):i}}function Rn(e){return e.total>0?`${e.done} / ${e.total} ${e.unit}`:`${e.unit} ${e.done}`}function Je(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 $s(e){return`${(Math.max(0,e)/1e3).toFixed(2)}`}function ri(e,t,s){let o=0;const a=()=>{const r=performance.now(),l=t(),c=s();for(const u of document.querySelectorAll('[data-live="elapsed"]'))u.textContent=Je(r-l);for(const u of document.querySelectorAll('[data-live="stage"]'))u.textContent=c===null?"":Je(r-c)},i=()=>{e()&&!o?(a(),o=window.setInterval(a,100)):!e()&&o&&(window.clearInterval(o),o=0)};return()=>{i(),o&&a()}}function Qt(e){const t=e.liveNow;if(t){const s=t.stage==="measure"?"measuring":t.stage;return t.step?`${s} · ${Rn(t.step)}`:`${s} · ${t.what}`}return e.liveStages[e.liveStages.length-1]?.name??"starting"}function en(e){return Je(performance.now()-e.traceStarted)}const li=["a[href]","button:not([disabled])","input:not([disabled]):not([type=hidden])","select:not([disabled])","textarea:not([disabled])","iframe","[tabindex]:not([tabindex='-1'])","[contenteditable='true']"].join(","),go="#toasts, #overlays, .tooltip, script, style, link";function Gt(e){return[...e.querySelectorAll(li)].filter(t=>{if(t.closest("[hidden], [inert]"))return!1;const s=t.checkVisibility;return typeof s=="function"?s.call(t):t.getClientRects().length>0})}const Le=[];function ci(e){const t=[];for(let s=e;s&&s!==document.body;s=s.parentElement){const o=s.parentElement;if(!o)break;for(const a of o.children)a!==s&&a instanceof HTMLElement&&!a.matches(go)&&t.push(a)}return t}function Ms(e){const t=Le[Le.length-1];if(!t)return;if(e.key==="Escape"&&t.opts.onEscape){e.preventDefault(),e.stopPropagation(),t.opts.onEscape();return}if(e.key!=="Tab")return;const s=Gt(t.root),o=document.activeElement,a=!!(o&&t.root.contains(o));if(!s.length){e.preventDefault(),t.root.focus();return}const i=s[0],r=s[s.length-1];(!a||e.shiftKey&&o===i||!e.shiftKey&&o===r)&&(e.preventDefault(),(e.shiftKey?r:i).focus())}function Es(e){const t=Le[Le.length-1],s=e.target;!t?.opts.onOutside||!s||t.root.contains(s)||s.closest?.(go)||t.opts.onOutside()}function As(e){e.ctrlKey||e.metaKey||e.key==="Tab"||/^F\d{1,2}$/.test(e.key)||e.stopPropagation()}function tn(e,t={}){const s=document.activeElement,o=s instanceof HTMLElement&&s!==document.body?s:null;e.hasAttribute("tabindex")||e.setAttribute("tabindex","-1");const a=ci(e).filter(c=>!c.inert);for(const c of a)c.inert=!0;const i={root:e,opts:t,opener:o,inerted:a};Le.length||(document.addEventListener("keydown",Ms,!0),document.addEventListener("pointerdown",Es,!0)),Le.push(i),e.addEventListener("keydown",As),(t.initial?.()??Gt(e)[0]??e).focus({preventScroll:!0});let l=!1;return(c=!0)=>{if(l)return;l=!0;const u=Le.indexOf(i);u>=0&&Le.splice(u,1),Le.length||(document.removeEventListener("keydown",Ms,!0),document.removeEventListener("pointerdown",Es,!0)),e.removeEventListener("keydown",As);for(const h of a)h.inert=!1;if(!c)return;(o?.isConnected?o:t.fallback?.())?.focus({preventScroll:!0})}}const Wt=()=>document.getElementById("overlays"),ui=()=>document.getElementById("toasts");function G(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,V(o,16)),n("span.text",null,e),t.action&&n("button",{class:"reset",onclick:()=>{t.action?.run(),a.remove()}},t.action.label));ui().append(a),window.setTimeout(()=>a.remove(),6e3)}let He=null,Pn=null;function he(){const e=Wt().contains(document.activeElement);D(Wt()),document.removeEventListener("keydown",Gn,!0);const t=He;He=null,t?.();const s=Pn;Pn=null,s?.isConnected&&(e||document.activeElement===document.body)&&s.focus({preventScroll:!0})}function Gn(e){e.key==="Escape"&&(e.stopPropagation(),he())}let di=0;function ot(e){He?.(!1),He=null;const t=n("div.scrim",{onmousedown:o=>{o.target===t&&he()}});e.setAttribute("role","dialog"),e.setAttribute("aria-modal","true");const s=e.querySelector("h1, h2, h3");s&&!e.hasAttribute("aria-label")&&!e.hasAttribute("aria-labelledby")&&(s.id||=`modal-title-${++di}`,e.setAttribute("aria-labelledby",s.id)),t.append(e),D(Wt(),t),document.removeEventListener("keydown",Gn,!0),He=tn(e,{initial:()=>e.querySelector("button, input, select, textarea, [tabindex]:not([tabindex='-1'])"),onEscape:he})}function nn(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 Wn(e,t){He?.(!1),He=null,Pn=e;const s=n("div.popover",null,t),o=n("div",{style:{position:"fixed",inset:"0",zIndex:"44"},onmousedown:he});D(Wt(),o,s),document.addEventListener("keydown",Gn,!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,u=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,u))}px`,(s.querySelector("button, input, [tabindex]")??s).focus?.()}let Ue=null,qn=0;function bt(){window.cancelAnimationFrame(qn),Ue?.node.remove(),Ue=null}function mo(){const e=Ue;if(!e)return;if(!(e.el.isConnected&&(e.el.matches(":hover")||e.el.matches(":focus-visible")))){bt();return}qn=window.requestAnimationFrame(mo)}function We(e,t){let s=0;const o=()=>{if(!e.isConnected||Ue?.el===e)return;bt();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),u=r.top-l.height-8;i.style.left=`${Math.round(c)}px`,i.style.top=`${Math.round(u<8?r.bottom+8:u)}px`,Ue={node:i,el:e},qn=window.requestAnimationFrame(mo)},a=()=>{window.clearTimeout(s),Ue?.el===e&&bt()};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"&&Ue&&bt()},!0);window.addEventListener("blur",bt);function pi(e,t,s,o,a=!1){ot(nn(e,[t],[n("button.btn",{onclick:he},"Cancel"),n(`button.btn.primary${a?".danger":""}`,{onclick:()=>{he(),o()}},s)]))}function bo(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 yo([e,t,s]){const o=g=>g<=.04045?g/12.92:((g+.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,u=(.0193339*a+.119192*i+.9503041*r)/1.08883,d=g=>g>216/24389?Math.cbrt(g):(24389/27*g+16)/116,[h,f,p]=[d(l),d(c),d(u)];return[116*f-16,500*(h-f),200*(f-p)]}function hi(e,t){const[s,o,a]=e,[i,r,l]=t,c=Math.PI/180,u=Math.hypot(o,a),d=Math.hypot(r,l),h=((u+d)/2)**7,f=.5*(1-Math.sqrt(h/(h+25**7))),p=(1+f)*o,g=(1+f)*r,m=Math.hypot(p,a),k=Math.hypot(g,l),E=(ce,Fe)=>{if(ce===0&&Fe===0)return 0;const ye=Math.atan2(Fe,ce)/c;return ye<0?ye+360:ye},x=E(p,a),$=E(g,l),y=i-s,v=k-m;let M=0;m*k!==0&&(M=$-x,M>180?M-=360:M<-180&&(M+=360));const A=2*Math.sqrt(m*k)*Math.sin(M*c/2),T=(s+i)/2,O=(m+k)/2;let Z=x+$;m*k!==0&&(Z=Math.abs(x-$)<=180?(x+$)/2:x+$<360?(x+$+360)/2:(x+$-360)/2);const te=1-.17*Math.cos((Z-30)*c)+.24*Math.cos(2*Z*c)+.32*Math.cos((3*Z+6)*c)-.2*Math.cos((4*Z-63)*c),P=30*Math.exp(-(((Z-275)/25)**2)),L=O**7,Q=2*Math.sqrt(L/(L+25**7)),U=1+.015*(T-50)**2/Math.sqrt(20+(T-50)**2),W=1+.045*O,ee=1+.015*O*te,K=-Math.sin(2*P*c)*Q,ae=y/U,ie=v/W,ne=A/ee;return Math.sqrt(ae*ae+ie*ie+ne*ne+K*ie*ne)}function qt(e){return e.kind==="gradient"&&e.stops.length>1?e.stops.map(t=>t.toLowerCase()).join(">"):e.traced.toLowerCase()}function Qe(e){return e.split(">").map(t=>t.trim().toLowerCase())}function xt(e){return e.includes(">")}function Hn(e,t=!1){const s=Qe(e);return s.length<2?s[0]??"transparent":t?`radial-gradient(circle, ${s.join(", ")})`:`linear-gradient(90deg, ${s.join(", ")})`}const Et=new Map;function Cs(e){const t=Et.get(e);if(t)return t;const s=bo(e);if(!s)return null;const o=yo(s);return Et.size>4096&&Et.clear(),Et.set(e,o),o}function Un(e,t){const s=Qe(e).map(Cs),o=Qe(t).map(Cs);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((u,d)=>u+Math.min(...c.map(h=>hi(d,h))),0)/l.length;return Math.max(r(a,i),r(i,a))}const fi=2;function sn(e,t){const s=xt(e),o=t.find(r=>qt(r)===e);if(o)return o;let a=null,i=fi;for(const r of t){if(r.kind==="gradient"!==s)continue;const l=Un(e,qt(r));l<=i&&(i=l,a=r)}return a}function Ce(e,t){return e===t||Un(e,t)<.5}const gi=2,mi=240;function bi(e,t={}){const s=t.skip??[],o=e.filter(d=>d.share>0),a=[...o].sort((d,h)=>h.share-d.share).slice(0,mi).map(d=>({member:qt(d),share:d.share})).filter(d=>!s.some(h=>Ce(h,d.member))),i=a.length;if(i<2)return[];const r=a.map((d,h)=>a.map((f,p)=>h===p?0:Un(d.member,f.member))),l=a.map((d,h)=>[h]);let c=i;const u=t.count?Math.max(1,Math.round(t.count)-(o.length-i)):null;for(;!(u!==null&&c<=u);){let d=1/0,h=-1,f=-1;for(let p=0;pgi)break;l[h]=[...l[h],...l[f]],l[f]=null,c--;for(let p=0;pd.length>1).map(d=>d.map(h=>a[h]).sort((h,f)=>f.share-h.share)).sort((d,h)=>h.reduce((f,p)=>f+p.share,0)-d.reduce((f,p)=>f+p.share,0)).map(d=>({members:d.map(h=>h.member),target:null}))}function jn(e){return[...e.members].sort().join(",")}function yi(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 wi(e,t){return e?e.unmatched.some(s=>s===t||Qe(t).includes(s)||Ce(s,t)):!1}function wo(e){const t=(e.groupSuggestions??[]).filter(a=>a.edited),s=[...e.colourGroups.flatMap(a=>a.members),...t.flatMap(a=>a.members)],o=bi(e.palette,{count:e.simplifyTo,skip:s}).filter(a=>!e.dismissedGroups.includes(jn(a)));return[...t,...o]}function vi(e,t){const s=e.members.filter(a=>sn(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 Vn(e){return(e.groupSuggestions??[]).map((t,s)=>({g:vi(t,e.palette),index:s})).filter(t=>t.g!==null)}function vo(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 ke(e,t,s=-1,o=!1){return e.map((a,i)=>{if(i===s)return a;const r=a.members.findIndex(l=>Ce(l,t));return r<0?a:{...vo(a,r),...o?{edited:!0}:{}}})}const et=e=>e.filter(t=>t.members.length>1);function xi(e,t,s,o,a){const i=e.state;let r=i.colourGroups.map(f=>({...f,members:[...f.members]})),l=(i.groupSuggestions??[]).map(f=>({...f,members:[...f.members]}));const c=a?.closest("[data-drop]")??null,u=c?.dataset.drop??null,d=Number(c?.dataset.index??-1);if((u==="group"||u==="sugg")&&o.kind===u&&o.index===d||u==="ink"&&Ce(c?.dataset.member??"",s))return;if(u==="group")r=ke(r,s,d),l=ke(l,s,-1,!0),r[d].members.some(f=>Ce(f,s))||r[d].members.push(s);else if(u==="sugg")r=ke(r,s),l=ke(l,s,d,!0),l[d].members.some(f=>Ce(f,s))||(l[d]={...l[d],members:[...l[d].members,s],edited:!0});else if(u==="ink"){const f=c?.dataset.member??"";r=ke(r,s),l=ke(l,s,-1,!0);const p=r.findIndex(g=>g.members.some(m=>Ce(m,f)));p>=0?r[p].members.push(s):(l=ke(l,f,-1,!0),r.push({members:[f,s],target:null}))}else{if(o.kind==="palette")return;o.kind==="group"?r=ke(r,s):l=ke(l,s,-1,!0)}const h=et(r);e.set({groupSuggestions:et(l),paletteSelection:[]}),JSON.stringify(h)!==JSON.stringify(i.colourGroups)&&t.setColourGroups(h)}function ki(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(u=>Ce(u,c))));let r=s.groupSuggestions??[];for(const l of o)a=ke(a,l,i),r=ke(r,l,-1,!0);if(i>=0)for(const l of o)a[i].members.some(c=>Ce(c,l))||a[i].members.push(l);else a.push({members:[...o],target:null});e.set({groupSuggestions:et(r),paletteSelection:[]}),t.setColourGroups(et(a))}const Si=4;function xo(e,t,s,o,a,i){e.addEventListener("pointerdown",r=>{if(r.button!==0)return;const l=r.clientX,c=r.clientY;let u=null,d=null;const h=g=>{g!==d&&(d?.classList.remove("dropping"),d=g,d?.classList.add("dropping"))},f=g=>{if(!u){if(Math.hypot(g.clientX-l,g.clientY-c){if(window.removeEventListener("pointermove",f),window.removeEventListener("pointerup",p),window.removeEventListener("pointercancel",p),!u)return;u.remove(),document.body.classList.remove("dragging-ink"),h(null);const m=k=>k.stopPropagation();window.addEventListener("click",m,!0),window.setTimeout(()=>window.removeEventListener("click",m,!0),0),g.type!=="pointercancel"&&xi(t,s,o,a,document.elementFromPoint(g.clientX,g.clientY))};window.addEventListener("pointermove",f),window.addEventListener("pointerup",p),window.addEventListener("pointercancel",p)})}function At(e,t){return sn(e,t)?.keys??[]}function ko(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 So(e,t){const s=e.state,o=Vn(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"}},V("palette",12),`Palette · ${s.palette.length} ink${s.palette.length===1?"":"s"}`),n("button.reset",{disabled:!a.length,onclick:()=>zi(e,t)},"Paste brand palette")),s.colourGroups.length?$i(e,t):null,s.palette.length?Ai(e,t,o):null,n("div.inklist",{"data-drop":"list","aria-label":"Inks"},...s.palette.map(r=>Ci(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:()=>ki(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 Li(a)},"Copy all as CSS")):n("span.muted",{style:{fontSize:"11px"}},"Trace an image to see its inks."))}function Ts(e,t=!1){return n(`span.swatch.mini${xt(e)?".ramp":""}`,null,n("i",{style:{background:Hn(e,t)}}))}function cn(e){const t=Qe(e);return t.length>1?`${t.length} stops`:t[0].toUpperCase()}function $i(e,t){const s=e.state,a=JSON.stringify(s.resultGroups)===JSON.stringify(s.colourGroups)&&s.result?yi(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)=>$o(e,t,i,r,"group",a[r]??null)))}function Mi(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:xt(a)?"extends this gradient":"becomes this"}}let s=e.members[0],o=-1;for(const a of e.members){const i=sn(a,t)?.share??0;i>o&&(o=i,s=a)}return{member:s,label:"most-used"}}function $o(e,t,s,o,a,i){const r=e.state,l=r.palette,c=k=>sn(k,l)?.gradient==="radial",u=Mi(s,l),d=[...new Set([...s.members,u.member].flatMap(k=>At(k,l)))],h=k=>{if(a==="group"){const E=r.colourGroups.map((x,$)=>$===o?k:x);t.setColourGroups(et(E))}else{const E=(r.groupSuggestions??[]).map((x,$)=>$===o?{...k,edited:!0}:x);e.set({groupSuggestions:et(E)})}},f=k=>wi(i,k),p=k=>{const E=At(k,l);return E.length||a==="sugg"?E:At(u.member,l)},g=s.members.map((k,E)=>{const x=s.target===`@${E+1}`,$=n(`div.cchip${x?".target":""}${f(k)?".unmatched":""}`,{"data-fill":JSON.stringify(p(k))},n("button.cchipmain",{"data-ctl":`${a}${o}:${k}`,"aria-pressed":String(x),title:x?"The group becomes this. Press again for the most-used colour.":xt(k)?"Make the group this gradient: it is extended over the other members":"Make the group this colour",onclick:()=>h({...s,target:x?null:`@${E+1}`})},Ts(k,c(k)),n("span.num",null,cn(k))),n("button.cchipx",{"data-ctl":`${a}${o}:${k}:x`,"aria-label":`Take ${cn(k)} out of the group`,title:"Take it out of the group",onclick:()=>h(vo(s,E))},V("x",11)));return xo($,e,t,k,{kind:a,index:o},c(k)),$}),m=n("button.reset",{title:"Paint the whole group one colour of your choosing",onclick:k=>Ti(k.currentTarget,u.member,E=>h({...s,target:E}))},"Custom…");return n(`div.cgroup${a==="sugg"?".suggested":""}`,{"data-drop":a,"data-index":String(o),"data-fill":JSON.stringify(d)},n("div.cchips",null,...g),n("div.cresult",null,n("span.arrow",{"aria-hidden":"true"},"→"),n("span.cresultchip",{"data-fill":JSON.stringify(At(u.member,l))},Ts(u.member,c(u.member)),n("span.num",null,cn(u.member))),n("span.muted",null,u.label),n("span",{style:{flex:"1"}}),m),a==="sugg"?n("div.cactions",null,n("button.btn.compact.primary",{onclick:()=>{const k=(r.groupSuggestions??[]).filter((E,x)=>x!==o);e.set({groupSuggestions:k}),t.setColourGroups([...r.colourGroups,{members:s.members,target:s.target}])}},"Accept"),n("button.btn.compact",{onclick:()=>e.set({groupSuggestions:(r.groupSuggestions??[]).filter((k,E)=>E!==o),dismissedGroups:[...r.dismissedGroups,jn(s)]})},"Dismiss")):[Ei(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((k,E)=>E!==o))},"Ungroup"))])}function Ei(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},V("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=>xt(o)?"a gradient":o.toUpperCase()).join(", ");return n("span.creport.warn",{title:`${e.line}; not found: ${e.unmatched.join(", ")}`},V("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 Ai(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))),u=(o.groupSuggestions??[]).filter(d=>d.edited);e.set({simplifyTo:c,groupSuggestions:wo({...o,simplifyTo:c,groupSuggestions:u})})},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)},V("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})=>$o(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 Ci(e,t,s){const o=e.state,a=qt(s),i=s.kind==="gradient",r=s.snappedDe00!==null,l=o.paletteSelection.includes(a),c=o.colourGroups.some(f=>f.members.some(p=>Ce(p,a))),u=n("button.inkmain",{"data-ctl":`ink:${a}`,title:i?`${s.gradient??"linear"} gradient · ${s.stops.map(f=>f.toUpperCase()).join(" → ")}`:"Snap this ink to a colour of your choosing","aria-disabled":i?"true":null,onclick:f=>{i||Pi(f.currentTarget,s,t)}},n(`span.swatch${r?".snapped":""}${i?".ramp":""}`,null,n("i",{style:{background:i?Hn(a,s.gradient==="radial"):s.hex}})),n("span.hex",null,i?s.gradient==="radial"?"radial":"linear":s.hex.toUpperCase()),n("span.share",null,Te(s.share)),n(`span.snap${r?".on":""}`,null,i?`${s.stops.length} stops`:r?`snapped · ΔE ${ue(s.snappedDe00)}`:"snap to…")),d=()=>e.set({paletteSelection:l?o.paletteSelection.filter(f=>f!==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:d,onkeydown:f=>{f.key===" "&&(f.preventDefault(),f.stopPropagation(),f.repeat||d())}},l?V("check",11):null),u);return xo(h,e,t,a,{kind:"palette"},s.gradient==="radial"),h}function Ti(e,t,s){const o=Qe(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)){G(`${a.value} is not a colour; write it as #RRGGBB.`,{kind:"bad"});return}s(c),he()};Wn(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 Pi(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.");Wn(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()} · ${Te(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()}]),he()}},"Snap")),i))}async function Li(e){const t=e.map((s,o)=>` --ink-${o+1}: ${s.hex.toLowerCase()}; /* ${Te(s.share)} of canvas */`).join(` `);try{await Zt(`:root { ${t} } `),G(`${e.length} ink${e.length===1?"":"s"} copied as CSS.`,{kind:"good"})}catch(s){G(String(s),{kind:"bad"})}}function zi(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 _.matchPalette(s.map(l=>l.traced),o.value),D(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":`${ue(l.de00)} dE00`))))};o.addEventListener("input",()=>void r()),ot(nn("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:he},"Cancel"),n("button.btn.primary",{onclick:()=>{t.snap(i.map(l=>({from:l.from,to:l.to}))),he()}},`Snap ${s.length} inks`)]))}const Mo=2.5,Di=256;function Eo(e,t){return!!e?.lossy||t!==null&&t.noiseLevels>=Mo}function Fi(e){return e.filter(t=>t.share>=.005)}function Ni(e){if(e.length!==2||e.some(l=>l.kind==="gradient"))return!1;const t=e.map(l=>bo(l.hex)).map(l=>l?yo(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 Ii(e){const{source:t,report:s,palette:o,losses:a,facts:i,settings:r}=e;if(!t||!s)return[];const l=[];if(Eo(t,i)&&r.cleanUpDamage==="off"){const f=[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:f.join(" · "),action:"Photo or scan, with the denoiser"})}const c=Fi(o);!r.blackAndWhite&&!r.monochrome&&Ni(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(f=>f.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 u=Math.max(t.width,t.height),d=o.some(f=>f.kind==="gradient");return!(r.speckleFloor<=1&&r.maxColours<=16)&&u<=Di&&!d&&c.length<=16&&l.length===0&&l.push({id:"icon",preset:"icon",title:"A small flat mark",evidence:`${u} px, ${c.length} colour${c.length===1?"":"s"}`,action:"Icon, keeping one-pixel details"}),l.slice(0,2)}function Ge(e,t,s){return(e?.controls??[]).filter(o=>t[o.key]!==s[o.key]).map(o=>o.key)}function Ao(e,t,s){const o=e?.presets.find(a=>a.id===s);if(o){const a=Ge(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 Kn(e){const t=e.state,s=t.auto;return s?.report?Ii({source:t.source,report:s.report,palette:s.palette,losses:s.losses,facts:t.facts,settings:s.settings,preset:s.preset}):[]}function Co(e){const t=e.state;return t.auto?t.preset!==t.auto.preset||Ge(t.caps,t.settings,t.auto.settings).length>0:!1}function Oi(e){const t=e.state;return t.auto?.report?Co(e)||Vn(t).length>0||Kn(e).length>0:!1}function To(e,t,s=!1){const o=e.state,a=o.auto;if(!a?.report)return null;const i=Kn(e),r=Vn(o),l=Co(e),c=o.caps?.presets.find(d=>d.id===o.preset),u=i.map(d=>{const h=o.preset===d.preset;return n("div.autorow",null,n("div.autotext",null,n("span.autotitle",null,d.title),n("span.muted.num",null,d.evidence)),h?n("span.autotaken",null,"chosen"):n("button.btn.compact",{"data-ctl":`auto-try:${d.id}`,title:`Switch to ${d.action}. An ordinary preset: Back to Auto undoes it.`,onclick:()=>t.setPreset(d.preset)},`Try ${d.action}`))});if(r.length){const d=r.reduce((h,{g:f})=>h+f.members.length,0);u.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,`${d} 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})=>jn(h))]})},"Dismiss"))))}return n("div.autochose",null,s?null:n("span.autolead",null,`Auto traced with ${Ao(o.caps,a.settings,a.preset)}.`),...u,!u.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 un=e=>`${Math.round(e/(1024*1024))}`;function Yn(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 Ht(e,t){if(!e)return t?"Starting the denoiser…":null;switch(e.phase){case"downloading":{const s=Yn(e);return e.total?`Downloading the denoiser, ${un(e.got)} of ${un(e.total)} MB, ${s}%`:`Downloading the denoiser, ${un(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 Zn(e){return/[….]$/.test(e)?e:`${e}.`}function Ut(e){if(!e||e.phase!=="downloading"&&e.phase!=="preparing"&&e.phase!=="stored")return null;const t=Yn(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 Po(e,t,s,o){const a=e.state.settings[t.key],i=a!==o[t.key],r=(...p)=>n(i?"div.control.changed":"div.control",null,...p);if(t.kind==="switch"){const p=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,We(n("span.label",{tabindex:"0"},t.label),t.help),p))}if(t.kind==="tri"){const p=[["off","Off"],["auto","Auto"],["on","On"]];return r(n("div.controlhead",null,We(n("span.label",{tabindex:"0"},t.label),t.help),n("div.seg",null,...p.map(([g,m])=>n("button",{"aria-pressed":String(a===g),"data-ctl":`${t.key}:${g}`,onclick:()=>s.changeSetting(t.key,g)},m)))))}if(t.kind==="choice"){const p=t.stops.map(g=>[g.label.toLowerCase(),g.label]);return r(n("div.controlhead",null,We(n("span.label",{tabindex:"0"},t.label),t.help),n("div.seg",null,...p.map(([g,m])=>n("button",{"aria-pressed":String(a===g),"data-ctl":`${t.key}:${g}`,onclick:()=>s.changeSetting(t.key,g)},m)))))}const l=p=>Math.pow((Number(p)-t.min)/(t.max-t.min||1),1/t.curve)*1e3,c=p=>t.min+Math.pow(p/1e3,t.curve)*(t.max-t.min),u=p=>t.decimals===0?String(Math.round(p)):p.toFixed(t.decimals),d=n("input.numberfield",{type:"text",value:u(Number(a)),inputmode:"decimal","aria-label":`${t.label}${t.unit?` in ${t.unit}`:""}`,"data-ctl":`${t.key}:field`,onchange:p=>{const g=Number(p.target.value);Number.isFinite(g)&&s.changeSetting(t.key,Math.min(t.max,Math.max(t.min,g)))}}),h=p=>{const g=c(Number(p.value));return t.decimals===0?Math.round(g):Number(g.toFixed(t.decimals))},f=n("input.slider",{type:"range",min:"0",max:"1000",step:"1",value:String(Math.round(l(Number(a)))),"aria-label":t.label,"aria-valuetext":`${u(Number(a))} ${t.unit}`.trim(),"data-ctl":`${t.key}:slider`,oninput:p=>{const g=p.target,m=h(g);d.value=u(m),g.setAttribute("aria-valuetext",`${u(m)} ${t.unit}`.trim())},onchange:p=>s.changeSetting(t.key,h(p.target))});return r(n("div.controlhead",null,We(n("span.label",{tabindex:"0"},t.label),t.help),t.unit?n("span.unit",null,t.unit):null,d),f,t.stops.length?n("div.stops",null,...t.stops.map(p=>n("span",null,p.label))):null)}const dn={axisHandles:.34,smoothJoins:.89,alignedNodes:.86};function _i(){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 Bi(){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 Ri(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 ${mt(t,t.tracingTier)??"—"} px`),n("span.muted.num",{style:{fontSize:"11px"},"data-live":"elapsed"},en(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?Rn(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 Gi(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,ue(o?.meanDe00)),n("div.of",null,n("span.dim",{style:{fontSize:"11.5px"}},"mean colour difference"),n("span.muted.num",{style:{fontSize:"11px"}},`median ${ue(o?.medianDe00)} · dE00`))),n("div",{style:{display:"flex",flexDirection:"column",gap:"5px"}},n("div.meter",{role:"img","aria-label":`Colour difference ${ue(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,...Wi(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 · ${ue(s.worstCorner.de00)} dE00 →`):null)}function Wi(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",le(t.bytes)],["minified",t.minifiedBytes===null?"already":le(t.minifiedBytes)],["traced at",`${t.tracedPx} px`],["time taken",gt(t.seconds)]]:[["coordinates","—"],["paths","—"],["colours found","—"],["segments","—"],["file size","—"],["minified","—"]]}function qi(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:dn.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:dn.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:dn.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 u=c.part/c.whole;return n("div.srow",{title:c.help},n("span.k",null,c.label),n("span.v",null,Te(u),n("span.of",null,`${c.part} of ${c.whole}`)),n("div.sbar",{role:"img","aria-label":`${c.label}: ${Te(u)}, ${c.part} of ${c.whole}; hand-drawn files ${Te(c.artist)}`},n("i",{style:{width:`${(u*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 Hi(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=>Ui(o,s(o.kind))),n("div.nothinglost",null,n("span.glyph",null,V("checkCircle",15)),n("span",null,t.losses.length?"Nothing else we can detect was lost.":"Nothing we can detect was lost.")))}function Ui(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,V(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 Jn=new Set(["mode","cleanUpDamage","editability"]);function ji(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 u=()=>{const p=e.state,g=document.activeElement instanceof HTMLElement&&a.contains(document.activeElement)?document.activeElement.getAttribute("data-ctl"):null;l[c]=a.scrollTop,D(a,...p.railTab==="result"?[p.tracing?Ri(e):null,...er(e,t)]:Zi(e,t)),c=p.railTab,a.scrollTop=l[c]??0,g&&a.querySelector(`[data-ctl="${g}"]`)?.focus({preventScroll:!0})},d=()=>{const p=e.state,g=i.querySelector(".readout.busy");if(p.tracing&&g){g.querySelector(".stage").textContent=Qt(p);return}D(i,...sr(e,t))},h=()=>{const p=document.activeElement instanceof HTMLElement&&o.contains(document.activeElement)?document.activeElement.getAttribute("data-ctl"):null;D(o,...Vi(e,t)),p&&o.querySelector(`[data-ctl="${p}"]`)?.focus({preventScroll:!0})},f=()=>{D(s,...Ki(e,t)),h(),u(),d()};return e.on(["caps","prefs","preset","settings","railTab","groupsOpen"],f),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"&&u(),d();const p=s.querySelector('[data-note="result"]');p&&(p.textContent=zo(e))}),ko(a,e),f(),r}const Ps={quality:"closest fit",balanced:"fast, then refined",fast:"one pass"};function Vi(e,t){const s=e.state,o=k=>s.caps?.controls.find(E=>E.key===k)?.help??"",a=s.settings.mode??"quality",i=n("div.mode.mode-engine",null,n("div.modehead",null,We(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. Balanced is Fast with a few steps of Quality's edge placement and a finer fit: closer than Fast on small images, for about twice its time; above 1024 px it is Fast. Fast traces each shape in a single pass, many times quicker, for previews, batches and very large images."),n("span.modestate",null,Ps[a]??Ps.quality)),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==="balanced"),"data-ctl":"mode:balanced",title:"Balanced: Fast with a few steps of Quality's edge placement and a finer fit; closer than Fast on small images, about twice its time",onclick:()=>t.changeSetting("mode","balanced")},"Balanced"),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,u=c&&l!=="off"&&s.denoiserFetch?.phase!=="ready"?s.denoiserFetch:void 0,d=u!==void 0,h=!d&&c&&r!==void 0&&!r.installed&&l!=="off",f={off:"pixels as they are",auto:"only if damaged",on:"always"},p=n("div.mode",null,n("div.modehead",null,We(n("span.modetitle",{tabindex:"0"},"Denoiser"),o("cleanUpDamage")),d&&u?.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?f[l]:"needs its own tab")),n("div.seg.big",{role:"group","aria-label":"Denoiser"},...["off","auto","on"].map(k=>n("button",{"aria-pressed":String(l===k),disabled:!c,"data-ctl":`cleanUpDamage:${k}`,onclick:()=>t.changeSetting("cleanUpDamage",k)},k==="off"?"Off":k==="auto"?"Auto":"On"))),d?n("div.fetchnote",{"data-ctl":"denoiser-progress",role:"status"},Ut(u??null),n("span.fetchline",null,u?.phase==="failed"?`${Ht(u,!0)}: ${u.message??"the connection dropped"}.`:Zn(Ht(u??null,!0)??"")),n("span.muted",null,u?.phase==="failed"?"The trace shown is without it.":"Shown without it until then; it traces again by itself.")):null),g=s.settings.editability,m=n("div.mode",null,n("div.modehead",null,We(n("span.modetitle",{tabindex:"0"},"Editable"),o("editability"))),n("button.bigswitch",{role:"switch","aria-checked":String(g),"aria-label":"Editable structure","data-ctl":"editability:switch",onclick:()=>t.changeSetting("editability",!g)},n("span.knob"),n("span.state",null,g?"On":"Off")));return[i,n("div.railmodes-row",null,p,m)]}function Lo(e,t){const s=t.baseSettings(),o=e.state;return(o.caps?.controls??[]).filter(a=>vt(a,o.settings)&&o.settings[a.key]!==s[a.key]).length}function zo(e){const t=e.state.report;return t?`${ue(t.meanDe00)} dE00`:""}function Ki(e,t){const s=e.state,o=Lo(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",zo(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 Yi(e,t){const s=e.state;if(!_n(s.settings.mode))return null;const o=(s.caps?.controls??[]).filter(a=>!Jn.has(a.key)&&!vt(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 Zi(e,t){return[Ji(e,t),Yi(e,t),Xi(e,t),...nr(e,t)]}function Ji(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 Xi(e,t){const s=e.state,o=s.caps?.presets??[],a=s.prefs?.saved??[],i=co(s.caps?.platform),r=(d,h,f,p,g)=>n(g?"button.preset.saved":"button.preset",{"aria-pressed":String(s.preset===d),title:`${h} — ${f}${p!==null?` (${i}+${p})`:""}`,onclick:()=>t.setPreset(d)},n("span.name",null,h),g?n("span.forget",{role:"button",tabindex:"0","aria-label":`Forget ${h}`,title:`Forget ${h}`,onclick:m=>{m.stopPropagation(),g()}}):null),l=o.find(d=>d.id===s.preset),c=l?`${l.subtitle}.`:a.find(d=>d.id===s.preset)?"One of your saved presets.":"Controls moved from the preset they started at.",u=Lo(e,t);return n("div.presetblock",null,n("div.cardhead",null,n("span.eyebrow",null,"Preset"),n("button.reset",{disabled:!s.caps||!u,title:"Put every control back to where the preset had it",onclick:()=>s.preset&&t.setPreset(s.preset)},u?`Reset ${u} changed`:"Nothing changed")),n("div.presets",{role:"group","aria-label":"Presets"},...o.map((d,h)=>r(d.id,d.name,d.subtitle,h<10?(h+1)%10:null,null)),...a.map(d=>r(d.id,d.name,"Saved preset",null,()=>t.deletePreset(d.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:()=>Qi(e,t)},"Save as preset")))}function Qi(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)),he()};ot(nn("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:he},"Cancel"),n("button.btn.primary",{onclick:a},"Save")])),o.focus(),o.addEventListener("keydown",i=>{i.key==="Enter"&&a()})}function er(e,t){return e.state.source?[tr(e,t),Gi(e,t),qi(e,t),Hi(e),So(e,t),Bi()]:[_i()]}function tr(e,t){const s=e.state;if(s.chooser||s.wizard||s.autoNoteHidden||!Oi(e))return null;const o=To(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 nr(e,t){const s=e.state,o=(s.caps?.controls??[]).filter(r=>!Jn.has(r.key)&&vt(r,s.settings)),a=t.baseSettings();return[...new Set(o.map(r=>r.group))].map(r=>{const l=o.filter(d=>d.group===r),c=s.groupsOpen[r]!==!1,u=l.filter(d=>s.settings[d.key]!==a[d.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")}},V(c?"chevronDown":"chevronRight",13),n("span.eyebrow",null,r),u?n("span.changed",null,`${u} changed`):null),u?n("button.reset",{onclick:()=>t.resetGroup(r)},"Reset"):null),c?n("div.groupbody",null,...l.map(d=>Po(e,d,t,a))):null)})}function sr(e,t){const s=e.state,o=!!s.svg&&!s.tracing;return[or(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"},V("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 or(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,Qt(s)),n("span.muted.num.elapsed",{"data-live":"elapsed"},en(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=(d,h,f)=>n("div.cell",null,n("span.v.num",null,d),n("span.k",null,h),f),r=s.settings.mode??"quality",l=_n(r),c=Mn(r),u=n("div.readout-badge-row",null,n("button.engine-pill"+(l?".fast":".quality"),{type:"button",title:l?`${c} vectorizer active. Click to switch to Quality mode.`:"Quality vectorizer active. Click to switch to Fast mode.",onclick:()=>t.changeSetting("mode",l?"quality":"fast")},n("span.engine-name",null,c)),o.seconds!=null?n("span.time-pill.num",{title:`Traced in ${gt(o.seconds)}`},gt(o.seconds)):null);return n("div.readout",{"aria-live":"polite"},u,n("div.readout-grid",null,i(ue(o.meanDe00),"dE00",a?pn(o.meanDe00,a.meanDe00,d=>d.toFixed(2),.005,!0):null),i(oe(o.coordinates),"coordinates",a?pn(o.coordinates,a.coordinates,oe,0,!1):null),i(le(o.bytes),"file",a?pn(o.bytes,a.bytes,le,16,!1):null)))}function pn(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 ar="https://logolabs.github.io/inkvec/";function ir(e,t,s){const o=n("div.chooser",{role:"region","aria-label":"How to trace this image"});let a=!1;const i=u=>{const d=e.state;a&&t.setOnOpen(u),d.prefs?.seenFirstRun||t.markSeen(),e.set({chooser:!1}),u==="custom"&&s()},r=()=>{const u=e.state;if(u.tracing&&u.generation===u.auto?.generation)return`Tracing now · ${Qt(u)} · ${en(u)}`;const d=u.auto?.report;return d?`Done · ${ue(d.meanDe00)} dE00 · ${oe(d.coordinates)} coordinates · ${le(d.bytes)}`:u.stageState.kind!=="drawing"&&u.stageState.kind!=="decoding"?"Stopped; the stage says why":"Starting…"},l=()=>{const u=e.state,d=u.chooser&&!!u.source&&u.tab==="vectorize"&&!u.wizard&&!u.screen;if(o.hidden=!d,!d){D(o);return}const h=!u.prefs?.seenFirstRun,f=To(e,t);D(o,n("div.choosehead",null,n("span.eyebrow",null,`Just opened · ${u.source?.name??""}`),n("button.btn.icon.compact.ghost",{"aria-label":"Close; Auto stands",title:"Close. Auto stands (Esc)",onclick:()=>i("auto")},V("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,V("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,V("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…"))),f?n("div.choosenews",null,n("span.eyebrow",null,"Auto chose"),f):null,n("label.remember",null,n("input",{type:"checkbox",checked:a,onchange:p=>{a=p.target.checked}}),n("span",null,"Remember my choice"),n("span.muted",null,"· change it in Settings")))},c=()=>{const u=o.querySelector("[data-status]");u&&(u.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 Ct={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"]}},Ls={detail:["precision","speckleFloor"],shape:["editability","fewerPaths","matchRepeatedShapes","bezierCost","cornerAngle"],output:["transparentBackground","margin","minify"]},rr=[{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 lr(e){const t=e.state,s=!!t.caps?.denoiser.supported&&Eo(t.source,t.facts);return["kind","cleanup","colours","detail","shape","output"].filter(o=>o!=="cleanup"||s)}function cr(e,t,s,o){let a=null,i=[],r=null,l=e.state.view,c="auto",u=null,d=null;const h=n("div.wizhead"),f=n("div.wizintro"),p=n("div.wizcontrols"),g=n("div.wizlive"),m=n("div.wizguide"),k=n("div.wizscroll",null,f,p,g,m),E=n("div.wiznav");ko(g,e);const x=()=>lr(e),$=()=>e.state.wizard?.step??"kind",y=S=>{S!==$()&&($()==="kind"&&o.pause(),e.set({wizard:{step:S},hoverFill:null}),k.scrollTop=0)},v=()=>{const S=e.state.auto?.svg;return c!=="auto"||!S?null:(u?.svg!==S&&(u={svg:S,label:"Auto"}),u)},M=()=>e.set({compare:v()}),A=()=>{const S=e.state.auto?.svg;return S?(d?.svg!==S&&(d&&URL.revokeObjectURL(d.url),d={svg:S,url:URL.createObjectURL(new Blob([S],{type:"image/svg+xml"}))}),d.url):null},T=()=>{const S=x(),F=Math.max(0,S.indexOf($())),C=Ct[$()],Y=r!==null&&(e.state.preset!==r.preset||Ge(e.state.caps,e.state.settings,r.settings).length>0||JSON.stringify(e.state.colourGroups)!==JSON.stringify(r.colourGroups));D(h,n("div.cardhead",null,n("span.eyebrow",null,`Custom · step ${F+1} of ${S.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:()=>Pe(!0)},"Close"))),n("h2.wiztitle.serif",null,C.title),n("div.wizdots",{role:"tablist","aria-label":"Steps"},...S.map((X,I)=>n("button",{role:"tab","aria-selected":String(X===$()),"aria-label":`Step ${I+1}: ${Ct[X].title}`,title:Ct[X].title,class:Iy(X)}))),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",M(),T()}},"Auto's trace"),n("button",{"aria-pressed":String(c==="source"||!e.state.auto?.svg),onclick:()=>{c="source",M(),T()}},"Source"))))},O=()=>{const S=Ct[$()];D(f,n("p.wizlead",null,S.does)),D(m,n("dl.wizexplain",null,n("dt",null,"Change it when"),n("dd",null,S.when),n("dt",null,"What it costs"),n("dd",null,S.costs)),n("a.wizlearn",{href:ar+S.learn[1]},`Learn more: ${S.learn[0]} →`))},Z=()=>{const S=x(),F=S.indexOf($()),C=F===S.length-1;D(E,n("button.btn",{"data-ctl":"wiz-back",disabled:F<=0,onclick:()=>y(S[F-1])},"Back"),C?null:n("button.reset",{"data-ctl":"wiz-skip",title:"Keep what is chosen so far and go to the last step",onclick:()=>y(S[S.length-1])},"Skip to finish"),n("span",{style:{flex:"1"}}),C?n("button.btn",{"data-ctl":"wiz-export",disabled:!e.state.svg,onclick:()=>Pe(!0,!0)},"Export now"):null,C?n("button.btn.primary",{"data-ctl":"wiz-finish",onclick:()=>Pe(!0)},"Finish"):n("button.btn.primary",{"data-ctl":"wiz-next",onclick:()=>y(S[F+1])},"Next"))},te=()=>{if(!a)return;const S=x();if(!S.includes($())){e.set({wizard:{step:S[Math.min(S.length-1,1)]}});return}T(),O(),Z(),Q(),K()},P=S=>e.state.caps?.controls.find(F=>F.key===S),L=S=>{const F=s.baseSettings();return S.map(C=>P(C)).filter(C=>!!C&&vt(C,e.state.settings)).map(C=>Po(e,C,s,F))},Q=()=>{const S=document.activeElement instanceof HTMLElement&&p.contains(document.activeElement)?document.activeElement.getAttribute("data-ctl"):null,F=e.state,C=$();let Y=[];C==="cleanup"?Y=W():C==="colours"?Y=L(F.facts?.hasAlpha?["maxColours","traceTransparency"]:["maxColours"]):C==="detail"?Y=[U(),...L(Ls.detail??[])]:(C==="shape"||C==="output")&&(Y=L(Ls[C]??[])),D(p,...Y),p.hidden=Y.length===0,S&&p.querySelector(`[data-ctl="${S}"]`)?.focus({preventScroll:!0}),a&&T()},U=()=>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(S=>n("button",{"aria-pressed":String(e.state.view===S),"data-ctl":`wiz-view:${S}`,onclick:()=>e.set({view:S})},S==="side"?"Side by side":"Wipe")))),W=()=>{const S=e.state,F=S.caps?.denoiser,C=S.settings.cleanUpDamage,Y=[S.source?.lossy?`the file is a ${S.source.container}, which stores colour approximately`:null,S.facts&&S.facts.noiseLevels>=Mo?`its pixels measure ${S.facts.noiseLevels.toFixed(1)} levels of noise (a clean file reads about 0.5)`:null].filter(Boolean),X=(I,j,re)=>n("button.wizchoice",{"aria-pressed":String(C===I||I==="auto"&&C==="on"),"data-ctl":`wiz-cleanup:${I}`,onclick:()=>s.changeSetting("cleanUpDamage",I)},n("span.name",null,j),n("span.muted",null,re));return[Y.length?n("p.wizwhy",null,`Offered because ${Y.join(", and ")}.`):null,n("div.wizchoices",null,X("off","Off","Trace the pixels as they are, damage and all."),X("auto","Auto","Clean the damage first, where it is found.")),F?.supported&&C!=="off"&&S.denoiserFetch?.phase!=="ready"?n("div.wiznote",{"data-ctl":"wiz-denoiser-progress"},Ut(S.denoiserFetch),n("span",null,`${Zn(Ht(S.denoiserFetch,!0)??"Starting the denoiser…")} It comes from ${F.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(I=>I!==null)};let ee=null;const K=()=>{const S=$();if(S==="kind"){Ie();return}ee=null;const F=e.state;S==="colours"?D(g,F.palette.length?So(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.")):S==="detail"||S==="shape"?D(g,ae()):S==="output"?D(g,ie(),ae()):D(g)},ae=()=>{const S=e.state,F=S.auto?.report??null,C=S.report,Y=S.result?.tier==="draft",X=j=>j?j.structure?.nodes||Math.round(j.coordinates/2):null,I=(j,re,we,se,je=!0,Ve=0)=>{const ve=re!==null&&we!==null&&!Y&&!S.tracing?we-re:null,w=ve===null||Math.abs(ve)<=Ve?"same":ve<0===je?"better":"worse";return n("tr",null,n("th",null,j),n("td.num",null,re===null?"—":se(re)),n("td.num",null,we===null?"—":se(we)),n(`td.num.delta.${w}`,null,ve===null?"":Math.abs(ve)<=Ve?"same":`${ve<0?"−":"+"}${se(Math.abs(ve))}`))};return n("div.card.wizreadout",{"aria-live":"polite"},n("div.cardhead",null,n("span.eyebrow",null,"Auto · yours"),n("span.muted",{style:{fontSize:"11px"}},S.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,I("colour difference",F?.meanDe00??null,C?.meanDe00??null,j=>j.toFixed(2),!0,.005),I("nodes",X(F),X(C),oe),I("paths",F?.paths??null,C?.paths??null,oe),I("file size",F?.bytes??null,C?.bytes??null,le,!0,16))),n("span.muted.wizsmall",null,"Colour difference in dE00: under 1 is invisible side by side."))},ie=()=>{const S=e.state,F=S.auto,C=F?Ge(S.caps,F.settings,S.settings):[],Y=(I,j)=>typeof j=="boolean"?j?"on":"off":typeof j=="number"?`${I.decimals?j.toFixed(I.decimals):Math.round(j)}${I.unit?` ${I.unit}`:""}`:String(j),X=S.caps?.presets.find(I=>I.id===S.preset);return n("div.card.wizchanges",null,n("span.eyebrow",null,"What you changed from Auto"),X&&F&&X.id!==F.preset?n("div.wizchange",null,n("span",null,"Preset"),n("span.num",null,X.name)):null,...C.map(I=>{const j=P(I);return!j||!F?null:n("div.wizchange",null,n("span",null,j.label),n("span.num",null,`${Y(j,F.settings[I])} → ${Y(j,S.settings[I])}`))}),S.colourGroups.length?n("div.wizchange",null,n("span",null,"Colour groups"),n("span.num",null,String(S.colourGroups.length))):null,!C.length&&!S.colourGroups.length&&!(X&&F&&X.id!==F.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."))},ne=()=>{const S=e.state,F=S.auto,C=S.caps,Y=new Set(Kn(e).map(se=>se.preset)),X=S.colourGroups.length?{colourGroups:S.colourGroups}:{},I=F?Re({...F.settings,...X}):null,j=[];let re=null;for(const se of rr){const je=C?.presets.find(ve=>ve.id===se.preset);if(!je)continue;const Ve={...je.settings,...X};if(I&&Re(Ve)===I&&se.preset!=="photo-or-scan"&&re===null){re=se.name;continue}j.push({id:se.preset,name:se.name,why:se.why,note:se.note,settings:Ve,preset:se.preset,suggested:Y.has(se.preset)})}return[{id:"auto",name:re?`${re} · Auto`:"As Auto traced",why:F?`Auto used ${Ao(C,F.settings,F.preset)}.`:"Auto's trace.",settings:null,preset:null,suggested:!1},...j.filter(se=>se.suggested),...j.filter(se=>!se.suggested)]},ce=S=>{const F=e.state;if(S.id==="auto"){const C=F.auto;return!!C&&F.preset===C?.preset&&Ge(F.caps,F.settings,C.settings).length===0}return F.preset===S.preset},Fe=S=>{S.id==="auto"?s.backToAuto():S.preset&&s.setPreset(S.preset)},ye=S=>{if(!S){M();return}const F=e.state;if(S.id==="auto"||Ne(S)){F.auto?.svg&&e.set({compare:v()??{svg:F.auto.svg,label:"Auto"}});return}const C=S.settings?o.get(Re(S.settings)):void 0;C?.state==="done"&&e.set({compare:{svg:C.svg,label:`Preview · ${S.name} (draft)`}})},Ne=S=>{const F=e.state.auto;if(!F||!S.settings)return!1;const C=e.state.colourGroups.length?{colourGroups:e.state.colourGroups}:{};return Re(S.settings)===Re({...F.settings,...C})},_e=S=>{const F=e.state;if(S.id==="auto"||Ne(S)){const Y=F.auto?.report;return Y?{url:A(),text:`${ue(Y.meanDe00)} dE00 · ${oe(Y.paths)} paths · ${le(Y.bytes)}`,busy:!1}:F.tracing?{url:null,text:"tracing…",busy:!0}:{url:null,text:"no trace yet",busy:!1}}const C=S.settings?o.get(Re(S.settings)):void 0;return!C||C.state==="queued"?{url:null,text:F.auto?.report?"queued":"after Auto",busy:!0}:C.state==="running"?{url:null,text:"tracing a draft…",busy:!0}:C.state==="failed"?{url:null,text:"no preview",busy:!1}:{url:C.url,text:`${ue(C.report.meanDe00)} dE00 · ${oe(C.report.paths)} paths · ${le(C.report.bytes)}`,busy:!1}},Ie=()=>{const S=ne(),F=S.map(I=>I.id).join(",");if(!ee||ee.dataset.signature!==F){ee=n("div.kinds",{"data-signature":F,role:"radiogroup","aria-label":"What is it?"});for(const I of S){const j=n("button.kind",{role:"radio","data-key":I.id,"data-ctl":`wiz-kind:${I.id}`,onclick:()=>Fe(I),onpointerenter:()=>ye(I),onpointerleave:()=>ye(null),onfocus:()=>ye(I),onblur:()=>ye(null)},n("span.kthumb",null,n("img",{alt:"",draggable:"false"}),n("span.kspin")),n("span.kname",null,I.name,I.suggested?n("span.ksugg",null,"suggested"):null),n("span.kwhy",null,I.why),I.note?n("span.knote",null,I.note):null,n("span.kstat.num"));ee.append(j)}D(g,ee,n("p.muted.wizsmall.kqueue"))}const C=e.state;C.auto&&!C.auto.report&&C.tracing||o.want(S.filter(I=>I.settings&&!Ne(I)).map(I=>({key:Re(I.settings),settings:I.settings})));for(const I of S){const j=ee.querySelector(`[data-key="${I.id}"]`);if(!j)continue;const re=_e(I),we=j.querySelector("img");re.url&&we.getAttribute("src")!==re.url&&(we.src=re.url),we.hidden=!re.url,j.classList.toggle("busy",re.busy),j.setAttribute("aria-checked",String(ce(I))),j.querySelector(".kstat").textContent=re.text}const Y=o.pending(),X=g.querySelector(".kqueue");X&&(X.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 fe(){if(a)return;if(!e.state.auto&&e.state.svg&&e.state.report&&!e.state.tracing){const C=e.state;e.set({auto:{generation:C.resultGeneration,settings:{...C.settings},preset:C.preset,svg:C.svg,report:C.report,palette:C.palette,losses:C.losses}})}const S=e.state;r={settings:{...S.settings},preset:S.preset,colourGroups:[...S.colourGroups]},l=S.view,c="auto",ee=null;const F=n("div.sheetbody.wizbody",null,h,k,E);a=n("div.sheet.wizsheet",null,F),t.classList.add("wizarding"),t.append(a),e.set({wizard:{step:x()[0]},chooser:!1,view:S.view==="ab"?"side":S.view,compare:v()}),S.prefs?.seenFirstRun||s.markSeen(),i=[e.on(["wizard","facts","caps","prefs"],te),e.on(["denoiserFetch"],()=>{a&&$()==="cleanup"&&Q()}),e.on(["settings","preset","colourGroups","view"],()=>{Q(),($()==="kind"||$()==="output")&&K()}),e.on(["report","result","tracing","palette","groupSuggestions","resultGroups","paletteSelection","simplifyTo","losses","auto","svg"],()=>{K(),(e.state.compare===null||e.state.compare===u||e.state.compare?.label==="Auto")&&M(),Z()}),o.on(()=>{$()==="kind"&&K()}),e.on(["auto"],T)],te(),(E.querySelector("[data-ctl=wiz-next]")??F).focus?.()}function Pe(S=!1,F=!1){if(!a)return;for(const I of i)I();i=[],o.pause(),a.remove(),a=null,ee=null,t.classList.remove("wizarding"),d&&URL.revokeObjectURL(d.url),d=null,u=null;const C=e.state,Y=r!==null&&(C.preset!==r.preset||Ge(C.caps,C.settings,r.settings).length>0||JSON.stringify(C.colourGroups)!==JSON.stringify(r.colourGroups)),X=C.auto?Ge(C.caps,C.auto.settings,C.settings).length:0;e.set({wizard:null,compare:null,hoverFill:null,view:l,railTab:Y?"tune":C.railTab}),r=null,S&&Y&&G(X?`Custom settings kept: ${X} control${X===1?"":"s"} changed from Auto, all of them under Tune.`:"Custom settings kept. Everything is under Tune.",{kind:"good"}),F&&s.openExport()}return{open:fe,close:Pe,isOpen:()=>a!==null}}const zs=4,Ds=24;function ur(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 v=await Ns(e,t);if(y!==i)return;s.src=v.toDataURL("image/png")}catch{y===i&&G("The card could not be drawn.",{kind:"bad"})}finally{y===i&&o.classList.remove("busy")}},l=()=>{const[y,v]=Do(t.size);o.style.setProperty("--ar",String(y/v)),a.textContent=`${y} × ${v} px · PNG · ${t.theme}`},c=[],u=()=>{c.forEach(y=>y()),l(),r()},d=(y,v,M)=>{const A=M.map(T=>n("button",{type:"button",role:"radio",onclick:()=>{t[y]=T.value,u()}},n("span",null,T.label),T.sub?n("small",null,T.sub):null));return c.push(()=>A.forEach((T,O)=>T.setAttribute("aria-checked",String(t[y]===M[O].value)))),Fs(v,n("div.cardseg",{role:"radiogroup","aria-label":v},...A))},h=(y,v,M)=>{const A=n("span.switch",{"aria-hidden":"true"}),T=n("button.optrow",{type:"button",role:"switch",onclick:()=>{t[y]=!t[y],u()}},n("span.optext",null,n("span.optlabel",null,v),n("span.optnote",null,M)),A);return c.push(()=>{T.setAttribute("aria-checked",String(t[y]))}),T},f=n("span.num",null,`${t.zoom}×`),p=n("input.slider",{type:"range",min:String(zs),max:String(Ds),step:"1",value:String(t.zoom),"aria-label":"Detail crop magnification",oninput:y=>{f.textContent=`${y.target.value}×`},onchange:y=>{t.zoom=Number(y.target.value),u()}}),g=n("div.optslider",null,n("div.optsliderhead",null,n("span",null,"Detail magnification"),f),p,n("div.stops",null,n("span",null,`${zs}×`),n("span",null,`${Ds}×`)));c.push(()=>{p.disabled=!t.detail,g.classList.toggle("off",!t.detail)});const m=(y,v,M)=>async()=>{const A=y.lastChild?.textContent??"";y.disabled=!0,y.lastChild&&(y.lastChild.textContent=v);try{await M()}finally{y.disabled=!1,y.lastChild&&(y.lastChild.textContent=A)}},k=async()=>{const y=await Ns(e,t);return new Promise(v=>y.toBlob(v,"image/png"))},E=n("button.btn",{type:"button"},V("copy",15),n("span",null,"Copy image"));E.onclick=m(E,"Copying…",async()=>{const y=await k();if(y)try{await navigator.clipboard.write([new ClipboardItem({"image/png":y})]),G("Card copied.",{kind:"good"})}catch{G("This system would not take an image on the clipboard; save it instead.",{kind:"bad"})}});const x=n("button.btn.primary",{type:"button"},V("download",15),n("span",null,"Download PNG"));x.onclick=m(x,"Saving…",async()=>{const y=e.state.source?.name.replace(/\.[^.]+$/,"")??"inkvec",v=await k();if(!v)return;const M=await ho(`${y}-card.png`,new Uint8Array(await v.arrayBuffer()),[{name:"PNG",extensions:["png"]}]);M&&(he(),G(M.path?`Card saved to ${M.path}`:`Downloaded ${M.name}`,{kind:"good",action:Xt(M.path)}))});const $=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:he},V("x",16))),n("div.composerbody",null,n("div.composerpreview",null,o,a),n("div.composerside",null,d("size","Format",[{value:"1600x900",label:"Landscape",sub:"1600 × 900"},{value:"1080x1080",label:"Square",sub:"1080 × 1080"}]),d("theme","Appearance",[{value:"dark",label:"Dark"},{value:"light",label:"Light"}]),Fs("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."),g,h("fileName","The file name","Off by default, so unreleased work stays private."))),n("div.composeractions",null,x,E))));ot($),u()}function Fs(e,t){return n("div.optgroup",null,n("span.eyebrow",null,e),t)}function Do(e){return e==="1600x900"?[1600,900]:[1080,1080]}async function Ns(e,t){const[s,o]=Do(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",u=r?"rgba(250,248,245,.4)":"rgba(26,24,22,.46)",d=r?"rgba(250,248,245,.55)":"rgba(26,24,22,.58)",h=r?"#c9754a":"#9c5730",f=r?"#26231f":"#ffffff",p=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 g=i.createRadialGradient(s*.2,-o*.1,0,s*.2,-o*.1,s*.6);g.addColorStop(0,"rgba(201,117,74,.16)"),g.addColorStop(1,"rgba(201,117,74,0)"),i.fillStyle=g,i.fillRect(0,0,s,o);const m=Math.round(s*.042),k=t.size==="1080x1080",E=k?s-m*2:Math.round(s*.46);i.fillStyle=u,i.font=`500 ${Math.round(s*.0105)}px "Inter Studio", Inter, sans-serif`,i.letterSpacing="3px",i.fillText("PNG TRACED TO SVG",m,m+14),i.letterSpacing="0px",i.fillStyle=c;const x=Math.round(s*(k?.05:.039));i.font=`500 ${x}px "Playfair Studio", Georgia, serif`,i.fillText("A logo, measured",m,m+x+26),i.fillText("rather than guessed.",m,m+x*2+30);let $=m+x*2+96;t.fileName&&e.state.source&&(i.fillStyle=d,i.font=`400 ${Math.round(s*.013)}px "Inter Studio", Inter, sans-serif`,i.fillText(e.state.source.name,m,$),$+=40);const y=e.state.report;if(t.numbers&&y){const U=[[ue(y.meanDe00),"mean dE00"],[oe(y.coordinates),"coordinates"],[`${(y.bytes/1024).toFixed(1)} KB`,"SVG"]];let W=m;for(const[ee,K]of U.slice(0,k?2:3)){i.fillStyle=ee===ue(y.meanDe00)?h:c;const ae=Math.round(s*.026);i.font=`500 ${ae}px "Playfair Studio", Georgia, serif`,i.fillText(ee,W,$+ae),i.fillStyle=u,i.font=`400 ${Math.round(s*.0085)}px "Inter Studio", Inter, sans-serif`,i.fillText(K,W,$+ae+20),W+=Math.max(i.measureText(K).width,120)+Math.round(s*.05)}}i.fillStyle=d,i.font=`400 ${Math.round(s*.0095)}px "Inter Studio", Inter, sans-serif`,i.fillText("traced with Inkvec Studio · by LogoLabs",m,o-m);const v=k?m:m+E,M=s-v-m,A=k?Math.round(o*.42):m,T=Math.round(k?o*.3:o*.44),O=(U,W,ee,K)=>{for(let ie=0;ie*14{i.save(),i.beginPath(),i.rect(W,A,Z,T),i.clip(),O(W,A,Z,T);const ae=await hn(U),ie=Math.min((Z-40)/ae.width,(T-40)/ae.height),ne=ae.width*ie,ce=ae.height*ie;i.drawImage(ae,W+(Z-ne)/2,A+(T-ce)/2,ne,ce),i.restore(),i.fillStyle=K,i.font=`500 ${Math.round(s*.0085)}px "Inter Studio", Inter, sans-serif`,i.letterSpacing="2px",i.fillText(ee,W+12,A+22),i.letterSpacing="0px"};if(te&&await Q(te.preview,v,te.container.toUpperCase(),u),L&&await Q(L.url,v+Z+12,"SVG",h),t.detail&&te&&L){const U=A+T+14,W=o-U-m-40;if(W>60){const ee=e.state.worstCorner,K=t.zoom,ae=ee?.x??L.w/2,ie=ee?.y??L.h/2,ne=v+M/2-ae*K,ce=U+W/2-ie*K;i.save(),i.beginPath(),i.rect(v,U,M,W),i.clip(),i.fillStyle=r?"#141210":"#f0ece6",i.fillRect(v,U,M,W);const Fe=await hn(te.preview);i.imageSmoothingEnabled=!1,i.drawImage(Fe,ne,ce,L.w*K,L.h*K),i.imageSmoothingEnabled=!0,i.strokeStyle=r?"rgba(250,248,245,.09)":"rgba(26,24,22,.12)",i.lineWidth=1;const ye=(ne%K+K)%K,Ne=(ce%K+K)%K;for(let fe=v+ye;fe0&&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 hn(e){return new Promise((t,s)=>{const o=new Image;o.onload=()=>t(o),o.onerror=s,o.src=e})}const Is=[512,1024,2048];let ct=null;function Os(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 Fo(e,t,s){if(ct?.isConnected){ct.querySelector(".format")?.focus();return}const o=Ra({svg:!0,svgMinified:!0,pngSizes:[...Is],favicon:!1,assetPack:!0});let a=[],i=e.state.prefs?.outputFolder??null;const r=n("div.sheetbody",{role:"dialog","aria-modal":"true","aria-labelledby":"export-title"}),l=n("div.sheet");l.append(r);let c=()=>{};const u=()=>{c(),l.remove(),ct===l&&(ct=null),e.set({exportOpen:!1})},d=g=>a.filter(m=>m.group===g).reduce((m,k)=>m+k.bytes,0),h=async()=>{An({export:{...o,pngSizes:[...o.pngSizes]}});const g=Os(e,o);if(g){try{a=await _.planExport(g)}catch(m){a=[],G(String(m),{kind:"bad"})}p()}},f=(g,m,k,E)=>n("button.format",{onclick:E,role:"checkbox","aria-checked":String(k),"data-ctl":`format:${m}`},n("span.checkbox",{"aria-hidden":"true"},V("check",11)),n("span",null,g),n("span.size",null,k?le(d(m)):"—"));function p(){const g=a.reduce((k,E)=>k+E.bytes,0),m=document.activeElement instanceof HTMLElement&&r.contains(document.activeElement)?document.activeElement.getAttribute("data-ctl"):null;D(r,n("div.cardhead",null,n("h2.serif",{id:"export-title",style:{fontSize:"19px",fontWeight:"400",margin:"0"}},"Export"),n("button.reset",{onclick:u,"data-ctl":"export-close"},"Close")),f("SVG","svg",o.svg,()=>{o.svg=!o.svg,h()}),f("SVG (minified)","svgMinified",o.svgMinified,()=>{o.svgMinified=!o.svgMinified,h()}),f("PNG · 512 / 1024 / 2048","png",o.pngSizes.length>0,()=>{o.pngSizes=o.pngSizes.length?[]:[...Is],h()}),f("ICO and favicon set","favicon",o.favicon,()=>{o.favicon=!o.favicon,h()}),f("Asset pack (.zip)","assetPack",o.assetPack,()=>{o.assetPack=!o.assetPack,h()}),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,"data-ctl":"export-go",onclick:async()=>{let k=i;k="",u(),await s();const E=Os(e,o);if(E)try{const x=await _.writeExport(E,k??"");G(ks?`${x.length} file${x.length===1?"":"s"} written to ${k}`:`Downloaded ${x[0]??"the export"}`,{kind:"good",action:Xt(ks?x[0]??null:null)})}catch(x){G(String(x),{kind:"bad"})}}},a.length?`Download ${a.length} file${a.length===1?"":"s"} · ${le(g)}`:"Nothing selected")),m&&r.querySelector(`[data-ctl="${m}"]`)?.focus({preventScroll:!0})}e.set({exportOpen:!0}),t.append(l),ct=l,p(),c=tn(r,{initial:()=>r.querySelector(".format"),onEscape:u,onOutside:u,fallback:()=>t.querySelector(".railfoot .btn.primary")}),h()}function kt(){return hr()}function _s(){const e=window.location.href.split("#")[0],t=On().openImage();if(!t){window.open(e,"_blank","noopener");return}const s=t.name??"image",o=va(s,t.bytes);if(o){window.open(e+o,"_blank","noopener");return}const a=(t.bytes.length/1e6).toFixed(1);G(`${s} is ${a} MB. A page address carries a file of up to ${(In/1e6).toFixed(1)} MB, so the new tab opens without it: open the file again there.`,{action:{label:"Open the tab",run:()=>window.open(e,"_blank","noopener")}})}function pr(){try{return window.self!==window.top}catch{return!0}}function hr(){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(()=>_s())}},V("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=pr()?n("button.btn.ghost.compact",{"data-ctl":"own-tab",title:`The same app at its own address, with the whole window and every core. The open image comes along, up to ${(In/1e6).toFixed(1)} MB.`,onclick:_s},V("external",14),n("span",null,"Open in its own tab")):null,a=n("a.btn.ghost.compact",{"data-ctl":"home",href:"../index.html",title:"The Inkvec front page: what's new, before and after, results"},V("globe",14),n("span",null,"Inkvec home")),i=n("a.btn.ghost.compact.desktopapp",{"data-ctl":"desktop-app",href:uo,target:"_blank",rel:"noopener noreferrer",title:Ja},V("download",14),n("span",null,"Desktop app"));return n("div.webcontrols",null,a,i,document.fullscreenEnabled?e:null,o)}const Bs="https://logolabs.github.io/inkvec/studio/",fn="./guide/";function No(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 gn(e){ua()?Ya(e).catch(()=>G("Could not open the link in your browser.",{kind:"bad"})):window.open(e,"_blank","noopener")}let Oe=null,Ln=null;function Xn(e="index.html"){if(Oe){Oe.querySelector("iframe")?.setAttribute("src",fn+e);return}const t=n("iframe",{title:"Inkvec Studio user guide",src:fn+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""};Oe=n("div.screen",{role:"dialog","aria-modal":"true","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",fn+"index.html")},"Contents"),n("button.btn.compact.ghost",{title:"The same page on logolabs.github.io, in your browser",onclick:()=>gn(Bs+o())},"Open in browser"),n("button.btn.compact",{onclick:Nt},"Done"),n("div.sep"),kt()),s),t.addEventListener("load",()=>{const a=t.contentDocument;if(!a||!a.querySelector('meta[name="inkvec-guide"]')){D(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:()=>gn(Bs)},"Open the online guide")));return}a.addEventListener("keydown",i=>{i.key==="Escape"&&Nt(),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(),gn(l))})}),window.addEventListener("keydown",Io,!0),(document.getElementById("app")??document.body).append(Oe),Ln=tn(Oe,{onEscape:Nt,fallback:()=>document.querySelector('.appbar [data-ctl="help"]')})}function Nt(){window.removeEventListener("keydown",Io,!0),Oe?.remove(),Oe=null;const e=Ln;Ln=null,e?.()}function Io(e){e.stopImmediatePropagation(),(e.key==="Escape"||e.key==="F1")&&(e.preventDefault(),e.key==="Escape"&&Nt())}function fr(e){window.addEventListener("keydown",t=>{t.key!=="F1"||Oe||(t.preventDefault(),Xn(No(e.state)))})}function gr(e,t,s){const o=t.state;D(e,n("h1.brand",null,ao(18),Yt),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.apptabs",{role:"group","aria-label":"Tabs"},...[["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.appactions",null,n("button.btn.ghost.compact",{onclick:()=>s.openFile()},"Open…"),null,n("button.btn.ghost.compact",{"data-ctl":"screen:settings",onclick:()=>t.set({screen:"settings"})},"Settings"),n("button.btn.ghost.compact",{"data-ctl":"screen:about",onclick:()=>t.set({screen:"about"})},"About"),n("button.btn.ghost.compact",{"data-ctl":"screen:showcase",title:"Before and after on real logos, and how Inkvec compares",onclick:()=>t.set({screen:"showcase"})},"Showcase"),n("button.btn.ghost.compact",{"data-ctl":"help",title:"The user guide (F1)",onclick:()=>Xn(No(t.state))},"Help"),n("div.sep"),kt())),e.setAttribute("data-tauri-drag-region","")}const Tt=30,Rs=8;function mr(e){const t=n("div.batchbar"),s=n("div.batchhead.eyebrow"),o=n("div.batchviewport"),a=n("div.batchfoot"),i=n("main.stage",{style:{background:"var(--paper)"}},t,s,o,a),r=n("div"),l=n("div"),c=n("div");o.append(r,l,c);const u=()=>{const x=e.state.batch;return x.failuresFirst?[...x.rows].sort(($,y)=>+(y.state==="failed")-+($.state==="failed")):x.rows},d=()=>n("div.firstrun",{style:{minHeight:"100%",background:"transparent"}},n("div.drop",null,n("span.glyph",null,V("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 p()},"Choose a folder…"))),h=()=>{const x=u();if(s.hidden=x.length===0,!x.length){r.style.height="0px",c.style.height="0px",D(l,d());return}const $=o.scrollTop,y=Math.ceil(o.clientHeight/Tt)+Rs*2,v=Math.max(0,Math.floor($/Tt)-Rs),M=x.slice(v,v+y);r.style.height=`${v*Tt}px`,c.style.height=`${Math.max(0,(x.length-v-M.length)*Tt)}px`,D(l,...M.map(A=>br(A,e,()=>e.touch("batch"))))};o.addEventListener("scroll",h,{passive:!0});const f=async()=>{const x=await Va({directory:!0,multiple:!1});return typeof x=="string"?x:null},p=async()=>{const x=await f();if(x)try{const $=await _.batchScan(x),y=e.state.batch;y.folder=x,y.outputDir??=`${x}${x.includes("\\")?"\\":"/"}svg`,y.rows=$.map((v,M)=>({id:M,path:v,file:v.split(/[\\/]/).pop()??v,preset:y.preset,state:"queued",de00:null,coordinates:null,outBytes:null,destination:"",message:null,seconds:null})),y.totals=null,e.touch("batch")}catch($){G(String($),{kind:"bad"})}},g=async()=>{const x=e.state.batch;if(!x.folder||!x.outputDir){G("Choose a folder of images and an output folder first.");return}x.running=!0,x.paused=!1,e.touch("batch");try{await _.batchStart({files:x.rows.map($=>$.path),preset:x.preset,overrides:x.rows.filter($=>$.preset!==x.preset).map($=>[$.id,$.preset]),outputDir:x.outputDir,skipExisting:x.skipExisting})}catch($){x.running=!1,e.touch("batch"),G(String($),{kind:"bad"})}},m=()=>{const x=e.state.batch,$=e.state.caps?.presets??[],y=(v,M,A)=>n("button.btn.compact",{onclick:M,title:null},v);D(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:v=>{const M=v.target.value;x.preset=M;for(const A of x.rows)A.state==="queued"&&(A.preset=M);e.touch("batch")}},...$.map(v=>n("option",{value:v.id,selected:x.preset===v.id?"":null},`Preset for all · ${v.name}`))),y(x.folder?`Input · ${x.folder}`:"Choose a folder…",()=>void p()),y(x.outputDir?`Output · ${x.outputDir}`:"Output folder…",async()=>{const v=await f();v&&(x.outputDir=v,e.touch("batch"))}),n("button.btn.compact",{"aria-pressed":String(x.skipExisting),onclick:()=>{x.skipExisting=!x.skipExisting,e.touch("batch")}},"Skip existing"),n("div.spacer"),n("button.btn.compact",{"aria-pressed":String(x.failuresFirst),disabled:!x.rows.some(v=>v.state==="failed"),onclick:()=>{x.failuresFirst=!x.failuresFirst,e.touch("batch")}},"Failures first"),x.running?y(x.paused?"Resume":"Pause",()=>{x.paused=!x.paused,_.batchPause(x.paused),e.touch("batch")}):null,x.running?y("Cancel",()=>void _.batchCancel()):n("button.btn.primary.compact",{disabled:!x.rows.length,onclick:()=>void g()},"Start")),D(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"))},k=()=>{const x=e.state.batch,$=x.totals,y=$?.finished??0,v=$?.total??x.rows.length,M=$&&$.sourceBytes>0?1-$.bytesWritten/$.sourceBytes:null;D(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,v?`${y} of ${v} done${$?.failed?` · ${$.failed} failed`:""}`:"Nothing queued"),n("span",null,$?.remaining!=null?`${Oa($.remaining)} left`:"")),n("div.progress",null,n("i",{style:{width:Te(v?y/v:0)}}))),$?n("span.faint",null,`mean dE00 ${ue($.meanDe00)} · ${le($.bytesWritten)} written${M!==null?` · ${Te(M)} smaller than source`:""}`):null,n("button.reset",{style:{marginLeft:"auto"},disabled:!x.rows.some(A=>A.state!=="queued"),onclick:async()=>{const A=await _.batchStatsCsv(),T=await Ka({defaultPath:"stats.csv",filters:[{name:"CSV",extensions:["csv"]}]});T&&(await _.saveBytes(T,[...new TextEncoder().encode(A)]),G(`Stats written to ${T}`,{kind:"good",action:{label:"Show in folder",run:()=>void Za(T)}}))}},"Export stats.csv"))},E=()=>{m(),k(),h()};return e.on(["batch","caps"],E),E(),i}function br(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:()=>yr(i,e,t,s)},wr(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?"—":ue(e.de00)),n("span.right.faint",null,e.coordinates===null?"—":oe(e.coordinates)),n("span.right.faint",null,e.outBytes===null?"—":le(e.outBytes)),n("span.trunc.muted",null,e.message??e.destination))}function yr(e,t,s,o){const a=s.state.caps?.presets??[];Wn(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),he(),o()}},n("span",{style:{flex:"1"}},i.name),n("span.when",null,i.id===t.preset?"current":i.subtitle)))))}function wr(e){return e.split("-").map((t,s)=>s===0?t[0].toUpperCase()+t.slice(1):t).join(" ")}const vr=.05,xr=20,kr=.97,Sr=.3,$r=.8;function Mr(e){const t=e.length/4;let s=0,o=0;for(let i=0;i+3kr*t)return null;const a=o/s;return a$r?"dark":null}const Qn=.02,es=.98,Er=.02,mn=.1,Ar=4;function Ke(e){return Math.min(es,Math.max(Qn,e))}function Cr(e,t,s){const o=s?mn:Er;switch(t){case"ArrowLeft":case"ArrowDown":return Ke(e-o);case"ArrowRight":case"ArrowUp":return Ke(e+o);case"PageDown":return Ke(e-mn);case"PageUp":return Ke(e+mn);case"Home":return Qn;case"End":return es;default:return null}}function Oo(e,t,s){e.setAttribute("role","slider"),e.setAttribute("tabindex","0"),e.setAttribute("aria-orientation","horizontal"),e.setAttribute("aria-valuemin",String(Math.round(Qn*100))),e.setAttribute("aria-valuemax",String(Math.round(es*100)));const o=()=>{const a=Math.round(s.get()*100),[i,r]=s.names();e.setAttribute("aria-valuenow",String(a)),e.setAttribute("aria-valuetext",`${a}% ${i}, ${100-a}% ${r}`),e.setAttribute("aria-label",`Wipe between ${i} and ${r}`)};return e.addEventListener("keydown",a=>{if(a.ctrlKey||a.metaKey||a.altKey)return;const i=Cr(s.get(),a.key,a.shiftKey);i!==null&&(a.preventDefault(),a.stopPropagation(),s.set(i))}),e.addEventListener("pointerdown",a=>{a.stopPropagation(),e.setPointerCapture(a.pointerId);const i=l=>{const c=t.getBoundingClientRect();s.set(Ke((l.clientX-c.left)/c.width))},r=()=>{e.removeEventListener("pointermove",i),e.removeEventListener("pointerup",r),e.removeEventListener("pointercancel",r)};e.addEventListener("pointermove",i),e.addEventListener("pointerup",r),e.addEventListener("pointercancel",r)}),o(),o}function _o(e,t,s){const o=e.getBoundingClientRect();o.width>0&&t.set(Ke((s-o.left)/o.width))}function Tr(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"),a=n("div.panes",null,t,s,o),i=n("div.viewertools"),r=n("main.stage",null,i,a);let l="side",c=.5,u=!1,d="auto",h=null,f=null,p=!1;const g=()=>{p||!e.state.prefs||(p=!0,{minifyView:l,minifyBackdrop:d}=Ba())},m=()=>{g();const P=d==="auto"?h:d;a.classList.toggle("onlight",P==="light"),a.classList.toggle("ondark",P==="dark")},k=()=>{if(g(),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 P=`${(c*100).toFixed(2)}%`;o.style.left=P,x(),t.style.clipPath=`inset(0 ${(100-c*100).toFixed(2)}% 0 0)`,s.style.clipPath=`inset(0 0 0 ${P})`}else t.style.clipPath="",s.style.clipPath="",t.classList.toggle("hidden",!u),s.classList.toggle("hidden",u)},E={get:()=>c,set:P=>{c=P,k()},names:()=>["before","after"]},x=Oo(o,a,E);a.addEventListener("click",P=>{l==="wipe"&&P.target!==o&&_o(a,E,P.clientX)}),window.addEventListener("keydown",P=>{e.state.tab!=="minify"||e.state.screen||P.key!==" "||P.repeat||P.target instanceof HTMLInputElement||P.target instanceof HTMLTextAreaElement||(P.preventDefault(),u=!0,k())}),window.addEventListener("keyup",P=>{P.key===" "&&u&&(u=!1,k())});const $=()=>{g();const P=e.state.minify,L=P.result;D(i,n("div.seg",null,...["side","wipe","ab"].map(Q=>n("button",{"aria-pressed":String(l===Q),disabled:!P.before,onclick:()=>{l=Q,An({minifyView:Q}),k(),$()}},Q==="side"?"Side by side":Q==="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(Q=>n("button",{"aria-pressed":String(d===Q),disabled:!P.before,onclick:()=>{d=Q,An({minifyBackdrop:Q}),m(),$()}},Q==="auto"?"Auto":Q==="light"?"Light":"Dark")))),n("span.muted",{style:{marginLeft:"auto",fontSize:"11.5px"}},L?`Nothing moved more than ${P.settings.tolerancePx} px${L.differenceDe00===null?"":` · measured ${ue(L.differenceDe00)} dE00`}`:""))},y=n("aside.minifyrail"),v=n("div",{style:{flex:"1",minHeight:"0",display:"flex"}},r,y);let M=0;const A=async()=>{const P=e.state.minify;if(!P.before)return;const L=++M;try{const Q=await _.minify(P.before,P.settings);if(L!==M)return;e.state.minify.result=Q,e.state.minify.error=null}catch(Q){if(L!==M)return;e.state.minify.result=null,e.state.minify.error=String(Q)}e.touch("minify")},T=async()=>{const[P]=await Bn([{name:"SVG",extensions:["svg"]}]);if(P)try{const L=await Rt(P);e.state.minify.name=P.name,e.state.minify.before=L,e.state.minify.result=null,e.state.minify.error=null,e.touch("minify"),await A()}catch(L){G(String(L),{kind:"bad"})}},O=async P=>{if(!P.name.toLowerCase().endsWith(".svg"))return!1;const L=await Rt(P);return e.state.minify.name=P.name,e.state.minify.before=L,e.touch("minify"),await A(),!0};v.acceptDropped=O;const Z=()=>n("div.drop.solid",null,n("span.glyph",null,V("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:T},"Open an SVG")),te=()=>{const P=e.state.minify,L=P.result;if($(),k(),D(t,n("span.eyebrow.panelabel",null,`Before${L?` · ${le(L.bytesBefore)}`:""}`),P.before?Ye(P.before):Z()),D(s,n("span.eyebrow.panelabel",null,`After${L?` · ${le(L.bytesAfter)}`:""}`),L?Ye(L.svg):P.before?null:n("span.faint.panehint",null,"The rewritten drawing appears here, on the same ground, so nothing can hide.")),P.before!==f){const U=P.before;f=U,h=null,m(),U&&Ro(U).then(W=>{f===U&&(h=W,m())})}if(!P.before){D(y,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 Q=L?1-L.bytesAfter/Math.max(1,L.bytesBefore):0;D(y,n("div.railscroll",{style:{gap:"20px",padding:"22px 20px"}},P.error?n("div.card",null,n("span.eyebrow",null,"This file would not parse"),n("span.faint",{style:{fontSize:"12.5px",lineHeight:"1.6"}},P.error)):n("div.minifyresult",null,n("span.eyebrow",null,L?`Result · ${gt((L.ms??0)/1e3)}`:"Working"),n("div",{style:{display:"flex",alignItems:"baseline",gap:"10px"}},n("span.figure",null,L?Te(Q):"—"),n("span.dim",{style:{fontSize:"14px",paddingBottom:"6px"}},"smaller")),L?n("span.faint.num",{style:{fontSize:"13px"}},`${le(L.bytesBefore)} → ${le(L.bytesAfter)}`):null),L?n("div.stats",null,...[["numbers before",oe(L.numbersBefore)],["numbers after",oe(L.numbersAfter)],["paths",`${L.pathsBefore} → ${L.pathsAfter}`],["segments",`${oe(L.segmentsBefore)} → ${oe(L.segmentsAfter)}`],["difference",L.differenceDe00===null?"—":`${ue(L.differenceDe00)} dE00`],["kept as drawn",`${L.guarded} run${L.guarded===1?"":"s"}`]].map(([U,W])=>n("div",null,n("span.k",null,U),n("span.v",null,W)))):null,Pr(e,A),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"}},`${P.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(P.settings.cornerDegrees),"aria-label":"Corner threshold in degrees",oninput:W=>{U.textContent=`${W.target.value}°`},onchange:W=>{P.settings.cornerDegrees=Number(W.target.value),e.touch("minify"),A()}}),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(P.settings.documentCleanup),"aria-label":"Document cleanup",onclick:()=>{P.settings.documentCleanup=!P.settings.documentCleanup,e.touch("minify"),A()}})),n("span.muted",{style:{fontSize:"11.5px",lineHeight:"1.5"}},"Removes ids, groups, editor metadata and trailing zeros. Same geometry.")),L&&L.removed.length?n("div",{style:{display:"flex",flexDirection:"column",gap:"7px"}},n("span.eyebrow",null,"Removed"),...L.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,P.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:P.before,name:P.name??"drawing.svg"}}))},V("wand",14),"Rebuild it clean by re-tracing"):null,n("div.row",null,n("button.btn.primary",{style:{flex:"1"},disabled:!L,onclick:async()=>{if(!L)return;const U=(P.name??"drawing").replace(/\.svg$/i,""),W=await ho(`${U}.min.svg`,L.svg);W&&G(W.path?`Saved to ${W.path}`:`Downloaded ${W.name}`,{kind:"good",action:Xt(W.path)})}},"Download SVG"),n("button.btn",{disabled:!L,onclick:async()=>{L&&(await Zt(L.svg),G("SVG copied. Paste straight into Figma or Illustrator."))}},"Copy"),n("button.btn",{onclick:T},"Open…"))))};return e.on(["minify"],te),te(),v}function Pr(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:u=>{const d=Number(u.target.value);Number.isFinite(d)&&d>0&&(c(d),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 Ye(e){const t=n("div.art.centred"),s=Bo(e);return s&&(s.setAttribute("width","100%"),s.setAttribute("height","100%"),t.append(s)),t}function Lr(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 Bo(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(Lr(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 u=o.getBBox();if(u.width>0&&u.height>0){const d=Math.max(u.width,u.height)*.02;o.setAttribute("viewBox",`${u.x-d} ${u.y-d} ${u.width+2*d} ${u.height+2*d}`)}else o.setAttribute("viewBox","0 0 100 100")}catch{o.setAttribute("viewBox","0 0 100 100")}finally{c.remove()}return o}async function Ro(e){const t=Bo(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 u=new Image;u.onload=()=>l(u),u.onerror=c,u.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),Mr(r.getImageData(0,0,s,s).data)):null}catch{return null}}const Se=25.4,Gs=[["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."]],Ws=[["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."]],qs=[{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 zr(e){const t=n("div.viewertools"),s=n("div.pane",null),o=n("div.panes.onlight",null,s),a=n("main.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 m=e.state.fab;if(!m.svg)return;const k=++l;try{const E=await _.fabPrepare(m.svg,m.options);if(k!==l)return;m.plan=E,m.error=null,m.shown>=E.layers.length&&(m.shown=-1)}catch(E){if(k!==l)return;m.plan=null,m.error=String(E)}e.touch("fab")},u=async(m,k)=>{const E=e.state.fab;E.name=m,E.svg=k,E.plan=null,E.error=null,E.shown=-1;try{const x=await _.fabAnalyze(k);E.analysis=x;const $=x.colours.filter(y=>!y.background).map(y=>y.hex);E.options.include=$,E.options.order=[...$],$.length>1&&E.options.mode==="singleColour"&&(E.options.mode="layered")}catch(x){E.analysis=null,E.error=String(x)}e.touch("fab"),await c()},d=async()=>{const[m]=await Bn([{name:"SVG",extensions:["svg"]}]);if(m)try{await u(m.name,await Rt(m))}catch(k){G(String(k),{kind:"bad"})}},h=async()=>{const m=e.state;m.svg&&await u(m.source?.name?`${m.source.name.replace(/\.[^.]+$/,"")}.svg`:"trace.svg",m.svg)},f=async m=>m.name.toLowerCase().endsWith(".svg")?(await u(m.name,await Rt(m)),!0):!1;r.acceptDropped=f;const p=(m,k=!1)=>{const E=e.state.fab;Object.assign(E.options,m),k||(E.preset=null),e.touch("fab"),c()},g=()=>{const m=e.state.fab;if(Ir(e,t,s),!m.svg){D(i,_r(e,d,h));return}D(i,n("div.railscroll",{style:{gap:"16px",padding:"20px"}},Br(e,d,h),m.error?n("div.card",null,n("span.eyebrow",null,"This file could not be prepared"),n("span.faint",null,m.error)):null,Rr(e,p),Gr(e,p),Wr(e,p),qr(e,p),Ur(e,p),jr(e),Vr(e)),Kr(e))};return e.on(["fab"],g),e.on(["svg"],()=>{e.state.tab==="fabricate"&&g()}),g(),r}function Go(e,t,s=1){return e.state.fab.unit==="in"?`${(t/Se).toFixed(s+1)} in`:`${t.toFixed(s)} mm`}function Dr(e,t){return e.state.fab.unit!=="in"?t:t.replace(/(\d+(?:\.\d+)?) mm²/g,(s,o)=>`${(Number(o)/(Se*Se)).toFixed(4)} in²`).replace(/(\d+(?:\.\d+)?) mm(?![²\w])/g,(s,o)=>`${(Number(o)/Se).toFixed(3)} in`)}function ht(e,t){return e.state.fab.unit==="in"?`${(t[0]/Se).toFixed(2)} × ${(t[1]/Se).toFixed(2)} in`:`${t[0].toFixed(1)} × ${t[1].toFixed(1)} mm`}function Fr(e){return e?e.checks.filter(t=>t.code==="thin"||t.code==="gaps"||t.code==="specks").length:0}function Nr(){return n("div.drop.solid",null,n("span.glyph",null,V("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 ts(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 Ir(e,t,s){const o=e.state.fab,a=o.plan,i=Fr(a),r=h=>{o.shown=h,e.touch("fab")};D(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,f)=>n("button",{"aria-pressed":String(o.shown===f),title:`${h.name}: ${ht(e,h.materialMm)} of material, ${h.parts} pieces, ${oe(h.nodes)} path segments`,onclick:()=>r(f)},ts(h.hex),n("span",{style:{marginLeft:"6px"}},(a?.layers.length??0)>1?`${f+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")}},V("alert",13),n("span",{style:{marginLeft:"6px"}},o.showProblems?"Showing problems":"Show problems")):null,n("span.muted",{style:{marginLeft:"auto",fontSize:"11.5px"}},a?`${ht(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,u=(h,f=!1)=>(h.style.gridArea="1 / 1",f&&(h.style.pointerEvents="none",h.style.opacity="0.85"),h),d=c?n("div",{style:{display:"grid",placeSelf:"stretch",minWidth:"0",minHeight:"0"}},l&&a&&o.shown<0?Or(u(Ye(a.previewSvg))):null,u(Ye(c)),a&&i&&o.showProblems?u(Ye(a.problemsSvg),!0):null):o.svg?Ye(o.svg):Nr();D(s,n("span.eyebrow.panelabel",null,o.shown>=0&&a?`${l?"Pen":"Sheet"} ${o.shown+1} · ${a.layers[o.shown].name} · ${ht(e,a.layers[o.shown].materialMm)}`:l?"What the pen draws, over the drawing":"All sheets, stacked"),d)}function It(e,t){const s=e.state.fab.options.mode==="lines"?"pen drawing":"sheet";return t===1?s:`${s}s`}function Or(e){return e.style.opacity="0.18",e.style.pointerEvents="none",e}function _r(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 Br(e,t,s){const o=e.state.fab;return n("div",{style:{display:"flex",alignItems:"center",gap:"8px"}},V("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 Rr(e,t){const s=e.state.fab,o=s.options.include.length,a=qs.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"}},...qs.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)}},V(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 Gr(e,t){const s=e.state.fab.options,o=Gs.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)"}},...Gs.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 Ot(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 me(e,t,s,o="58px"){const a=e.state.fab.unit==="in",i=Number(a?(t/Se).toFixed(3):t.toFixed(2));return Ot(i,r=>s(a?r*Se:r),o)}function ns(e){return e.state.fab.unit==="in"?"in":"mm"}function Wr(e,t){const s=e.state.fab,o=s.analysis?.aspect??1,a=s.options.widthMm,i=ns(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 ",me(e,a,r=>r>0&&t({widthMm:r})),` ${i} wide and `,me(e,a*o,r=>r>0&&t({widthMm:r/o})),` ${i} tall.`),qe("Cut a size-check square",s.options.sizeCheckMm>0,()=>t({sizeCheckMm:s.options.sizeCheckMm>0?0:s.unit==="in"?Se:20}),s.options.sizeCheckMm>0?`A ${Go(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)"}},...Ws.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"}},Ws.find(r=>r[0]===s.options.fileUnits)?.[2]??"")))}function qr(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(u=>a.include.includes(u)),...o.colours.map(u=>u.hex).filter(u=>!a.include.includes(u))]:o.colours.map(u=>u.hex),l=u=>o.colours.find(d=>d.hex===u),c=(u,d)=>{const h=a.order.filter(g=>a.include.includes(g)),f=h.indexOf(u),p=f+d;f<0||p<0||p>=h.length||([h[f],h[p]]=[h[p],h[f]],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(u=>{const d=l(u);if(!d)return null;const h=a.include.includes(u),f=[d.background?"page":null,d.gradient?"gradient":null,d.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 ${u}`,onclick:()=>{const p=h?a.include.filter(m=>m!==u):[...a.include,u],g=h?a.order:[...a.order.filter(m=>m!==u),u];t({include:p,order:g})}}),ts(u,16),n("span.num",{style:{flex:"1"}},u,f.length?n("span.muted",{style:{marginLeft:"6px",fontSize:"11px"}},f.join(" · ")):null),n("span.muted.num",{style:{fontSize:"11px"}},Te(d.coverage,1)),i&&h?n("span",{style:{display:"flex",gap:"2px"}},n("button.btn.ghost.compact",{title:"Lower in the stack",onclick:()=>c(u,-1)},"↓"),n("button.btn.ghost.compact",{title:"Higher in the stack",onclick:()=>c(u,1)},"↑")):null)}))}function qe(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 Hr(e,t){const s=e.state.fab.options,o=ns(e);return n("div.card",null,n("span.eyebrow",null,"Drawing"),n("span.sentence",null,"The pen or tool draws ",me(e,s.penMm,a=>a>0&&t({penMm:a})),` ${o} wide.`),n("span.sentence",null,"Lines up to ",me(e,s.maxLineMm,a=>t({maxLineMm:a})),` ${o} wide are drawn once (0 for any width); wider parts are drawn round their outline.`),qe("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."),Wo(e,t),n("span.sentence",null,"Lines stay within ",me(e,s.toleranceMm,a=>a>0&&t({toleranceMm:a})),` ${o} of the drawing.`))}function Wo(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/Se).toFixed(1)):Math.round(o.gcodeFeedMmMin);return n("div",{style:{display:"flex",flexDirection:"column",gap:"8px"}},qe("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 ",Ot(i,r=>r>0&&t({gcodeFeedMmMin:s.unit==="in"?r*Se:r}),"64px"),` ${a}, power S`,Ot(o.gcodePower,r=>t({gcodePower:r}),"58px"),", ",Ot(o.gcodePasses,r=>r>=1&&t({gcodePasses:Math.round(r)}),"40px",1),o.gcodePasses===1?" pass.":" passes."):null)}function Ur(e,t){const s=e.state.fab.options;if(s.mode==="lines")return Hr(e,t);const o=ns(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 ",me(e,s.bleedMm,i=>t({bleedMm:i})),` ${o} under the colours above it.`):null,s.mode==="sticker"?n("span.sentence",null,"The contour is ",me(e,s.stickerMarginMm,i=>t({stickerMarginMm:i})),` ${o} outside the artwork.`):null,s.mode==="stencil"?n("span.sentence",null,"Bridges are ",me(e,s.bridgeMm,i=>i>0&&t({bridgeMm:i})),` ${o} wide; the sheet reaches `,me(e,s.stencilMarginMm,i=>t({stencilMarginMm:i})),` ${o} past the artwork.`):null,n("span.sentence",null,"Parts narrower than ",me(e,s.minFeatureMm,i=>i>0&&t({minFeatureMm:i})),` ${o} are too thin to weed.`),qe("Remove them",s.removeThin,()=>t({removeThin:!s.removeThin}),"Drops the slivers and necks narrower than that from the sheets."),a?qe("Registration marks",s.registration,()=>t({registration:!s.registration})):null,n("span.sentence",null,"Weeding border ",me(e,s.weedBorderMm,i=>t({weedBorderMm:i})),` ${o} outside (0 for none).`),qe("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."),Wo(e,t),s.mode==="sticker"||s.mode==="stencil"?null:qe("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 ",me(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 ",me(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 ",me(e,s.toleranceMm,i=>i>0&&t({toleranceMm:i})),` ${o} of the drawing.`))}function jr(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,...[[It(e,2),String(t.layers.length)],["pieces",oe(o)],["path segments",oe(s)],["whole job",ht(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"}},V(i.level==="info"?"info":"alert",14)),n("span.faint",null,Dr(e,i.message))))):n("div",{style:{display:"flex",gap:"8px",alignItems:"center",fontSize:"12.5px"}},V("check",14),n("span.dim",null,"Nothing here will trouble the cutter.")))}function Vr(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)),ts(o.hex,14),n("span.num",{style:{flex:"1"}},o.name),n("span.dim.num",null,ht(e,o.materialMm)),n("span.muted.num",{style:{width:"54px",textAlign:"right",fontSize:"11px"}},`${o.parts} pc`))))}function Kr(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((u,d)=>({name:a(s,d),data:u.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 Xa(i,`${o}-sheets.zip`);if(!r)return;const l=s.layers.length>1,c=`${s.layers.length} ${It(e,s.layers.length)}${l?" and one combined file":""}`;G(r.folder?`Saved ${c} to ${r.folder}`:`Downloaded ${c}${i.length>1?" as one .zip":""}`,{kind:"good",action:Xt(r.first)})}},`Download ${s&&s.layers.length>1?`${s.layers.length} ${It(e,2)}`:`the ${It(e,1)}`}`),n("button.btn",{disabled:!s,title:"Copies the shown sheet, or every sheet in one file",onclick:async()=>{s&&(await Zt(t.shown>=0?s.layers[t.shown].svg:s.combinedSvg),G("Copied. Paste it into Design Space, Silhouette Studio or LightBurn."))}},"Copy")),n("div.note",null,n("span",null,s?`Real size: ${Go(e,s.sizeMm[0])} wide`:"Real sizes"),n("span",null,"Nothing leaves this computer")))}const Pt={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"},qo=e=>fetch(new URL(e,document.baseURI)).then(t=>{if(!t.ok)throw new Error(`${e}: HTTP ${t.status}`);return t.json()});let Hs=null;function Yr(){return Hs??=qo("../showcase.json"),Hs}function Zr(e){return qo(`../showcase/${e}.json`)}function Jr(e){const t=n("div.screenbody",null,n("div.sc-loading.faint",null,"Loading the showcase…"));return Yr().then(s=>D(t,Xr(s)),s=>D(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",{id:"screen-title",style:{fontSize:"12.5px",fontWeight:"600"}},"Showcase"),n("div.spacer"),n("button.btn.compact",{onclick:e},"Done"),n("div.sep"),kt()),t)}function Xr(e){let t=e.sets[0].cases[0],s="vtracer-1.0-simplify",o="fill",a=0;const i=Qr(),r=n("div.sc-chips"),l=n("p.sc-caption"),c=n("div.sc-list"),u=new Map,d=e.gallery_engines.filter(y=>y!=="inkvec"),h=(y,v,M)=>{const A=n("div.seg"),T=()=>{for(const O of A.querySelectorAll("button"))O.setAttribute("aria-pressed",String(O.dataset.k===v()))};for(const[O,Z]of y)A.append(n("button",{"data-k":O,onclick:()=>{M(O),T()}},Z));return T(),A},f=async()=>{const y=t,v=++a;for(const[Z,te]of u)te.setAttribute("aria-pressed",String(Z===y.key));const M=y.what.includes("brand")||y.key.startsWith("brand");D(l,n("b",null,y.label),` · ${y.what}`,M?n("span.faint",{style:{fontSize:"11px",marginLeft:"6px"},title:"Nominative benchmark demonstration. All trademarks belong to their respective owners."},"(nominative benchmark)"):null);const A=y.m.inkvec,T=y.m[s];D(r,n("span.sc-legend.faint",null,n("i.sc-dot.a"),"anchor",n("i.sc-dot.hd"),"control point"),ut(`Inkvec dE00 ${A.de1024.toFixed(3)}`,!0),ut(`${Pt[s]} dE00 ${T.de1024.toFixed(3)}`),ut(`Inkvec ${A.paths} paths · ${A.coordinates} coordinates · ${Us(A.bytes)}`,!0),ut(`${Pt[s]} ${T.paths} paths · ${T.coordinates} coordinates · ${Us(T.bytes)}`),ut(`The artist's file: ${y.artist.paths} paths · ${y.artist.coordinates} coordinates`));const O=await Zr(y.key);v===a&&(i.set(O.input,O.svg.inkvec,O.svg[s],"Inkvec",Pt[s]??s),i.layer(o))},p=[];new ResizeObserver(()=>{const y=i.el.getBoundingClientRect().height;y>200&&(c.style.height=`${y}px`)}).observe(i.el);const g=(y,v)=>{t=y,f();const M=u.get(y.key);if(!M)return;const A=c.getBoundingClientRect(),T=M.getBoundingClientRect(),O=34;T.topA.bottom&&(c.scrollTop+=T.bottom-A.bottom+6),v&&M.focus({preventScroll:!0})};c.addEventListener("keydown",y=>{const v=p.findIndex(T=>T.key===t.key),A={ArrowDown:3,ArrowUp:-3,ArrowRight:1,ArrowLeft:-1,Home:-v,End:p.length-1-v}[y.key];A!==void 0&&(y.preventDefault(),g(p[Math.min(p.length-1,Math.max(0,v+A))],!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(v=>{const M=n("button.sc-case",{onclick:()=>g(v,!1),title:`${v.label} · ${v.what}`},n("span.sc-thumb",null,n("img",{src:v.thumb,alt:"",loading:"lazy"})),n("span.sc-name",null,v.label));return u.set(v.key,M),p.push(v),M})));const m=n("div.sc-tools",null,n("span.eyebrow",null,"Compare with"),h(d.map(y=>[y,Pt[y]??y]),()=>s,y=>{s=y,f()}),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}×`)));f();const k=e.engines.find(y=>y.id==="inkvec"),E=e.best_de_wins.inkvec??0,x=e.brands.best_de_wins.inkvec??0,$=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,m,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 ${E} of ${e.cases}; in the other sets, on ${x} of ${e.brands.cases} brand logos and ${$} of ${e.corpus.cases} more icons and emoji.`),k?n("div.sc-bignums",null,Lt("median colour difference",k.de_median.toFixed(3),"dE00; under 1 is invisible"),Lt("geometry vs the artist",`${k.geometry_mean.toFixed(2)}×`,`mean; median ${k.geometry_median.toFixed(2)}×`),Lt("DINOv3 similarity",k.dino.toFixed(4),"1 is identical"),Lt("seconds per case",k.seconds_median.toFixed(2),"median, native command line")):null,bn(`${e.cases} benchmark cases, re-run at ${e.size} px`,e.engines),bn(`${e.brands.cases} brand logos`,e.brands.engines),bn(`${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(),Jt("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 bn(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 Us=e=>`${(e/1024).toFixed(1)} KB`,ut=(e,t=!1)=>n(`span.sc-chip${t?".win":""}`,null,e),Lt=(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 Qr(){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 u=r.getBoundingClientRect(),d=c.clientX-u.left,h=c.clientY-u.top,f=Math.min(40,Math.max(1,s.z*Math.exp(-c.deltaY*.0015))),p=f/s.z;s.x=d-(d-s.x)*p,s.y=h-(h-s.y)*p,s.z=f,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,u,d){D(e[0].cam,n("img.sc-raster",{src:r,alt:"",draggable:"false"})),o=[Ks(l),Ks(c)],D(e[1].cam,o[0].root),D(e[2].cam,o[1].root),e[0].tag.textContent="Raster",e[1].tag.textContent=`${u} · ${o[0].anchors} anchors`,e[2].tag.textContent=`${d} · ${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 js="http://www.w3.org/2000/svg";function Vs(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 Ks(e){const t=Vs(e),s=Vs(e),o=n("div.sc-layer");if(!t||!s)return{root:o,anchors:0,set:()=>{}};s.classList.add("sc-wire");const a=el(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 el(e){const t=document.createElementNS(js,"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[f,p]=Ys(h),g=tl(h.getAttribute("d")??"",f,p);i+=g.anchors.length;for(const[m,k]of g.anchors)s+=`M${r(m)} ${r(k)}h0.001`;for(const[[m,k],[E,x]]of g.handles)o+=`M${r(m)} ${r(k)}L${r(E)} ${r(x)}`,a+=`M${r(E)} ${r(x)}h0.001`}for(const h of e.querySelectorAll("circle, ellipse")){const[f,p]=Ys(h);s+=`M${r(Number(h.getAttribute("cx")??0)+f)} ${r(Number(h.getAttribute("cy")??0)+p)}h0.001`,i++}const l=(h,f)=>{const p=document.createElementNS(js,"path");return p.setAttribute("d",h||"M0 0"),p.setAttribute("class",f),t.append(p),p},c=l(o,"h"),u=l(a,"hd"),d=l(s,"a");return{svg:t,anchors:d,handles:c,handleDots:u,count:i}}function Ys(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 tl(e,t,s){const o=e.match(/[MLHVCSQTAZmlhvcsqtaz]|-?\d*\.?\d+(?:e[-+]?\d+)?/gi)??[],a=[],i=[];let r=0,l=0,c=0,u=0,d=0,h="",f=null,p=0,g=0;const m=()=>parseFloat(o[r++]),k=(x,$)=>a.push([x+t,$+s]),E=(x,$,y,v)=>i.push([[x+t,$+s],[y+t,v+s]]);for(;r{const r=e.state;if(!r.screen){D(s),s.style.display="none",o?.(),o=null;return}s.style.display="";const c=(o?Gt(s):[]).indexOf(document.activeElement);D(s,r.screen==="settings"?sl(e,t):r.screen==="showcase"?Jr(t.close):al(e,t)),a=r.screen,o?c>=0&&Gt(s)[c]?.focus({preventScroll:!0}):o=tn(s,{onEscape:t.close,fallback:()=>document.querySelector(`.appbar [data-ctl="screen:${a}"]`)})};return e.on(["screen","prefs","caps"],i),i(),s}function sl(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",{id:"screen-title",style:{fontSize:"12.5px",fontWeight:"600"}},"Settings"),n("div.spacer"),n("button.btn.compact",{onclick:t.close},"Done"),n("div.sep"),kt()),n("div.screenbody",null,n("div.settingsgrid",null,zt("General",[null,be("Theme","The light theme matches the dark one token for token.",yn([["system","System"],["dark","Dark"],["light","Light"]],s?.theme??"system",a=>t.applyPrefs({theme:a}))),be("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.",yn([["ask","Ask"],["auto","Auto only"],["custom","Custom wizard"]],s?.onOpen??"ask",a=>t.applyPrefs({onOpen:a}))),be("Language","English only in this release. Nothing in the app is machine-translated, which is why there is one.",yn([["en","English"]],"en",()=>{}))]),zt("Performance",[be("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")),be("Draft resolution","Used while you move a control.",wn(s?.draftPx??512,128,2048,a=>t.applyPrefs({draftPx:a}),"px")),be("Draft time limit","A draft that overruns is abandoned, not shown late.",wn(s?.draftSeconds??.4,.05,10,a=>t.applyPrefs({draftSeconds:a}),"s",2)),be("Settle before the full trace","How long the controls stay still before the full-resolution trace is queued.",wn(s?.settleMs??800,100,5e3,a=>t.applyPrefs({settleMs:a}),"ms"))]),zt("Denoiser",ol(e,o)),null,zt("Advanced",[null,null,be("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:()=>pi("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 _.resetPrefs();e.set({prefs:a,settings:a.trace}),t.afterReset(a),G("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 ol(e,t){return t?t.supported?[be("Status","Optional. Improves traces from photographs and screenshots.",n("span",{style:{fontSize:"12.5px",color:t.installed?"var(--good)":"var(--muted)"}},t.installed?`Installed · ${le(t.bytes??0)}`:e.state.denoiserFetch?.phase==="downloading"?"Downloading in the background":"Not installed")),be(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 _.denoiserRemove(),o=e.state.caps;o&&e.set({caps:{...o,denoiser:s}}),G("Denoiser removed.")}},"Remove"):n("button.btn.compact",{onclick:()=>ss(e)},"Download")),be("Verified against",t.repo,n("span.mono.muted",{style:{fontSize:"11px"}},`${t.sha256.slice(0,16)}…`))]:[be("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"))]:[be("Status","",n("span.muted",null,"—"))]}function ss(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","aria-labelledby":"denoiser-title"});let i=!1;const r=()=>{D(a,n("h2",{id:"denoiser-title"},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&&_.denoiserCancel(),he()}},i?"Cancel download":"Not now"),i?null:n("button.btn.primary",{onclick:()=>{i=!0,r(),_.denoiserDownload()}},"Download")))};(async()=>{const l=await Ae.denoiserProgress(({got:u,total:d})=>{const h=d?u/d*100:0;s.firstElementChild.style.width=`${h.toFixed(1)}%`,o.textContent=d?`${le(u)} of ${le(d)} · you can keep tracing`:`${le(u)} · you can keep tracing`}),c=await Ae.denoiserDone(({ok:u,message:d,status:h})=>{const f=e.state.caps;f&&e.set({caps:{...f,denoiser:h}}),l(),c(),he(),u?G("Denoiser installed and verified.",{kind:"good"}):d!=="cancelled"&&G(d??"The download did not finish.",{kind:"bad"})})})(),r(),ot(a)}function al(e,t){const s=e.state.caps,o=n("pre",{tabindex:"0","aria-label":"Licence and third-party notices"},"Loading the notices…");_.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(),Jt(r)}},i);return n("div.screen",null,n("div.screenbar",{"data-tauri-drag-region":""},n("span",{id:"screen-title",style:{fontSize:"12.5px",fontWeight:"600"}},"About"),n("div.spacer"),n("button.btn.compact",{onclick:t.close},"Done"),n("div.sep"),kt()),n("div.screenbody",null,n("div.about",null,n("div.aboutleft",null,n("span.mark",null,ao(72)),n("div",{style:{display:"flex",flexDirection:"column",gap:"8px"}},n("span.serif",{style:{fontSize:"40px",lineHeight:"1"}},Yt),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(),Xn()}},"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:()=>il(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 il(e){ot(nn("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:he},"Close")]))}function zt(e,t){return n("div.settinggroup",null,n("div.head.eyebrow",null,e),...t)}let rl=0;function be(e,t,s){const o=`setting-${++rl}`,a=s.matches("select, input, [role=switch]")?s:s.querySelector("select, input, [role=switch]");return a&&(a.setAttribute("aria-labelledby",`${o}-label`),t&&a.setAttribute("aria-describedby",`${o}-help`)),n("div.settingrow",null,n("div.about",null,n("span.label",{id:`${o}-label`},e),t?n("span.help",{id:`${o}-help`},t):null),n("div.control",null,s))}function yn(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 wn(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 ll={M:2,L:2,T:2,H:1,V:1,C:6,S:4,Q:4,A:7,Z:0};function*cl(e){const t=e.match(/[a-zA-Z]|-?\d*\.?\d+(?:[eE][-+]?\d+)?/g)??[];let s=0,o="";for(;s="a"&&l<="z",d=l.toUpperCase(),h=s,f=o,p=m=>u?h+c[m]:c[m],g=m=>u?f+c[m]:c[m];switch(d){case"M":s=p(0),o=g(1),a=s,i=o,t.push({x:s,y:o}),r=null;break;case"L":s=p(0),o=g(1),t.push({x:s,y:o}),r=null;break;case"H":s=u?h+c[0]:c[0],t.push({x:s,y:o}),r=null;break;case"V":o=u?f+c[0]:c[0],t.push({x:s,y:o}),r=null;break;case"C":{const m={x:p(0),y:g(1)},k={x:p(2),y:g(3)};s=p(4),o=g(5),t.push({x:s,y:o,in:m,out:k}),r=k;break}case"S":{const m=r?{x:2*h-r.x,y:2*f-r.y}:{x:h,y:f},k={x:p(0),y:g(1)};s=p(2),o=g(3),t.push({x:s,y:o,in:m,out:k}),r=k;break}case"Q":{const m={x:p(0),y:g(1)};s=p(2),o=g(3),t.push({x:s,y:o,in:m,out:m}),r=m;break}case"T":{const m=r?{x:2*h-r.x,y:2*f-r.y}:{x:h,y:f};s=p(0),o=g(1),t.push({x:s,y:o,in:m,out:m}),r=m;break}case"A":s=p(5),o=g(6),t.push({x:s,y:o}),r=null;break;case"Z":s=a,o=i,r=null;break}}return t}const dl="path, circle, ellipse, rect, line, polyline, polygon";function pl(e){const t=[];for(const s of e.querySelectorAll(dl)){const o=fl(s);o&&t.push(o)}return t}function pe(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 hl(e){const t=/rotate\(\s*(-?[\d.]+)/.exec(e.getAttribute("transform")??"");return t?Number(t[1]):0}function fl(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=pe(e,"cx"),o=pe(e,"cy"),a=e.nodeName.toLowerCase()==="circle",i=a?pe(e,"r"):pe(e,"rx"),r=a?pe(e,"r"):pe(e,"ry");if(!(i>0)||!(r>0))return null;const l=hl(e),c=l*Math.PI/180,u=Math.cos(c),d=Math.sin(c),h=(p,g)=>`${t(s+p*u-g*d)},${t(o+p*d+g*u)}`,f=`A${t(i)},${t(r)} ${t(l)} 0 1 `;return`M${h(i,0)}${f}${h(0,r)}${f}${h(-i,0)}${f}${h(0,-r)}${f}${h(i,0)}Z`}case"rect":{const s=pe(e,"x"),o=pe(e,"y"),a=pe(e,"width"),i=pe(e,"height");if(!(a>0)||!(i>0))return null;const r=Math.min(pe(e,"rx",pe(e,"ry")),a/2),l=Math.min(pe(e,"ry",pe(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(pe(e,"x1"))},${t(pe(e,"y1"))}L${t(pe(e,"x2"))},${t(pe(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 gl(e){const t=Math.min(1,Math.max(0,(e-1)/11));return{r:1.2+t*1.2,opacity:.4+t*.6}}const Js=.25,Xs=64;function ml(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=Ee("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"),u=n("div.pane.vector",null,c,a,l),d=n("div.wipehandle"),h=n("span.vh",{id:"viewer-keys"},"Arrow keys pan, Shift with an arrow pans further. Plus and minus zoom, 0 fits. In Wipe, Tab to the divider and use the arrow keys."),f=n("div.panes",{tabindex:"0",role:"group","aria-label":"Source and vector, side by side","aria-describedby":"viewer-keys"},r,u,d,h),p=f;let g={x:0,y:0,w:1,h:1},m=null,k,E,x,$=0,y=0,v=Number.NaN,M,A=null,T={x:0,y:0,w:1,h:1};const O={certainty:Ee("g.certainty"),wireframe:Ee("g.wireframe"),handles:Ee("g.handles")},Z={certainty:!1,wireframe:!1,handles:!1};for(const w of Object.values(O))w.style.display="none";l.style.display="none",i.append(O.certainty,O.wireframe,O.handles);let te=null,P=null;function L(){const w=e.state,z=w.svg,N=w.source!==E,q=z!==k;if((N||q)&&(g=(z?Zs(z):null)??{x:0,y:0,w:w.source?.width??1,h:w.source?.height??1},v=Number.NaN),N&&(E=w.source,D(t,w.source?n("img",{src:w.source.preview,alt:"",draggable:"false"}):null)),q){if(k=z,m=null,z){const H=new DOMParser().parseFromString(z,"image/svg+xml").documentElement;H&&H.nodeName==="svg"&&!H.querySelector("parsererror")&&(H.setAttribute("width",String(g.w)),H.setAttribute("height",String(g.h)),m=document.importNode(H,!0))}D(a,m,i);const J=++$;(z?Ro(z):Promise.resolve(null)).then(H=>{J===$&&(f.classList.toggle("onlight",H==="light"),f.classList.toggle("ondark",H==="dark"))}),i.setAttribute("viewBox",`${g.x} ${g.y} ${g.w} ${g.h}`),te=null,P=null;for(const H of["wireframe","handles"])D(O[H]),Z[H]=!1;ce(!0)}(w.result!==x||q&&!z)&&W(),N||q?(_e(),Ie()):w.show.certainty&&_e()}function Q(){const w=e.state.compare;if(w===M)return;M=w,A&&URL.revokeObjectURL(A),A=null,w?(T=Zs(w.svg)??g,A=URL.createObjectURL(new Blob([w.svg],{type:"image/svg+xml"})),D(s,n("img",{src:A,alt:"",draggable:"false",style:{imageRendering:"auto"}}))):D(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,U()}function U(){const w=s.querySelector("img");if(!w)return;const z=e.state.zoom;w.style.position="absolute",w.style.left=`${(T.x-g.x)*z}px`,w.style.top=`${(T.y-g.y)*z}px`,w.style.width=`${T.w*z}px`,w.style.height=`${T.h*z}px`,w.style.maxWidth="none"}function W(){x=void 0,y++,D(O.certainty),Z.certainty=!1}function ee(){return te??=m?pl(m):[],te}function K(){if(P)return P;const w=[],z=[],N=[];for(const q of ee()){const J=new Set;for(const H of ul(q)){const de=Math.round(H.x*1e3)*1e8+Math.round(H.y*1e3);J.has(de)||(J.add(de),w.push({x:H.x,y:H.y}));for(const ge of[H.in,H.out])ge&&(z.push(`M${H.x} ${H.y}L${ge.x} ${ge.y}`),N.push(`M${ge.x} ${ge.y}l.001 .001`))}}return P={lines:z.join(""),knobs:N.join(""),dots:w},P}function ae(w){if(Z[w]=!0,!m)return;const z=O[w];if(w==="wireframe"){const N=yl(ee());N&&z.append(Ee("path",{d:N}))}else{const{lines:N,knobs:q}=K();N&&z.append(Ee("path.lines",{d:N}),Ee("path.knobs",{d:q}))}}let ie="",ne=!1;function ce(w=!1){w&&(ie=""),!ne&&(ne=!0,requestAnimationFrame(()=>{ne=!1,Fe()}))}function Fe(){const w=e.state,z=!!m&&w.show.anchors,N=Math.round(u.clientWidth),q=Math.round(u.clientHeight);if(z&&(!N||!q))return;const J=window.devicePixelRatio||1,H=z?`${w.zoom} ${w.pan.x} ${w.pan.y} ${N} ${q} ${J}`:"off";if(H===ie)return;ie=H;const de=l.getContext("2d");if(!de)return;if(!z){l.width=0,l.height=0;return}const ge=Math.max(1,Math.round(N*J)),Be=Math.max(1,Math.round(q*J));(l.width!==ge||l.height!==Be)&&(l.width=ge,l.height=Be),l.style.width=`${N}px`,l.style.height=`${q}px`,de.setTransform(J,0,0,J,0,0),de.clearRect(0,0,N,q);const{dots:xe}=K();if(!xe.length)return;const at=getComputedStyle(u),na=at.getPropertyValue("--stage").trim()||"#808080",sa=at.getPropertyValue("--anchor").trim()||"#ff5c00",{r:ls,opacity:oa}=gl(w.zoom);l.style.opacity=String(oa);const aa=w.pan.x-g.x*w.zoom,ia=w.pan.y-g.y*w.zoom,it=ls*1.15,ra=ls*.85,$t=[],rn=[];for(const rt of xe){const lt=aa+rt.x*w.zoom,$e=ia+rt.y*w.zoom;lt<-it||$e<-it||lt>N+it||$e>q+it||($t.push(lt),rn.push($e))}for(const[rt,lt]of[[it,na],[ra,sa]]){de.beginPath();for(let $e=0;$e<$t.length;$e++)de.moveTo($t[$e]+rt,rn[$e]),de.arc($t[$e],rn[$e],rt,0,2*Math.PI);de.fillStyle=lt,de.fill()}}function ye(){const w=e.state,z=w.result;if(Z.certainty=!0,x=z,!z||!w.svg)return;if(z.bands){Ne(z.bands);return}const N=++y,q=()=>{N===y&&e.set({bandsMissing:!0,show:{...e.state.show,certainty:!1}})};_.traceBands(w.resultGeneration).then(J=>{N===y&&(J?Ne(J):q())},q)}function Ne(w){D(O.certainty);for(const{cls:z,d:N}of $l(wl(w)))O.certainty.append(Ee(`path.${z}`,{d:N}))}function _e(){const{show:w}=e.state,z=(N,q)=>{q&&!Z[N]&&(N==="certainty"?ye():ae(N)),O[N].style.display=q?"":"none"};z("wireframe",w.wireframe),z("handles",w.handles),z("certainty",w.certainty),l.style.display=w.anchors?"":"none",ce(),m&&(m.style.opacity=w.fill?"1":"0.12")}function Ie(){const w=e.state,z=`translate(${w.pan.x}px, ${w.pan.y}px)`;for(const N of M?[s,a]:[t,a])N.style.transform=z;if(w.zoom!==v){v=w.zoom;const N=g.w*w.zoom,q=g.h*w.zoom,J=t.querySelector("img");J&&(J.style.width=`${N}px`,J.style.height=`${q}px`),m&&(m.setAttribute("width",String(N)),m.setAttribute("height",String(q))),i.setAttribute("width",String(N)),i.setAttribute("height",String(q)),U()}w.show.anchors&&ce(),fe()}function fe(){const w=e.state;f.classList.toggle("stacked",w.view!=="side"),d.style.display=w.view==="wipe"?"":"none",re();const[z,N]=we(),q=w.view==="side"?"side by side":w.view==="wipe"?"wiped":"A/B";if(f.setAttribute("aria-label",`${bl(z)} and ${N}, ${q}`),w.view==="side")r.classList.remove("hidden"),u.classList.remove("hidden"),r.style.clipPath="",u.style.clipPath="";else if(w.view==="wipe"){r.classList.remove("hidden"),u.classList.remove("hidden");const ge=`${(w.wipe*100).toFixed(2)}%`;d.style.left=ge,r.style.clipPath=`inset(0 ${(100-w.wipe*100).toFixed(2)}% 0 0)`,u.style.clipPath=`inset(0 0 0 ${ge})`}else r.style.clipPath="",u.style.clipPath="",r.classList.toggle("hidden",!w.flicked),u.classList.toggle("hidden",w.flicked);const J=w.detail&&w.zoom>=6;let H=u.querySelector(".pixelgrid");J&&!H&&(H=n("div.pixelgrid"),u.append(H)),H&&(H.style.display=J?"":"none",H.style.backgroundSize=`${w.zoom}px ${w.zoom}px`,H.style.backgroundPosition=`${w.pan.x}px ${w.pan.y}px`);const de=t.querySelector("img");de&&(de.style.imageRendering=w.zoom>=4?"pixelated":"auto")}function Pe(){const w=u.getBoundingClientRect();return{w:w.width||1,h:w.height||1}}function S(){const{w,h:z}=Pe(),N=Math.min(w/g.w,z/g.h)*.88;e.set({zoom:N,pan:{x:(w-g.w*N)/2,y:(z-g.h*N)/2},fitted:!0})}function F(w,z,N){const{w:q,h:J}=Pe();e.set({zoom:N,pan:{x:q/2-w*N,y:J/2-z*N},fitted:!1})}function C(w){const{w:z,h:N}=Pe();X(z/2,N/2,Math.min(Xs,Math.max(Js,w))/e.state.zoom)}function Y(w,z){const{w:N,h:q}=Pe(),J=e.state;e.set({pan:{x:J.pan.x-w*N,y:J.pan.y-z*q},fitted:!1})}function X(w,z,N){const q=e.state,J=Math.min(Xs,Math.max(Js,q.zoom*N)),H=J/q.zoom;e.set({zoom:J,pan:{x:w-(w-q.pan.x)*H,y:z-(z-q.pan.y)*H},fitted:!1})}for(const w of[r,u])w.addEventListener("wheel",z=>{z.preventDefault();const N=w.getBoundingClientRect();X(z.clientX-N.left,z.clientY-N.top,Math.exp(-z.deltaY*.0016))},{passive:!1}),w.addEventListener("dblclick",S),w.addEventListener("pointerdown",z=>{if(z.button!==0)return;w.setPointerCapture(z.pointerId);const N=z.clientX,q=z.clientY;let J=z.clientX,H=z.clientY,de=0;const ge=xe=>{const at=e.state;de=Math.max(de,Math.hypot(xe.clientX-N,xe.clientY-q)),e.set({pan:{x:at.pan.x+(xe.clientX-J),y:at.pan.y+(xe.clientY-H)},fitted:!1}),J=xe.clientX,H=xe.clientY},Be=xe=>{w.removeEventListener("pointermove",ge),w.removeEventListener("pointerup",Be),w.removeEventListener("pointercancel",Be),xe.type==="pointerup"&&de<=Ar&&e.state.view==="wipe"&&_o(f,j,xe.clientX)};w.addEventListener("pointermove",ge),w.addEventListener("pointerup",Be),w.addEventListener("pointercancel",Be)});const I={ArrowLeft:[-1,0],ArrowRight:[1,0],ArrowUp:[0,-1],ArrowDown:[0,1]};f.addEventListener("keydown",w=>{if(w.target!==f||w.ctrlKey||w.metaKey||w.altKey)return;const z=I[w.key];if(!z)return;w.preventDefault();const N=w.shiftKey?.5:.1;Y(z[0]*N,z[1]*N)});const j={get:()=>e.state.wipe,set:w=>e.set({wipe:w}),names:we},re=Oo(d,f,j);function we(){const w=e.state.compare;return w?[w.label.toLowerCase(),"yours"]:["source","vector"]}const se=n("style",{"data-owner":"viewer-isolate"});document.head.append(se);function je(){const w=e.state.hoverFill;u.classList.toggle("isolating",!!w?.length),se.textContent=w?.length?Ml(w):""}e.on(["svg","source","result"],L),e.on(["compare"],Q),e.on(["zoom","pan","view","wipe","flicked","detail"],Ie),e.on(["show"],_e),e.on(["hoverFill"],je),new ResizeObserver(()=>{!e.state.svg&&!e.state.source||(fe(),e.state.show.anchors&&ce(),e.state.fitted&&S())}).observe(f),new MutationObserver(()=>{e.state.show.anchors&&ce(!0)}).observe(document.documentElement,{attributes:!0,attributeFilter:["data-theme"]});const ve=()=>{window.matchMedia(`(resolution: ${window.devicePixelRatio||1}dppx)`).addEventListener("change",()=>{e.state.show.anchors&&ce(),ve()},{once:!0})};return ve(),{el:p,redraw:L,transform:Ie,fit:S,goTo:F,zoomTo:C,panBy:Y}}function bl(e){return e&&e[0].toUpperCase()+e.slice(1)}function yl(e){let t="";for(const s of e){const o=s.trim();o&&(t+=o[0]==="M"?o:`M0 0${o}`)}return t}function wl(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=xl(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 vl=[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 xl(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)){d||l++;continue}let h=0;for(;c>=48&&c<=57;)h=h*10+(c-48),c=e.charCodeAt(++l);if(c===46){let f=0,p=0;for(c=e.charCodeAt(++l);c>=48&&c<=57;)p<15&&(f=f*10+(c-48),p++),c=e.charCodeAt(++l);h+=f*vl[p]}if(c===101||c===69){c=e.charCodeAt(++l);const f=c===45;(f||c===43)&&(c=e.charCodeAt(++l));let p=0;for(;c>=48&&c<=57;)p=p*10+(c-48),c=e.charCodeAt(++l);h*=10**(f?-p:p)}u&&(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 kl=1,Dt=16,Sl=256;function $l(e,t=Sl){const s=new Map,o=new Int32Array(e.length),a=[],i={sure:[],soft:[],unsure:[]},r=[],l=kl/2;for(let u=0;uh&&O.y0-lf&&(O.cls===d.cls?r[o[T]]=u+1:o[T]>$&&($=o[T]))}}let y=-1;for(const v of i[d.cls])if(v>$&&r[v]!==u+1){y=v;break}y<0&&(y=a.length,a.push(d.cls),i[d.cls].push(y)),o[u]=y;for(let v=E;v<=x;v++)for(let M=m;M<=k;M++){const A=M*65536+v,T=s.get(A);T?T.push(u):s.set(A,[u])}}const c=new Map;for(let u=0;uu[0]-d[0]).map(u=>u[1])}const vn=".pane.vector > .art > svg:not(.overlay)";function Ml(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[`${vn} * { fill-opacity: 0.12 !important; stroke-opacity: 0.12 !important; }`,`${vn} :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; }`,`${vn} :is(${s("stroke")}) { stroke-opacity: 1 !important; }`].join(` `)}const El="(max-width: 760px), (max-height: 500px)";function Al(){return typeof window<"u"&&typeof window.matchMedia=="function"&&window.matchMedia(El).matches}function Cl(e){const t=n("aside.activity",{"aria-label":"Engine activity","aria-live":"off"});Al()&&e.set({logOpen:!1});const s=n("button.activityhead",{type:"button","data-ctl":"activity-toggle",onclick:()=>e.set({logOpen:!e.state.logOpen})}),o=n("div.activitybody",{role:"log",tabindex:"0","aria-label":"Engine log lines"});t.append(s,o);const a=(r,l)=>{if(r.kind==="note")return n("div.activityline.note",null,n("span.t",null,$s(r.at)),n("span.w",null,r.text??""));const c=l&&r.step?` · ${Rn(r.step)}`:"";return n(`div.activityline${l?".running":".done"}`,null,n("span.t",null,$s(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?"":Je(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?Je(r.traceEnded-r.traceStarted):null;if(D(s,r.tracing?n("span.pulse"):null,n("span.title",null,"Engine log"),r.tracing?n("span.num.muted",{"data-live":"elapsed"},Je(performance.now()-r.traceStarted)):c?n("span.num.muted",null,c):null,n("span.chev",null,r.logOpen?"Hide":"Show")),!r.logOpen){D(o);return}const u=o.scrollHeight-o.scrollTop-o.clientHeight<24,d=r.traceLog.slice(-160);let h=-1;if(r.tracing)for(let f=d.length-1;f>=0&&h<0;f--)d[f].kind==="stage"&&d[f].ms===void 0&&(h=f);D(o,...d.map((f,p)=>a(f,p===h))),u&&(o.scrollTop=o.scrollHeight)};return e.on(["traceLog","tracing","logOpen","source","tab","screen","liveNow"],i),i(),t}function Tl(e,t,s){const o=ml(e),a=n("div.viewertools"),i=n("div",{style:{flex:"1",minHeight:"0",position:"relative",display:"flex"}}),r=n("div.statusstrip"),l=n("main.stage",null,a,i,r);let c="";const u=()=>{const p=e.state,g=!!p.source,m=[1,4,12].map(v=>!p.fitted&&Math.abs(p.zoom-v)<.01),k=JSON.stringify([g,p.view,p.show,p.fitted,m,p.detail,!!p.svg,p.bandsMissing,p.settings.mode]);if(k===c)return;c=k;const E=document.activeElement instanceof HTMLElement&&a.contains(document.activeElement)?document.activeElement.getAttribute("data-ctl"):null,x=(v,M)=>n("button",{"aria-pressed":String(M==="fit"?p.fitted:!p.fitted&&Math.abs(p.zoom-M)<.01),disabled:!g,"data-ctl":`zoom:${M}`,onclick:()=>M==="fit"?o.fit():o.zoomTo(M)},v),$=.25,y=(v,M,A)=>n(`button.pan.${v}`,{"aria-label":`Pan ${v}`,title:`Pan ${v} (or focus the viewer and use the arrow keys)`,"data-ctl":`pan:${v}`,onclick:()=>o.panBy(M*$,A*$)},V(v==="left"||v==="right"?"chevronRight":"chevronDown",12));D(a,n("div.seg",null,...["side","wipe","ab"].map(v=>n("button",{"aria-pressed":String(p.view===v),disabled:!g,"data-ctl":`view:${v}`,onclick:()=>e.set({view:v})},v==="side"?"Side by side":v==="wipe"?"Wipe":"A/B"))),n("span.muted.flickhint",{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,M])=>n("button.toggle",{"aria-pressed":String(p.show[v]),"data-ctl":`show:${v}`,disabled:!p.svg||v==="certainty"&&p.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:()=>{p.show[v]=!p.show[v],e.touch("show")}},M))),n("div",{style:{marginLeft:"auto",display:"flex",alignItems:"center",gap:"12px"}},p.show.wireframe||p.show.anchors||p.show.handles||p.show.certainty?n("div.legend",null,p.show.anchors?n("span",null,n("i.anchor"),"anchor"):null,p.show.handles?n("span",null,n("i.handle"),"handle"):null,p.show.wireframe?n("span",null,n("i.wire"),"wireframe"):null,...p.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(p.detail),disabled:!p.svg,"data-ctl":"detail",title:"Show the source's pixel grid under the vector edge",onclick:()=>{const v=!p.detail;e.set({detail:v}),v&&p.zoom<12&&o.zoomTo(12)}},"Detail"),g&&!p.fitted?n("div.seg.panpad",{role:"group","aria-label":"Pan the view"},y("left",-1,0),y("up",0,-1),y("down",0,1),y("right",1,0)):null,n("div.seg.num",null,x("Fit","fit"),x("1×",1),x("4×",4),x("12×",12)))),E&&(a.querySelector(`[data-ctl="${E}"]`)??(E.startsWith("pan:")?a.querySelector('[data-ctl="zoom:fit"]'):null))?.focus({preventScroll:!0})},d=n("div",{style:{display:"contents"}});i.append(o.el,d,Cl(e));const h=()=>{const p=e.state;if(!p.source){o.el.style.display="none",D(d,Ol(e,t,s(),Il(p.stageState)));return}o.el.style.display="",D(d,Dl(p.stageState,p,t),p.svg?Ll(e):null,p.detail&&p.svg?zl(e,t):null),p.zoom===1&&p.pan.x===0&&p.pan.y===0&&o.fit()},f=()=>{const p=e.state,{lead:g,rest:m,colour:k}=Nl(p),E=p.tracing?n("span",null,n("span.num",{"data-live":"elapsed"},en(p))," elapsed · Esc to cancel"):null;D(r,n("span.lead",{style:{color:k}},g),m?n("span",null,m):null,E,p.update?.newer?n("span",{style:{display:"flex",alignItems:"center",gap:"8px",marginLeft:"12px"}},n("span.updatechip",null,n("i"),`${p.update.latest} available`),n("button.reset",{onclick:t.showUpdate},"What changed")):null,Pl(e,t),n("span.privacy",null,"Your image never leaves this computer."))};return e.on(["source","view","show","zoom","fitted","detail","svg","bandsMissing","settings"],u),e.on(["source","svg","stageState","result","detail","worstCorner","prefs"],h),e.on(["tracing","tracingEngine","liveStages","liveNow","report","result","resultEngine","source","update","denoiserFetch","settings"],f),u(),h(),f(),window.addEventListener("keydown",p=>{if(e.state.screen||e.state.tab!=="vectorize"||p.target instanceof HTMLInputElement||p.target instanceof HTMLTextAreaElement)return;const g=e.state;p.key===" "&&!p.repeat?(p.preventDefault(),e.set({flicked:!0})):p.key==="0"?o.fit():p.key==="+"||p.key==="="?o.zoomTo(g.zoom*1.4):p.key==="-"&&o.zoomTo(g.zoom/1.4)}),window.addEventListener("keyup",p=>{p.key===" "&&e.set({flicked:!1})}),{el:l,viewer:o,mount:p=>i.append(p)}}function Pl(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=Yn(o);return n("span.fetchchip",{"data-ctl":"denoiser-chip",title:"The denoiser downloads in the background, once; later visits read it from this browser"},Ut(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"},Ut(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 Ll(e){const t=e.state,s=t.tracing?"stale":t.result?.tier==="draft"?"draft":"final",o=t.tracing?`re-tracing · ${mt(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 zl(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,_l(e)))}function Dl(e,t,s){const o=(a,i,r,l,c)=>n("div.stagestate",null,n("span",{style:{color:i,display:"flex"}},V(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",zn(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 Fl(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 Nl(e){const t=e.report;if(e.tracing){const s=Mn(e.tracingEngine).toLowerCase();return{lead:Qt(e),rest:`· ${s} · ${mt(e,e.tracingTier)??"—"} px ·`,colour:"var(--state-stale)"}}if(t&&e.result){const s=e.result.tier==="draft",o=Mn(e.resultEngine);return s?{lead:`${o} draft`,rest:`· ${t.tracedPx} px · full trace queued`,colour:"var(--state-draft)"}:{lead:`${o} in ${gt(t.seconds)}`,rest:`· ${t.tracedPx} px · final · ${oe(t.segments)} segments`,colour:"var(--state-final)"}}return e.source?{lead:"Opened",rest:`· ${e.source.width} × ${e.source.height} · ${e.source.container}`,colour:"var(--dim)"}:{lead:"Ready",rest:"",colour:"var(--dim)"}}function Il(e){switch(e.kind){case"undecodable":return{title:"That file did not open",body:zn(e.message,!0),formats:!0};case"failed":return{title:"The trace stopped",body:zn(e.message),formats:!1};case"outOfMemory":return{title:"That image is too large to open here",body:`About ${e.neededGb.toFixed(1)} GB would be needed. A smaller copy of it will open.`,formats:!1};default:return null}}function zn(e,t=!1){let s=e.replace(/^(Error:\s*)+/,"");return t&&(s=s.replace(/\s*PNG, JPEG, WebP, BMP, GIF and TIFF are supported\.?\s*$/,"")),s}const Qs="PNG, JPEG, WebP, GIF, BMP, TIFF or SVG";function Ol(e,t,s,o=null){const a=co(e.state.caps?.platform);return n("div.firstrun",null,o?n("div.firstfail",{role:"alert"},n("span.glyph",null,V("alert",20)),n("div",null,n("span.title",null,o.title),n("span.body",null,o.body),o.formats?n("span.body.faint",null,`Inkvec opens ${Qs} files. A photo from a phone (HEIC, AVIF) or a PDF opens once it is saved as a PNG or JPEG.`):null)):null,o?null:Fl(e,t),n("div.drop",null,n("span.glyph",null,V("image",32)),n("div",{style:{display:"flex",flexDirection:"column",gap:"6px"}},n("span.headline.resting",null,"Drop an image"),n("span.headline.release",null,"Release to trace"),n("span.faint.resting",{style:{fontSize:"13px"}},Qs),n("span.faint.resting.kbdhint",{style:{fontSize:"13px"}},"or press ",n("kbd",null,`${a}+O`)," to choose a file")),n("button.btn.primary.resting",{onclick:t.openFile},o?"Open another image":"Open an image")),s.length?n("div.samples",null,n("span.eyebrow",null,"Or try one of these"),n("div.samplerow",null,...s.map(i=>n("button.sample",{onclick:()=>t.openSample(i.file)},n("span.thumb",null,n("img",{src:i.preview,alt:""})),n("span.label",null,i.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:uo,target:"_blank",rel:"noopener noreferrer"},"Inkvec Studio for the desktop"),"."))}function Ho(e,t){const s=e.state.worstCorner;s&&(e.set({detail:!0}),t.goTo(s.x,s.y,12))}function _l(e){const t=e.state.worstCorner;return t?`Worst disagreement: ${ue(t.de00)} dE00, at ${t.x} × ${t.y} in the traced raster.`:"The trace and the source agree everywhere we can measure."}function Uo(e,t,s){e[t]=s}const b=new Ia(Na(fo,{tolerancePx:.1,judgePx:1024,cornerDegrees:30,documentCleanup:!0}));let jo=[];const Vo=new ai;function eo(){const e={...b.state.settings};return delete e.colourGroups,b.state.colourGroups.length&&(e.colourGroups=b.state.colourGroups),e}const dt=new Map,Bl=8,De=new si({hasSource:()=>!!b.state.source,groups:()=>b.state.colourGroups,start:async(e,t)=>{const s=t?{...eo(),mode:"fast"}:eo(),o=await _.startTrace(s,e);dt.set(o,s.mode);for(const a of dt.keys())ab.state.prefs?.settleMs??800,shown:()=>({generation:b.state.generation,tracing:b.state.tracing}),began:(e,t,s)=>b.set({generation:e,tracing:!0,tracingTier:t,tracingEngine:dt.get(e)??b.state.settings.mode,liveStages:[],liveNow:null,traceLog:[],traceStarted:s,traceEnded:0}),progressed:e=>b.set(ii(b.state,e,performance.now())),finished:(e,t)=>Rl(e,t),failed:e=>b.set({tracing:!1,liveNow:null,traceEnded:performance.now(),stageState:{kind:"failed",message:String(e)}}),cancel:()=>{_.cancelTrace(),b.set({tracing:!1,liveNow:null,traceEnded:performance.now(),stageState:b.state.svg?{kind:"cancelled"}:{kind:"empty"}})}});function yt(e){return De.trace(e)}function os(){De.cancel()}function Xe(){De.controlChanged()}function Ko(){return new Promise(e=>{if(!b.state.source){e();return}const t=b.on(["result","stageState"],()=>{b.state.tracing||(t(),e())});De.clearSettle(),yt("final")})}const xn=new oi;function Rl(e,t=b.state.generation){const s=xn.take();if(e.state!=="traced"||!b.state.snaps.length){kn(e,t);return}Yo(e).then(o=>{xn.isNewest(s)&&kn(e,t,o)},o=>{xn.isNewest(s)&&(G(`The snapped colours could not be applied to this trace: ${o}`,{kind:"bad"}),kn(e,t))})}async function Yo(e){for(;;){const t=b.state.snaps;if(!t.length)return{svg:e.svg,inks:e.palette};const s=e.report.tracedPx,o=await _.snapInks(e.svg,t,s,s);if(b.state.snaps===t)return o}}function kn(e,t,s=null){const o=t!==b.state.generation&&b.state.tracing;if(e.state!=="cancelled"){if(o||b.set({liveNow:null,traceEnded:performance.now()}),e.state==="traced"){const a=b.state.result?.tier==="draft",i=b.state.result?.tier==="final"?b.state.report:b.state.previous;b.set({result:e,resultGeneration:t,resultEngine:dt.get(t)??b.state.settings.mode,resultGroups:De.groupsFor(t)??b.state.resultGroups,bandsMissing:!1,previous:e.tier==="final"?i:b.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"}),b.state.justSwapped&&window.setTimeout(()=>b.set({justSwapped:!1}),600),e.tier==="final"&&b.set({groupSuggestions:wo(b.state)});const r=b.state.auto;r&&t===r.generation&&e.tier==="final"&&!r.report&&(b.set({auto:{...r,svg:e.svg,report:e.report,palette:e.palette,losses:e.losses}}),_.sourceFacts(r.settings.traceSize).then(l=>{b.state.auto?.generation===t&&b.set({facts:l})},()=>{})),e.oversized&&e.tier==="final"&&G(`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(b.set({tracing:!1}),e.state){case"flat":b.set({stageState:{kind:"flat"},svg:null,report:null,palette:[],losses:[]});break;case"undecodable":b.set({stageState:{kind:"undecodable",message:e.message}});break;case"outOfMemory":b.set({stageState:{kind:"outOfMemory",neededGb:e.neededGb,suggestPx:e.suggestPx}});break;case"failed":b.set({stageState:{kind:"failed",message:e.message}});break}}}async function tt(e,t=!0){nt.close(),De.detach(),b.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(),b.set({zoom:1,pan:{x:0,y:0},stageState:{kind:"drawing"}}),await De.open(Gl()),Vo.reset();const s=b.state;s.tracing&&b.set({auto:{generation:s.generation,settings:{...s.settings},preset:s.preset,svg:null,report:null,palette:[],losses:[]}}),t&&Wl()}catch(s){b.set({stageState:{kind:"undecodable",message:String(s)}})}}function Gl(){const e=b.state;if(e.settings.mode==="fast")return!1;const t=mt(e,"final"),s=mt(e,"draft");return t!==null&&s!==null&&t>s}function Wl(){const e=b.state.prefs?.onOpen??"ask";e==="custom"?nt.open():e==="ask"&&b.set({chooser:!0})}async function as(e){await tt(async()=>{const t=await _.openPath(e);b.set({source:t});const s=await _.loadPrefs();b.set({prefs:s})})}async function _t(e){if(e.path)return as(e.path);const t=e.file;t&&await tt(async()=>{const s=await _.openBytes(new Uint8Array(await t.arrayBuffer()),e.name);b.set({source:s})})}async function is(){const[e]=await Bn([{name:"Images and SVG",extensions:["png","jpg","jpeg","webp","gif","bmp","tif","tiff","svg"]}]);e&&await _t(e)}async function ql(e,t){b.set({tab:"vectorize"}),jt(),await tt(async()=>{const s=await _.openBytes([...new TextEncoder().encode(e)],t);b.set({source:s})},!1)}window.addEventListener("inkvec:retrace",e=>{const{svg:t,name:s}=e.detail;ql(t,s)});const Zo=n("header.appbar"),pt=n("div.body"),Hl=nl(b,{applyPrefs:e=>void st(e),afterReset:e=>{Vt(e.theme),ea(e)},close:()=>b.set({screen:null})});function Jo(e,t){Uo(b.state.settings,e,t),b.touch("settings"),b.state.caps?.denoiser,e==="cleanUpDamage"&&t!=="off"&&ta(),Xe()}const on=Tl(b,{openFile:()=>void is(),openSample:e=>void tt(async()=>{const t=await _.openSample(e);b.set({source:t})}),traceNow:()=>void yt("final"),cancel:os,retryAt:e=>{b.state.settings.traceSize=e,b.touch("settings"),yt("final")},jumpToWorst:()=>Ho(b,on.viewer),openDenoiser:()=>ss(b),retryDenoiser:()=>St.retryDenoiser(),showUpdate:()=>{const e=b.state.update;e&&Jt(e.url)},markSeen:()=>void st({seenFirstRun:!0}),changeSetting:Jo},()=>jo);function rs(e){return ei(b.state.caps,b.state.prefs,e)}function to(){const e=b.state.preset;return e&&rs(e)?.settings||fo}function Xo(e){b.set({settings:{...e.settings},preset:e.preset,colourGroups:[...e.colourGroups]}),Xe()}const St={setPreset:e=>{const t=rs(e);t&&(b.set({preset:e,settings:{...t.settings}}),t.settings.cleanUpDamage!=="off"&&ta(),Xe())},savePreset:e=>{const t=b.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:{...b.state.settings}}];st({saved:o}).then(()=>{const a=b.state.prefs?.saved.find(i=>i.id===s);a?(b.set({preset:s}),G(`Saved as "${a.name}".`,{kind:"good"})):G("The preset tray is full. Forget one and try again.",{kind:"bad"})})},deletePreset:e=>{const t=b.state.prefs;t&&(st({saved:t.saved.filter(s=>s.id!==e)}),b.state.preset===e&&b.set({preset:null}))},baseSettings:to,changeSetting:Jo,resetGroup:e=>{const t=to(),s={...b.state.settings};for(const o of b.state.caps?.controls??[])o.group===e&&!Jn.has(o.key)&&vt(o,s)&&Uo(b.state.settings,o.key,t[o.key]);b.touch("settings"),Xe()},traceNow:()=>void yt("final"),cancel:os,snap:e=>void Ul(e),setColourGroups:e=>{b.set({colourGroups:e,paletteSelection:[]}),Xe()},openExport:()=>Fo(b,an,Ko),copySvg:async()=>{b.state.svg&&(await Zt(b.state.svg),G("SVG copied. Paste straight into Figma or Illustrator."))},saveCard:()=>ur(b),openDenoiser:()=>ss(b),retryDenoiser:()=>void _.denoiserDownload().catch(e=>G(String(e),{kind:"bad"})),jumpToWorst:()=>Ho(b,on.viewer),backToAuto:()=>{const e=b.state.auto;e&&Xo({settings:e.settings,preset:e.preset,colourGroups:b.state.colourGroups})},hideAutoNote:()=>b.set({autoNoteHidden:!0}),openWizard:()=>nt.open()},an=ji(b,St),Qo={...St,restore:Xo,setOnOpen:e=>void st({onOpen:e}),markSeen:()=>void st({seenFirstRun:!0})},nt=cr(b,an,Qo,Vo);on.mount(ir(b,Qo,()=>nt.open()));async function Ul(e){const t=b.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())];b.set({snaps:i});try{const r=await Yo(s);if(b.state.result!==s)return;b.set({svg:r.svg,palette:r.inks})}catch(r){b.state.snaps===i&&b.set({snaps:o}),G(String(r),{kind:"bad"})}}let Dn=null,Fn=null,no=null;function jt(){const e=b.state;e.tab==="vectorize"?D(pt,on.el,an):e.tab==="minify"?(Dn??=Tr(b),D(pt,Dn)):e.tab==="fabricate"?(Fn??=zr(b),D(pt,Fn)):(no??=mr(b),D(pt,no))}function so(){gr(Zo,b,{openFile:()=>void is(),openPath:e=>void as(e)})}async function st(e){const t=b.state.prefs;if(!t)return;const s={...t,trace:Tn(b.state.settings),ui:Cn(b.state),...e},o=await _.savePrefs(s);b.set({prefs:o}),Vt(o.theme)}async function jl(e){const t=b.state.prefs;if(t)try{const s=await _.savePrefs({...t,...e});b.state.prefs===t&&(b.state.prefs=s)}catch{}}const Vl=["vectorize","minify","fabricate"];function ea(e){Ga(b,e,Vl,t=>rs(t)!==null)}function Vt(e){const t=e==="dark"||e==="system"&&window.matchMedia("(prefers-color-scheme: dark)").matches;document.documentElement.dataset.theme=t?"dark":"light"}function Kl(e){const t=e.metaKey||e.ctrlKey,s=e.target instanceof HTMLInputElement||e.target instanceof HTMLTextAreaElement;if(t&&e.key.toLowerCase()==="o"){e.preventDefault(),is();return}if(t&&e.key.toLowerCase()==="e"&&b.state.svg){e.preventDefault(),Fo(b,an,Ko);return}if(t&&e.key===","){e.preventDefault(),b.set({screen:"settings"});return}if(s)return;const o=ti(e.key);if(t&&o!==null){const a=b.state.caps?.presets[o];a&&(e.preventDefault(),b.set({preset:a.id,settings:{...a.settings}}),Xe());return}e.key==="Escape"&&(b.state.screen?b.set({screen:null}):nt.isOpen()?nt.close(!0):b.state.chooser?b.set({chooser:!1}):b.state.tracing&&os())}async function Yl(){{fa(b,e=>void Zl(e.map(po)));return}}async function Zl(e){if(!e.length)return;const t=e.filter(s=>s.name.toLowerCase().endsWith(".svg"));if(t.length===1&&e.length===1&&b.state.tab==="vectorize"){await _t(t[0]);return}if(t.length){const s=b.state.tab==="fabricate"?"fabricate":"minify";b.set({tab:s}),jt(),await(s==="fabricate"?Fn:Dn)?.acceptDropped?.(t[0]);return}if(e.length>1){G(`${e.length} files dropped. ${Yt} opens one image at a time; the desktop app has Batch.`),await _t(e[0]);return}b.set({tab:"vectorize"}),await _t(e[0])}async function Jl(){const e=document.getElementById("app");if(!e)return;e.append(Zo,pt,Hl),ba(e),Ea(),so(),jt(),b.on(["tab","source","prefs","caps"],so),b.on(["tab"],jt),window.addEventListener("keydown",Kl),fr(b),window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",()=>Vt(b.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(),Jt(l).catch(()=>G("Could not open the link in your browser.",{kind:"bad"})))});const t=ri(()=>b.state.tracing,()=>b.state.traceStarted,()=>b.state.liveNow?.since??null);b.on(["tracing","liveNow","traceLog","liveStages","tab","railTab"],t),await Ae.traceProgress(i=>De.onProgress(i)),await Ae.traceDone(({generation:i,outcome:r})=>De.onDone(i,r)),await Ae.openPath(i=>void oo(i)),await Ae.batchRow(i=>{const r=b.state.batch,l=r.rows.findIndex(c=>c.id===i.id);l>=0&&(r.rows[l]=i),b.touch("batch")}),await Ae.batchTotals(i=>{b.state.batch.totals=i,b.touch("batch")}),await Ae.batchFinished(i=>{const r=b.state.batch;r.rows=i,r.running=!1,r.paused=!1,b.touch("batch");const l=i.filter(c=>c.state==="failed").length;G(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 Ae.denoiserFetch(Ql);const[s,o]=await Promise.all([_.capabilities(),_.loadPrefs()]);b.set({caps:s,prefs:o,settings:o.trace}),Vt(o.theme),ea(o),qa(b,i=>void jl(i)),Sn(`Engine ${s.engineVersion} · ${s.buildTarget}`,.55),jo=await _.listSamples(),b.touch("source"),Sn("Preparing the workspace…",.85),await Yl(),Sn("Ready",1),_.appReady().catch(()=>{});const a=await _.launchPath().catch(()=>null);a&&oo(a),ya().then(Xl),o.checkUpdates&&_.checkUpdate().then(i=>b.set({update:i})).catch(()=>{})}async function Xl(e){e&&(b.set({tab:"vectorize",screen:null}),"sample"in e?await tt(async()=>b.set({source:await _.openSample(e.sample)})):await tt(async()=>b.set({source:await _.openBytes(e.bytes,e.name)})))}async function oo(e){b.set({tab:"vectorize",screen:null}),await as(e)}function ta(){const e=b.state.denoiserFetch;if(!b.state.caps?.denoiser.supported||e?.phase==="ready")return;const t=Ht(e,!0);G(e?.phase==="failed"?"The denoiser did not download. The trace runs without it.":`${Zn(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:()=>St.retryDenoiser()}}:{})}function Ql(e){const t=b.state.denoiserFetch?.phase;b.set({denoiserFetch:e});const s=b.state.caps;(e.phase==="stored"||e.phase==="ready")&&s&&!s.denoiser.installed&&_.denoiserStatus().then(a=>{const i=b.state.caps;i&&b.set({caps:{...i,denoiser:a}})});const o=b.state.settings.cleanUpDamage!=="off";e.retrace&&o&&b.state.source?(G("The denoiser is ready. Tracing again with it.",{kind:"good"}),yt("final")):e.phase==="failed"&&t!=="failed"&&o&&G(`The denoiser did not download: ${e.message??"the connection dropped"}.`,{kind:"bad",action:{label:"Retry",run:()=>St.retryDenoiser()}})}function Sn(e,t){_.startupProgress(e,t).catch(()=>{})}Jl().catch(e=>{_.appReady().catch(()=>{});const t=document.getElementById("app");t&&D(t,n("div.firstrun",null,n("span.serif",{style:{fontSize:"24px"}},`${Yt} could not start`),n("span.faint",{style:{maxWidth:"50ch",textAlign:"center"}},String(e))))});