diff --git "a/studio/assets/main-Dn0s9OJH.js" "b/studio/assets/main-Dn0s9OJH.js" new file mode 100644--- /dev/null +++ "b/studio/assets/main-Dn0s9OJH.js" @@ -0,0 +1,17 @@ +var wo=Object.defineProperty;var vo=(e,t,s)=>t in e?wo(e,t,{enumerable:!0,configurable:!0,writable:!0,value:s}):e[t]=s;var Y=(e,t,s)=>vo(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 Tt(e,t={},s){return window.__TAURI_INTERNALS__.invoke(e,t,s)}function xo(){return!!(globalThis||window).isTauri}var Dn;(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"})(Dn||(Dn={}));var Fn;(function(e){e[e.Critical=1]="Critical",e[e.Informational=2]="Informational"})(Fn||(Fn={}));var Nn;(function(e){e.None="none",e.Normal="normal",e.Indeterminate="indeterminate",e.Paused="paused",e.Error="error"})(Nn||(Nn={}));var In;(function(e){e.Disabled="disabled",e.Throttle="throttle",e.Suspend="suspend"})(In||(In={}));var _n;(function(e){e.Default="default",e.FluentOverlay="fluentOverlay"})(_n||(_n={}));var Bn;(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"})(Bn||(Bn={}));var On;(function(e){e.FollowsWindowActiveState="followsWindowActiveState",e.Active="active",e.Inactive="inactive"})(On||(On={}));const ko=` + +`;function n(e,t=null,...s){const[o,...a]=e.split("."),i=document.createElement(o||"div");return a.length&&(i.className=a.join(" ")),So(i,t),cn(i,s),i}function ve(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 cn(i,s),i}function So(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 cn(e,t){for(const s of t.flat(8))s==null||s===!1||e.append(s instanceof Node?s:document.createTextNode(String(s)))}function N(e,...t){e.replaceChildren(),cn(e,t)}const Rn={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 q(e,t=16){const s=Rn[e]??Rn.info;return ve("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"},ve("path",{d:s}))}function ks(e=18){const t=$o.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 $o=(()=>{const e=new DOMParser().parseFromString(ko,"image/svg+xml").documentElement;return document.importNode(e,!0)})();function Mo(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)}),window.addEventListener("paste",i=>{if(i.target?.closest?.("input, textarea, [contenteditable]"))return;const l=[...i.clipboardData?.files??[]];l.length&&(i.preventDefault(),t(l))})}const Co=760;function To(e){const t=window.matchMedia(`(max-width: ${Co}px)`).matches,s=window.matchMedia("(pointer: coarse)").matches;if(!(t&&s))return;const o=n("div.phonenote",{role:"dialog","aria-label":"Best on a larger screen"},n("div.phonecard",null,n("span.serif",{style:{fontSize:"22px"}},"Best on a larger screen"),n("p",null,"Inkvec Studio Lite is a full editor: a viewer, a rail of controls and a palette side by side. On a phone they do not fit. Open this page on a laptop or desktop to use it; everything runs in your browser and nothing is uploaded."),n("button.btn",{onclick:()=>o.remove()},"Continue anyway")));e.append(o)}async function Ao(){const e=new URLSearchParams(window.location.search),t=e.get("sample"),s=e.get("open")==="handoff";return!t&&!s?null:(window.history.replaceState(null,"",window.location.pathname),t?{sample:t}:new Promise(o=>{let a;try{a=indexedDB.open("inkvec-handoff",1)}catch{o(null);return}a.onupgradeneeded=()=>a.result.createObjectStore("files"),a.onerror=()=>o(null),a.onsuccess=()=>{const i=a.result.transaction("files","readwrite").objectStore("files"),r=i.get("open");r.onsuccess=()=>{i.delete("open");const l=r.result;o(l?.bytes?{name:l.name??"image",bytes:l.bytes}:null)},r.onerror=()=>o(null)}}))}let gt=0,vt=null,Kt=0,Qe=!1,Ss=performance.now();const _t=e=>(e/(1024*1024)).toFixed(1);function zo(){Kt=0;const e=vt;if(vt=null,!e||Qe)return;gt=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(gt*100);s&&(s.style.transform=`scaleX(${gt})`),t?.setAttribute("aria-valuenow",String(r)),o&&(o.textContent=e.text),a&&(a.textContent=`${r}%`),i&&(i.textContent=e.detail)}function tt(e,t,s=""){Qe||(Ss=performance.now(),vt={text:e,fraction:Math.max(gt,vt?.fraction??0,Math.min(1,t)),detail:s},Kt||(Kt=requestAnimationFrame(zo)))}function Eo(e,t){const s=t>0?Math.min(1,e/t):0;tt("Downloading the engine",.04+.8*s,t>0?`${_t(e)} of ${_t(t)} MB`:`${_t(e)} MB`)}function Lo(){tt("Starting the engine",.88,"Compiling, and starting a thread per core")}function Po(e,t){tt(e,.9+.1*Math.min(1,Math.max(0,t)))}function Do(){if(Qe)return;const e=document.getElementById("boot-fill");e&&(e.style.transitionDuration="0.12s"),tt("Ready",1),window.setTimeout(()=>{Qe=!0;const t=document.getElementById("boot");t&&(t.classList.add("leaving"),window.setTimeout(()=>t.remove(),260))},130)}{window.__inkvecBoot=!0,tt("Loading the Studio",.03);const e=window.setInterval(()=>{if(Qe){window.clearInterval(e);return}if(performance.now()-Ss>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 Fo(){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 Oe(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)}var No={pkg:5329722,"pkg-threads":5366604};const Gn="inkvec-studio-lite:prefs",Wn="inkvec-denoiser-v1",Io="inkvec-ort-",_o=3e4,Hn="Not available in the browser. Inkvec Studio, the desktop app, has it.",Un=[["flat-logo.png","Flat logo"],["icon-64.png","Icon, 64 px"],["crest-filigree.png","Crest, filigree"],["signature-bw.png","Signature, B&W"]];class Bo{constructor(){Y(this,"worker",null);Y(this,"ready",null);Y(this,"queue",[]);Y(this,"running",null);Y(this,"nextId",1);Y(this,"listeners",new Map);Y(this,"generation",0);Y(this,"previewGeneration",0);Y(this,"shared",null);Y(this,"source",null);Y(this,"prefs",null);Y(this,"prefsTimer",0);Y(this,"denoiser",null);Y(this,"denoiserPort",null);Y(this,"downloading",!1);Y(this,"fetchState",{phase:"idle",got:0,total:null});Y(this,"wanted",!1);Y(this,"cancelled",!1);Y(this,"retried",!1);Y(this,"info",null);Y(this,"denoiserRuns",0);Y(this,"last",null)}base(){return new URL("./",document.baseURI).href}start(){const t=typeof crossOriginIsolated<"u"&&crossOriginIsolated;t&&!this.shared&&(this.shared=new Int32Array(new SharedArrayBuffer(4)));const s=new Worker(new URL(""+new URL("engine.worker-DxLbviFI.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"?(Eo(c.got,c.total),c.done&&Lo()):c.type==="instantiated"?t&&(this.startDenoiser(c.denoiserUrl,c.denoiserSha256),this.prefetchDenoiser()):c.type==="ready"?(this.info={threads:c.threads,isolated:c.isolated,version:c.version},c.poolError&&console.warn("inkvec: thread pool did not start:",c.poolError),t&&this.startDenoiser(c.denoiserUrl,c.denoiserSha256),i(c)):c.type==="loadfail"?r(new Error(`The engine could not load: ${c.error}`)):c.type==="stage"?c.generation===this.generation&&this.emit("trace:stage",{generation:c.generation,name:c.name,ms:c.ms}):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:"bb166903cf5e",generations:this.shared?this.shared.buffer:null,denoiserPort:o,wasmBytes:No},a),this.ready}whenReady(){return this.ready??this.start()}crashed(t){const s=this.running;this.running=null,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;this.whenReady().then(async()=>{o&&await this.enqueue("open_bytes",-1,{bytes:o.bytes.slice(),name:o.name}).catch(()=>{}),this.pump()})}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,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 Po(String(s.text??""),Number(s.progress??0)),null;case"app_ready":return Do(),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(!Un.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 Un.map(([o,a])=>({file:o,label:a,preview:new URL(`samples/${o}`,this.base()).href}));case"launch_path":return null;case"start_trace":{const o=s.request,a=this.bump();this.rememberTrace(o.settings);const i=this.withoutUnreadyDenoiser(o),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),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 Oe(a[0].name,a[0].data),[a[0].name];const r=`${i}-inkvec.zip`;return a[0]&&Oe(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 Oe(o.split(/[\\/]/).pop()||"download",new Uint8Array(s.bytes)),null}case"batch_scan":case"batch_start":case"batch_stats_csv":throw new Error(Hn);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(Wn);for(const o of await caches.keys())o.startsWith(Io)&&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:Hn}}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.enqueue("open_bytes",0,{bytes:t,name:s})}async loadPrefs(){if(this.prefs)return this.prefs;let t=null;try{const s=localStorage.getItem(Gn);t=s?JSON.parse(s):null}catch{t=null}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(){try{localStorage.setItem(Gn,JSON.stringify(this.prefs))}catch{}}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:"bb166903cf5e",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"})},_o));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(Wn)).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}}let it=null;function $s(){return it||(it=new Bo,globalThis.__inkvecStudioLite=it),it}function G(e,t){return $s().invoke(e,t)}function we(e,t){return $s().listen(e,t)}const O={capabilities:()=>G("capabilities"),startupProgress:(e,t)=>G("startup_progress",{text:e,progress:t}),appReady:()=>G("app_ready"),openPath:e=>G("open_path",{path:e}),openBytes:(e,t)=>G("open_bytes",{bytes:e,name:t??null}),openSample:e=>G("open_sample",{name:e}),listSamples:()=>G("list_samples"),startTrace:(e,t)=>G("start_trace",{request:{settings:e,tier:t}}),cancelTrace:()=>G("cancel_trace"),startPreview:e=>G("start_preview",{request:{settings:e,tier:"draft"}}),cancelPreviews:()=>G("cancel_previews"),sourceFacts:e=>G("source_facts",{maxDim:e}),launchPath:()=>G("launch_path"),traceBands:e=>G("trace_bands",{generation:e}),snapInks:(e,t,s,o)=>G("snap_inks",{svg:e,snaps:t,width:s,height:o}),matchPalette:(e,t)=>G("match_palette",{traced:e,pasted:t}),planExport:e=>G("plan_export",{request:e}),writeExport:(e,t)=>G("write_export",{request:e,destination:t}),minify:(e,t)=>G("minify_svg",{svg:e,settings:t}),fabAnalyze:e=>G("fab_analyze",{svg:e}),fabPrepare:(e,t)=>G("fab_prepare",{svg:e,options:t}),readTextFile:e=>G("read_text_file",{path:e}),saveBytes:(e,t)=>G("save_bytes",{path:e,bytes:t}),batchScan:e=>G("batch_scan",{folder:e}),batchStart:e=>G("batch_start",{plan:e}),batchPause:e=>G("batch_pause",{paused:e}),batchCancel:()=>G("batch_cancel"),batchStatsCsv:()=>G("batch_stats_csv"),denoiserStatus:()=>G("denoiser_status"),denoiserDownload:()=>G("denoiser_download"),denoiserCancel:()=>G("denoiser_cancel"),denoiserRemove:()=>G("denoiser_remove"),loadPrefs:()=>G("load_prefs"),savePrefs:e=>G("save_prefs",{prefs:e}),resetPrefs:()=>G("reset_prefs"),thirdPartyNotices:()=>G("third_party_notices"),checkUpdate:()=>G("check_update"),cliStatus:()=>G("cli_status"),installCli:()=>G("install_cli"),removeCli:()=>G("remove_cli"),contextMenuStatus:()=>G("context_menu_status"),installContextMenu:()=>G("install_context_menu"),removeContextMenu:()=>G("remove_context_menu")},xe={traceStage:e=>we("trace:stage",t=>e(t.payload)),traceDone:e=>we("trace:done",t=>e(t.payload)),previewDone:e=>we("preview:done",t=>e(t.payload)),openPath:e=>we("open:path",t=>e(t.payload)),batchRow:e=>we("batch:row",t=>e(t.payload)),batchTotals:e=>we("batch:totals",t=>e(t.payload)),batchFinished:e=>we("batch:finished",t=>e(t.payload)),denoiserProgress:e=>we("denoiser:progress",t=>e(t.payload)),denoiserDone:e=>we("denoiser:done",t=>e(t.payload)),denoiserFetch:e=>we("denoiser:fetch",t=>e(t.payload))};function Oo(e,t){return{caps:null,prefs:null,tab:"vectorize",screen:null,source:null,settings:e,preset:"logo",generation:0,tracing:!1,tracingTier:"final",liveStages:[],result:null,resultGeneration:0,bandsMissing:!1,previous:null,svg:null,palette:[],snaps:[],colourGroups:[],groupSuggestions:null,resultGroups:[],dismissedGroups:[],simplifyTo:null,paletteSelection:[],hoverFill:null,losses:[],report:null,worstCorner:null,stageState:{kind:"empty"},justSwapped:!1,view:"side",show:{fill:!0,wireframe:!1,anchors:!1,handles:!1,certainty:!1},zoom:1,pan:{x:0,y:0},fitted:!0,flicked:!1,wipe:.5,detail:!1,railTab:"result",groupsOpen:{Detail:!0,Colour:!0,Shape:!0,Output:!1},exportOpen:!1,dragging:!1,minify:{name:null,before:null,settings:t,result:null,error:null},fab:{name:null,svg:null,analysis:null,options:Ms(),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 Ms(){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 Ro{constructor(t){Y(this,"listeners",new Set);this.state=t}set(t){const s=new Set;for(const[o,a]of Object.entries(t))this.state[o]!==a&&(this.state[o]=a,s.add(o));s.size&&this.emit(s)}touch(...t){this.emit(new Set(t))}on(t,s){const o=new Set(t),a=i=>{for(const r of i)if(o.has(r)){s();return}};return this.listeners.add(a),()=>this.listeners.delete(a)}emit(t){for(const s of[...this.listeners])s(t)}}function oe(e){return e.toLocaleString("en-US")}function ie(e){return e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KB`:`${(e/(1024*1024)).toFixed(1)} MB`}function re(e){return e==null?"—":e.toFixed(2)}function Ae(e){return e<1?`${Math.round(e*1e3)} ms`:`${e.toFixed(2)} s`}function un(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 Se(e,t=0){return`${(e*100).toFixed(t)}%`}function Go(e){const t=Math.max(0,Math.round(e));return t<60?`${t} s`:`${Math.floor(t/60)} min ${t%60} s`}function Cs(e){return e==="macos"?"⌘":"Ctrl"}function Wo(){return{preset:"logo",tab:"vectorize",railTab:"result",groupsOpen:{},view:"side",wipe:.5,show:{fill:!0,wireframe:!1,anchors:!1,handles:!1,certainty:!1},detail:!1,export:null,minify:null,minifyBackdrop:"auto",minifyView:"side",fab:null,fabUnit:"mm",fabShowProblems:!0,fabPreset:null,fabDxf:!1,fabGcode:!1,batchPreset:null,batchSkipExisting:!0,batchFailuresFirst:!1}}const Ce={export:null,minifyBackdrop:"auto",minifyView:"side"};let Qt=()=>{};function Ho(){return{...Ce}}function en(e){Object.assign(Ce,e),Qt()}function Uo(e){const t=Ce.export;return t?{...e,...t,pngSizes:[...t.pngSizes??e.pngSizes]}:e}function tn(e){const t={...e.fab.options};return delete t.include,delete t.order,{preset:e.preset,tab:e.tab,railTab:e.railTab,groupsOpen:{...e.groupsOpen},view:e.view,wipe:e.wipe,show:{...e.show},detail:e.detail,export:Ce.export,minify:{...e.minify.settings},minifyBackdrop:Ce.minifyBackdrop,minifyView:Ce.minifyView,fab:t,fabUnit:e.fab.unit,fabShowProblems:e.fab.showProblems,fabPreset:e.fab.preset,fabDxf:e.fab.dxf,fabGcode:e.fab.gcode,batchPreset:e.batch.preset,batchSkipExisting:e.batch.skipExisting,batchFailuresFirst:e.batch.failuresFirst}}function nn(e){const t={...e};return delete t.colourGroups,t}function qo(e,t,s,o){const a=e.state,i={...Wo(),...t.ui??{}};Ce.export=i.export,Ce.minifyBackdrop=i.minifyBackdrop,Ce.minifyView=i.minifyView,e.set({preset:i.preset&&o(i.preset)?i.preset:null,tab:s.includes(i.tab)?i.tab:"vectorize",railTab:i.railTab,groupsOpen:{...a.groupsOpen,...i.groupsOpen},view:i.view,wipe:i.wipe,show:{...a.show,...i.show},detail:i.detail,minify:{...a.minify,settings:{...a.minify.settings,...i.minify??{}}},fab:{...a.fab,options:{...Ms(),...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 jo=["settings","preset","tab","railTab","groupsOpen","view","wipe","show","detail","minify","fab","batch"];function Vo(e,t,s=500){let o=0,a=JSON.stringify({ui:tn(e.state),trace:nn(e.state.settings)});const i=()=>{if(window.clearTimeout(o),o=0,!e.state.prefs)return;const c={ui:tn(e.state),trace:nn(e.state.settings)},d=JSON.stringify(c);d!==a&&(a=d,t(c))};Qt=()=>{window.clearTimeout(o),o=window.setTimeout(i,s)};const r=e.on(jo,Qt),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 Yo="modulepreload",Zo=function(e,t){return new URL(e,t).href},qn={},Jo=function(t,s,o){let a=Promise.resolve();if(s&&s.length>0){let r=function(p){return Promise.all(p.map(h=>Promise.resolve(h).then(g=>({status:"fulfilled",value:g}),g=>({status:"rejected",reason:g}))))};const l=document.getElementsByTagName("link"),c=document.querySelector("meta[property=csp-nonce]"),d=c?.nonce||c?.getAttribute("nonce");a=r(s.map(p=>{if(p=Zo(p,o),p in qn)return;qn[p]=!0;const h=p.endsWith(".css"),g=h?'[rel="stylesheet"]':"";if(!!o)for(let b=l.length-1;b>=0;b--){const x=l[b];if(x.href===p&&(!h||x.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${p}"]${g}`))return;const f=document.createElement("link");if(f.rel=h?"stylesheet":Yo,h||(f.as="script"),f.crossOrigin="",f.href=p,d&&f.setAttribute("nonce",d),document.head.appendChild(f),h)return new Promise((b,x)=>{f.addEventListener("load",b),f.addEventListener("error",()=>x(new Error(`Unable to preload CSS for ${p}`)))})}))}function i(r){const l=new Event("vite:preloadError",{cancelable:!0});if(l.payload=r,window.dispatchEvent(l),!l.defaultPrevented)throw r}return a.then(r=>{for(const l of r||[])l.status==="rejected"&&i(l.reason);return t().catch(i)})};async function Xo(e={}){return typeof e=="object"&&Object.freeze(e),await Tt("plugin:dialog|open",{options:e})}async function Ko(e={}){return typeof e=="object"&&Object.freeze(e),await Tt("plugin:dialog|save",{options:e})}async function Qo(e,t){await Tt("plugin:opener|open_url",{url:e,with:t})}async function ea(e){return Tt("plugin:opener|reveal_item_in_dir",{paths:typeof e=="string"?[e]:e})}const At="Inkvec Studio Lite",Ts="https://github.com/logolabs/inkvec/releases",ta="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 As(e){return{name:e.name||"pasted image",path:null,file:e}}async function dn(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(As))),o.addEventListener("cancel",()=>i([])),document.body.append(o),o.click()})}async function xt(e){if(e.file)return e.file.text();if(e.path)return O.readTextFile(e.path);throw new Error("nothing to read")}const jn=!1;async function zs(e,t,s){const o=typeof t=="string"?new TextEncoder().encode(t):t;return Oe(e,o),{path:null,name:e}}async function na(e,t,s){const o=new TextEncoder,a=i=>typeof i=="string"?o.encode(i):i;{if(e.length===1)Oe(e[0].name,a(e[0].data));else{const{zipStore:i}=await Jo(async()=>{const{zipStore:r}=await import("./zip-DlX9-WSC.js");return{zipStore:r}},[],import.meta.url);Oe(t,i(e.map(r=>({name:r.name,data:a(r.data)}))))}return{folder:null,first:null}}}const sa=!1;async function zt(e){await navigator.clipboard.writeText(e)}async function Et(e){{window.open(e,"_blank","noopener,noreferrer");return}}function Lt(e){return e&&sa?{label:"Show in folder",run:()=>void 0}:void 0}function Ee(e){const t={...e,cleanUpDamage:"off"};return JSON.stringify(Object.keys(t).sort().map(s=>[s,t[s]]))}class oa{constructor(){Y(this,"results",new Map);Y(this,"queue",[]);Y(this,"running",null);Y(this,"epoch",0);Y(this,"early",new Map);Y(this,"listeners",new Set);xe.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&&O.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(),O.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":"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 pn=()=>document.getElementById("overlays"),aa=()=>document.getElementById("toasts");function W(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,q(o,16)),n("span.text",null,e),t.action&&n("button",{class:"reset",onclick:()=>{t.action?.run(),a.remove()}},t.action.label));aa().append(a),window.setTimeout(()=>a.remove(),6e3)}function ce(){N(pn()),document.removeEventListener("keydown",hn,!0)}function hn(e){e.key==="Escape"&&(e.stopPropagation(),ce())}function qe(e){const t=n("div.scrim",{onmousedown:s=>{s.target===t&&ce()}});e.setAttribute("role","dialog"),e.setAttribute("aria-modal","true"),t.append(e),N(pn(),t),document.addEventListener("keydown",hn,!0),(e.querySelector("button, input, [tabindex]")??e).focus()}function Pt(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 fn(e,t){const s=n("div.popover",null,t),o=n("div",{style:{position:"fixed",inset:"0",zIndex:"44"},onmousedown:ce});N(pn(),o,s),document.addEventListener("keydown",hn,!0);const a=e.getBoundingClientRect(),i=s.getBoundingClientRect(),r=8,l=Math.min(Math.max(r,a.left),window.innerWidth-i.width-r),c=a.bottom+6,d=c+i.height+r>window.innerHeight?a.top-i.height-6:c;s.style.left=`${Math.round(l)}px`,s.style.top=`${Math.round(Math.max(r,d))}px`,(s.querySelector("button, input, [tabindex]")??s).focus?.()}let Fe=null,gn=0;function et(){window.cancelAnimationFrame(gn),Fe?.node.remove(),Fe=null}function Es(){const e=Fe;if(!e)return;if(!(e.el.isConnected&&(e.el.matches(":hover")||e.el.matches(":focus-visible")))){et();return}gn=window.requestAnimationFrame(Es)}function Xe(e,t){let s=0;const o=()=>{if(!e.isConnected||Fe?.el===e)return;et();const i=n("div.tooltip",{role:"tooltip"},t);document.body.append(i);const r=e.getBoundingClientRect(),l=i.getBoundingClientRect(),c=Math.min(Math.max(8,r.left),window.innerWidth-l.width-8),d=r.top-l.height-8;i.style.left=`${Math.round(c)}px`,i.style.top=`${Math.round(d<8?r.bottom+8:d)}px`,Fe={node:i,el:e},gn=window.requestAnimationFrame(Es)},a=()=>{window.clearTimeout(s),Fe?.el===e&&et()};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"&&Fe&&et()},!0);window.addEventListener("blur",et);function ia(e,t,s,o,a=!1){qe(Pt(e,[t],[n("button.btn",{onclick:ce},"Cancel"),n(`button.btn.primary${a?".danger":""}`,{onclick:()=>{ce(),o()}},s)]))}function Ls(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 Ps([e,t,s]){const o=f=>f<=.04045?f/12.92:((f+.055)/1.055)**2.4,[a,i,r]=[o(e),o(t),o(s)],l=(.4124564*a+.3575761*i+.1804375*r)/.95047,c=.2126729*a+.7151522*i+.072175*r,d=(.0193339*a+.119192*i+.9503041*r)/1.08883,p=f=>f>216/24389?Math.cbrt(f):(24389/27*f+16)/116,[h,g,u]=[p(l),p(c),p(d)];return[116*g-16,500*(h-g),200*(g-u)]}function ra(e,t){const[s,o,a]=e,[i,r,l]=t,c=Math.PI/180,d=Math.hypot(o,a),p=Math.hypot(r,l),h=((d+p)/2)**7,g=.5*(1-Math.sqrt(h/(h+25**7))),u=(1+g)*o,f=(1+g)*r,b=Math.hypot(u,a),x=Math.hypot(f,l),M=(de,$e)=>{if(de===0&&$e===0)return 0;const pe=Math.atan2($e,de)/c;return pe<0?pe+360:pe},w=M(u,a),$=M(f,l),v=i-s,S=x-b;let C=0;b*x!==0&&(C=$-w,C>180?C-=360:C<-180&&(C+=360));const z=2*Math.sqrt(b*x)*Math.sin(C*c/2),P=(s+i)/2,B=(b+x)/2;let T=w+$;b*x!==0&&(T=Math.abs(w-$)<=180?(w+$)/2:w+$<360?(w+$+360)/2:(w+$-360)/2);const D=1-.17*Math.cos((T-30)*c)+.24*Math.cos(2*T*c)+.32*Math.cos((3*T+6)*c)-.2*Math.cos((4*T-63)*c),V=30*Math.exp(-(((T-275)/25)**2)),H=B**7,Z=2*Math.sqrt(H/(H+25**7)),ne=1+.015*(P-50)**2/Math.sqrt(20+(P-50)**2),Q=1+.045*B,X=1+.015*B*D,U=-Math.sin(2*V*c)*Z,se=v/ne,te=S/Q,ae=z/X;return Math.sqrt(se*se+te*te+ae*ae+U*te*ae)}function kt(e){return e.kind==="gradient"&&e.stops.length>1?e.stops.map(t=>t.toLowerCase()).join(">"):e.traced.toLowerCase()}function Re(e){return e.split(">").map(t=>t.trim().toLowerCase())}function nt(e){return e.includes(">")}function mn(e,t=!1){const s=Re(e);return s.length<2?s[0]??"transparent":t?`radial-gradient(circle, ${s.join(", ")})`:`linear-gradient(90deg, ${s.join(", ")})`}const rt=new Map;function Vn(e){const t=rt.get(e);if(t)return t;const s=Ls(e);if(!s)return null;const o=Ps(s);return rt.size>4096&&rt.clear(),rt.set(e,o),o}function bn(e,t){const s=Re(e).map(Vn),o=Re(t).map(Vn);if(s.some(l=>!l)||o.some(l=>!l)||!s.length||!o.length)return 1/0;const a=s,i=o,r=(l,c)=>l.reduce((d,p)=>d+Math.min(...c.map(h=>ra(p,h))),0)/l.length;return Math.max(r(a,i),r(i,a))}const la=2;function Dt(e,t){const s=nt(e),o=t.find(r=>kt(r)===e);if(o)return o;let a=null,i=la;for(const r of t){if(r.kind==="gradient"!==s)continue;const l=bn(e,kt(r));l<=i&&(i=l,a=r)}return a}function ke(e,t){return e===t||bn(e,t)<.5}const ca=2,ua=240;function da(e,t={}){const s=t.skip??[],o=e.filter(p=>p.share>0),a=[...o].sort((p,h)=>h.share-p.share).slice(0,ua).map(p=>({member:kt(p),share:p.share})).filter(p=>!s.some(h=>ke(h,p.member))),i=a.length;if(i<2)return[];const r=a.map((p,h)=>a.map((g,u)=>h===u?0:bn(p.member,g.member))),l=a.map((p,h)=>[h]);let c=i;const d=t.count?Math.max(1,Math.round(t.count)-(o.length-i)):null;for(;!(d!==null&&c<=d);){let p=1/0,h=-1,g=-1;for(let u=0;uca)break;l[h]=[...l[h],...l[g]],l[g]=null,c--;for(let u=0;up.length>1).map(p=>p.map(h=>a[h]).sort((h,g)=>g.share-h.share)).sort((p,h)=>h.reduce((g,u)=>g+u.share,0)-p.reduce((g,u)=>g+u.share,0)).map(p=>({members:p.map(h=>h.member),target:null}))}function yn(e){return[...e.members].sort().join(",")}function pa(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 ha(e,t){return e?e.unmatched.some(s=>s===t||Re(t).includes(s)||ke(s,t)):!1}function Ds(e){const t=(e.groupSuggestions??[]).filter(a=>a.edited),s=[...e.colourGroups.flatMap(a=>a.members),...t.flatMap(a=>a.members)],o=da(e.palette,{count:e.simplifyTo,skip:s}).filter(a=>!e.dismissedGroups.includes(yn(a)));return[...t,...o]}function fa(e,t){const s=e.members.filter(a=>Dt(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 wn(e){return(e.groupSuggestions??[]).map((t,s)=>({g:fa(t,e.palette),index:s})).filter(t=>t.g!==null)}function Fs(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 me(e,t,s=-1,o=!1){return e.map((a,i)=>{if(i===s)return a;const r=a.members.findIndex(l=>ke(l,t));return r<0?a:{...Fs(a,r),...o?{edited:!0}:{}}})}const Ge=e=>e.filter(t=>t.members.length>1);function ga(e,t,s,o,a){const i=e.state;let r=i.colourGroups.map(g=>({...g,members:[...g.members]})),l=(i.groupSuggestions??[]).map(g=>({...g,members:[...g.members]}));const c=a?.closest("[data-drop]")??null,d=c?.dataset.drop??null,p=Number(c?.dataset.index??-1);if((d==="group"||d==="sugg")&&o.kind===d&&o.index===p||d==="ink"&&ke(c?.dataset.member??"",s))return;if(d==="group")r=me(r,s,p),l=me(l,s,-1,!0),r[p].members.some(g=>ke(g,s))||r[p].members.push(s);else if(d==="sugg")r=me(r,s),l=me(l,s,p,!0),l[p].members.some(g=>ke(g,s))||(l[p]={...l[p],members:[...l[p].members,s],edited:!0});else if(d==="ink"){const g=c?.dataset.member??"";r=me(r,s),l=me(l,s,-1,!0);const u=r.findIndex(f=>f.members.some(b=>ke(b,g)));u>=0?r[u].members.push(s):(l=me(l,g,-1,!0),r.push({members:[g,s],target:null}))}else{if(o.kind==="palette")return;o.kind==="group"?r=me(r,s):l=me(l,s,-1,!0)}const h=Ge(r);e.set({groupSuggestions:Ge(l),paletteSelection:[]}),JSON.stringify(h)!==JSON.stringify(i.colourGroups)&&t.setColourGroups(h)}function ma(e,t){const s=e.state,o=s.paletteSelection;if(o.length<2)return;let a=s.colourGroups.map(l=>({...l,members:[...l.members]}));const i=a.findIndex(l=>l.members.some(c=>o.some(d=>ke(d,c))));let r=s.groupSuggestions??[];for(const l of o)a=me(a,l,i),r=me(r,l,-1,!0);if(i>=0)for(const l of o)a[i].members.some(c=>ke(c,l))||a[i].members.push(l);else a.push({members:[...o],target:null});e.set({groupSuggestions:Ge(r),paletteSelection:[]}),t.setColourGroups(Ge(a))}const ba=4;function Ns(e,t,s,o,a,i){e.addEventListener("pointerdown",r=>{if(r.button!==0)return;const l=r.clientX,c=r.clientY;let d=null,p=null;const h=f=>{f!==p&&(p?.classList.remove("dropping"),p=f,p?.classList.add("dropping"))},g=f=>{if(!d){if(Math.hypot(f.clientX-l,f.clientY-c){if(window.removeEventListener("pointermove",g),window.removeEventListener("pointerup",u),window.removeEventListener("pointercancel",u),!d)return;d.remove(),document.body.classList.remove("dragging-ink"),h(null);const b=x=>x.stopPropagation();window.addEventListener("click",b,!0),window.setTimeout(()=>window.removeEventListener("click",b,!0),0),f.type!=="pointercancel"&&ga(t,s,o,a,document.elementFromPoint(f.clientX,f.clientY))};window.addEventListener("pointermove",g),window.addEventListener("pointerup",u),window.addEventListener("pointercancel",u)})}function lt(e,t){return Dt(e,t)?.keys??[]}function Is(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 _s(e,t){const s=e.state,o=wn(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"}},q("palette",12),`Palette · ${s.palette.length} ink${s.palette.length===1?"":"s"}`),n("button.reset",{disabled:!a.length,onclick:()=>Ca(e,t)},"Paste brand palette")),s.colourGroups.length?ya(e,t):null,s.palette.length?xa(e,t,o):null,n("div.inklist",{"data-drop":"list","aria-label":"Inks"},...s.palette.map(r=>ka(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:()=>ma(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 Ma(a)},"Copy all as CSS")):n("span.muted",{style:{fontSize:"11px"}},"Trace an image to see its inks."))}function Yn(e,t=!1){return n(`span.swatch.mini${nt(e)?".ramp":""}`,null,n("i",{style:{background:mn(e,t)}}))}function Bt(e){const t=Re(e);return t.length>1?`${t.length} stops`:t[0].toUpperCase()}function ya(e,t){const s=e.state,a=JSON.stringify(s.resultGroups)===JSON.stringify(s.colourGroups)&&s.result?pa(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)=>Bs(e,t,i,r,"group",a[r]??null)))}function wa(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:nt(a)?"extends this gradient":"becomes this"}}let s=e.members[0],o=-1;for(const a of e.members){const i=Dt(a,t)?.share??0;i>o&&(o=i,s=a)}return{member:s,label:"most-used"}}function Bs(e,t,s,o,a,i){const r=e.state,l=r.palette,c=x=>Dt(x,l)?.gradient==="radial",d=wa(s,l),p=[...new Set([...s.members,d.member].flatMap(x=>lt(x,l)))],h=x=>{if(a==="group"){const M=r.colourGroups.map((w,$)=>$===o?x:w);t.setColourGroups(Ge(M))}else{const M=(r.groupSuggestions??[]).map((w,$)=>$===o?{...x,edited:!0}:w);e.set({groupSuggestions:Ge(M)})}},g=x=>ha(i,x),u=x=>{const M=lt(x,l);return M.length||a==="sugg"?M:lt(d.member,l)},f=s.members.map((x,M)=>{const w=s.target===`@${M+1}`,$=n(`div.cchip${w?".target":""}${g(x)?".unmatched":""}`,{"data-fill":JSON.stringify(u(x))},n("button.cchipmain",{"data-ctl":`${a}${o}:${x}`,"aria-pressed":String(w),title:w?"The group becomes this. Press again for the most-used colour.":nt(x)?"Make the group this gradient: it is extended over the other members":"Make the group this colour",onclick:()=>h({...s,target:w?null:`@${M+1}`})},Yn(x,c(x)),n("span.num",null,Bt(x))),n("button.cchipx",{"data-ctl":`${a}${o}:${x}:x`,"aria-label":`Take ${Bt(x)} out of the group`,title:"Take it out of the group",onclick:()=>h(Fs(s,M))},q("x",11)));return Ns($,e,t,x,{kind:a,index:o},c(x)),$}),b=n("button.reset",{title:"Paint the whole group one colour of your choosing",onclick:x=>Sa(x.currentTarget,d.member,M=>h({...s,target:M}))},"Custom…");return n(`div.cgroup${a==="sugg"?".suggested":""}`,{"data-drop":a,"data-index":String(o),"data-fill":JSON.stringify(p)},n("div.cchips",null,...f),n("div.cresult",null,n("span.arrow",{"aria-hidden":"true"},"→"),n("span.cresultchip",{"data-fill":JSON.stringify(lt(d.member,l))},Yn(d.member,c(d.member)),n("span.num",null,Bt(d.member))),n("span.muted",null,d.label),n("span",{style:{flex:"1"}}),b),a==="sugg"?n("div.cactions",null,n("button.btn.compact.primary",{onclick:()=>{const x=(r.groupSuggestions??[]).filter((M,w)=>w!==o);e.set({groupSuggestions:x}),t.setColourGroups([...r.colourGroups,{members:s.members,target:s.target}])}},"Accept"),n("button.btn.compact",{onclick:()=>e.set({groupSuggestions:(r.groupSuggestions??[]).filter((x,M)=>M!==o),dismissedGroups:[...r.dismissedGroups,yn(s)]})},"Dismiss")):[va(i),n("div.cactions",null,n("span",{style:{flex:"1"}}),s.target?n("button.reset",{title:"Become the member covering the most of the image",onclick:()=>h({...s,target:null})},"Most-used"):null,n("button.reset",{title:"Trace these inks on their own again",onclick:()=>t.setColourGroups(r.colourGroups.filter((x,M)=>M!==o))},"Ungroup"))])}function va(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},q("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=>nt(o)?"a gradient":o.toUpperCase()).join(", ");return n("span.creport.warn",{title:`${e.line}; not found: ${e.unmatched.join(", ")}`},q("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 xa(e,t,s){const o=e.state,a=o.palette.length,i=l=>{const c=l===null?null:Math.max(1,Math.min(a,Math.round(l))),d=(o.groupSuggestions??[]).filter(p=>p.edited);e.set({simplifyTo:c,groupSuggestions:Ds({...o,simplifyTo:c,groupSuggestions:d})})},r=n("input.numberfield.simplify",{type:"text",inputmode:"numeric",value:String(o.simplifyTo??a),"aria-label":"Simplify the palette to this many colours",onchange:l=>{const c=Number(l.target.value);Number.isFinite(c)&&c>=1&&i(c>=a?null:c)}});return n("div.cgroups.suggestions",null,n("div.cgroupshead",null,n("span.eyebrow",null,s.length?`Suggested · ${s.length}`:"Suggested"),s.length>1?n("button.reset",{title:"Accept every suggestion below",onclick:()=>{e.set({groupSuggestions:[]}),t.setColourGroups([...o.colourGroups,...s.map(({g:l})=>({members:l.members,target:l.target}))])}},"Accept all"):null),n("div.simplifyrow",null,n("span.muted",null,"Colours"),n("button.btn.compact.icon",{"aria-label":"One colour fewer",disabled:(o.simplifyTo??a)<=1,onclick:()=>i((o.simplifyTo??a)-1)},q("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})=>Bs(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 ka(e,t,s){const o=e.state,a=kt(s),i=s.kind==="gradient",r=s.snappedDe00!==null,l=o.paletteSelection.includes(a),c=o.colourGroups.some(g=>g.members.some(u=>ke(u,a))),d=n("button.inkmain",{"data-ctl":`ink:${a}`,title:i?`${s.gradient??"linear"} gradient · ${s.stops.map(g=>g.toUpperCase()).join(" → ")}`:"Snap this ink to a colour of your choosing","aria-disabled":i?"true":null,onclick:g=>{i||$a(g.currentTarget,s,t)}},n(`span.swatch${r?".snapped":""}${i?".ramp":""}`,null,n("i",{style:{background:i?mn(a,s.gradient==="radial"):s.hex}})),n("span.hex",null,i?s.gradient==="radial"?"radial":"linear":s.hex.toUpperCase()),n("span.share",null,Se(s.share)),n(`span.snap${r?".on":""}`,null,i?`${s.stops.length} stops`:r?`snapped · ΔE ${re(s.snappedDe00)}`:"snap to…")),p=()=>e.set({paletteSelection:l?o.paletteSelection.filter(g=>g!==a):[...o.paletteSelection,a]}),h=n(`div.ink${c?".grouped":""}`,{"data-drop":"ink","data-member":a,"data-fill":JSON.stringify(s.keys)},n("button.inkpick",{"data-ctl":`pick:${a}`,role:"checkbox","aria-checked":String(l),"aria-label":`Tick ${i?"this gradient":s.hex.toUpperCase()} to merge it`,title:"Tick inks, then Merge to draw them as one. Or drag one onto another.",onclick:p,onkeydown:g=>{g.key===" "&&(g.preventDefault(),g.stopPropagation(),g.repeat||p())}},l?q("check",11):null),d);return Ns(h,e,t,a,{kind:"palette"},s.gradient==="radial"),h}function Sa(e,t,s){const o=Re(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)){W(`${a.value} is not a colour; write it as #RRGGBB.`,{kind:"bad"});return}s(c),ce()};fn(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 $a(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.");fn(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()} · ${Se(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()}]),ce()}},"Snap")),i))}async function Ma(e){const t=e.map((s,o)=>` --ink-${o+1}: ${s.hex.toLowerCase()}; /* ${Se(s.share)} of canvas */`).join(` +`);try{await zt(`:root { +${t} +} +`),W(`${e.length} ink${e.length===1?"":"s"} copied as CSS.`,{kind:"good"})}catch(s){W(String(s),{kind:"bad"})}}function Ca(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 O.matchPalette(s.map(l=>l.traced),o.value),N(a,...i.map(l=>n("div",{style:{display:"flex",alignItems:"center",gap:"9px",fontSize:"12px"},class:"num"},n("span.swatch",{style:{width:"20px",height:"20px"}},n("i",{style:{background:l.from}})),n("span.muted",null,l.from.toUpperCase()),n("span.muted",null,"→"),n("span.swatch",{style:{width:"20px",height:"20px"}},n("i",{style:{background:l.to}})),n("span.dim",null,l.to.toUpperCase()),n("span",{style:{marginLeft:"auto",color:l.de00<.5?"var(--good)":"var(--gold)"}},l.de00<.05?"exact":`${re(l.de00)} dE00`))))};o.addEventListener("input",()=>void r()),qe(Pt("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:ce},"Cancel"),n("button.btn.primary",{onclick:()=>{t.snap(i.map(l=>({from:l.from,to:l.to}))),ce()}},`Snap ${s.length} inks`)]))}const Os=2.5,Ta=256;function Rs(e,t){return!!e?.lossy||t!==null&&t.noiseLevels>=Os}function Aa(e){return e.filter(t=>t.share>=.005)}function za(e){if(e.length!==2||e.some(l=>l.kind==="gradient"))return!1;const t=e.map(l=>Ls(l.hex)).map(l=>l?Ps(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 Ea(e){const{source:t,report:s,palette:o,losses:a,facts:i,settings:r}=e;if(!t||!s)return[];const l=[];if(Rs(t,i)&&r.cleanUpDamage==="off"){const g=[t.lossy?t.container:null,i?`noise ${i.noiseLevels.toFixed(1)} levels`:null].filter(Boolean);l.push({id:"photo",preset:"photo-or-scan",title:"Looks like a photo, a scan or a screenshot",evidence:g.join(" · "),action:"Photo or scan, with the denoiser"})}const c=Aa(o);!r.blackAndWhite&&za(c)&&l.push({id:"bw",preset:"black-and-white",title:"Two colours, black and white",evidence:`${c.length} inks cover the canvas`,action:"Black & white"}),!r.lineArt&&a.some(g=>g.kind==="strokes")&&!r.blackAndWhite&&l.push({id:"lineart",preset:"line-art",title:"Drawn with strokes",evidence:"line work came back as filled outlines",action:"Line art, as strokes"});const d=Math.max(t.width,t.height),p=o.some(g=>g.kind==="gradient");return!(r.speckleFloor<=1&&r.maxColours<=16)&&d<=Ta&&!p&&c.length<=16&&l.length===0&&l.push({id:"icon",preset:"icon",title:"A small flat mark",evidence:`${d} px, ${c.length} colour${c.length===1?"":"s"}`,action:"Icon, keeping one-pixel details"}),l.slice(0,2)}function Le(e,t,s){return(e?.controls??[]).filter(o=>t[o.key]!==s[o.key]).map(o=>o.key)}function Gs(e,t,s){const o=e?.presets.find(a=>a.id===s);if(o){const a=Le(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 vn(e){const t=e.state,s=t.auto;return s?.report?Ea({source:t.source,report:s.report,palette:s.palette,losses:s.losses,facts:t.facts,settings:s.settings,preset:s.preset}):[]}function Ws(e){const t=e.state;return t.auto?t.preset!==t.auto.preset||Le(t.caps,t.settings,t.auto.settings).length>0:!1}function La(e){const t=e.state;return t.auto?.report?Ws(e)||wn(t).length>0||vn(e).length>0:!1}function Hs(e,t,s=!1){const o=e.state,a=o.auto;if(!a?.report)return null;const i=vn(e),r=wn(o),l=Ws(e),c=o.caps?.presets.find(p=>p.id===o.preset),d=i.map(p=>{const h=o.preset===p.preset;return n("div.autorow",null,n("div.autotext",null,n("span.autotitle",null,p.title),n("span.muted.num",null,p.evidence)),h?n("span.autotaken",null,"chosen"):n("button.btn.compact",{"data-ctl":`auto-try:${p.id}`,title:`Switch to ${p.action}. An ordinary preset: Back to Auto undoes it.`,onclick:()=>t.setPreset(p.preset)},`Try ${p.action}`))});if(r.length){const p=r.reduce((h,{g})=>h+g.members.length,0);d.push(n("div.autorow",null,n("div.autotext",null,n("span.autotitle",null,`${r.length} colour group${r.length===1?"":"s"} suggested`),n("span.muted.num",null,`${p} inks that look like the same colour measured twice`)),n("div.autobtns",null,n("button.btn.compact",{"data-ctl":"auto-groups-accept",title:"Draw each suggested group as one colour; the trace runs again so the shapes join",onclick:()=>{e.set({groupSuggestions:[]}),t.setColourGroups([...o.colourGroups,...r.map(({g:h})=>({members:h.members,target:h.target}))])}},"Accept"),n("button.reset",{"data-ctl":"auto-groups-dismiss",onclick:()=>e.set({groupSuggestions:[],dismissedGroups:[...o.dismissedGroups,...r.map(({g:h})=>yn(h))]})},"Dismiss"))))}return n("div.autochose",null,s?null:n("span.autolead",null,`Auto traced with ${Gs(o.caps,a.settings,a.preset)}.`),...d,!d.length&&!s?n("span.muted",null,"Nothing in its report suggests another preset."):null,l?n("div.autorow.moved",null,n("div.autotext",null,n("span.autotitle",null,c?`Now: ${c.name}`:"Now: your own settings"),n("span.muted",null,"An ordinary setting, shown under Tune")),n("button.reset",{"data-ctl":"auto-back",onclick:t.backToAuto},"Back to Auto")):null)}const Ot=e=>`${Math.round(e/(1024*1024))}`;function xn(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 St(e,t){if(!e)return t?"Starting the denoiser…":null;switch(e.phase){case"downloading":{const s=xn(e);return e.total?`Downloading the denoiser, ${Ot(e.got)} of ${Ot(e.total)} MB, ${s}%`:`Downloading the denoiser, ${Ot(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 kn(e){return/[….]$/.test(e)?e:`${e}.`}function $t(e){if(!e||e.phase!=="downloading"&&e.phase!=="preparing"&&e.phase!=="stored")return null;const t=xn(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}%`}}))}const Pa=new Set(["cleanUpDamage","editability"]),Rt={axisHandles:.34,smoothJoins:.89,alignedNodes:.86};function Da(e,t){const s=n("div.railtabs"),o=n("div.railmodes"),a=n("div.railscroll"),i=n("div.railfoot"),r=n("aside.rail",{"aria-label":"Controls"},s,o,a,i),l={result:0,tune:0};let c=e.state.railTab;const d=()=>{const u=e.state,f=document.activeElement instanceof HTMLElement&&a.contains(document.activeElement)?document.activeElement.getAttribute("data-ctl"):null;l[c]=a.scrollTop,N(a,...u.railTab==="result"?[u.tracing?Ua(e):null,...Ra(e,t)]:Ia(e,t)),c=u.railTab,a.scrollTop=l[c]??0,f&&a.querySelector(`[data-ctl="${f}"]`)?.focus({preventScroll:!0})},p=()=>{const u=e.state,f=i.querySelector(".readout.busy");if(u.tracing&&f){const b=u.liveStages[u.liveStages.length-1];f.querySelector(".stage").textContent=b?.name??"starting",f.querySelector(".elapsed").textContent=Ae(u.liveStages.reduce((x,M)=>x+M.ms,0)/1e3);return}N(i,...Xa(e,t))},h=()=>{const u=document.activeElement instanceof HTMLElement&&o.contains(document.activeElement)?document.activeElement.getAttribute("data-ctl"):null;N(o,...Fa(e,t)),u&&o.querySelector(`[data-ctl="${u}"]`)?.focus({preventScroll:!0})},g=()=>{N(s,...Na(e,t)),h(),d(),p()};return e.on(["caps","prefs","preset","settings","railTab","groupsOpen"],g),e.on(["denoiserFetch"],h),e.on(["tracing","liveStages","result","report","palette","losses","source","worstCorner","previous","colourGroups","groupSuggestions","resultGroups","paletteSelection","simplifyTo","auto","facts","chooser","wizard","autoNoteHidden"],()=>{e.state.railTab==="result"&&d(),p();const u=s.querySelector('[data-note="result"]');u&&(u.textContent=qs(e))}),Is(a,e),g(),r}function Fa(e,t){const s=e.state,o=f=>s.caps?.controls.find(b=>b.key===f)?.help??"",a=s.caps?.denoiser,i=s.settings.cleanUpDamage,r=a?.supported??!1,l=r&&i!=="off"&&s.denoiserFetch?.phase!=="ready"?s.denoiserFetch:void 0,c=l!==void 0,d=!c&&r&&a!==void 0&&!a.installed&&i!=="off",p={off:"pixels as they are",auto:"only if damaged",on:"always"},h=n("div.mode",null,n("div.modehead",null,Xe(n("span.modetitle",{tabindex:"0"},"Denoiser"),o("cleanUpDamage")),c&&l?.phase==="failed"?n("button.reset",{"data-ctl":"denoiser-retry",onclick:t.retryDenoiser},"Retry"):d?n("button.reset",{onclick:t.openDenoiser,title:"It runs on this computer; nothing is uploaded"},"Download it"):n("span.modestate",null,r?p[i]:"needs its own tab")),n("div.seg.big",{role:"group","aria-label":"Denoiser"},...["off","auto","on"].map(f=>n("button",{"aria-pressed":String(i===f),disabled:!r,"data-ctl":`cleanUpDamage:${f}`,onclick:()=>t.changeSetting("cleanUpDamage",f)},f==="off"?"Off":f==="auto"?"Auto":"On"))),c?n("div.fetchnote",{"data-ctl":"denoiser-progress",role:"status"},$t(l??null),n("span.fetchline",null,l?.phase==="failed"?`${St(l,!0)}: ${l.message??"the connection dropped"}.`:kn(St(l??null,!0)??"")),n("span.muted",null,l?.phase==="failed"?"The trace shown is without it.":"Shown without it until then; it traces again by itself.")):null),g=s.settings.editability,u=n("div.mode",null,n("div.modehead",null,Xe(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[h,u]}function Us(e,t){const s=t.baseSettings(),o=e.state;return(o.caps?.controls??[]).filter(a=>o.settings[a.key]!==s[a.key]).length}function qs(e){const t=e.state.report;return t?`${re(t.meanDe00)} dE00`:""}function Na(e,t){const s=e.state,o=Us(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",qs(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 Ia(e,t){return[_a(e,t),Ba(e,t),...Ja(e,t)]}function _a(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 Ba(e,t){const s=e.state,o=s.caps?.presets??[],a=s.prefs?.saved??[],i=Cs(s.caps?.platform),r=(p,h,g,u,f)=>n(f?"button.preset.saved":"button.preset",{"aria-pressed":String(s.preset===p),title:`${h} — ${g}${u?` (${i}+${u})`:""}`,onclick:()=>t.setPreset(p)},n("span.name",null,h),f?n("span.forget",{role:"button",tabindex:"0","aria-label":`Forget ${h}`,title:`Forget ${h}`,onclick:b=>{b.stopPropagation(),f()}}):null),l=o.find(p=>p.id===s.preset),c=l?`${l.subtitle}.`:a.find(p=>p.id===s.preset)?"One of your saved presets.":"Controls moved from the preset they started at.",d=Us(e,t);return n("div.presetblock",null,n("div.cardhead",null,n("span.eyebrow",null,"Preset"),n("button.reset",{disabled:!s.caps||!d,title:"Put every control back to where the preset had it",onclick:()=>s.preset&&t.setPreset(s.preset)},d?`Reset ${d} changed`:"Nothing changed")),n("div.presets",{role:"group","aria-label":"Presets"},...o.map((p,h)=>r(p.id,p.name,p.subtitle,h<9?h+1:null,null)),...a.map(p=>r(p.id,p.name,"Saved preset",null,()=>t.deletePreset(p.id)))),n("div.traybar",null,n("span.faint",null,c),n("button.reset",{disabled:!s.caps,title:`Remember all ${s.caps?.controls.length??""} controls exactly as they stand`,onclick:()=>Oa(e,t)},"Save as preset")))}function Oa(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)),ce()};qe(Pt("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:ce},"Cancel"),n("button.btn.primary",{onclick:a},"Save")])),o.focus(),o.addEventListener("keydown",i=>{i.key==="Enter"&&a()})}function Ra(e,t){return e.state.source?[Ga(e,t),qa(e,t),Va(e,t),Ya(e),_s(e,t),Ha()]:[Wa()]}function Ga(e,t){const s=e.state;if(s.chooser||s.wizard||s.autoNoteHidden||!La(e))return null;const o=Hs(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 Wa(){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 Ha(){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 Ua(e){const t=e.state,s=t.caps?.stages??[],o=new Map(t.liveStages.map(i=>[i.name,i.ms])),a=t.liveStages.reduce((i,r)=>i+r.ms,0)/1e3;return n("div.card",null,n("div.cardhead",null,n("span",{style:{fontSize:"12px",fontWeight:"500"}},`Tracing at ${un(t,t.tracingTier)??"—"} px`),n("span.muted.num",{style:{fontSize:"11px"}},Ae(a))),n("div.sweep",null,n("i")),n("div.stagelist",null,...s.map(i=>{const r=o.get(i);return 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 qa(e,t){const s=e.state,o=s.report,a=o?s.result?.tier==="draft"?`draft · ${o.tracedPx} px`:`full trace · ${o.tracedPx} px`:"—",i=r=>`${(Math.sqrt(Math.min(r,5)/5)*100).toFixed(1)}%`;return n("div.card",{style:{opacity:s.tracing?"0.45":"1",transition:"opacity var(--d-fast)"}},n("div.cardhead",null,n("span.eyebrow",null,"Quality report"),n("span.muted",{style:{fontSize:"11px"}},a)),n("div.headline",null,n("span.figure",null,re(o?.meanDe00)),n("div.of",null,n("span.dim",{style:{fontSize:"11.5px"}},"mean colour difference"),n("span.muted.num",{style:{fontSize:"11px"}},`median ${re(o?.medianDe00)} · dE00`))),n("div",{style:{display:"flex",flexDirection:"column",gap:"5px"}},n("div.meter",{role:"img","aria-label":`Colour difference ${re(o?.meanDe00)} of a 0 to 5 scale`},n("span.threshold",{style:{left:i(1)}}),o?.meanDe00!=null?n("span.needle",{style:{left:i(o.meanDe00)}}):null),n("div.meterscale",null,n("span",null,"0 invisible"),n("span",null,"1.0 just visible"),n("span",null,"5.0"))),n("div.stats",null,...ja(e).map(([r,l])=>n("div",null,n("span.k",null,r),n("span.v",null,l)))),o&&s.worstCorner?n("button.reset",{style:{textAlign:"left"},onclick:t.jumpToWorst},`Find the worst corner · ${re(s.worstCorner.de00)} dE00 →`):null)}function ja(e){const t=e.state.report;return t?[["coordinates",oe(t.coordinates)],["paths",oe(t.paths)],["colours found",oe(t.colours)],["segments",oe(t.segments)],["file size",ie(t.bytes)],["minified",t.minifiedBytes===null?"already":ie(t.minifiedBytes)],["traced at",`${t.tracedPx} px`],["time taken",Ae(t.seconds)]]:[["coordinates","—"],["paths","—"],["colours found","—"],["segments","—"],["file size","—"],["minified","—"]]}function Va(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:Rt.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:Rt.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:Rt.alignedNodes}]:[]).filter(c=>c.whole>0),l=o!==void 0&&o.cubics===0;return n("div.card.structure",{style:{opacity:s.tracing?"0.45":"1",transition:"opacity var(--d-fast)"}},n("div.cardhead",null,n("span.eyebrow",null,"Editability"),n("button.reset",{title:a?"Stop moving nodes and handles onto what an artist would draw":"Move nodes and handles onto what an artist would draw. Costs about 0.04 dE00 on icons, and the report above shows the price on this image.",onclick:()=>t.changeSetting("editability",!a)},a?"Editable structure on · turn off":"Make it editable")),...r.map(c=>{const d=c.part/c.whole;return n("div.srow",{title:c.help},n("span.k",null,c.label),n("span.v",null,Se(d),n("span.of",null,`${c.part} of ${c.whole}`)),n("div.sbar",{role:"img","aria-label":`${c.label}: ${Se(d)}, ${c.part} of ${c.whole}; hand-drawn files ${Se(c.artist)}`},n("i",{style:{width:`${(d*100).toFixed(1)}%`}}),n("b",{style:{left:`${(c.artist*100).toFixed(1)}%`}})))}),l?n("span.muted",{style:{fontSize:"11px",lineHeight:"1.45"}},"This drawing is lines and arcs, so there are no curve handles to tidy; only its nodes can line up."):null,n("span.muted",{style:{fontSize:"11px",lineHeight:"1.45"}},"The tick is where hand-drawn files sit (median of 1,544 artist SVGs). ",a?"Nothing moves further than the trace's own tolerance.":"A trace fitted for pixels alone starts near zero on all three."))}function Ya(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=>Za(o,s(o.kind))),n("div.nothinglost",null,n("span.glyph",null,q("checkCircle",15)),n("span",null,t.losses.length?"Nothing else we can detect was lost.":"Nothing we can detect was lost.")))}function Za(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,q(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))}function Ja(e,t){const s=e.state,o=(s.caps?.controls??[]).filter(r=>!Pa.has(r.key)),a=t.baseSettings();return[...new Set(o.map(r=>r.group))].map(r=>{const l=o.filter(p=>p.group===r),c=s.groupsOpen[r]!==!1,d=l.filter(p=>s.settings[p.key]!==a[p.key]).length;return n("section.group",{class:c?"open":void 0},n("div.grouphead",null,n("button.groupbtn",{"aria-expanded":String(c),"data-ctl":`group:${r}`,onclick:()=>{s.groupsOpen[r]=!c,e.touch("groupsOpen")}},q(c?"chevronDown":"chevronRight",13),n("span.eyebrow",null,r),d?n("span.changed",null,`${d} changed`):null),d?n("button.reset",{onclick:()=>t.resetGroup(r)},"Reset"):null),c?n("div.groupbody",null,...l.map(p=>js(e,p,t,a))):null)})}function js(e,t,s,o){const a=e.state.settings[t.key],i=a!==o[t.key],r=(...u)=>n(i?"div.control.changed":"div.control",null,...u);if(t.kind==="switch"){const u=n("button.switch",{role:"switch","aria-checked":String(!!a),"aria-label":t.label,"data-ctl":`${t.key}:switch`,onclick:()=>s.changeSetting(t.key,!a)});return r(n("div.controlhead",null,Xe(n("span.label",{tabindex:"0"},t.label),t.help),u))}if(t.kind==="tri"){const u=[["off","Off"],["auto","Auto"],["on","On"]];return r(n("div.controlhead",null,Xe(n("span.label",{tabindex:"0"},t.label),t.help),n("div.seg",null,...u.map(([f,b])=>n("button",{"aria-pressed":String(a===f),"data-ctl":`${t.key}:${f}`,onclick:()=>s.changeSetting(t.key,f)},b)))))}const l=u=>Math.pow((Number(u)-t.min)/(t.max-t.min||1),1/t.curve)*1e3,c=u=>t.min+Math.pow(u/1e3,t.curve)*(t.max-t.min),d=u=>t.decimals===0?String(Math.round(u)):u.toFixed(t.decimals),p=n("input.numberfield",{type:"text",value:d(Number(a)),inputmode:"decimal","aria-label":`${t.label}${t.unit?` in ${t.unit}`:""}`,"data-ctl":`${t.key}:field`,onchange:u=>{const f=Number(u.target.value);Number.isFinite(f)&&s.changeSetting(t.key,Math.min(t.max,Math.max(t.min,f)))}}),h=u=>{const f=c(Number(u.value));return t.decimals===0?Math.round(f):Number(f.toFixed(t.decimals))},g=n("input.slider",{type:"range",min:"0",max:"1000",step:"1",value:String(Math.round(l(Number(a)))),"aria-label":t.label,"aria-valuetext":`${d(Number(a))} ${t.unit}`.trim(),"data-ctl":`${t.key}:slider`,oninput:u=>{const f=u.target,b=h(f);p.value=d(b),f.setAttribute("aria-valuetext",`${d(b)} ${t.unit}`.trim())},onchange:u=>s.changeSetting(t.key,h(u.target))});return r(n("div.controlhead",null,Xe(n("span.label",{tabindex:"0"},t.label),t.help),t.unit?n("span.unit",null,t.unit):null,p),g,t.stops.length?n("div.stops",null,...t.stops.map(u=>n("span",null,u.label))):null)}function Xa(e,t){const s=e.state,o=!!s.svg&&!s.tracing;return[Ka(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"},q("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 Ka(e,t){const s=e.state,o=s.report;if(s.tracing){const r=s.liveStages[s.liveStages.length-1],l=s.liveStages.reduce((c,d)=>c+d.ms,0)/1e3;return n("div.readout.busy",null,n("span.pulse"),n("span.stage",null,r?.name??"starting"),n("span.muted.num.elapsed",null,Ae(l)),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=(r,l,c)=>n("div.cell",null,n("span.v.num",null,r),n("span.k",null,l),c);return n("div.readout",{"aria-live":"polite"},i(re(o.meanDe00),"dE00",a?Gt(o.meanDe00,a.meanDe00,r=>r.toFixed(2),.005,!0):null),i(oe(o.coordinates),"coordinates",a?Gt(o.coordinates,a.coordinates,oe,0,!1):null),i(ie(o.bytes),"file",a?Gt(o.bytes,a.bytes,ie,16,!1):null))}function Gt(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 Qa="https://logolabs.github.io/inkvec/";function ei(e,t,s){const o=n("div.chooser",{role:"region","aria-label":"How to trace this image"});let a=!1;const i=d=>{const p=e.state;a&&t.setOnOpen(d),p.prefs?.seenFirstRun||t.markSeen(),e.set({chooser:!1}),d==="custom"&&s()},r=()=>{const d=e.state;if(d.tracing&&d.generation===d.auto?.generation){const h=d.liveStages[d.liveStages.length-1],g=d.liveStages.reduce((u,f)=>u+f.ms,0)/1e3;return`Tracing now · ${h?.name??"starting"} · ${Ae(g)}`}const p=d.auto?.report;return p?`Done · ${re(p.meanDe00)} dE00 · ${oe(p.coordinates)} coordinates · ${ie(p.bytes)}`:d.stageState.kind!=="drawing"&&d.stageState.kind!=="decoding"?"Stopped; the stage says why":"Starting…"},l=()=>{const d=e.state,p=d.chooser&&!!d.source&&d.tab==="vectorize"&&!d.wizard&&!d.screen;if(o.hidden=!p,!p){N(o);return}const h=!d.prefs?.seenFirstRun,g=Hs(e,t);N(o,n("div.choosehead",null,n("span.eyebrow",null,`Just opened · ${d.source?.name??""}`),n("button.btn.icon.compact.ghost",{"aria-label":"Close; Auto stands",title:"Close. Auto stands (Esc)",onclick:()=>i("auto")},q("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,q("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,q("settings",14),"Custom"),n("span.choicebody",null,"What it is, colours, detail, shape, output: each explained, beside Auto's result."),n("button.btn.compact",{"data-ctl":"chooser-custom",onclick:()=>i("custom")},"Customise…"))),g?n("div.choosenews",null,n("span.eyebrow",null,"Auto chose"),g):null,n("label.remember",null,n("input",{type:"checkbox",checked:a,onchange:u=>{a=u.target.checked}}),n("span",null,"Remember my choice"),n("span.muted",null,"· change it in Settings")))},c=()=>{const d=o.querySelector("[data-status]");d&&(d.textContent=r())};return e.on(["chooser","source","tab","wizard","screen","auto","facts","prefs","preset","settings","groupSuggestions","palette","stageState"],l),e.on(["tracing","liveStages","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"]}},Zn={detail:["precision","speckleFloor"],shape:["editability","fewerPaths","matchRepeatedShapes","bezierCost","cornerAngle"],output:["transparentBackground","margin","minify"]},ti=[{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:"black-and-white",name:"Black & white",why:"Two inks: stamps, signatures, scans of print."},{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 ni(e){const t=e.state,s=!!t.caps?.denoiser.supported&&Rs(t.source,t.facts);return["kind","cleanup","colours","detail","shape","output"].filter(o=>o!=="cleanup"||s)}function si(e,t,s,o){let a=null,i=[],r=null,l=e.state.view,c="auto",d=null,p=null;const h=n("div.wizhead"),g=n("div.wizintro"),u=n("div.wizcontrols"),f=n("div.wizlive"),b=n("div.wizguide"),x=n("div.wizscroll",null,g,u,f,b),M=n("div.wiznav");Is(f,e);const w=()=>ni(e),$=()=>e.state.wizard?.step??"kind",v=k=>{k!==$()&&($()==="kind"&&o.pause(),e.set({wizard:{step:k},hoverFill:null}),x.scrollTop=0)},S=()=>{const k=e.state.auto?.svg;return c!=="auto"||!k?null:(d?.svg!==k&&(d={svg:k,label:"Auto"}),d)},C=()=>e.set({compare:S()}),z=()=>{const k=e.state.auto?.svg;return k?(p?.svg!==k&&(p&&URL.revokeObjectURL(p.url),p={svg:k,url:URL.createObjectURL(new Blob([k],{type:"image/svg+xml"}))}),p.url):null},P=()=>{const k=w(),F=Math.max(0,k.indexOf($())),E=ct[$()],j=r!==null&&(e.state.preset!==r.preset||Le(e.state.caps,e.state.settings,r.settings).length>0||JSON.stringify(e.state.colourGroups)!==JSON.stringify(r.colourGroups));N(h,n("div.cardhead",null,n("span.eyebrow",null,`Custom · step ${F+1} of ${k.length}`),n("div",{style:{display:"flex",gap:"10px",alignItems:"baseline"}},j?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:()=>Ie(!0)},"Close"))),n("h2.wiztitle.serif",null,E.title),n("div.wizdots",{role:"tablist","aria-label":"Steps"},...k.map((J,_)=>n("button",{role:"tab","aria-selected":String(J===$()),"aria-label":`Step ${_+1}: ${ct[J].title}`,title:ct[J].title,class:_v(J)}))),n("div.wizcompare",null,n("span.muted",null,"Left pane"),n("div.seg",{role:"group","aria-label":"What the left pane shows"},n("button",{"aria-pressed":String(c==="auto"&&!!e.state.auto?.svg),disabled:!e.state.auto?.svg,title:e.state.auto?.svg?"Auto's trace, to compare yours with":"Auto's trace has not landed yet",onclick:()=>{c="auto",C(),P()}},"Auto's trace"),n("button",{"aria-pressed":String(c==="source"||!e.state.auto?.svg),onclick:()=>{c="source",C(),P()}},"Source"))))},B=()=>{const k=ct[$()];N(g,n("p.wizlead",null,k.does)),N(b,n("dl.wizexplain",null,n("dt",null,"Change it when"),n("dd",null,k.when),n("dt",null,"What it costs"),n("dd",null,k.costs)),n("a.wizlearn",{href:Qa+k.learn[1]},`Learn more: ${k.learn[0]} →`))},T=()=>{const k=w(),F=k.indexOf($()),E=F===k.length-1;N(M,n("button.btn",{"data-ctl":"wiz-back",disabled:F<=0,onclick:()=>v(k[F-1])},"Back"),E?null:n("button.reset",{"data-ctl":"wiz-skip",title:"Keep what is chosen so far and go to the last step",onclick:()=>v(k[k.length-1])},"Skip to finish"),n("span",{style:{flex:"1"}}),E?n("button.btn",{"data-ctl":"wiz-export",disabled:!e.state.svg,onclick:()=>Ie(!0,!0)},"Export now"):null,E?n("button.btn.primary",{"data-ctl":"wiz-finish",onclick:()=>Ie(!0)},"Finish"):n("button.btn.primary",{"data-ctl":"wiz-next",onclick:()=>v(k[F+1])},"Next"))},D=()=>{if(!a)return;const k=w();if(!k.includes($())){e.set({wizard:{step:k[Math.min(k.length-1,1)]}});return}P(),B(),T(),Z(),U()},V=k=>e.state.caps?.controls.find(F=>F.key===k),H=k=>{const F=s.baseSettings();return k.map(E=>V(E)).filter(E=>!!E).map(E=>js(e,E,s,F))},Z=()=>{const k=document.activeElement instanceof HTMLElement&&u.contains(document.activeElement)?document.activeElement.getAttribute("data-ctl"):null,F=e.state,E=$();let j=[];E==="cleanup"?j=Q():E==="colours"?j=H(F.facts?.hasAlpha?["maxColours","traceTransparency"]:["maxColours"]):E==="detail"?j=[ne(),...H(Zn.detail??[])]:(E==="shape"||E==="output")&&(j=H(Zn[E]??[])),N(u,...j),u.hidden=j.length===0,k&&u.querySelector(`[data-ctl="${k}"]`)?.focus({preventScroll:!0}),a&&P()},ne=()=>n("div.wizrow",null,n("span.label",null,"Compare as"),n("div.seg",{role:"group","aria-label":"How the two panes are arranged"},...["side","wipe"].map(k=>n("button",{"aria-pressed":String(e.state.view===k),"data-ctl":`wiz-view:${k}`,onclick:()=>e.set({view:k})},k==="side"?"Side by side":"Wipe")))),Q=()=>{const k=e.state,F=k.caps?.denoiser,E=k.settings.cleanUpDamage,j=[k.source?.lossy?`the file is a ${k.source.container}, which stores colour approximately`:null,k.facts&&k.facts.noiseLevels>=Os?`its pixels measure ${k.facts.noiseLevels.toFixed(1)} levels of noise (a clean file reads about 0.5)`:null].filter(Boolean),J=(_,y,A)=>n("button.wizchoice",{"aria-pressed":String(E===_||_==="auto"&&E==="on"),"data-ctl":`wiz-cleanup:${_}`,onclick:()=>s.changeSetting("cleanUpDamage",_)},n("span.name",null,y),n("span.muted",null,A));return[j.length?n("p.wizwhy",null,`Offered because ${j.join(", and ")}.`):null,n("div.wizchoices",null,J("off","Off","Trace the pixels as they are, damage and all."),J("auto","Auto","Clean the damage first, where it is found.")),F?.supported&&E!=="off"&&k.denoiserFetch?.phase!=="ready"?n("div.wiznote",{"data-ctl":"wiz-denoiser-progress"},$t(k.denoiserFetch),n("span",null,`${kn(St(k.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(_=>_!==null)};let X=null;const U=()=>{const k=$();if(k==="kind"){Te();return}X=null;const F=e.state;k==="colours"?N(f,F.palette.length?_s(e,s):n("p.muted.wizsmall",null,"The palette appears when a trace lands."),n("p.muted.wizsmall",null,"Hover an ink or a group to single it out in the drawing on the right.")):k==="detail"||k==="shape"?N(f,se()):k==="output"?N(f,te(),se()):N(f)},se=()=>{const k=e.state,F=k.auto?.report??null,E=k.report,j=k.result?.tier==="draft",J=y=>y?y.structure?.nodes||Math.round(y.coordinates/2):null,_=(y,A,L,I,R=!0,ee=0)=>{const K=A!==null&&L!==null&&!j&&!k.tracing?L-A:null,fe=K===null||Math.abs(K)<=ee?"same":K<0===R?"better":"worse";return n("tr",null,n("th",null,y),n("td.num",null,A===null?"—":I(A)),n("td.num",null,L===null?"—":I(L)),n(`td.num.delta.${fe}`,null,K===null?"":Math.abs(K)<=ee?"same":`${K<0?"−":"+"}${I(Math.abs(K))}`))};return n("div.card.wizreadout",{"aria-live":"polite"},n("div.cardhead",null,n("span.eyebrow",null,"Auto · yours"),n("span.muted",{style:{fontSize:"11px"}},k.tracing?"tracing…":j?"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,_("colour difference",F?.meanDe00??null,E?.meanDe00??null,y=>y.toFixed(2),!0,.005),_("nodes",J(F),J(E),oe),_("paths",F?.paths??null,E?.paths??null,oe),_("file size",F?.bytes??null,E?.bytes??null,ie,!0,16))),n("span.muted.wizsmall",null,"Colour difference in dE00: under 1 is invisible side by side."))},te=()=>{const k=e.state,F=k.auto,E=F?Le(k.caps,F.settings,k.settings):[],j=(_,y)=>typeof y=="boolean"?y?"on":"off":typeof y=="number"?`${_.decimals?y.toFixed(_.decimals):Math.round(y)}${_.unit?` ${_.unit}`:""}`:String(y),J=k.caps?.presets.find(_=>_.id===k.preset);return n("div.card.wizchanges",null,n("span.eyebrow",null,"What you changed from Auto"),J&&F&&J.id!==F.preset?n("div.wizchange",null,n("span",null,"Preset"),n("span.num",null,J.name)):null,...E.map(_=>{const y=V(_);return!y||!F?null:n("div.wizchange",null,n("span",null,y.label),n("span.num",null,`${j(y,F.settings[_])} → ${j(y,k.settings[_])}`))}),k.colourGroups.length?n("div.wizchange",null,n("span",null,"Colour groups"),n("span.num",null,String(k.colourGroups.length))):null,!E.length&&!k.colourGroups.length&&!(J&&F&&J.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."))},ae=()=>{const k=e.state,F=k.auto,E=k.caps,j=new Set(vn(e).map(I=>I.preset)),J=k.colourGroups.length?{colourGroups:k.colourGroups}:{},_=F?Ee({...F.settings,...J}):null,y=[];let A=null;for(const I of ti){const R=E?.presets.find(K=>K.id===I.preset);if(!R)continue;const ee={...R.settings,...J};if(_&&Ee(ee)===_&&I.preset!=="photo-or-scan"&&A===null){A=I.name;continue}y.push({id:I.preset,name:I.name,why:I.why,note:I.note,settings:ee,preset:I.preset,suggested:j.has(I.preset)})}return[{id:"auto",name:A?`${A} · Auto`:"As Auto traced",why:F?`Auto used ${Gs(E,F.settings,F.preset)}.`:"Auto's trace.",settings:null,preset:null,suggested:!1},...y.filter(I=>I.suggested),...y.filter(I=>!I.suggested)]},de=k=>{const F=e.state;if(k.id==="auto"){const E=F.auto;return!!E&&F.preset===E?.preset&&Le(F.caps,F.settings,E.settings).length===0}return F.preset===k.preset},$e=k=>{k.id==="auto"?s.backToAuto():k.preset&&s.setPreset(k.preset)},pe=k=>{if(!k){C();return}const F=e.state;if(k.id==="auto"||Me(k)){F.auto?.svg&&e.set({compare:S()??{svg:F.auto.svg,label:"Auto"}});return}const E=k.settings?o.get(Ee(k.settings)):void 0;E?.state==="done"&&e.set({compare:{svg:E.svg,label:`Preview · ${k.name} (draft)`}})},Me=k=>{const F=e.state.auto;if(!F||!k.settings)return!1;const E=e.state.colourGroups.length?{colourGroups:e.state.colourGroups}:{};return Ee(k.settings)===Ee({...F.settings,...E})},Ne=k=>{const F=e.state;if(k.id==="auto"||Me(k)){const j=F.auto?.report;return j?{url:z(),text:`${re(j.meanDe00)} dE00 · ${oe(j.paths)} paths · ${ie(j.bytes)}`,busy:!1}:F.tracing?{url:null,text:"tracing…",busy:!0}:{url:null,text:"no trace yet",busy:!1}}const E=k.settings?o.get(Ee(k.settings)):void 0;return!E||E.state==="queued"?{url:null,text:F.auto?.report?"queued":"after Auto",busy:!0}:E.state==="running"?{url:null,text:"tracing a draft…",busy:!0}:E.state==="failed"?{url:null,text:"no preview",busy:!1}:{url:E.url,text:`${re(E.report.meanDe00)} dE00 · ${oe(E.report.paths)} paths · ${ie(E.report.bytes)}`,busy:!1}},Te=()=>{const k=ae(),F=k.map(_=>_.id).join(",");if(!X||X.dataset.signature!==F){X=n("div.kinds",{"data-signature":F,role:"radiogroup","aria-label":"What is it?"});for(const _ of k){const y=n("button.kind",{role:"radio","data-key":_.id,"data-ctl":`wiz-kind:${_.id}`,onclick:()=>$e(_),onpointerenter:()=>pe(_),onpointerleave:()=>pe(null),onfocus:()=>pe(_),onblur:()=>pe(null)},n("span.kthumb",null,n("img",{alt:"",draggable:"false"}),n("span.kspin")),n("span.kname",null,_.name,_.suggested?n("span.ksugg",null,"suggested"):null),n("span.kwhy",null,_.why),_.note?n("span.knote",null,_.note):null,n("span.kstat.num"));X.append(y)}N(f,X,n("p.muted.wizsmall.kqueue"))}const E=e.state;E.auto&&!E.auto.report&&E.tracing||o.want(k.filter(_=>_.settings&&!Me(_)).map(_=>({key:Ee(_.settings),settings:_.settings})));for(const _ of k){const y=X.querySelector(`[data-key="${_.id}"]`);if(!y)continue;const A=Ne(_),L=y.querySelector("img");A.url&&L.getAttribute("src")!==A.url&&(L.src=A.url),L.hidden=!A.url,y.classList.toggle("busy",A.busy),y.setAttribute("aria-checked",String(de(_))),y.querySelector(".kstat").textContent=A.text}const j=o.pending(),J=f.querySelector(".kqueue");J&&(J.textContent=j?`Previews are small drafts of this image, traced one at a time behind the main trace · ${j} 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 ue(){if(a)return;if(!e.state.auto&&e.state.svg&&e.state.report&&!e.state.tracing){const E=e.state;e.set({auto:{generation:E.resultGeneration,settings:{...E.settings},preset:E.preset,svg:E.svg,report:E.report,palette:E.palette,losses:E.losses}})}const k=e.state;r={settings:{...k.settings},preset:k.preset,colourGroups:[...k.colourGroups]},l=k.view,c="auto",X=null;const F=n("div.sheetbody.wizbody",null,h,x,M);a=n("div.sheet.wizsheet",null,F),t.classList.add("wizarding"),t.append(a),e.set({wizard:{step:w()[0]},chooser:!1,view:k.view==="ab"?"side":k.view,compare:S()}),k.prefs?.seenFirstRun||s.markSeen(),i=[e.on(["wizard","facts","caps","prefs"],D),e.on(["denoiserFetch"],()=>{a&&$()==="cleanup"&&Z()}),e.on(["settings","preset","colourGroups","view"],()=>{Z(),($()==="kind"||$()==="output")&&U()}),e.on(["report","result","tracing","palette","groupSuggestions","resultGroups","paletteSelection","simplifyTo","losses","auto","svg"],()=>{U(),(e.state.compare===null||e.state.compare===d||e.state.compare?.label==="Auto")&&C(),T()}),o.on(()=>{$()==="kind"&&U()}),e.on(["auto"],P)],D(),(M.querySelector("[data-ctl=wiz-next]")??F).focus?.()}function Ie(k=!1,F=!1){if(!a)return;for(const _ of i)_();i=[],o.pause(),a.remove(),a=null,X=null,t.classList.remove("wizarding"),p&&URL.revokeObjectURL(p.url),p=null,d=null;const E=e.state,j=r!==null&&(E.preset!==r.preset||Le(E.caps,E.settings,r.settings).length>0||JSON.stringify(E.colourGroups)!==JSON.stringify(r.colourGroups)),J=E.auto?Le(E.caps,E.auto.settings,E.settings).length:0;e.set({wizard:null,compare:null,hoverFill:null,view:l,railTab:j?"tune":E.railTab}),r=null,k&&j&&W(J?`Custom settings kept: ${J} control${J===1?"":"s"} changed from Auto, all of them under Tune.`:"Custom settings kept. Everything is under Tune.",{kind:"good"}),F&&s.openExport()}return{open:ue,close:Ie,isOpen:()=>a!==null}}const Jn=4,Xn=24;function oi(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 v=++i;o.classList.add("busy");try{const S=await Qn(e,t);if(v!==i)return;s.src=S.toDataURL("image/png")}catch{v===i&&W("The card could not be drawn.",{kind:"bad"})}finally{v===i&&o.classList.remove("busy")}},l=()=>{const[v,S]=Vs(t.size);o.style.setProperty("--ar",String(v/S)),a.textContent=`${v} × ${S} px · PNG · ${t.theme}`},c=[],d=()=>{c.forEach(v=>v()),l(),r()},p=(v,S,C)=>{const z=C.map(P=>n("button",{type:"button",role:"radio",onclick:()=>{t[v]=P.value,d()}},n("span",null,P.label),P.sub?n("small",null,P.sub):null));return c.push(()=>z.forEach((P,B)=>P.setAttribute("aria-checked",String(t[v]===C[B].value)))),Kn(S,n("div.cardseg",{role:"radiogroup","aria-label":S},...z))},h=(v,S,C)=>{const z=n("span.switch"),P=n("button.optrow",{type:"button",role:"switch",onclick:()=>{t[v]=!t[v],d()}},n("span.optext",null,n("span.optlabel",null,S),n("span.optnote",null,C)),z);return c.push(()=>{P.setAttribute("aria-checked",String(t[v])),z.setAttribute("aria-checked",String(t[v]))}),P},g=n("span.num",null,`${t.zoom}×`),u=n("input.slider",{type:"range",min:String(Jn),max:String(Xn),step:"1",value:String(t.zoom),"aria-label":"Detail crop magnification",oninput:v=>{g.textContent=`${v.target.value}×`},onchange:v=>{t.zoom=Number(v.target.value),d()}}),f=n("div.optslider",null,n("div.optsliderhead",null,n("span",null,"Detail magnification"),g),u,n("div.stops",null,n("span",null,`${Jn}×`),n("span",null,`${Xn}×`)));c.push(()=>{u.disabled=!t.detail,f.classList.toggle("off",!t.detail)});const b=(v,S,C)=>async()=>{const z=v.lastChild?.textContent??"";v.disabled=!0,v.lastChild&&(v.lastChild.textContent=S);try{await C()}finally{v.disabled=!1,v.lastChild&&(v.lastChild.textContent=z)}},x=async()=>{const v=await Qn(e,t);return new Promise(S=>v.toBlob(S,"image/png"))},M=n("button.btn",{type:"button"},q("copy",15),n("span",null,"Copy image"));M.onclick=b(M,"Copying…",async()=>{const v=await x();if(v)try{await navigator.clipboard.write([new ClipboardItem({"image/png":v})]),W("Card copied.",{kind:"good"})}catch{W("This system would not take an image on the clipboard; save it instead.",{kind:"bad"})}});const w=n("button.btn.primary",{type:"button"},q("download",15),n("span",null,"Download PNG"));w.onclick=b(w,"Saving…",async()=>{const v=e.state.source?.name.replace(/\.[^.]+$/,"")??"inkvec",S=await x();if(!S)return;const C=await zs(`${v}-card.png`,new Uint8Array(await S.arrayBuffer()),[{name:"PNG",extensions:["png"]}]);C&&(ce(),W(C.path?`Card saved to ${C.path}`:`Downloaded ${C.name}`,{kind:"good",action:Lt(C.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:ce},q("x",16))),n("div.composerbody",null,n("div.composerpreview",null,o,a),n("div.composerside",null,p("size","Format",[{value:"1600x900",label:"Landscape",sub:"1600 × 900"},{value:"1080x1080",label:"Square",sub:"1080 × 1080"}]),p("theme","Appearance",[{value:"dark",label:"Dark"},{value:"light",label:"Light"}]),Kn("On the card",n("div.optlist",null,h("numbers","The numbers","Colour difference, coordinates and file size."),h("detail","A zoomed detail","The vector edge cutting through the source's pixels."),f,h("fileName","The file name","Off by default, so unreleased work stays private."))),n("div.composeractions",null,w,M))));qe($),d()}function Kn(e,t){return n("div.optgroup",null,n("span.eyebrow",null,e),t)}function Vs(e){return e==="1600x900"?[1600,900]:[1080,1080]}async function Qn(e,t){const[s,o]=Vs(t.size),a=document.createElement("canvas");a.width=s,a.height=o;const i=a.getContext("2d");if(!i)return a;const r=t.theme==="dark",l=r?"#1a1816":"#faf8f5",c=r?"#faf8f5":"#1a1816",d=r?"rgba(250,248,245,.4)":"rgba(26,24,22,.46)",p=r?"rgba(250,248,245,.55)":"rgba(26,24,22,.58)",h=r?"#c9754a":"#9c5730",g=r?"#26231f":"#ffffff",u=r?"#211f1c":"#e8e3db";await Promise.all([document.fonts.load('500 20px "Playfair Studio"'),document.fonts.load('400 20px "Inter Studio"'),document.fonts.load('500 20px "Inter Studio"')]).catch(()=>{}),i.fillStyle=l,i.fillRect(0,0,s,o);const f=i.createRadialGradient(s*.2,-o*.1,0,s*.2,-o*.1,s*.6);f.addColorStop(0,"rgba(201,117,74,.16)"),f.addColorStop(1,"rgba(201,117,74,0)"),i.fillStyle=f,i.fillRect(0,0,s,o);const b=Math.round(s*.042),x=t.size==="1080x1080",M=x?s-b*2:Math.round(s*.46);i.fillStyle=d,i.font=`500 ${Math.round(s*.0105)}px "Inter Studio", Inter, sans-serif`,i.letterSpacing="3px",i.fillText("PNG TRACED TO SVG",b,b+14),i.letterSpacing="0px",i.fillStyle=c;const w=Math.round(s*(x?.05:.039));i.font=`500 ${w}px "Playfair Studio", Georgia, serif`,i.fillText("A logo, measured",b,b+w+26),i.fillText("rather than guessed.",b,b+w*2+30);let $=b+w*2+96;t.fileName&&e.state.source&&(i.fillStyle=p,i.font=`400 ${Math.round(s*.013)}px "Inter Studio", Inter, sans-serif`,i.fillText(e.state.source.name,b,$),$+=40);const v=e.state.report;if(t.numbers&&v){const ne=[[re(v.meanDe00),"mean dE00"],[oe(v.coordinates),"coordinates"],[`${(v.bytes/1024).toFixed(1)} KB`,"SVG"]];let Q=b;for(const[X,U]of ne.slice(0,x?2:3)){i.fillStyle=X===re(v.meanDe00)?h:c;const se=Math.round(s*.026);i.font=`500 ${se}px "Playfair Studio", Georgia, serif`,i.fillText(X,Q,$+se),i.fillStyle=d,i.font=`400 ${Math.round(s*.0085)}px "Inter Studio", Inter, sans-serif`,i.fillText(U,Q,$+se+20),Q+=Math.max(i.measureText(U).width,120)+Math.round(s*.05)}}i.fillStyle=p,i.font=`400 ${Math.round(s*.0095)}px "Inter Studio", Inter, sans-serif`,i.fillText("traced with Inkvec Studio · by LogoLabs",b,o-b);const S=x?b:b+M,C=s-S-b,z=x?Math.round(o*.42):b,P=Math.round(x?o*.3:o*.44),B=(ne,Q,X,U)=>{for(let te=0;te*14{i.save(),i.beginPath(),i.rect(Q,z,T,P),i.clip(),B(Q,z,T,P);const se=await Wt(ne),te=Math.min((T-40)/se.width,(P-40)/se.height),ae=se.width*te,de=se.height*te;i.drawImage(se,Q+(T-ae)/2,z+(P-de)/2,ae,de),i.restore(),i.fillStyle=U,i.font=`500 ${Math.round(s*.0085)}px "Inter Studio", Inter, sans-serif`,i.letterSpacing="2px",i.fillText(X,Q+12,z+22),i.letterSpacing="0px"};if(D&&await Z(D.preview,S,D.container.toUpperCase(),d),H&&await Z(H.url,S+T+12,"SVG",h),t.detail&&D&&H){const ne=z+P+14,Q=o-ne-b-40;if(Q>60){const X=e.state.worstCorner,U=t.zoom,se=X?.x??H.w/2,te=X?.y??H.h/2,ae=S+C/2-se*U,de=ne+Q/2-te*U;i.save(),i.beginPath(),i.rect(S,ne,C,Q),i.clip(),i.fillStyle=r?"#141210":"#f0ece6",i.fillRect(S,ne,C,Q);const $e=await Wt(D.preview);i.imageSmoothingEnabled=!1,i.drawImage($e,ae,de,H.w*U,H.h*U),i.imageSmoothingEnabled=!0,i.strokeStyle=r?"rgba(250,248,245,.09)":"rgba(26,24,22,.12)",i.lineWidth=1;const pe=(ae%U+U)%U,Me=(de%U+U)%U;for(let ue=S+pe;ue0&&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 Wt(e){return new Promise((t,s)=>{const o=new Image;o.onload=()=>t(o),o.onerror=s,o.src=e})}const es=[512,1024,2048];function ts(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 Ys(e,t,s){const o=Uo({svg:!0,svgMinified:!0,pngSizes:[...es],favicon:!1,assetPack:!0});let a=[],i=e.state.prefs?.outputFolder??null;const r=n("div.sheetbody"),l=n("div.sheet",{onmousedown:u=>{u.target===l&&c()}});l.append(r);const c=()=>{l.remove(),e.set({exportOpen:!1})},d=u=>a.filter(f=>f.group===u).reduce((f,b)=>f+b.bytes,0),p=async()=>{en({export:{...o,pngSizes:[...o.pngSizes]}});const u=ts(e,o);if(u){try{a=await O.planExport(u)}catch(f){a=[],W(String(f),{kind:"bad"})}g()}},h=(u,f,b,x)=>n("button.format",{onclick:x,"aria-pressed":String(b)},n("span.checkbox",{"aria-checked":String(b)},q("check",11)),n("span",null,u),n("span.size",null,b?ie(d(f)):"—"));function g(){const u=a.reduce((f,b)=>f+b.bytes,0);N(r,n("div.cardhead",null,n("span.serif",{style:{fontSize:"19px"}},"Export"),n("button.reset",{onclick:c},"Close")),h("SVG","svg",o.svg,()=>{o.svg=!o.svg,p()}),h("SVG (minified)","svgMinified",o.svgMinified,()=>{o.svgMinified=!o.svgMinified,p()}),h("PNG · 512 / 1024 / 2048","png",o.pngSizes.length>0,()=>{o.pngSizes=o.pngSizes.length?[]:[...es],p()}),h("ICO and favicon set","favicon",o.favicon,()=>{o.favicon=!o.favicon,p()}),h("Asset pack (.zip)","assetPack",o.assetPack,()=>{o.assetPack=!o.assetPack,p()}),n("div.format",{style:{cursor:"default"}},n("span.muted",{style:{fontSize:"11px"}},"To"),n("span",{style:{flex:"1"}},a.length>1?"Your downloads, as one .zip":"Your downloads")),n("button.btn.primary",{disabled:!a.length,onclick:async()=>{let f=i;f="",c(),await s();const b=ts(e,o);if(b)try{const x=await O.writeExport(b,f??"");W(jn?`${x.length} file${x.length===1?"":"s"} written to ${f}`:`Downloaded ${x[0]??"the export"}`,{kind:"good",action:Lt(jn?x[0]??null:null)})}catch(x){W(String(x),{kind:"bad"})}}},a.length?`Download ${a.length} file${a.length===1?"":"s"} · ${ie(u)}`:"Nothing selected"))}e.set({exportOpen:!0}),t.append(l),g(),p()}function st(){return ri()}function ii(){try{return window.self!==window.top}catch{return!0}}function ri(){const e=n("button.btn.ghost.compact.fullscreen",{"data-ctl":"fullscreen",title:"Fill the screen (Esc to leave)",onclick:()=>{document.fullscreenElement?document.exitFullscreen():document.documentElement.requestFullscreen().catch(()=>o())}},q("maximize",14),n("span",null,"Full screen")),t=e.querySelector("span"),s=()=>{t&&(t.textContent=document.fullscreenElement?"Exit full screen":"Full screen")};document.addEventListener("fullscreenchange",s);const o=()=>window.open(window.location.href,"_blank","noopener"),a=ii()?n("button.btn.ghost.compact",{"data-ctl":"own-tab",title:"The same app at its own address, with the whole window",onclick:o},q("external",14),n("span",null,"Open in its own tab")):null,i=n("a.btn.ghost.compact",{"data-ctl":"home",href:"../index.html",title:"The Inkvec front page: what's new, before and after, results"},q("globe",14),n("span",null,"Inkvec home")),r=n("a.btn.ghost.compact.desktopapp",{"data-ctl":"desktop-app",href:Ts,target:"_blank",rel:"noopener noreferrer",title:ta},q("download",14),n("span",null,"Desktop app"));return n("div.webcontrols",null,i,r,document.fullscreenEnabled?e:null,a)}const ns="https://logolabs.github.io/inkvec/studio/",Ht="./guide/";function Zs(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 Ut(e){xo()?Qo(e).catch(()=>W("Could not open the link in your browser.",{kind:"bad"})):window.open(e,"_blank","noopener")}let Pe=null;function Sn(e="index.html"){if(Pe){Pe.querySelector("iframe")?.setAttribute("src",Ht+e);return}const t=n("iframe",{title:"Inkvec Studio user guide",src:Ht+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""};Pe=n("div.screen",{role:"dialog","aria-label":"User guide"},n("div.screenbar",{"data-tauri-drag-region":""},n("span",{style:{fontSize:"12.5px",fontWeight:"600"}},"User guide"),n("div.spacer"),n("button.btn.compact.ghost",{onclick:()=>t.setAttribute("src",Ht+"index.html")},"Contents"),n("button.btn.compact.ghost",{title:"The same page on logolabs.github.io, in your browser",onclick:()=>Ut(ns+o())},"Open in browser"),n("button.btn.compact",{onclick:sn},"Done"),n("div.sep"),st()),s),t.addEventListener("load",()=>{const a=t.contentDocument;if(!a||!a.querySelector('meta[name="inkvec-guide"]')){N(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:()=>Ut(ns)},"Open the online guide")));return}a.addEventListener("keydown",i=>{i.key==="Escape"&&sn(),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(),Ut(l))})}),window.addEventListener("keydown",Js,!0),(document.getElementById("app")??document.body).append(Pe)}function sn(){window.removeEventListener("keydown",Js,!0),Pe?.remove(),Pe=null}function Js(e){e.stopImmediatePropagation(),(e.key==="Escape"||e.key==="F1")&&(e.preventDefault(),e.key==="Escape"&&sn())}function li(e){window.addEventListener("keydown",t=>{t.key!=="F1"||Pe||(t.preventDefault(),Sn(Zs(e.state)))})}const ut=30,ss=8;function ci(e){const t=n("div.batchbar"),s=n("div.batchhead.eyebrow"),o=n("div.batchviewport"),a=n("div.batchfoot"),i=n("section.stage",{style:{background:"var(--paper)"}},t,s,o,a),r=n("div"),l=n("div"),c=n("div");o.append(r,l,c);const d=()=>{const w=e.state.batch;return w.failuresFirst?[...w.rows].sort(($,v)=>+(v.state==="failed")-+($.state==="failed")):w.rows},p=()=>n("div.firstrun",{style:{minHeight:"100%",background:"transparent"}},n("div.drop",null,n("span.glyph",null,q("folder",32)),n("div",{style:{display:"flex",flexDirection:"column",gap:"6px"}},n("span.headline",null,"Trace a whole folder"),n("span.faint",{style:{fontSize:"13px",maxWidth:"46ch"}},"Every PNG, JPEG and WebP in it becomes an SVG in the output folder. A row that fails stays listed after the run.")),n("button.btn.primary",{onclick:()=>void u()},"Choose a folder…"))),h=()=>{const w=d();if(s.hidden=w.length===0,!w.length){r.style.height="0px",c.style.height="0px",N(l,p());return}const $=o.scrollTop,v=Math.ceil(o.clientHeight/ut)+ss*2,S=Math.max(0,Math.floor($/ut)-ss),C=w.slice(S,S+v);r.style.height=`${S*ut}px`,c.style.height=`${Math.max(0,(w.length-S-C.length)*ut)}px`,N(l,...C.map(z=>ui(z,e,()=>e.touch("batch"))))};o.addEventListener("scroll",h,{passive:!0});const g=async()=>{const w=await Xo({directory:!0,multiple:!1});return typeof w=="string"?w:null},u=async()=>{const w=await g();if(w)try{const $=await O.batchScan(w),v=e.state.batch;v.folder=w,v.outputDir??=`${w}${w.includes("\\")?"\\":"/"}svg`,v.rows=$.map((S,C)=>({id:C,path:S,file:S.split(/[\\/]/).pop()??S,preset:v.preset,state:"queued",de00:null,coordinates:null,outBytes:null,destination:"",message:null,seconds:null})),v.totals=null,e.touch("batch")}catch($){W(String($),{kind:"bad"})}},f=async()=>{const w=e.state.batch;if(!w.folder||!w.outputDir){W("Choose a folder of images and an output folder first.");return}w.running=!0,w.paused=!1,e.touch("batch");try{await O.batchStart({files:w.rows.map($=>$.path),preset:w.preset,overrides:w.rows.filter($=>$.preset!==w.preset).map($=>[$.id,$.preset]),outputDir:w.outputDir,skipExisting:w.skipExisting})}catch($){w.running=!1,e.touch("batch"),W(String($),{kind:"bad"})}},b=()=>{const w=e.state.batch,$=e.state.caps?.presets??[],v=(S,C,z)=>n("button.btn.compact",{onclick:C,title:null},S);N(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:S=>{const C=S.target.value;w.preset=C;for(const z of w.rows)z.state==="queued"&&(z.preset=C);e.touch("batch")}},...$.map(S=>n("option",{value:S.id,selected:w.preset===S.id?"":null},`Preset for all · ${S.name}`))),v(w.folder?`Input · ${w.folder}`:"Choose a folder…",()=>void u()),v(w.outputDir?`Output · ${w.outputDir}`:"Output folder…",async()=>{const S=await g();S&&(w.outputDir=S,e.touch("batch"))}),n("button.btn.compact",{"aria-pressed":String(w.skipExisting),onclick:()=>{w.skipExisting=!w.skipExisting,e.touch("batch")}},"Skip existing"),n("div.spacer"),n("button.btn.compact",{"aria-pressed":String(w.failuresFirst),disabled:!w.rows.some(S=>S.state==="failed"),onclick:()=>{w.failuresFirst=!w.failuresFirst,e.touch("batch")}},"Failures first"),w.running?v(w.paused?"Resume":"Pause",()=>{w.paused=!w.paused,O.batchPause(w.paused),e.touch("batch")}):null,w.running?v("Cancel",()=>void O.batchCancel()):n("button.btn.primary.compact",{disabled:!w.rows.length,onclick:()=>void f()},"Start")),N(s,n("span"),n("span",null,"File"),n("span",null,"Preset"),n("span",null,"Status"),n("span.right",null,"dE00"),n("span.right",null,"Coordinates"),n("span.right",null,"Output"),n("span",null,"Destination"))},x=()=>{const w=e.state.batch,$=w.totals,v=$?.finished??0,S=$?.total??w.rows.length,C=$&&$.sourceBytes>0?1-$.bytesWritten/$.sourceBytes:null;N(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,S?`${v} of ${S} done${$?.failed?` · ${$.failed} failed`:""}`:"Nothing queued"),n("span",null,$?.remaining!=null?`${Go($.remaining)} left`:"")),n("div.progress",null,n("i",{style:{width:Se(S?v/S:0)}}))),$?n("span.faint",null,`mean dE00 ${re($.meanDe00)} · ${ie($.bytesWritten)} written${C!==null?` · ${Se(C)} smaller than source`:""}`):null,n("button.reset",{style:{marginLeft:"auto"},disabled:!w.rows.some(z=>z.state!=="queued"),onclick:async()=>{const z=await O.batchStatsCsv(),P=await Ko({defaultPath:"stats.csv",filters:[{name:"CSV",extensions:["csv"]}]});P&&(await O.saveBytes(P,[...new TextEncoder().encode(z)]),W(`Stats written to ${P}`,{kind:"good",action:{label:"Show in folder",run:()=>void ea(P)}}))}},"Export stats.csv"))},M=()=>{b(),x(),h()};return e.on(["batch","caps"],M),M(),i}function ui(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:()=>di(i,e,t,s)},pi(e.preset));return n(`div.batchrow${o}`,{title:e.message??e.path},n("span",{style:{width:"14px",height:"14px",borderRadius:"4px",background:"var(--surface)",display:"block"}}),n("span.trunc.dim",null,e.file),i,n(`span.state.${e.state}`,null,n("i"),e.state),n("span.right",{class:e.de00===null?"muted":"dim"},e.de00===null?"—":re(e.de00)),n("span.right.faint",null,e.coordinates===null?"—":oe(e.coordinates)),n("span.right.faint",null,e.outBytes===null?"—":ie(e.outBytes)),n("span.trunc.muted",null,e.message??e.destination))}function di(e,t,s,o){const a=s.state.caps?.presets??[];fn(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),ce(),o()}},n("span",{style:{flex:"1"}},i.name),n("span.when",null,i.id===t.preset?"current":i.subtitle)))))}function pi(e){return e.split("-").map((t,s)=>s===0?t[0].toUpperCase()+t.slice(1):t).join(" ")}function hi(e){const t=n("div.pane",null,n("span.eyebrow.panelabel",null,"Before")),s=n("div.pane",null,n("span.eyebrow.panelabel",null,"After")),o=n("div.wipehandle",{role:"separator","aria-label":"Wipe"}),a=n("div.panes",null,t,s,o),i=n("div.viewertools"),r=n("section.stage",null,i,a);let l="side",c=.5,d=!1,p="auto",h=null,g=null,u=!1;const f=()=>{u||!e.state.prefs||(u=!0,{minifyView:l,minifyBackdrop:p}=Ho())},b=()=>{f();const T=p==="auto"?h:p;a.classList.toggle("onlight",T==="light"),a.classList.toggle("ondark",T==="dark")},x=()=>{if(f(),a.classList.toggle("stacked",l!=="side"),o.style.display=l==="wipe"?"":"none",l==="side")t.classList.remove("hidden"),s.classList.remove("hidden"),t.style.clipPath="",s.style.clipPath="";else if(l==="wipe"){t.classList.remove("hidden"),s.classList.remove("hidden");const T=`${(c*100).toFixed(2)}%`;o.style.left=T,t.style.clipPath=`inset(0 ${(100-c*100).toFixed(2)}% 0 0)`,s.style.clipPath=`inset(0 0 0 ${T})`}else t.style.clipPath="",s.style.clipPath="",t.classList.toggle("hidden",!d),s.classList.toggle("hidden",d)};o.addEventListener("pointerdown",T=>{o.setPointerCapture(T.pointerId);const D=H=>{const Z=a.getBoundingClientRect();c=Math.min(.98,Math.max(.02,(H.clientX-Z.left)/Z.width)),x()},V=()=>{o.removeEventListener("pointermove",D),o.removeEventListener("pointerup",V)};o.addEventListener("pointermove",D),o.addEventListener("pointerup",V)}),window.addEventListener("keydown",T=>{e.state.tab!=="minify"||e.state.screen||T.key!==" "||T.repeat||T.target instanceof HTMLInputElement||T.target instanceof HTMLTextAreaElement||(T.preventDefault(),d=!0,x())}),window.addEventListener("keyup",T=>{T.key===" "&&d&&(d=!1,x())});const M=()=>{f();const T=e.state.minify,D=T.result;N(i,n("div.seg",null,...["side","wipe","ab"].map(V=>n("button",{"aria-pressed":String(l===V),disabled:!T.before,onclick:()=>{l=V,en({minifyView:V}),x(),M()}},V==="side"?"Side by side":V==="wipe"?"Wipe":"A/B"))),n("span.muted",{style:{fontSize:"11.5px"}},"hold ",n("kbd",null,"Space")," to flick"),n("div",{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(V=>n("button",{"aria-pressed":String(p===V),disabled:!T.before,onclick:()=>{p=V,en({minifyBackdrop:V}),b(),M()}},V==="auto"?"Auto":V==="light"?"Light":"Dark")))),n("span.muted",{style:{marginLeft:"auto",fontSize:"11.5px"}},D?`Nothing moved more than ${T.settings.tolerancePx} px${D.differenceDe00===null?"":` · measured ${re(D.differenceDe00)} dE00`}`:""))},w=n("aside.minifyrail"),$=n("div",{style:{flex:"1",minHeight:"0",display:"flex"}},r,w);let v=0;const S=async()=>{const T=e.state.minify;if(!T.before)return;const D=++v;try{const V=await O.minify(T.before,T.settings);if(D!==v)return;e.state.minify.result=V,e.state.minify.error=null}catch(V){if(D!==v)return;e.state.minify.result=null,e.state.minify.error=String(V)}e.touch("minify")},C=async()=>{const[T]=await dn([{name:"SVG",extensions:["svg"]}]);if(T)try{const D=await xt(T);e.state.minify.name=T.name,e.state.minify.before=D,e.state.minify.result=null,e.state.minify.error=null,e.touch("minify"),await S()}catch(D){W(String(D),{kind:"bad"})}},z=async T=>{if(!T.name.toLowerCase().endsWith(".svg"))return!1;const D=await xt(T);return e.state.minify.name=T.name,e.state.minify.before=D,e.touch("minify"),await S(),!0};$.acceptDropped=z;const P=()=>n("div.drop.solid",null,n("span.glyph",null,q("file",32)),n("div",{style:{display:"flex",flexDirection:"column",gap:"6px"}},n("span.headline",null,"Drop an SVG here"),n("span.faint",{style:{fontSize:"13px"}},"or choose one to rewrite it in the fewest segments")),n("button.btn.primary",{onclick:C},"Open an SVG")),B=()=>{const T=e.state.minify,D=T.result;if(M(),x(),N(t,n("span.eyebrow.panelabel",null,`Before${D?` · ${ie(D.bytesBefore)}`:""}`),T.before?_e(T.before):P()),N(s,n("span.eyebrow.panelabel",null,`After${D?` · ${ie(D.bytesAfter)}`:""}`),D?_e(D.svg):T.before?null:n("span.faint.panehint",null,"The rewritten drawing appears here, on the same ground, so nothing can hide.")),T.before!==g){const H=T.before;g=H,h=null,b(),H&&mi(H).then(Z=>{g===H&&(h=Z,b())})}if(!T.before){N(w,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 V=D?1-D.bytesAfter/Math.max(1,D.bytesBefore):0;N(w,n("div.railscroll",{style:{gap:"20px",padding:"22px 20px"}},T.error?n("div.card",null,n("span.eyebrow",null,"This file would not parse"),n("span.faint",{style:{fontSize:"12.5px",lineHeight:"1.6"}},T.error)):n("div.minifyresult",null,n("span.eyebrow",null,D?`Result · ${Ae((D.ms??0)/1e3)}`:"Working"),n("div",{style:{display:"flex",alignItems:"baseline",gap:"10px"}},n("span.figure",null,D?Se(V):"—"),n("span.dim",{style:{fontSize:"14px",paddingBottom:"6px"}},"smaller")),D?n("span.faint.num",{style:{fontSize:"13px"}},`${ie(D.bytesBefore)} → ${ie(D.bytesAfter)}`):null),D?n("div.stats",null,...[["numbers before",oe(D.numbersBefore)],["numbers after",oe(D.numbersAfter)],["paths",`${D.pathsBefore} → ${D.pathsAfter}`],["segments",`${oe(D.segmentsBefore)} → ${oe(D.segmentsAfter)}`],["difference",D.differenceDe00===null?"—":`${re(D.differenceDe00)} dE00`],["kept as drawn",`${D.guarded} run${D.guarded===1?"":"s"}`]].map(([H,Z])=>n("div",null,n("span.k",null,H),n("span.v",null,Z)))):null,fi(e,S),n("div.card",null,n("div",{style:{display:"flex",alignItems:"center",justifyContent:"space-between"}},n("span.dim",{style:{fontSize:"12.5px"}},"Corner threshold"),(()=>{const H=n("span.num.faint",{style:{fontSize:"12px",width:"34px"}},`${T.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(T.settings.cornerDegrees),"aria-label":"Corner threshold in degrees",oninput:Z=>{H.textContent=`${Z.target.value}°`},onchange:Z=>{T.settings.cornerDegrees=Number(Z.target.value),e.touch("minify"),S()}}),H)})()),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(T.settings.documentCleanup),"aria-label":"Document cleanup",onclick:()=>{T.settings.documentCleanup=!T.settings.documentCleanup,e.touch("minify"),S()}})),n("span.muted",{style:{fontSize:"11.5px",lineHeight:"1.5"}},"Removes ids, groups, editor metadata and trailing zeros. Same geometry.")),D&&D.removed.length?n("div",{style:{display:"flex",flexDirection:"column",gap:"7px"}},n("span.eyebrow",null,"Removed"),...D.removed.map(H=>n("div",{style:{display:"flex",alignItems:"baseline",gap:"10px",fontSize:"12px"}},n("span.faint",{style:{flex:"1"}},H.what),n("span.dim.num",null,H.amount)))):null),n("div.railfoot",null,T.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:T.before,name:T.name??"drawing.svg"}}))},q("wand",14),"Rebuild it clean by re-tracing"):null,n("div.row",null,n("button.btn.primary",{style:{flex:"1"},disabled:!D,onclick:async()=>{if(!D)return;const H=(T.name??"drawing").replace(/\.svg$/i,""),Z=await zs(`${H}.min.svg`,D.svg);Z&&W(Z.path?`Saved to ${Z.path}`:`Downloaded ${Z.name}`,{kind:"good",action:Lt(Z.path)})}},"Download SVG"),n("button.btn",{disabled:!D,onclick:async()=>{D&&(await zt(D.svg),W("SVG copied. Paste straight into Figma or Illustrator."))}},"Copy"),n("button.btn",{onclick:C},"Open…"))))};return e.on(["minify"],B),B(),$}function fi(e,t){const s=e.state.minify,o=(r,l,c)=>n("input",{class:`inlinefield${l?" accent":""}`,type:"text",inputmode:"decimal",value:String(r),onchange:d=>{const p=Number(d.target.value);Number.isFinite(p)&&p>0&&(c(p),e.touch("minify"),t())}}),a=o(s.settings.tolerancePx,!0,r=>s.settings.tolerancePx=r),i=r=>{const l=Number(r.value)/1e3;return Number((l*l).toFixed(3))};return n("div.card",null,n("span.sentence",null,"Nothing moves more than ",a," px when the drawing is ",o(s.settings.judgePx,!1,r=>s.settings.judgePx=r)," px wide."),n("input.slider",{type:"range",min:"0",max:"1000",step:"1",value:String(Math.round(Math.sqrt(Math.min(1,s.settings.tolerancePx))*1e3)),"aria-label":"Tolerance",oninput:r=>{a.value=String(i(r.target))},onchange:r=>{s.settings.tolerancePx=i(r.target),e.touch("minify"),t()}}),n("div.stops",null,n("span",null,"exact"),n("span",null,"gentle"),n("span",null,"aggressive")))}function _e(e){const t=n("div.art.centred"),s=Xs(e);return s&&(s.setAttribute("width","100%"),s.setAttribute("height","100%"),t.append(s)),t}function gi(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 Xs(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(gi(o))return o;const a=o.getAttribute("width")??"",i=o.getAttribute("height")??"",r=parseFloat(a),l=parseFloat(i);if(r>0&&l>0&&!a.includes("%")&&!i.includes("%"))return o.setAttribute("viewBox",`0 0 ${r} ${l}`),o;const c=document.createElement("div");c.style.cssText="position:absolute;visibility:hidden;width:0;height:0;overflow:hidden",o.setAttribute("width","1"),o.setAttribute("height","1"),c.append(o),document.body.append(c);try{const d=o.getBBox();if(d.width>0&&d.height>0){const p=Math.max(d.width,d.height)*.02;o.setAttribute("viewBox",`${d.x-p} ${d.y-p} ${d.width+2*p} ${d.height+2*p}`)}else o.setAttribute("viewBox","0 0 100 100")}catch{o.setAttribute("viewBox","0 0 100 100")}finally{c.remove()}return o}async function mi(e){const t=Xs(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((h,g)=>{const u=new Image;u.onload=()=>h(u),u.onerror=g,u.src=o}),i=document.createElement("canvas");i.width=s,i.height=s;const r=i.getContext("2d",{willReadFrequently:!0});if(!r)return null;r.drawImage(a,0,0,s,s);const l=r.getImageData(0,0,s,s).data;let c=0,d=0;for(let h=0;h.8?"dark":null}catch{return null}}const be=25.4,os=[["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."]],as=[["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."]],is=[{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 bi(e){const t=n("div.viewertools"),s=n("div.pane",null),o=n("div.panes.onlight",null,s),a=n("section.stage",null,t,o),i=n("aside.minifyrail"),r=n("div",{style:{flex:"1",minHeight:"0",display:"flex"}},a,i);let l=0;const c=async()=>{const b=e.state.fab;if(!b.svg)return;const x=++l;try{const M=await O.fabPrepare(b.svg,b.options);if(x!==l)return;b.plan=M,b.error=null,b.shown>=M.layers.length&&(b.shown=-1)}catch(M){if(x!==l)return;b.plan=null,b.error=String(M)}e.touch("fab")},d=async(b,x)=>{const M=e.state.fab;M.name=b,M.svg=x,M.plan=null,M.error=null,M.shown=-1;try{const w=await O.fabAnalyze(x);M.analysis=w;const $=w.colours.filter(v=>!v.background).map(v=>v.hex);M.options.include=$,M.options.order=[...$],$.length>1&&M.options.mode==="singleColour"&&(M.options.mode="layered")}catch(w){M.analysis=null,M.error=String(w)}e.touch("fab"),await c()},p=async()=>{const[b]=await dn([{name:"SVG",extensions:["svg"]}]);if(b)try{await d(b.name,await xt(b))}catch(x){W(String(x),{kind:"bad"})}},h=async()=>{const b=e.state;b.svg&&await d(b.source?.name?`${b.source.name.replace(/\.[^.]+$/,"")}.svg`:"trace.svg",b.svg)},g=async b=>b.name.toLowerCase().endsWith(".svg")?(await d(b.name,await xt(b)),!0):!1;r.acceptDropped=g;const u=(b,x=!1)=>{const M=e.state.fab;Object.assign(M.options,b),x||(M.preset=null),e.touch("fab"),c()},f=()=>{const b=e.state.fab;if(xi(e,t,s),!b.svg){N(i,Si(e,p,h));return}N(i,n("div.railscroll",{style:{gap:"16px",padding:"20px"}},$i(e,p,h),b.error?n("div.card",null,n("span.eyebrow",null,"This file could not be prepared"),n("span.faint",null,b.error)):null,Mi(e,u),Ci(e,u),Ti(e,u),Ai(e,u),Ei(e,u),Li(e),Pi(e)),Di(e))};return e.on(["fab"],f),e.on(["svg"],()=>{e.state.tab==="fabricate"&&f()}),f(),r}function Ks(e,t,s=1){return e.state.fab.unit==="in"?`${(t/be).toFixed(s+1)} in`:`${t.toFixed(s)} mm`}function yi(e,t){return e.state.fab.unit!=="in"?t:t.replace(/(\d+(?:\.\d+)?) mm²/g,(s,o)=>`${(Number(o)/(be*be)).toFixed(4)} in²`).replace(/(\d+(?:\.\d+)?) mm(?![²\w])/g,(s,o)=>`${(Number(o)/be).toFixed(3)} in`)}function Ke(e,t){return e.state.fab.unit==="in"?`${(t[0]/be).toFixed(2)} × ${(t[1]/be).toFixed(2)} in`:`${t[0].toFixed(1)} × ${t[1].toFixed(1)} mm`}function wi(e){return e?e.checks.filter(t=>t.code==="thin"||t.code==="gaps"||t.code==="specks").length:0}function vi(){return n("div.drop.solid",null,n("span.glyph",null,q("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 $n(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 xi(e,t,s){const o=e.state.fab,a=o.plan,i=wi(a),r=h=>{o.shown=h,e.touch("fab")};N(t,n("div.seg",{role:"group","aria-label":"Sheet"},n("button",{"aria-pressed":String(o.shown===-1),disabled:!a,onclick:()=>r(-1)},"All sheets"),...(a?.layers??[]).map((h,g)=>n("button",{"aria-pressed":String(o.shown===g),title:`${h.name}: ${Ke(e,h.materialMm)} of material, ${h.parts} pieces, ${oe(h.nodes)} path segments`,onclick:()=>r(g)},$n(h.hex),n("span",{style:{marginLeft:"6px"}},(a?.layers.length??0)>1?`${g+1}`:h.name)))),a&&i?n("button.btn.ghost.compact",{"aria-pressed":String(o.showProblems),style:{marginLeft:"12px",color:o.showProblems?"#e5484d":void 0},title:"Draw the parts too thin to weed (red), the waste gaps too narrow to weed (amber) and the loose specks (ringed)",onclick:()=>{o.showProblems=!o.showProblems,e.touch("fab")}},q("alert",13),n("span",{style:{marginLeft:"6px"}},o.showProblems?"Showing problems":"Show problems")):null,n("span.muted",{style:{marginLeft:"auto",fontSize:"11.5px"}},a?`${Ke(e,a.sizeMm)}${o.options.mode==="lines"?"":o.options.registration&&a.layers.length>1&&(o.options.mode==="layered"||o.options.mode==="inlay")?", marks included":o.options.weedBorderMm>0?", border included":""}`:""));const l=o.options.mode==="lines",c=a?o.shown>=0?a.layers[o.shown]?.svg:l?a.combinedSvg:a.previewSvg:null,d=(h,g=!1)=>(h.style.gridArea="1 / 1",g&&(h.style.pointerEvents="none",h.style.opacity="0.85"),h),p=c?n("div",{style:{display:"grid",placeSelf:"stretch",minWidth:"0",minHeight:"0"}},l&&a&&o.shown<0?ki(d(_e(a.previewSvg))):null,d(_e(c)),a&&i&&o.showProblems?d(_e(a.problemsSvg),!0):null):o.svg?_e(o.svg):vi();N(s,n("span.eyebrow.panelabel",null,o.shown>=0&&a?`${l?"Pen":"Sheet"} ${o.shown+1} · ${a.layers[o.shown].name} · ${Ke(e,a.layers[o.shown].materialMm)}`:l?"What the pen draws, over the drawing":"All sheets, stacked"),p)}function mt(e,t){const s=e.state.fab.options.mode==="lines"?"pen drawing":"sheet";return t===1?s:`${s}s`}function ki(e){return e.style.opacity="0.18",e.style.pointerEvents="none",e}function Si(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 $i(e,t,s){const o=e.state.fab;return n("div",{style:{display:"flex",alignItems:"center",gap:"8px"}},q("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 Mi(e,t){const s=e.state.fab,o=s.options.include.length,a=is.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"}},...is.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)}},q(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 Ci(e,t){const s=e.state.fab.options,o=os.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)"}},...os.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 bt(e,t,s="58px",o=0){return n("input",{class:"inlinefield",type:"text",inputmode:"decimal",value:String(e),style:{width:s},onchange:a=>{const i=Number(a.target.value);Number.isFinite(i)&&i>=o&&t(i)}})}function he(e,t,s,o="58px"){const a=e.state.fab.unit==="in",i=Number(a?(t/be).toFixed(3):t.toFixed(2));return bt(i,r=>s(a?r*be:r),o)}function Mn(e){return e.state.fab.unit==="in"?"in":"mm"}function Ti(e,t){const s=e.state.fab,o=s.analysis?.aspect??1,a=s.options.widthMm,i=Mn(e);return n("div.card",null,n("div.cardhead",null,n("span.eyebrow",null,"Size"),n("div.seg",{role:"group","aria-label":"Units"},...["mm","in"].map(r=>n("button",{"aria-pressed":String(s.unit===r),onclick:()=>{s.unit=r,e.touch("fab")}},r)))),n("span.sentence",null,"The design is ",he(e,a,r=>r>0&&t({widthMm:r})),` ${i} wide and `,he(e,a*o,r=>r>0&&t({widthMm:r/o})),` ${i} tall.`),De("Cut a size-check square",s.options.sizeCheckMm>0,()=>t({sizeCheckMm:s.options.sizeCheckMm>0?0:s.unit==="in"?be:20}),s.options.sizeCheckMm>0?`A ${Ks(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)"}},...as.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"}},as.find(r=>r[0]===s.options.fileUnits)?.[2]??"")))}function Ai(e,t){const s=e.state.fab,o=s.analysis;if(!o)return null;const a=s.options,i=a.mode==="layered",r=i?[...a.order.filter(d=>a.include.includes(d)),...o.colours.map(d=>d.hex).filter(d=>!a.include.includes(d))]:o.colours.map(d=>d.hex),l=d=>o.colours.find(p=>p.hex===d),c=(d,p)=>{const h=a.order.filter(f=>a.include.includes(f)),g=h.indexOf(d),u=g+p;g<0||u<0||u>=h.length||([h[g],h[u]]=[h[u],h[g]],t({order:h}))};return n("div.card",null,n("div.cardhead",null,n("span.eyebrow",null,i?"Colours, bottom sheet first":"Colours"),n("span.muted",{style:{fontSize:"11px"}},`${a.include.length} of ${o.colours.length}`)),...r.map(d=>{const p=l(d);if(!p)return null;const h=a.include.includes(d),g=[p.background?"page":null,p.gradient?"gradient":null,p.translucent?"translucent":null].filter(Boolean);return n("div",{style:{display:"flex",alignItems:"center",gap:"8px",fontSize:"12.5px",opacity:h?"1":"0.55"}},n("button.switch",{role:"switch","aria-checked":String(h),"aria-label":`Cut ${d}`,onclick:()=>{const u=h?a.include.filter(b=>b!==d):[...a.include,d],f=h?a.order:[...a.order.filter(b=>b!==d),d];t({include:u,order:f})}}),$n(d,16),n("span.num",{style:{flex:"1"}},d,g.length?n("span.muted",{style:{marginLeft:"6px",fontSize:"11px"}},g.join(" · ")):null),n("span.muted.num",{style:{fontSize:"11px"}},Se(p.coverage,1)),i&&h?n("span",{style:{display:"flex",gap:"2px"}},n("button.btn.ghost.compact",{title:"Lower in the stack",onclick:()=>c(d,-1)},"↓"),n("button.btn.ghost.compact",{title:"Higher in the stack",onclick:()=>c(d,1)},"↑")):null)}))}function De(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 zi(e,t){const s=e.state.fab.options,o=Mn(e);return n("div.card",null,n("span.eyebrow",null,"Drawing"),n("span.sentence",null,"The pen or tool draws ",he(e,s.penMm,a=>a>0&&t({penMm:a})),` ${o} wide.`),n("span.sentence",null,"Lines up to ",he(e,s.maxLineMm,a=>t({maxLineMm:a})),` ${o} wide are drawn once (0 for any width); wider parts are drawn round their outline.`),De("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."),Qs(e,t),n("span.sentence",null,"Lines stay within ",he(e,s.toleranceMm,a=>a>0&&t({toleranceMm:a})),` ${o} of the drawing.`))}function Qs(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/be).toFixed(1)):Math.round(o.gcodeFeedMmMin);return n("div",{style:{display:"flex",flexDirection:"column",gap:"8px"}},De("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 ",bt(i,r=>r>0&&t({gcodeFeedMmMin:s.unit==="in"?r*be:r}),"64px"),` ${a}, power S`,bt(o.gcodePower,r=>t({gcodePower:r}),"58px"),", ",bt(o.gcodePasses,r=>r>=1&&t({gcodePasses:Math.round(r)}),"40px",1),o.gcodePasses===1?" pass.":" passes."):null)}function Ei(e,t){const s=e.state.fab.options;if(s.mode==="lines")return zi(e,t);const o=Mn(e),a=s.mode==="layered"||s.mode==="inlay";return n("div.card",null,n("span.eyebrow",null,"Cutting"),s.mode==="layered"?n("span.sentence",null,"Each colour runs ",he(e,s.bleedMm,i=>t({bleedMm:i})),` ${o} under the colours above it.`):null,s.mode==="sticker"?n("span.sentence",null,"The contour is ",he(e,s.stickerMarginMm,i=>t({stickerMarginMm:i})),` ${o} outside the artwork.`):null,s.mode==="stencil"?n("span.sentence",null,"Bridges are ",he(e,s.bridgeMm,i=>i>0&&t({bridgeMm:i})),` ${o} wide; the sheet reaches `,he(e,s.stencilMarginMm,i=>t({stencilMarginMm:i})),` ${o} past the artwork.`):null,n("span.sentence",null,"Parts narrower than ",he(e,s.minFeatureMm,i=>i>0&&t({minFeatureMm:i})),` ${o} are too thin to weed.`),De("Remove them",s.removeThin,()=>t({removeThin:!s.removeThin}),"Drops the slivers and necks narrower than that from the sheets."),a?De("Registration marks",s.registration,()=>t({registration:!s.registration})):null,n("span.sentence",null,"Weeding border ",he(e,s.weedBorderMm,i=>t({weedBorderMm:i})),` ${o} outside (0 for none).`),De("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."),Qs(e,t),s.mode==="sticker"||s.mode==="stencil"?null:De("Mirror",s.mirror,()=>t({mirror:!s.mirror}),"For heat-transfer vinyl, which is cut from the back."),n("div",{style:{display:"flex",flexDirection:"column",gap:"6px"}},n("span.dim",{style:{fontSize:"12.5px"}},"Cut lines as"),n("div.seg",{role:"group","aria-label":"Cut lines as"},n("button",{"aria-pressed":String(s.cutStyle==="filled"),onclick:()=>t({cutStyle:"filled"})},"Filled shapes"),n("button",{"aria-pressed":String(s.cutStyle==="hairline"),onclick:()=>t({cutStyle:"hairline"})},"Hairlines")),n("span.muted",{style:{fontSize:"11px",lineHeight:"1.45"}},s.cutStyle==="filled"?"What Cricut Design Space and Silhouette Studio expect: they cut around every shape.":"The laser and sign-cutter convention: only a hairline is a cut. Each sheet keeps its colour, so a laser program can give it its own operation.")),s.cutStyle==="hairline"?n("span.sentence",null,"The cut removes ",he(e,s.kerfMm,i=>t({kerfMm:i})),` ${o} (kerf); parts are grown by half of it so they come out the drawn size.`):null,n("span.sentence",null,"Router bit ",he(e,2*s.dogboneMm,i=>t({dogboneMm:i/2})),` ${o} across (0 for none): every inside corner gets a dogbone so parts seat square.`),n("span.sentence",null,"Cuts stay within ",he(e,s.toleranceMm,i=>i>0&&t({toleranceMm:i})),` ${o} of the drawing.`))}function Li(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,...[[mt(e,2),String(t.layers.length)],["pieces",oe(o)],["path segments",oe(s)],["whole job",Ke(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"}},q(i.level==="info"?"info":"alert",14)),n("span.faint",null,yi(e,i.message))))):n("div",{style:{display:"flex",gap:"8px",alignItems:"center",fontSize:"12.5px"}},q("check",14),n("span.dim",null,"Nothing here will trouble the cutter.")))}function Pi(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)),$n(o.hex,14),n("span.num",{style:{flex:"1"}},o.name),n("span.dim.num",null,Ke(e,o.materialMm)),n("span.muted.num",{style:{width:"54px",textAlign:"right",fontSize:"11px"}},`${o.parts} pc`))))}function Di(e){const t=e.state.fab,s=t.plan,o=(t.name??"drawing").replace(/\.svg$/i,""),a=(i,r)=>{const l=i.layers[r].name.replace(/[^A-Za-z0-9_-]+/g,"");return i.layers.length>1?`${o}-${r+1}-${l}.svg`:`${o}-cut.svg`};return n("div.railfoot",null,n("div.row",null,n("button.btn.primary",{style:{flex:"1"},disabled:!s||s.layers.length===0,onclick:async()=>{if(!s)return;const i=s.layers.map((d,p)=>({name:a(s,p),data:d.svg}));s.layers.length>1&&i.push({name:`${o}-all.svg`,data:s.combinedSvg}),t.dxf&&i.push({name:`${o}.dxf`,data:s.dxf}),t.gcode&&i.push({name:`${o}.gcode`,data:s.gcode});const r=await na(i,`${o}-sheets.zip`);if(!r)return;const l=s.layers.length>1,c=`${s.layers.length} ${mt(e,s.layers.length)}${l?" and one combined file":""}`;W(r.folder?`Saved ${c} to ${r.folder}`:`Downloaded ${c}${i.length>1?" as one .zip":""}`,{kind:"good",action:Lt(r.first)})}},`Download ${s&&s.layers.length>1?`${s.layers.length} ${mt(e,2)}`:`the ${mt(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),W("Copied. Paste it into Design Space, Silhouette Studio or LightBurn."))}},"Copy")),n("div.note",null,n("span",null,s?`Real size: ${Ks(e,s.sizeMm[0])} wide`:"Real sizes"),n("span",null,"Nothing leaves this computer")))}const dt={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"},eo=e=>fetch(new URL(e,document.baseURI)).then(t=>{if(!t.ok)throw new Error(`${e}: HTTP ${t.status}`);return t.json()});let rs=null;function Fi(){return rs??=eo("../showcase.json"),rs}function Ni(e){return eo(`../showcase/${e}.json`)}function Ii(e,t){const s=n("div.screenbody",null,n("div.sc-loading.faint",null,"Loading the showcase…"));return Fi().then(o=>N(s,_i(o)),o=>N(s,n("div.sc-loading.faint",null,`The showcase could not be loaded: ${String(o)}`))),n("div.screen",null,n("div.screenbar",{"data-tauri-drag-region":""},n("span",{style:{fontSize:"12.5px",fontWeight:"600"}},"Showcase"),n("div.spacer"),n("button.btn.compact",{onclick:t},"Done"),n("div.sep"),st()),s)}function _i(e){let t=e.sets[0].cases[0],s="vtracer-1.0-simplify",o="fill",a=0;const i=Bi(),r=n("div.sc-chips"),l=n("p.sc-caption"),c=n("div.sc-list"),d=new Map,p=e.gallery_engines.filter(v=>v!=="inkvec"),h=(v,S,C)=>{const z=n("div.seg"),P=()=>{for(const B of z.querySelectorAll("button"))B.setAttribute("aria-pressed",String(B.dataset.k===S()))};for(const[B,T]of v)z.append(n("button",{"data-k":B,onclick:()=>{C(B),P()}},T));return P(),z},g=async()=>{const v=t,S=++a;for(const[T,D]of d)D.setAttribute("aria-pressed",String(T===v.key));const C=v.what.includes("brand")||v.key.startsWith("brand");N(l,n("b",null,v.label),` · ${v.what}`,C?n("span.faint",{style:{fontSize:"11px",marginLeft:"6px"},title:"Nominative benchmark demonstration. All trademarks belong to their respective owners."},"(nominative benchmark)"):null);const z=v.m.inkvec,P=v.m[s];N(r,n("span.sc-legend.faint",null,n("i.sc-dot.a"),"anchor",n("i.sc-dot.hd"),"control point"),Ze(`Inkvec dE00 ${z.de1024.toFixed(3)}`,!0),Ze(`${dt[s]} dE00 ${P.de1024.toFixed(3)}`),Ze(`Inkvec ${z.paths} paths · ${z.coordinates} coordinates · ${ls(z.bytes)}`,!0),Ze(`${dt[s]} ${P.paths} paths · ${P.coordinates} coordinates · ${ls(P.bytes)}`),Ze(`The artist's file: ${v.artist.paths} paths · ${v.artist.coordinates} coordinates`));const B=await Ni(v.key);S===a&&(i.set(B.input,B.svg.inkvec,B.svg[s],"Inkvec",dt[s]??s),i.layer(o))},u=[];new ResizeObserver(()=>{const v=i.el.getBoundingClientRect().height;v>200&&(c.style.height=`${v}px`)}).observe(i.el);const f=(v,S)=>{t=v,g();const C=d.get(v.key);if(!C)return;const z=c.getBoundingClientRect(),P=C.getBoundingClientRect(),B=34;P.topz.bottom&&(c.scrollTop+=P.bottom-z.bottom+6),S&&C.focus({preventScroll:!0})};c.addEventListener("keydown",v=>{const S=u.findIndex(P=>P.key===t.key),z={ArrowDown:3,ArrowUp:-3,ArrowRight:1,ArrowLeft:-1,Home:-S,End:u.length-1-S}[v.key];z!==void 0&&(v.preventDefault(),f(u[Math.min(u.length-1,Math.max(0,S+z))],!0))});for(const v of e.sets)c.append(n("div.sc-sethead",null,v.label,n("span",null,String(v.cases.length)))),c.append(n("div.sc-tiles",null,...v.cases.map(S=>{const C=n("button.sc-case",{onclick:()=>f(S,!1),title:`${S.label} · ${S.what}`},n("span.sc-thumb",null,n("img",{src:S.thumb,alt:"",loading:"lazy"})),n("span.sc-name",null,S.label));return d.set(S.key,C),u.push(S),C})));const b=n("div.sc-tools",null,n("span.eyebrow",null,"Compare with"),h(p.map(v=>[v,dt[v]??v]),()=>s,v=>{s=v,g()}),n("span.eyebrow",null,"Show"),h([["fill","Fill"],["wire","Wireframe"],["anchor","Anchors"],["handle","Handles"]],()=>o,v=>{o=v,i.layer(o)}),n("span.eyebrow",null,"Zoom"),...[1,4,12].map(v=>n("button.btn.compact.ghost",{onclick:()=>i.zoom(v)},`${v}×`)));g();const x=e.engines.find(v=>v.id==="inkvec"),M=e.best_de_wins.inkvec??0,w=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,b,i.el,l,r)),n("div.sc-results",null,n("h2.serif",null,"Inkvec vs the field"),n("p.faint",null,`${e.cases} benchmark cases (real icons, emoji, synthetic probes and real brand logos; selection fixed from the seed ${e.seed}), each rendered from its source SVG at ${e.size} px, traced by every engine from that raster and scored against the render: CIEDE2000 colour difference, DISTS and DINOv3 perceptual similarity, and geometry against the artist's own file (1.00× is exactly the artist's number of parameters). Inkvec had the lowest colour difference on ${M} of ${e.cases}; in the other sets, on ${w} of ${e.brands.cases} brand logos and ${$} of ${e.corpus.cases} more icons and emoji.`),x?n("div.sc-bignums",null,pt("median colour difference",x.de_median.toFixed(3),"dE00; under 1 is invisible"),pt("geometry vs the artist",`${x.geometry_mean.toFixed(2)}×`,`mean; median ${x.geometry_median.toFixed(2)}×`),pt("DINOv3 similarity",x.dino.toFixed(4),"1 is identical"),pt("seconds per case",x.seconds_median.toFixed(2),"median, native command line")):null,qt(`${e.cases} benchmark cases, re-run at ${e.size} px`,e.engines),qt(`${e.brands.cases} brand logos`,e.brands.engines),qt(`${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:v=>{v.preventDefault(),Et("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 qt(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 ls=e=>`${(e/1024).toFixed(1)} KB`,Ze=(e,t=!1)=>n(`span.sc-chip${t?".win":""}`,null,e),pt=(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 Bi(){const e=[0,1,2].map(()=>{const r=n("div.sc-cam"),l=n("span.sc-tag");return{pane:n("div.sc-pane",null,r,l),cam:r,tag:l}}),t=n("div.sc-viewer",null,...e.map(r=>r.pane)),s={z:1,x:0,y:0};let o=[];const a=()=>{for(const r of e)r.cam.style.transform=`translate(${s.x}px, ${s.y}px)`,r.cam.style.width=r.cam.style.height=`${s.z*100}%`};let i=null;for(const{pane:r}of e){r.addEventListener("wheel",c=>{c.preventDefault();const d=r.getBoundingClientRect(),p=c.clientX-d.left,h=c.clientY-d.top,g=Math.min(40,Math.max(1,s.z*Math.exp(-c.deltaY*.0015))),u=g/s.z;s.x=p-(p-s.x)*u,s.y=h-(h-s.y)*u,s.z=g,a()},{passive:!1}),r.addEventListener("pointerdown",c=>{i=[c.clientX-s.x,c.clientY-s.y],r.setPointerCapture(c.pointerId)}),r.addEventListener("pointermove",c=>{i&&(s.x=c.clientX-i[0],s.y=c.clientY-i[1],a())});const l=()=>i=null;r.addEventListener("pointerup",l),r.addEventListener("pointercancel",l),r.addEventListener("dblclick",()=>{s.z=1,s.x=s.y=0,a()})}return{el:t,set(r,l,c,d,p){N(e[0].cam,n("img.sc-raster",{src:r,alt:"",draggable:"false"})),o=[ds(l),ds(c)],N(e[1].cam,o[0].root),N(e[2].cam,o[1].root),e[0].tag.textContent="Raster",e[1].tag.textContent=`${d} · ${o[0].anchors} anchors`,e[2].tag.textContent=`${p} · ${o[1].anchors} anchors`,a()},layer(r){for(const l of o)l.set(r)},zoom(r){const l=e[0].pane.getBoundingClientRect();s.z=r,s.x=(l.width-l.width*r)/2,s.y=(l.height-l.height*r)/2,a()}}}const cs="http://www.w3.org/2000/svg";function us(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 ds(e){const t=us(e),s=us(e),o=n("div.sc-layer");if(!t||!s)return{root:o,anchors:0,set:()=>{}};s.classList.add("sc-wire");const a=Oi(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 Oi(e){const t=document.createElementNS(cs,"svg");t.setAttribute("viewBox",e.getAttribute("viewBox")??"0 0 100 100"),t.setAttribute("preserveAspectRatio","xMidYMid meet"),t.setAttribute("class","sc-ov");let s="",o="",a="",i=0;const r=h=>h.toFixed(2);for(const h of e.querySelectorAll("path")){const[g,u]=ps(h),f=Ri(h.getAttribute("d")??"",g,u);i+=f.anchors.length;for(const[b,x]of f.anchors)s+=`M${r(b)} ${r(x)}h0.001`;for(const[[b,x],[M,w]]of f.handles)o+=`M${r(b)} ${r(x)}L${r(M)} ${r(w)}`,a+=`M${r(M)} ${r(w)}h0.001`}for(const h of e.querySelectorAll("circle, ellipse")){const[g,u]=ps(h);s+=`M${r(Number(h.getAttribute("cx")??0)+g)} ${r(Number(h.getAttribute("cy")??0)+u)}h0.001`,i++}const l=(h,g)=>{const u=document.createElementNS(cs,"path");return u.setAttribute("d",h||"M0 0"),u.setAttribute("class",g),t.append(u),u},c=l(o,"h"),d=l(a,"hd"),p=l(s,"a");return{svg:t,anchors:p,handles:c,handleDots:d,count:i}}function ps(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 Ri(e,t,s){const o=e.match(/[MLHVCSQTAZmlhvcsqtaz]|-?\d*\.?\d+(?:e[-+]?\d+)?/gi)??[],a=[],i=[];let r=0,l=0,c=0,d=0,p=0,h="",g=null,u=0,f=0;const b=()=>parseFloat(o[r++]),x=(w,$)=>a.push([w+t,$+s]),M=(w,$,v,S)=>i.push([[w+t,$+s],[v+t,S+s]]);for(;r{const a=e.state;if(!a.screen){N(s),s.style.display="none";return}s.style.display="",N(s,a.screen==="settings"?Wi(e,t):a.screen==="showcase"?Ii(e,t.close):Ui(e,t))};return e.on(["screen","prefs","caps"],o),o(),s}function Wi(e,t){const s=e.state.prefs,o=e.state.caps?.denoiser;return n("div.screen",null,n("div.screenbar",{"data-tauri-drag-region":""},n("span",{style:{fontSize:"12.5px",fontWeight:"600"}},"Settings"),n("div.spacer"),n("button.btn.compact",{onclick:t.close},"Done"),n("div.sep"),st()),n("div.screenbody",null,n("div.settingsgrid",null,ht("General",[null,ge("Theme","The light theme matches the dark one token for token.",jt([["system","System"],["dark","Dark"],["light","Light"]],s?.theme??"system",a=>t.applyPrefs({theme:a}))),ge("When an image is opened","Auto always starts tracing at once. Ask shows a small card offering the Custom wizard beside it; Custom opens the wizard straight away.",jt([["ask","Ask"],["auto","Auto only"],["custom","Custom wizard"]],s?.onOpen??"ask",a=>t.applyPrefs({onOpen:a}))),ge("Language","English only in this release. Nothing in the app is machine-translated, which is why there is one.",jt([["en","English"]],"en",()=>{}))]),ht("Performance",[ge("Threads",crossOriginIsolated?"The engine runs on a pool of workers, one per core the browser reports.":"This page is not cross-origin isolated, so the engine runs on one core. Open it in its own tab for all of them.",n("span.muted.num",{style:{fontSize:"12.5px"}},crossOriginIsolated?`${navigator.hardwareConcurrency||4}`:"1")),ge("Draft resolution","Used while you move a control.",Vt(s?.draftPx??512,128,2048,a=>t.applyPrefs({draftPx:a}),"px")),ge("Draft time limit","A draft that overruns is abandoned, not shown late.",Vt(s?.draftSeconds??.4,.05,10,a=>t.applyPrefs({draftSeconds:a}),"s",2)),ge("Settle before the full trace","How long the controls stay still before the full-resolution trace is queued.",Vt(s?.settleMs??800,100,5e3,a=>t.applyPrefs({settleMs:a}),"ms"))]),ht("Denoiser",Hi(e,o)),null,ht("Advanced",[null,null,ge("Reset settings","Puts every preference and the trace controls back to their defaults. Your saved presets and your files are untouched.",n("button.btn.compact.danger",{onclick:()=>ia("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 O.resetPrefs();e.set({prefs:a,settings:a.trace}),t.afterReset(a),W("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 Hi(e,t){return t?t.supported?[ge("Status","Optional. Improves traces from photographs and screenshots.",n("span",{style:{fontSize:"12.5px",color:t.installed?"var(--good)":"var(--muted)"}},t.installed?`Installed · ${ie(t.bytes??0)}`:e.state.denoiserFetch?.phase==="downloading"?"Downloading in the background":"Not installed")),ge(t.installed?"Remove":"Download",t.installed?t.path??"Kept in this browser's storage.":e.state.denoiserFetch?.saveData?"Not downloaded in the background, because this browser asks to save data. Turning the denoiser on downloads it (about 100 MB), once, into this browser's storage. The model comes down, your image never goes up.":"Downloaded in the background when the Studio opens, once, into this browser's storage (about 100 MB), and run in this tab on your GPU where it has one. The model comes down, your image never goes up.",t.installed?n("button.btn.compact.danger",{onclick:async()=>{const s=await O.denoiserRemove(),o=e.state.caps;o&&e.set({caps:{...o,denoiser:s}}),W("Denoiser removed.")}},"Remove"):n("button.btn.compact",{onclick:()=>Cn(e)},"Download")),ge("Verified against",t.repo,n("span.mono.muted",{style:{fontSize:"11px"}},`${t.sha256.slice(0,16)}…`))]:[ge("Status","The denoiser needs a cross-origin isolated page, which this one is not. Open the app in its own tab to use it.",n("span.muted",{style:{fontSize:"12.5px"}},"Not in this build"))]:[ge("Status","",n("span.muted",null,"—"))]}function Cn(e){const t=e.state.caps?.denoiser;if(!t)return;const s=n("div.progress",null,n("i",{style:{width:"0%"}})),o=n("span.faint.num",{style:{fontSize:"12.5px"}},""),a=n("div.modal",{tabindex:"-1"});let i=!1;const r=()=>{N(a,n("h2",null,i?"Downloading the denoiser":"Download the denoiser"),i?n("div",{style:{display:"flex",flexDirection:"column",gap:"10px"}},s,o):n("p",null,"Downloaded once (about 76 MB of weights and 26 MB of ONNX Runtime Web) and kept in this browser; the Studio starts it in the background when it opens, unless the browser asks to save data. It repairs JPEG and screenshot damage before tracing, which usually halves the colour error on photographed logos. It runs in this tab, on your GPU where there is one — the model comes down, your image never goes up."),n("div.stats",null,n("div",null,n("span.k",null,"from"),n("span.v",{style:{fontSize:"12px"}},t.repo)),n("div",null,n("span.k",null,"adds per trace"),n("span.v",null,"seconds"))),n("span.muted",{style:{fontSize:"11.5px",lineHeight:"1.5"}},`SHA-256 verified on arrival against ${t.sha256.slice(0,16)}… · removable in Settings.`),n("div.actions",null,n("button.btn",{onclick:()=>{i&&O.denoiserCancel(),ce()}},i?"Cancel download":"Not now"),i?null:n("button.btn.primary",{onclick:()=>{i=!0,r(),O.denoiserDownload()}},"Download")))};(async()=>{const l=await xe.denoiserProgress(({got:d,total:p})=>{const h=p?d/p*100:0;s.firstElementChild.style.width=`${h.toFixed(1)}%`,o.textContent=p?`${ie(d)} of ${ie(p)} · you can keep tracing`:`${ie(d)} · you can keep tracing`}),c=await xe.denoiserDone(({ok:d,message:p,status:h})=>{const g=e.state.caps;g&&e.set({caps:{...g,denoiser:h}}),l(),c(),ce(),d?W("Denoiser installed and verified.",{kind:"good"}):p!=="cancelled"&&W(p??"The download did not finish.",{kind:"bad"})})})(),qe(a),r()}function Ui(e,t){const s=e.state.caps,o=n("pre",null,"Loading the notices…");O.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(),Et(r)}},i);return n("div.screen",null,n("div.screenbar",{"data-tauri-drag-region":""},n("span",{style:{fontSize:"12.5px",fontWeight:"600"}},"About"),n("div.spacer"),n("button.btn.compact",{onclick:t.close},"Done"),n("div.sep"),st()),n("div.screenbody",null,n("div.about",null,n("div.aboutleft",null,n("span.mark",null,ks(72)),n("div",{style:{display:"flex",flexDirection:"column",gap:"8px"}},n("span.serif",{style:{fontSize:"40px",lineHeight:"1"}},At),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(),Sn()}},"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:()=>qi(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 qi(e){qe(Pt("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:ce},"Close")]))}function ht(e,t){return n("div.settinggroup",null,n("div.head.eyebrow",null,e),...t)}function ge(e,t,s){return n("div.settingrow",null,n("div.about",null,n("span.label",null,e),t?n("span.help",null,t):null),n("div.control",null,s))}function jt(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 Vt(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 ji={M:2,L:2,T:2,H:1,V:1,C:6,S:4,Q:4,A:7,Z:0};function*Vi(e){const t=e.match(/[a-zA-Z]|-?\d*\.?\d+(?:[eE][-+]?\d+)?/g)??[];let s=0,o="";for(;s="a"&&l<="z",p=l.toUpperCase(),h=s,g=o,u=b=>d?h+c[b]:c[b],f=b=>d?g+c[b]:c[b];switch(p){case"M":s=u(0),o=f(1),a=s,i=o,t.push({x:s,y:o}),r=null;break;case"L":s=u(0),o=f(1),t.push({x:s,y:o}),r=null;break;case"H":s=d?h+c[0]:c[0],t.push({x:s,y:o}),r=null;break;case"V":o=d?g+c[0]:c[0],t.push({x:s,y:o}),r=null;break;case"C":{const b={x:u(0),y:f(1)},x={x:u(2),y:f(3)};s=u(4),o=f(5),t.push({x:s,y:o,in:b,out:x}),r=x;break}case"S":{const b=r?{x:2*h-r.x,y:2*g-r.y}:{x:h,y:g},x={x:u(0),y:f(1)};s=u(2),o=f(3),t.push({x:s,y:o,in:b,out:x}),r=x;break}case"Q":{const b={x:u(0),y:f(1)};s=u(2),o=f(3),t.push({x:s,y:o,in:b,out:b}),r=b;break}case"T":{const b=r?{x:2*h-r.x,y:2*g-r.y}:{x:h,y:g};s=u(0),o=f(1),t.push({x:s,y:o,in:b,out:b}),r=b;break}case"A":s=u(5),o=f(6),t.push({x:s,y:o}),r=null;break;case"Z":s=a,o=i,r=null;break}}return t}const Zi="path, circle, ellipse, rect, line, polyline, polygon";function Ji(e){const t=[];for(const s of e.querySelectorAll(Zi)){const o=Ki(s);o&&t.push(o)}return t}function le(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 Xi(e){const t=/rotate\(\s*(-?[\d.]+)/.exec(e.getAttribute("transform")??"");return t?Number(t[1]):0}function Ki(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=le(e,"cx"),o=le(e,"cy"),a=e.nodeName.toLowerCase()==="circle",i=a?le(e,"r"):le(e,"rx"),r=a?le(e,"r"):le(e,"ry");if(!(i>0)||!(r>0))return null;const l=Xi(e),c=l*Math.PI/180,d=Math.cos(c),p=Math.sin(c),h=(u,f)=>`${t(s+u*d-f*p)},${t(o+u*p+f*d)}`,g=`A${t(i)},${t(r)} ${t(l)} 0 1 `;return`M${h(i,0)}${g}${h(0,r)}${g}${h(-i,0)}${g}${h(0,-r)}${g}${h(i,0)}Z`}case"rect":{const s=le(e,"x"),o=le(e,"y"),a=le(e,"width"),i=le(e,"height");if(!(a>0)||!(i>0))return null;const r=Math.min(le(e,"rx",le(e,"ry")),a/2),l=Math.min(le(e,"ry",le(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(le(e,"x1"))},${t(le(e,"y1"))}L${t(le(e,"x2"))},${t(le(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 Qi(e){const t=Math.min(1,Math.max(0,(e-1)/11));return{r:1.2+t*1.2,opacity:.4+t*.6}}const fs=.25,gs=64;function er(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=ve("svg.overlay"),r=n("div.pane.source",null,o,t,s);a.append(i);const l=n("canvas.anchors"),c=n("span.eyebrow.panelabel",null,"Vector · SVG"),d=n("div.pane.vector",null,c,a,l),p=n("div.wipehandle",{role:"separator","aria-label":"Wipe"}),h=n("div.panes",null,r,d,p),g=h;let u={x:0,y:0,w:1,h:1},f=null,b,x,M,w=0,$=Number.NaN,v,S=null,C={x:0,y:0,w:1,h:1};const z={certainty:ve("g.certainty"),wireframe:ve("g.wireframe"),handles:ve("g.handles")},P={certainty:!1,wireframe:!1,handles:!1};for(const y of Object.values(z))y.style.display="none";l.style.display="none",i.append(z.certainty,z.wireframe,z.handles);let B=null,T=null;function D(){const y=e.state,A=y.svg,L=y.source!==x,I=A!==b;if((L||I)&&(u=(A?hs(A):null)??{x:0,y:0,w:y.source?.width??1,h:y.source?.height??1},$=Number.NaN),L&&(x=y.source,N(t,y.source?n("img",{src:y.source.preview,alt:"",draggable:"false"}):null)),I){if(b=A,f=null,A){const R=new DOMParser().parseFromString(A,"image/svg+xml").documentElement;R&&R.nodeName==="svg"&&!R.querySelector("parsererror")&&(R.setAttribute("width",String(u.w)),R.setAttribute("height",String(u.h)),f=document.importNode(R,!0))}N(a,f,i),i.setAttribute("viewBox",`${u.x} ${u.y} ${u.w} ${u.h}`),B=null,T=null;for(const R of["wireframe","handles"])N(z[R]),P[R]=!1;te(!0)}(y.result!==M||I&&!A)&&Z(),L||I?(pe(),Me()):y.show.certainty&&pe()}function V(){const y=e.state.compare;if(y===v)return;v=y,S&&URL.revokeObjectURL(S),S=null,y?(C=hs(y.svg)??u,S=URL.createObjectURL(new Blob([y.svg],{type:"image/svg+xml"})),N(s,n("img",{src:S,alt:"",draggable:"false",style:{imageRendering:"auto"}}))):N(s),s.style.display=y?"":"none",t.style.display=y?"none":"",o.textContent=y?y.label:"Source",c.textContent=y?"Yours":"Vector · SVG",r.classList.toggle("comparing",!!y);const A=`translate(${e.state.pan.x}px, ${e.state.pan.y}px)`;(y?s:t).style.transform=A,H()}function H(){const y=s.querySelector("img");if(!y)return;const A=e.state.zoom;y.style.position="absolute",y.style.left=`${(C.x-u.x)*A}px`,y.style.top=`${(C.y-u.y)*A}px`,y.style.width=`${C.w*A}px`,y.style.height=`${C.h*A}px`,y.style.maxWidth="none"}function Z(){M=void 0,w++,N(z.certainty),P.certainty=!1}function ne(){return B??=f?Ji(f):[],B}function Q(){if(T)return T;const y=[],A=[],L=[];for(const I of ne()){const R=new Set;for(const ee of Yi(I)){const K=Math.round(ee.x*1e3)*1e8+Math.round(ee.y*1e3);R.has(K)||(R.add(K),y.push({x:ee.x,y:ee.y}));for(const fe of[ee.in,ee.out])fe&&(A.push(`M${ee.x} ${ee.y}L${fe.x} ${fe.y}`),L.push(`M${fe.x} ${fe.y}l.001 .001`))}}return T={lines:A.join(""),knobs:L.join(""),dots:y},T}function X(y){if(P[y]=!0,!f)return;const A=z[y];if(y==="wireframe"){const L=tr(ne());L&&A.append(ve("path",{d:L}))}else{const{lines:L,knobs:I}=Q();L&&A.append(ve("path.lines",{d:L}),ve("path.knobs",{d:I}))}}let U="",se=!1;function te(y=!1){y&&(U=""),!se&&(se=!0,requestAnimationFrame(()=>{se=!1,ae()}))}function ae(){const y=e.state,A=!!f&&y.show.anchors,L=Math.round(d.clientWidth),I=Math.round(d.clientHeight);if(A&&(!L||!I))return;const R=window.devicePixelRatio||1,ee=A?`${y.zoom} ${y.pan.x} ${y.pan.y} ${L} ${I} ${R}`:"off";if(ee===U)return;U=ee;const K=l.getContext("2d");if(!K)return;if(!A){l.width=0,l.height=0;return}const fe=Math.max(1,Math.round(L*R)),zn=Math.max(1,Math.round(I*R));(l.width!==fe||l.height!==zn)&&(l.width=fe,l.height=zn),l.style.width=`${L}px`,l.style.height=`${I}px`,K.setTransform(R,0,0,R,0,0),K.clearRect(0,0,L,I);const{dots:En}=Q();if(!En.length)return;const Ln=getComputedStyle(d),ho=Ln.getPropertyValue("--stage").trim()||"#808080",fo=Ln.getPropertyValue("--anchor").trim()||"#ff5c00",{r:Pn,opacity:go}=Qi(y.zoom);l.style.opacity=String(go);const mo=y.pan.x-u.x*y.zoom,bo=y.pan.y-u.y*y.zoom,je=Pn*1.15,yo=Pn*.85,at=[],It=[];for(const Ve of En){const Ye=mo+Ve.x*y.zoom,ye=bo+Ve.y*y.zoom;Ye<-je||ye<-je||Ye>L+je||ye>I+je||(at.push(Ye),It.push(ye))}for(const[Ve,Ye]of[[je,ho],[yo,fo]]){K.beginPath();for(let ye=0;ye{L===w&&e.set({bandsMissing:!0,show:{...e.state.show,certainty:!1}})};O.traceBands(y.resultGeneration).then(R=>{L===w&&(R?$e(R):I())},I)}function $e(y){N(z.certainty);for(const{cls:A,d:L}of rr(nr(y)))z.certainty.append(ve(`path.${A}`,{d:L}))}function pe(){const{show:y}=e.state,A=(L,I)=>{I&&!P[L]&&(L==="certainty"?de():X(L)),z[L].style.display=I?"":"none"};A("wireframe",y.wireframe),A("handles",y.handles),A("certainty",y.certainty),l.style.display=y.anchors?"":"none",te(),f&&(f.style.opacity=y.fill?"1":"0.12")}function Me(){const y=e.state,A=`translate(${y.pan.x}px, ${y.pan.y}px)`;for(const L of v?[s,a]:[t,a])L.style.transform=A;if(y.zoom!==$){$=y.zoom;const L=u.w*y.zoom,I=u.h*y.zoom,R=t.querySelector("img");R&&(R.style.width=`${L}px`,R.style.height=`${I}px`),f&&(f.setAttribute("width",String(L)),f.setAttribute("height",String(I))),i.setAttribute("width",String(L)),i.setAttribute("height",String(I)),H()}y.show.anchors&&te(),Ne()}function Ne(){const y=e.state;if(h.classList.toggle("stacked",y.view!=="side"),p.style.display=y.view==="wipe"?"":"none",y.view==="side")r.classList.remove("hidden"),d.classList.remove("hidden"),r.style.clipPath="",d.style.clipPath="";else if(y.view==="wipe"){r.classList.remove("hidden"),d.classList.remove("hidden");const R=`${(y.wipe*100).toFixed(2)}%`;p.style.left=R,r.style.clipPath=`inset(0 ${(100-y.wipe*100).toFixed(2)}% 0 0)`,d.style.clipPath=`inset(0 0 0 ${R})`}else r.style.clipPath="",d.style.clipPath="",r.classList.toggle("hidden",!y.flicked),d.classList.toggle("hidden",y.flicked);const A=y.detail&&y.zoom>=6;let L=d.querySelector(".pixelgrid");A&&!L&&(L=n("div.pixelgrid"),d.append(L)),L&&(L.style.display=A?"":"none",L.style.backgroundSize=`${y.zoom}px ${y.zoom}px`,L.style.backgroundPosition=`${y.pan.x}px ${y.pan.y}px`);const I=t.querySelector("img");I&&(I.style.imageRendering=y.zoom>=4?"pixelated":"auto")}function Te(){const y=d.getBoundingClientRect();return{w:y.width||1,h:y.height||1}}function ue(){const{w:y,h:A}=Te(),L=Math.min(y/u.w,A/u.h)*.88;e.set({zoom:L,pan:{x:(y-u.w*L)/2,y:(A-u.h*L)/2},fitted:!0})}function Ie(y,A,L){const{w:I,h:R}=Te();e.set({zoom:L,pan:{x:I/2-y*L,y:R/2-A*L},fitted:!1})}function k(y){const{w:A,h:L}=Te();F(A/2,L/2,Math.min(gs,Math.max(fs,y))/e.state.zoom)}function F(y,A,L){const I=e.state,R=Math.min(gs,Math.max(fs,I.zoom*L)),ee=R/I.zoom;e.set({zoom:R,pan:{x:y-(y-I.pan.x)*ee,y:A-(A-I.pan.y)*ee},fitted:!1})}for(const y of[r,d])y.addEventListener("wheel",A=>{A.preventDefault();const L=y.getBoundingClientRect();F(A.clientX-L.left,A.clientY-L.top,Math.exp(-A.deltaY*.0016))},{passive:!1}),y.addEventListener("dblclick",ue),y.addEventListener("pointerdown",A=>{if(A.button!==0)return;y.setPointerCapture(A.pointerId);let L=A.clientX,I=A.clientY;const R=K=>{const fe=e.state;e.set({pan:{x:fe.pan.x+(K.clientX-L),y:fe.pan.y+(K.clientY-I)},fitted:!1}),L=K.clientX,I=K.clientY},ee=()=>{y.removeEventListener("pointermove",R),y.removeEventListener("pointerup",ee),y.removeEventListener("pointercancel",ee)};y.addEventListener("pointermove",R),y.addEventListener("pointerup",ee),y.addEventListener("pointercancel",ee)});p.addEventListener("pointerdown",y=>{y.stopPropagation(),p.setPointerCapture(y.pointerId);const A=I=>{const R=h.getBoundingClientRect();e.set({wipe:Math.min(.98,Math.max(.02,(I.clientX-R.left)/R.width))})},L=()=>{p.removeEventListener("pointermove",A),p.removeEventListener("pointerup",L)};p.addEventListener("pointermove",A),p.addEventListener("pointerup",L)});const E=n("style",{"data-owner":"viewer-isolate"});document.head.append(E);function j(){const y=e.state.hoverFill;d.classList.toggle("isolating",!!y?.length),E.textContent=y?.length?lr(y):""}e.on(["svg","source","result"],D),e.on(["compare"],V),e.on(["zoom","pan","view","wipe","flicked","detail"],Me),e.on(["show"],pe),e.on(["hoverFill"],j),new ResizeObserver(()=>{!e.state.svg&&!e.state.source||(Ne(),e.state.show.anchors&&te(),e.state.fitted&&ue())}).observe(h),new MutationObserver(()=>{e.state.show.anchors&&te(!0)}).observe(document.documentElement,{attributes:!0,attributeFilter:["data-theme"]});const _=()=>{window.matchMedia(`(resolution: ${window.devicePixelRatio||1}dppx)`).addEventListener("change",()=>{e.state.show.anchors&&te(),_()},{once:!0})};return _(),{el:g,redraw:D,transform:Me,fit:ue,goTo:Ie,zoomTo:k}}function tr(e){let t="";for(const s of e){const o=s.trim();o&&(t+=o[0]==="M"?o:`M0 0${o}`)}return t}function nr(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=or(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 sr=[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 or(e){let t=1/0,s=1/0,o=-1/0,a=-1/0,i=!1;const r=e.length;let l=0;for(;l=48&&c<=57||c===46)){p||l++;continue}let h=0;for(;c>=48&&c<=57;)h=h*10+(c-48),c=e.charCodeAt(++l);if(c===46){let g=0,u=0;for(c=e.charCodeAt(++l);c>=48&&c<=57;)u<15&&(g=g*10+(c-48),u++),c=e.charCodeAt(++l);h+=g*sr[u]}if(c===101||c===69){c=e.charCodeAt(++l);const g=c===45;(g||c===43)&&(c=e.charCodeAt(++l));let u=0;for(;c>=48&&c<=57;)u=u*10+(c-48),c=e.charCodeAt(++l);h*=10**(g?-u:u)}d&&(h=-h),i?(ha&&(a=h)):(ho&&(o=h)),i=!i}return t<=o&&s<=a?{x0:t,y0:s,x1:o,y1:a}:null}const ar=1,ft=16,ir=256;function rr(e,t=ir){const s=new Map,o=new Int32Array(e.length),a=[],i={sure:[],soft:[],unsure:[]},r=[],l=ar/2;for(let d=0;dh&&B.y0-lg&&(B.cls===p.cls?r[o[P]]=d+1:o[P]>$&&($=o[P]))}}let v=-1;for(const S of i[p.cls])if(S>$&&r[S]!==d+1){v=S;break}v<0&&(v=a.length,a.push(p.cls),i[p.cls].push(v)),o[d]=v;for(let S=M;S<=w;S++)for(let C=b;C<=x;C++){const z=C*65536+S,P=s.get(z);P?P.push(d):s.set(z,[d])}}const c=new Map;for(let d=0;dd[0]-p[0]).map(d=>d[1])}const Yt=".pane.vector > .art > svg:not(.overlay)";function lr(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[`${Yt} * { fill-opacity: 0.12 !important; stroke-opacity: 0.12 !important; }`,`${Yt} :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; }`,`${Yt} :is(${s("stroke")}) { stroke-opacity: 1 !important; }`].join(` +`)}function cr(e,t,s){const o=er(e),a=n("div.viewertools"),i=n("div",{style:{flex:"1",minHeight:"0",position:"relative",display:"flex"}}),r=n("div.statusstrip"),l=n("section.stage",null,a,i,r);let c="";const d=()=>{const u=e.state,f=!!u.source,b=[1,4,12].map(w=>!u.fitted&&Math.abs(u.zoom-w)<.01),x=JSON.stringify([f,u.view,u.show,u.fitted,b,u.detail,!!u.svg,u.bandsMissing]);if(x===c)return;c=x;const M=(w,$)=>n("button",{"aria-pressed":String($==="fit"?u.fitted:!u.fitted&&Math.abs(u.zoom-$)<.01),disabled:!f,onclick:()=>$==="fit"?o.fit():o.zoomTo($)},w);N(a,n("div.seg",null,...["side","wipe","ab"].map(w=>n("button",{"aria-pressed":String(u.view===w),disabled:!f,onclick:()=>e.set({view:w})},w==="side"?"Side by side":w==="wipe"?"Wipe":"A/B"))),n("span.muted",{style:{fontSize:"11.5px"}},"hold ",n("kbd",null,"Space")," to flick"),n("div.sep"),n("span.eyebrow",null,"Show"),n("div",{style:{display:"flex",gap:"6px"}},...[["fill","Fill"],["wireframe","Wireframe"],["anchors","Anchors"],["handles","Handles"],["certainty","Certainty"]].map(([w,$])=>n("button.toggle",{"aria-pressed":String(u.show[w]),disabled:!u.svg||w==="certainty"&&u.bandsMissing,title:w==="certainty"?"Where each boundary could be: a band two sigmas either side, measured from the pixels. Thin is certain; a wide band is a soft, blurred or compressed edge, and the curve there is a best guess.":void 0,onclick:()=>{u.show[w]=!u.show[w],e.touch("show")}},$))),n("div",{style:{marginLeft:"auto",display:"flex",alignItems:"center",gap:"12px"}},u.show.wireframe||u.show.anchors||u.show.handles||u.show.certainty?n("div.legend",null,u.show.anchors?n("span",null,n("i.anchor"),"anchor"):null,u.show.handles?n("span",null,n("i.handle"),"handle"):null,u.show.wireframe?n("span",null,n("i.wire"),"wireframe"):null,...u.show.certainty?[n("span",null,n("i.band.sure"),"sure"),n("span",null,n("i.band.soft"),"soft"),n("span",null,n("i.band.unsure"),"unsure")]:[]):null,n("button.toggle",{"aria-pressed":String(u.detail),disabled:!u.svg,title:"Show the source's pixel grid under the vector edge",onclick:()=>{const w=!u.detail;e.set({detail:w}),w&&u.zoom<12&&o.zoomTo(12)}},"Detail"),n("div.seg.num",null,M("Fit","fit"),M("1×",1),M("4×",4),M("12×",12))))},p=n("div",{style:{display:"contents"}});i.append(o.el,p);const h=()=>{const u=e.state;if(!u.source){o.el.style.display="none",N(p,gr(e,t,s()));return}o.el.style.display="",N(p,hr(u.stageState,u,t),u.svg?dr(e):null,u.detail&&u.svg?pr(e,t):null),u.zoom===1&&u.pan.x===0&&u.pan.y===0&&o.fit()},g=()=>{const u=e.state,f=u.report;let b="Ready",x="",M="var(--dim)";if(u.tracing){b=u.liveStages[u.liveStages.length-1]?.name??"starting";const $=u.liveStages.reduce((v,S)=>v+S.ms,0)/1e3;x=`· ${un(u,u.tracingTier)??"—"} px · ${Ae($)} elapsed · Esc to cancel`,M="var(--state-stale)"}else if(f&&u.result){const w=u.result.tier==="draft";b=w?"draft shown":`traced in ${Ae(f.seconds)}`,x=w?`· ${f.tracedPx} px · full trace queued`:`· ${f.tracedPx} px · final · ${oe(f.segments)} segments`,M=w?"var(--state-draft)":"var(--state-final)"}else u.source&&(b="Opened",x=`· ${u.source.width} × ${u.source.height} · ${u.source.container}`);N(r,n("span.lead",{style:{color:M}},b),x?n("span",null,x):null,u.update?.newer?n("span",{style:{display:"flex",alignItems:"center",gap:"8px",marginLeft:"12px"}},n("span.updatechip",null,n("i"),`${u.update.latest} available`),n("button.reset",{onclick:t.showUpdate},"What changed")):null,ur(e,t),n("span.privacy",null,"Your image never leaves this computer."))};return e.on(["source","view","show","zoom","fitted","detail","svg","bandsMissing"],d),e.on(["source","svg","stageState","result","detail","worstCorner","prefs"],h),e.on(["tracing","liveStages","report","result","source","update","denoiserFetch"],g),d(),h(),g(),window.addEventListener("keydown",u=>{if(e.state.screen||e.state.tab!=="vectorize"||u.target instanceof HTMLInputElement||u.target instanceof HTMLTextAreaElement)return;const f=e.state;u.key===" "&&!u.repeat?(u.preventDefault(),e.set({flicked:!0})):u.key==="0"?o.fit():u.key==="+"||u.key==="="?o.zoomTo(f.zoom*1.4):u.key==="-"&&o.zoomTo(f.zoom/1.4)}),window.addEventListener("keyup",u=>{u.key===" "&&e.set({flicked:!1})}),{el:l,viewer:o,mount:u=>i.append(u)}}function ur(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=xn(o);return n("span.fetchchip",{"data-ctl":"denoiser-chip",title:"The denoiser downloads in the background, once; later visits read it from this browser"},$t(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"},$t(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 dr(e){const t=e.state,s=t.tracing?"stale":t.result?.tier==="draft"?"draft":"final",o=t.tracing?`re-tracing · ${un(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 pr(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,mr(e)))}function hr(e,t,s){const o=(a,i,r,l,c)=>n("div.stagestate",null,n("span",{style:{color:i,display:"flex"}},q(a,24)),n("span.title",null,r),n("span.body",null,l),c?n("button.action.reset",{onclick:c[1]},c[0]):null);switch(e.kind){case"decoding":return o("clock","var(--faint)",`Reading ${t.source?.name??"the image"}`,`${t.source?.width} × ${t.source?.height}. Tracing starts as soon as the pixels are in memory.`);case"flat":return o("info","var(--gold)","This image is one flat colour","There are no boundaries to trace. A single rectangle would be the whole output.",["Open another image",s.openFile]);case"undecodable":return o("alert","var(--bad)","This file will not decode",e.message,["Open another image",s.openFile]);case"outOfMemory":return o("alert","var(--bad)","The trace would run out of memory",`About ${e.neededGb.toFixed(1)} GB is needed at this trace size. Lowering it to ${e.suggestPx} px usually costs nothing visible.`,[`Retry at ${e.suggestPx} px`,()=>s.retryAt(e.suggestPx)]);case"failed":return o("alert","var(--bad)","The trace stopped",e.message,["Trace again",s.traceNow]);case"cancelled":return o("x","var(--faint)","Trace cancelled","The last full trace is still on screen and still exportable. Nothing was discarded.",["Trace again",s.traceNow]);case"denoiserMissing":return o("download","var(--faint)","This preset wants the denoiser","Photo or scan works without it, but compression damage stays in the colours. The download is explained before anything is fetched, and it runs locally like everything else.",["Download the denoiser",s.openDenoiser]);case"offline":return o("globe","var(--faint)","No network",`Tracing is unaffected — it never needed one. ${e.message}`);default:return null}}function fr(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 gr(e,t,s){const o=Cs(e.state.caps?.platform);return n("div.firstrun",null,fr(e,t),n("div.drop",null,n("span.glyph",null,q("image",32)),n("div",{style:{display:"flex",flexDirection:"column",gap:"6px"}},n("span.headline.resting",null,"Drop a PNG, JPEG or WebP"),n("span.headline.release",null,"Release to trace"),n("span.faint.resting",{style:{fontSize:"13px"}},"or press ",n("kbd",null,`${o}+O`)," to choose a file")),n("button.btn.primary.resting",{onclick:t.openFile},"Open an image")),s.length?n("div.samples",null,n("span.eyebrow",null,"Or try one of these"),n("div.samplerow",null,...s.map(a=>n("button.sample",{onclick:()=>t.openSample(a.file)},n("span.thumb",null,n("img",{src:a.preview,alt:""})),n("span.label",null,a.label))))):null,n("p.desktopnote.faint",null,"For the fastest engine, whole folders in one batch, no browser limits and offline use, get ",n("a",{href:Ts,target:"_blank",rel:"noopener noreferrer"},"Inkvec Studio for the desktop"),"."))}function to(e,t){const s=e.state.worstCorner;s&&(e.set({detail:!0}),t.goTo(s.x,s.y,12))}function mr(e){const t=e.state.worstCorner;return t?`Worst disagreement: ${re(t.de00)} dE00, at ${t.x} × ${t.y} in the traced raster.`:"The trace and the source agree everywhere we can measure."}const no={precision:.1,speckleFloor:2,traceSize:2048,timeLimit:0,maxColours:64,colourMerging:.035,flatFills:!1,blackAndWhite:!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 ms(e,t,s){e[t]=s}const m=new Ro(Oo(no,{tolerancePx:.1,judgePx:1024,cornerDegrees:30,documentCleanup:!0}));let so=[],on=0,oo=0;const yt=new Map,ao=new oa;function br(){const e={...m.state.settings};return delete e.colourGroups,m.state.colourGroups.length&&(e.colourGroups=m.state.colourGroups),e}async function ze(e){if(m.state.source)try{const t=m.state.colourGroups,s=await O.startTrace(br(),e);yt.set(s,t);for(const o of yt.keys())ovoid ze("final"),m.state.prefs?.settleMs??800))}function io(){return new Promise(e=>{if(!m.state.source){e();return}const t=m.on(["result","stageState"],()=>{m.state.tracing||(t(),e())});window.clearTimeout(on),ze("final")})}let Zt=0;function yr(e,t=m.state.generation){const s=++Zt;if(e.state!=="traced"||!m.state.snaps.length){Jt(e,t);return}ro(e).then(o=>{s===Zt&&Jt(e,t,o)},o=>{s===Zt&&(W(`The snapped colours could not be applied to this trace: ${o}`,{kind:"bad"}),Jt(e,t))})}async function ro(e){for(;;){const t=m.state.snaps;if(!t.length)return{svg:e.svg,inks:e.palette};const s=e.report.tracedPx,o=await O.snapInks(e.svg,t,s,s);if(m.state.snaps===t)return o}}function Jt(e,t,s=null){if(e.state==="traced"){const o=m.state.result?.tier==="draft",a=m.state.result?.tier==="final"?m.state.report:m.state.previous;m.set({result:e,resultGeneration:t,resultGroups:yt.get(t)??m.state.resultGroups,bandsMissing:!1,previous:e.tier==="final"?a:m.state.previous,svg:s?.svg??e.svg,report:e.report,palette:s?.inks??e.palette,losses:e.losses,worstCorner:e.worstCorner,stageState:{kind:"drawing"},tracing:!1,justSwapped:o&&e.tier==="final"}),m.state.justSwapped&&window.setTimeout(()=>m.set({justSwapped:!1}),600),e.tier==="final"&&m.set({groupSuggestions:Ds(m.state)});const i=m.state.auto;i&&t===i.generation&&e.tier==="final"&&!i.report&&(m.set({auto:{...i,svg:e.svg,report:e.report,palette:e.palette,losses:e.losses}}),O.sourceFacts(i.settings.traceSize).then(r=>{m.state.auto?.generation===t&&m.set({facts:r})},()=>{})),e.oversized&&e.tier==="final"&&W(`Traced at ${e.tracedPx} px; the SVG is still ${e.sourcePx[0]} × ${e.sourcePx[1]} and scales without limit. This is normal.`);return}switch(m.set({tracing:!1}),e.state){case"flat":m.set({stageState:{kind:"flat"},svg:null,report:null,palette:[],losses:[]});break;case"undecodable":m.set({stageState:{kind:"undecodable",message:e.message}});break;case"outOfMemory":m.set({stageState:{kind:"outOfMemory",neededGb:e.neededGb,suggestPx:e.suggestPx}});break;case"failed":m.set({stageState:{kind:"failed",message:e.message}});break}}async function We(e,t=!0){He.close(),m.set({stageState:{kind:"decoding"},svg:null,report:null,previous:null,palette:[],losses:[],result:null,snaps:[],colourGroups:[],resultGroups:[],groupSuggestions:null,dismissedGroups:[],simplifyTo:null,paletteSelection:[],hoverFill:null,auto:null,facts:null,chooser:!1,compare:null,autoNoteHidden:!1});try{await e(),m.set({zoom:1,pan:{x:0,y:0},stageState:{kind:"drawing"}}),await ze("final"),ao.reset();const s=m.state;s.tracing&&m.set({auto:{generation:s.generation,settings:{...s.settings},preset:s.preset,svg:null,report:null,palette:[],losses:[]}}),t&&wr()}catch(s){m.set({stageState:{kind:"undecodable",message:String(s)}})}}function wr(){const e=m.state.prefs?.onOpen??"ask";e==="custom"?He.open():e==="ask"&&m.set({chooser:!0})}async function lo(e){await We(async()=>{const t=await O.openPath(e);m.set({source:t});const s=await O.loadPrefs();m.set({prefs:s})})}async function wt(e){if(e.path)return lo(e.path);const t=e.file;t&&await We(async()=>{const s=await O.openBytes(new Uint8Array(await t.arrayBuffer()),e.name);m.set({source:s})})}async function Tn(){const[e]=await dn([{name:"Images and SVG",extensions:["png","jpg","jpeg","webp","gif","bmp","tif","tiff","svg"]}]);e&&await wt(e)}async function vr(e,t){m.set({tab:"vectorize"}),Mt(),await We(async()=>{const s=await O.openBytes([...new TextEncoder().encode(e)],t);m.set({source:s})},!1)}window.addEventListener("inkvec:retrace",e=>{const{svg:t,name:s}=e.detail;vr(t,s)});const an=n("header.appbar"),Je=n("div.body"),xr=Gi(m,{applyPrefs:e=>void Ue(e),afterReset:e=>{Ct(e.theme),po(e)},close:()=>m.set({screen:null})}),Ft=cr(m,{openFile:()=>void Tn(),openSample:e=>void We(async()=>{const t=await O.openSample(e);m.set({source:t})}),traceNow:()=>void ze("final"),cancel:()=>{O.cancelTrace(),m.set({tracing:!1,stageState:m.state.svg?{kind:"cancelled"}:{kind:"empty"}})},retryAt:e=>{m.state.settings.traceSize=e,m.touch("settings"),ze("final")},jumpToWorst:()=>to(m,Ft.viewer),openDenoiser:()=>Cn(m),retryDenoiser:()=>ot.retryDenoiser(),showUpdate:()=>{const e=m.state.update;e&&Et(e.url)},markSeen:()=>void Ue({seenFirstRun:!0})},()=>so);function An(e){const t=m.state.caps?.presets.find(o=>o.id===e);if(t)return{settings:t.settings,wantsDenoiser:t.wantsDenoiser};const s=m.state.prefs?.saved.find(o=>o.id===e);return s?{settings:s.settings,wantsDenoiser:!1}:null}function bs(){const e=m.state.preset;return e&&An(e)?.settings||no}function co(e){m.set({settings:{...e.settings},preset:e.preset,colourGroups:[...e.colourGroups]}),Be()}const ot={setPreset:e=>{const t=An(e);t&&(m.set({preset:e,settings:{...t.settings}}),t.settings.cleanUpDamage!=="off"&&xs(),Be())},savePreset:e=>{const t=m.state.prefs;if(!t)return;const s=`saved:${Date.now().toString(36)}${Math.random().toString(36).slice(2,6)}`,o=[...t.saved,{id:s,name:e,settings:{...m.state.settings}}];Ue({saved:o}).then(()=>{const a=m.state.prefs?.saved.find(i=>i.id===s);a?(m.set({preset:s}),W(`Saved as "${a.name}".`,{kind:"good"})):W("The preset tray is full. Forget one and try again.",{kind:"bad"})})},deletePreset:e=>{const t=m.state.prefs;t&&(Ue({saved:t.saved.filter(s=>s.id!==e)}),m.state.preset===e&&m.set({preset:null}))},baseSettings:bs,changeSetting:(e,t)=>{ms(m.state.settings,e,t),m.touch("settings"),m.state.caps?.denoiser,e==="cleanUpDamage"&&t!=="off"&&xs(),Be()},resetGroup:e=>{const t=bs();for(const s of m.state.caps?.controls??[])s.group===e&&ms(m.state.settings,s.key,t[s.key]);m.touch("settings"),Be()},traceNow:()=>void ze("final"),cancel:()=>{O.cancelTrace(),m.set({tracing:!1,stageState:m.state.svg?{kind:"cancelled"}:{kind:"empty"}})},snap:e=>void kr(e),setColourGroups:e=>{m.set({colourGroups:e,paletteSelection:[]}),Be()},openExport:()=>Ys(m,Nt,io),copySvg:async()=>{m.state.svg&&(await zt(m.state.svg),W("SVG copied. Paste straight into Figma or Illustrator."))},saveCard:()=>oi(m),openDenoiser:()=>Cn(m),retryDenoiser:()=>void O.denoiserDownload().catch(e=>W(String(e),{kind:"bad"})),jumpToWorst:()=>to(m,Ft.viewer),backToAuto:()=>{const e=m.state.auto;e&&co({settings:e.settings,preset:e.preset,colourGroups:m.state.colourGroups})},hideAutoNote:()=>m.set({autoNoteHidden:!0}),openWizard:()=>He.open()},Nt=Da(m,ot),uo={...ot,restore:co,setOnOpen:e=>void Ue({onOpen:e}),markSeen:()=>void Ue({seenFirstRun:!0})},He=si(m,Nt,uo,ao);Ft.mount(ei(m,uo,()=>He.open()));async function kr(e){const t=m.state,s=t.result;if(!s||!e.length)return;const o=t.snaps,a=new Set(e.map(r=>r.from.toLowerCase())),i=[...t.snaps.filter(r=>!a.has(r.from.toLowerCase())),...e.filter(r=>r.to.toLowerCase()!==r.from.toLowerCase())];m.set({snaps:i});try{const r=await ro(s);if(m.state.result!==s)return;m.set({svg:r.svg,palette:r.inks})}catch(r){m.state.snaps===i&&m.set({snaps:o}),W(String(r),{kind:"bad"})}}let rn=null,ln=null,ys=null;function Mt(){const e=m.state;e.tab==="vectorize"?N(Je,Ft.el,Nt):e.tab==="minify"?(rn??=hi(m),N(Je,rn)):e.tab==="fabricate"?(ln??=bi(m),N(Je,ln)):(ys??=ci(m),N(Je,ys))}function ws(){const e=m.state;N(an,n("span.brand",null,ks(18),At),e.tab==="vectorize"&&e.source?n("div.filechip",null,n("span.name",null,e.source.name),n("span.muted.num",null,`${e.source.width} × ${e.source.height} · ${e.source.container}`)):null,n("div.seg",{style:{marginLeft:"auto"}},...[["vectorize","Vectorize"],["minify","Minify SVG"],["fabricate","Fabricate"]].map(([t,s])=>n("button",{"aria-pressed":String(e.tab===t),onclick:()=>m.set({tab:t})},s))),n("div",{style:{marginLeft:"auto",display:"flex",alignItems:"center",gap:"2px"}},n("button.btn.ghost.compact",{onclick:()=>void Tn()},"Open…"),null,n("button.btn.ghost.compact",{onclick:()=>m.set({screen:"settings"})},"Settings"),n("button.btn.ghost.compact",{onclick:()=>m.set({screen:"about"})},"About"),n("button.btn.ghost.compact",{title:"Before and after on real logos, and how Inkvec compares",onclick:()=>m.set({screen:"showcase"})},"Showcase"),n("button.btn.ghost.compact",{title:"The user guide (F1)",onclick:()=>Sn(Zs(m.state))},"Help"),n("div.sep"),st())),an.setAttribute("data-tauri-drag-region","")}async function Ue(e){const t=m.state.prefs;if(!t)return;const s={...t,trace:nn(m.state.settings),ui:tn(m.state),...e},o=await O.savePrefs(s);m.set({prefs:o}),Ct(o.theme)}async function Sr(e){const t=m.state.prefs;if(t)try{const s=await O.savePrefs({...t,...e});m.state.prefs===t&&(m.state.prefs=s)}catch{}}const $r=["vectorize","minify","fabricate"];function po(e){qo(m,e,$r,t=>An(t)!==null)}function Ct(e){const t=e==="dark"||e==="system"&&window.matchMedia("(prefers-color-scheme: dark)").matches;document.documentElement.dataset.theme=t?"dark":"light"}function Mr(e){const t=e.metaKey||e.ctrlKey,s=e.target instanceof HTMLInputElement||e.target instanceof HTMLTextAreaElement;if(t&&e.key.toLowerCase()==="o"){e.preventDefault(),Tn();return}if(t&&e.key.toLowerCase()==="e"&&m.state.svg){e.preventDefault(),Ys(m,Nt,io);return}if(t&&e.key===","){e.preventDefault(),m.set({screen:"settings"});return}if(!s){if(t&&/^[1-8]$/.test(e.key)){const o=m.state.caps?.presets[Number(e.key)-1];o&&(e.preventDefault(),m.set({preset:o.id,settings:{...o.settings}}),Be());return}e.key==="Escape"&&(m.state.screen?m.set({screen:null}):He.isOpen()?He.close(!0):m.state.chooser?m.set({chooser:!1}):m.state.tracing&&(O.cancelTrace(),m.set({tracing:!1,stageState:m.state.svg?{kind:"cancelled"}:{kind:"empty"}})))}}async function Cr(){{Mo(m,e=>void Tr(e.map(As)));return}}async function Tr(e){if(!e.length)return;const t=e.filter(s=>s.name.toLowerCase().endsWith(".svg"));if(t.length===1&&e.length===1&&m.state.tab==="vectorize"){await wt(t[0]);return}if(t.length){const s=m.state.tab==="fabricate"?"fabricate":"minify";m.set({tab:s}),Mt(),await(s==="fabricate"?ln:rn)?.acceptDropped?.(t[0]);return}if(e.length>1){W(`${e.length} files dropped. ${At} opens one image at a time; the desktop app has Batch.`),await wt(e[0]);return}m.set({tab:"vectorize"}),await wt(e[0])}async function Ar(){const e=document.getElementById("app");if(!e)return;e.append(an,Je,xr),To(e),Fo(),ws(),Mt(),m.on(["tab","source","prefs","caps"],ws),m.on(["tab"],Mt),window.addEventListener("keydown",Mr),li(m),window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",()=>Ct(m.state.prefs?.theme??"system")),document.addEventListener("click",a=>{const i=a.target?.closest?.("a[href]"),r=i?.getAttribute("href")??"";i&&/^(https?:|mailto:)/i.test(r)&&(a.preventDefault(),Et(r).catch(()=>W("Could not open the link in your browser.",{kind:"bad"})))}),await xe.traceStage(({generation:a,name:i,ms:r})=>{if(a!==oo)return;const l=[...m.state.liveStages],c=l.find(d=>d.name===i);c?c.ms+=r:l.push({name:i,ms:r}),m.set({liveStages:l})}),await xe.traceDone(({generation:a,outcome:i})=>{a===m.state.generation&&yr(i,a)}),await xe.openPath(a=>void vs(a)),await xe.batchRow(a=>{const i=m.state.batch,r=i.rows.findIndex(l=>l.id===a.id);r>=0&&(i.rows[r]=a),m.touch("batch")}),await xe.batchTotals(a=>{m.state.batch.totals=a,m.touch("batch")}),await xe.batchFinished(a=>{const i=m.state.batch;i.rows=a,i.running=!1,i.paused=!1,m.touch("batch");const r=a.filter(l=>l.state==="failed").length;W(r?`Batch finished · ${r} failed. Sort failures first to see them.`:`Batch finished · ${a.filter(l=>l.state==="done").length} written.`,{kind:r?"bad":"good"})}),await xe.denoiserFetch(Er);const[t,s]=await Promise.all([O.capabilities(),O.loadPrefs()]);m.set({caps:t,prefs:s,settings:s.trace}),Ct(s.theme),po(s),Vo(m,a=>void Sr(a)),Xt(`Engine ${t.engineVersion} · ${t.buildTarget}`,.55),so=await O.listSamples(),m.touch("source"),Xt("Preparing the workspace…",.85),await Cr(),Xt("Ready",1),O.appReady().catch(()=>{});const o=await O.launchPath().catch(()=>null);o&&vs(o),Ao().then(zr),s.checkUpdates&&O.checkUpdate().then(a=>m.set({update:a})).catch(()=>{})}async function zr(e){e&&(m.set({tab:"vectorize",screen:null}),"sample"in e?await We(async()=>m.set({source:await O.openSample(e.sample)})):await We(async()=>m.set({source:await O.openBytes(e.bytes,e.name)})))}async function vs(e){m.set({tab:"vectorize",screen:null}),await lo(e)}function xs(){const e=m.state.denoiserFetch;if(!m.state.caps?.denoiser.supported||e?.phase==="ready")return;const t=St(e,!0);W(e?.phase==="failed"?"The denoiser did not download. The trace runs without it.":`${kn(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:()=>ot.retryDenoiser()}}:{})}function Er(e){const t=m.state.denoiserFetch?.phase;m.set({denoiserFetch:e});const s=m.state.caps;(e.phase==="stored"||e.phase==="ready")&&s&&!s.denoiser.installed&&O.denoiserStatus().then(a=>{const i=m.state.caps;i&&m.set({caps:{...i,denoiser:a}})});const o=m.state.settings.cleanUpDamage!=="off";e.retrace&&o&&m.state.source?(W("The denoiser is ready. Tracing again with it.",{kind:"good"}),ze("final")):e.phase==="failed"&&t!=="failed"&&o&&W(`The denoiser did not download: ${e.message??"the connection dropped"}.`,{kind:"bad",action:{label:"Retry",run:()=>ot.retryDenoiser()}})}function Xt(e,t){O.startupProgress(e,t).catch(()=>{})}Ar().catch(e=>{O.appReady().catch(()=>{});const t=document.getElementById("app");t&&N(t,n("div.firstrun",null,n("span.serif",{style:{fontSize:"24px"}},`${At} could not start`),n("span.faint",{style:{maxWidth:"50ch",textAlign:"center"}},String(e))))});