Incorporo-user commited on
Commit
c2b84fb
·
verified ·
1 Parent(s): 721cf84

Inkvec 0.2: the presentation page, and Inkvec Studio Lite under studio/

Browse files
README.md CHANGED
@@ -35,7 +35,8 @@ It is the desktop app's interface and the desktop app's code, not a demo of them
35
  - **Minify SVG**: rewrite an SVG you already have in fewer bytes, with the difference measured.
36
  - **Fabricate**: vinyl sheets, stickers, stencils and laser cut lines, with a preflight.
37
  - **Clean up damage**: the trained denoiser for JPEG and screenshot damage, downloaded once
38
- into your browser and run on your GPU where there is one (ONNX Runtime Web).
 
39
 
40
  Use **Full screen** in the top bar, or **Open in its own tab** when the Space's frame will not
41
  go full screen; its own tab also gives it every core. It is built for a laptop or desktop
 
35
  - **Minify SVG**: rewrite an SVG you already have in fewer bytes, with the difference measured.
36
  - **Fabricate**: vinyl sheets, stickers, stencils and laser cut lines, with a preflight.
37
  - **Clean up damage**: the trained denoiser for JPEG and screenshot damage, downloaded once
38
+ into your browser, in the background as the Studio opens (not when your browser asks to save
39
+ data), and run on your GPU where there is one (ONNX Runtime Web).
40
 
41
  Use **Full screen** in the top bar, or **Open in its own tab** when the Space's frame will not
42
  go full screen; its own tab also gives it every core. It is built for a laptop or desktop
index.html CHANGED
@@ -229,7 +229,7 @@ html.framed .top{box-shadow:0 -6px 0 var(--void),inset 0 1px 0 rgba(201,117,74,.
229
  <section class="sec how" id="how">
230
  <h2>How it works</h2>
231
  <p>Inkvec decides the geometry from the evidence in the pixels. It finds the inks by minimum description length, builds a planar map where every boundary is shared by exactly two shapes so there are never gaps, solves every boundary at once against an exact coverage render of the image, then fits lines, arcs, curves, circles and gradients priced in parameters. The result carries the artist's structure: a word is one path, a circle is a <code>&lt;circle&gt;</code>, and the number of coordinates follows the artwork rather than a tolerance slider.</p>
232
- <p>Inkvec Studio Lite is the desktop app's own interface and code: the Studio's Rust core compiled to WebAssembly, run in a Web Worker on a pool of threads, one per core (where the browser allows it: a page opened in its own tab does). The trained denoiser for JPEG and screenshot damage is downloaded once, if you ask for it, and runs on your GPU through ONNX Runtime Web.</p>
233
  </section>
234
 
235
  <section class="sec">
 
229
  <section class="sec how" id="how">
230
  <h2>How it works</h2>
231
  <p>Inkvec decides the geometry from the evidence in the pixels. It finds the inks by minimum description length, builds a planar map where every boundary is shared by exactly two shapes so there are never gaps, solves every boundary at once against an exact coverage render of the image, then fits lines, arcs, curves, circles and gradients priced in parameters. The result carries the artist's structure: a word is one path, a circle is a <code>&lt;circle&gt;</code>, and the number of coordinates follows the artwork rather than a tolerance slider.</p>
232
+ <p>Inkvec Studio Lite is the desktop app's own interface and code: the Studio's Rust core compiled to WebAssembly, run in a Web Worker on a pool of threads, one per core (where the browser allows it: a page opened in its own tab does). The trained denoiser for JPEG and screenshot damage is downloaded once, in the background as the Studio opens, and runs on your GPU through ONNX Runtime Web.</p>
233
  </section>
234
 
235
  <section class="sec">
studio/assets/denoiser.worker-DjPuHhk4.js ADDED
@@ -0,0 +1 @@
 
 
1
+ const a=self;let u="",c="",p="",d="",r=null,o=null,i=null;function h(){return r||(r=import(new URL(`denoise.js${c?`?v=${c}`:""}`,u).href),r.catch(()=>{r=null})),r}const l=t=>String(t?.message??t);function y(t){if(!o){let e=0;o=(async()=>(await h()).prefetch({url:p,sha256:d,priority:t,onProgress:s=>{if(s.cached)return;const g=performance.now();(s.received===s.total||g-e>200)&&(e=g,a.postMessage({type:"progress",got:s.received,total:s.total||null}))}}))(),o.catch(()=>{o=null})}return o}function f(){return i||(i=(async()=>{const{cached:t}=await y("high");a.postMessage({type:"stored",cached:t}),a.postMessage({type:"preparing"});const{backend:e}=await(await h()).load({url:p,sha256:d});return e})(),i.catch(()=>{i=null})),i}async function w(t){const e=new Int32Array(t.shared,0,2);try{await f();const n=await(await h()).run(t.input,t.width,t.height);new Float32Array(t.shared,8,n.length).set(n),Atomics.store(e,0,1),a.postMessage({type:"ran",width:t.width,height:t.height})}catch(n){const s=new TextEncoder().encode(l(n)).slice(0,t.shared.byteLength-8);new Uint8Array(t.shared,8,s.length).set(s),Atomics.store(e,1,s.length),Atomics.store(e,0,2)}Atomics.notify(e,0)}a.onmessage=t=>{const e=t.data;if(e.type==="init"&&({base:u,token:c,url:p,sha256:d}=e),(e.type==="init"||e.type==="port")&&e.port){e.port.onmessage=n=>{n.data?.type==="run"&&w(n.data)};return}(e.type==="prefetch"||e.type==="download")&&y(e.type==="prefetch"?"low":"high").then(({cached:n})=>a.postMessage({type:"stored",cached:n}),n=>a.postMessage({type:"failed",during:"download",message:l(n)})),e.type==="prepare"&&f().then(n=>a.postMessage({type:"loaded",backend:n}),n=>a.postMessage({type:"failed",during:o?"prepare":"download",message:l(n)})),e.type==="abort"&&r?.then(n=>n.abort()),e.type==="forget"&&(o=null)};
studio/assets/denoiser.worker-ER2QG0XM.js DELETED
@@ -1 +0,0 @@
1
- const r=self;let l="",i="",d="",g="",a=null;function h(){return a||(a=import(new URL(`denoise.js${i?`?v=${i}`:""}`,l).href),a.catch(()=>{a=null})),a}async function u(n){const t=await h();let e=0;const{backend:s}=await t.load({url:d,sha256:g,onProgress:n?o=>{const c=performance.now();(o.cached||o.received===o.total||c-e>200)&&(e=c,r.postMessage({type:"progress",got:o.received,total:o.total||null}))}:void 0});return s}async function y(n){const t=new Int32Array(n.shared,0,2);try{await u(!1);const e=await(await h()).run(n.input,n.width,n.height);new Float32Array(n.shared,8,e.length).set(e),Atomics.store(t,0,1)}catch(e){const s=new TextEncoder().encode(String(e?.message??e)).slice(0,n.shared.byteLength-8);new Uint8Array(n.shared,8,s.length).set(s),Atomics.store(t,1,s.length),Atomics.store(t,0,2)}Atomics.notify(t,0)}r.onmessage=n=>{const t=n.data;if(t.type==="init"&&({base:l,token:i,url:d,sha256:g}=t),t.type==="init"||t.type==="port"){t.port.onmessage=e=>{e.data?.type==="run"&&y(e.data)};return}t.type==="download"&&u(!0).then(e=>r.postMessage({type:"done",ok:!0,backend:e}),e=>r.postMessage({type:"done",ok:!1,message:String(e?.message??e)}))};
 
 
studio/assets/engine.worker-Cn2MnOae.js DELETED
@@ -1 +0,0 @@
1
- const i=self;let o=null,g=null,y=null,p=null,u=null;async function h(e){const t=typeof crossOriginIsolated<"u"&&crossOriginIsolated,s=t?"pkg-threads":"pkg",r=e.token?`?v=${e.token}`:"";o=await import(new URL(`${s}/inkvec_studio_wasm.js${r}`,e.base).href),g=(await o.default({module_or_path:new URL(`${s}/inkvec_studio_wasm_bg.wasm${r}`,e.base)})).memory??null;let l=1,d=null;if(t&&o.initThreadPool){const c=Math.max(1,Math.min(navigator.hardwareConcurrency||4,16)-1);try{await o.initThreadPool(c),l=c+1}catch(f){d=String(f?.message??f)}}p=e.generations?new Int32Array(e.generations):null,u=e.denoiserPort,u&&t&&(w(u),o.enable_denoiser()),y=new o.Studio,i.postMessage({type:"ready",threads:l,poolError:d,isolated:t,version:o.version(),denoiserUrl:o.denoiser_model_url(),denoiserSha256:o.denoiser_model_sha256(),denoiserRepo:o.denoiser_model_repo()})}function w(e){i.inkvecDenoiseSync=(t,s,r)=>{const n=t.length,a=new SharedArrayBuffer(8+Math.max(n*4,4096)),l=new Int32Array(a,0,2);if(e.postMessage({type:"run",input:t.slice(),width:s,height:r,shared:a}),Atomics.wait(l,0,0,6e5)==="timed-out")throw new Error("the denoiser did not answer in ten minutes");if(Atomics.load(l,0)!==1){const c=new TextDecoder().decode(new Uint8Array(a,8,Atomics.load(l,1)).slice());throw new Error(c||"the denoiser failed")}return new Float32Array(a,8,n).slice()}}function S(e){return p?Atomics.load(p,0)===e:!0}function _(e){const t=y;if(!t)throw new Error("the engine is not loaded");switch(e.op){case"capabilities":return JSON.parse(t.capabilities(!!e.supported,!!e.installed,e.bytes??void 0));case"open_bytes":return JSON.parse(t.open_bytes(e.bytes,e.name??void 0));case"trace":{const s=e.generation,r=t.trace(JSON.stringify(e.request),e.draftPx,e.draftSeconds,s,(n,a)=>i.postMessage({type:"stage",generation:s,name:n,ms:a}),S);return r===void 0?null:JSON.parse(r)}case"preview":return JSON.parse(t.preview(JSON.stringify(e.request),e.draftPx,e.draftSeconds));case"export":return t.export_files(JSON.stringify({request:e.request}),!!e.zip).map(r=>({name:r.name,data:r.data}));case"close":return t.close(),null;default:return JSON.parse(t.call(e.op,JSON.stringify(e.args??{})))}}function v(e){return e instanceof WebAssembly.RuntimeError||e instanceof RangeError&&/memory|allocation/i.test(e.message)||/unreachable|out of memory|memory access out of bounds/i.test(String(e?.message??e))}let m=Promise.resolve();i.onmessage=e=>{const t=e.data;m=m.then(async()=>{if(t.type==="init"){try{await h(t)}catch(r){i.postMessage({type:"loadfail",error:String(r?.stack??r)})}return}const s=performance.now();try{const r=_(t),n=[];if(t.op==="export")for(const a of r)n.push(a.data.buffer);i.postMessage({type:"reply",id:t.id,ok:!0,value:r,ms:performance.now()-s,mem:g?.buffer.byteLength??null},n)}catch(r){const n=String(r?.message??r);i.postMessage({type:"reply",id:t.id,ok:!1,error:n,crashed:v(r)})}})};
 
 
studio/assets/engine.worker-DxLbviFI.js ADDED
@@ -0,0 +1 @@
 
 
1
+ const i=self;let a=null,y=null,h=null,p=null,u=null;function w(e,t){if(!e.ok||!e.body||typeof TransformStream>"u")return e;const s=t||Number(e.headers.get("content-length"))||0;let n=0,r=0;const o=e.body.pipeThrough(new TransformStream({transform(l,d){n+=l.byteLength;const c=performance.now();c-r>40&&(r=c,i.postMessage({type:"loading",got:n,total:s})),d.enqueue(l)},flush(){i.postMessage({type:"loading",got:n,total:Math.max(s,n),done:!0})}}));return new Response(o,{status:e.status,headers:{"content-type":"application/wasm"}})}async function S(e){const t=typeof crossOriginIsolated<"u"&&crossOriginIsolated,s=t?"pkg-threads":"pkg",n=e.token?`?v=${e.token}`:"",r=new URL(`${s}/inkvec_studio_wasm.js${n}`,e.base).href,o=fetch(new URL(`${s}/inkvec_studio_wasm_bg.wasm${n}`,e.base));o.catch(()=>{}),a=await import(r),y=(await a.default({module_or_path:w(await o,e.wasmBytes?.[s])})).memory??null,i.postMessage({type:"instantiated",denoiserUrl:a.denoiser_model_url(),denoiserSha256:a.denoiser_model_sha256()});let d=1,c=null;if(t&&a.initThreadPool){const f=Math.max(1,Math.min(navigator.hardwareConcurrency||4,16)-1);try{await a.initThreadPool(f),d=f+1}catch(m){c=String(m?.message??m)}}p=e.generations?new Int32Array(e.generations):null,u=e.denoiserPort,u&&t&&(_(u),a.enable_denoiser()),h=new a.Studio,i.postMessage({type:"ready",threads:d,poolError:c,isolated:t,version:a.version(),denoiserUrl:a.denoiser_model_url(),denoiserSha256:a.denoiser_model_sha256(),denoiserRepo:a.denoiser_model_repo()})}function _(e){i.inkvecDenoiseSync=(t,s,n)=>{const r=t.length,o=new SharedArrayBuffer(8+Math.max(r*4,4096)),l=new Int32Array(o,0,2);if(e.postMessage({type:"run",input:t.slice(),width:s,height:n,shared:o}),Atomics.wait(l,0,0,6e5)==="timed-out")throw new Error("the denoiser did not answer in ten minutes");if(Atomics.load(l,0)!==1){const c=new TextDecoder().decode(new Uint8Array(o,8,Atomics.load(l,1)).slice());throw new Error(c||"the denoiser failed")}return new Float32Array(o,8,r).slice()}}function v(e){return p?Atomics.load(p,0)===e:!0}function M(e){const t=h;if(!t)throw new Error("the engine is not loaded");switch(e.op){case"capabilities":return JSON.parse(t.capabilities(!!e.supported,!!e.installed,e.bytes??void 0));case"open_bytes":return JSON.parse(t.open_bytes(e.bytes,e.name??void 0));case"trace":{const s=e.generation,n=t.trace(JSON.stringify(e.request),e.draftPx,e.draftSeconds,s,(r,o)=>i.postMessage({type:"stage",generation:s,name:r,ms:o}),v);return n===void 0?null:JSON.parse(n)}case"preview":return JSON.parse(t.preview(JSON.stringify(e.request),e.draftPx,e.draftSeconds));case"export":return t.export_files(JSON.stringify({request:e.request}),!!e.zip).map(n=>({name:n.name,data:n.data}));case"close":return t.close(),null;default:return JSON.parse(t.call(e.op,JSON.stringify(e.args??{})))}}function k(e){return e instanceof WebAssembly.RuntimeError||e instanceof RangeError&&/memory|allocation/i.test(e.message)||/unreachable|out of memory|memory access out of bounds/i.test(String(e?.message??e))}let g=Promise.resolve();i.onmessage=e=>{const t=e.data;g=g.then(async()=>{if(t.type==="init"){try{await S(t)}catch(n){i.postMessage({type:"loadfail",error:String(n?.stack??n)})}return}const s=performance.now();try{const n=M(t),r=[];if(t.op==="export")for(const o of n)r.push(o.data.buffer);i.postMessage({type:"reply",id:t.id,ok:!0,value:n,ms:performance.now()-s,mem:y?.buffer.byteLength??null},r)}catch(n){const r=String(n?.message??n);i.postMessage({type:"reply",id:t.id,ok:!1,error:r,crashed:k(n)})}})};
studio/assets/main-Bt_M4BWC.js DELETED
The diff for this file is too large to render. See raw diff
 
studio/assets/main-CTlNDZmC.css DELETED
@@ -1 +0,0 @@
1
- *,*:before,*:after{box-sizing:border-box}html,body{height:100%;margin:0;overflow:hidden}body{background:var(--paper);color:var(--ink);font:400 var(--t-panel) / 1.5 var(--font-body);-webkit-font-smoothing:antialiased;user-select:none;cursor:default}#app{height:100%;display:flex;flex-direction:column}:focus{outline:none}:focus-visible{outline:2px solid var(--focus-line);outline-offset:2px;border-radius:var(--radius-sm)}a{color:var(--accent-text);text-decoration:none}a:hover{text-decoration:underline}button{font:inherit;color:inherit;background:none;border:none;padding:0;cursor:pointer}.num{font-variant-numeric:tabular-nums}.mono{font-family:var(--font-mono);font-variant-numeric:tabular-nums}.serif{font-family:var(--font-display);letter-spacing:-.03em}.eyebrow{font-size:var(--t-eyebrow);letter-spacing:.14em;text-transform:uppercase;color:var(--muted)}.dim{color:var(--dim)}.faint{color:var(--faint)}.muted{color:var(--muted)}.sep{width:1px;height:18px;background:var(--rule2);flex:none}.spacer{flex:1}kbd{font:500 10.5px var(--font-mono);border:1px solid var(--highlight);border-radius:4px;padding:1px 5px;color:var(--faint)}.btn{height:var(--h-control);padding:0 16px;border-radius:var(--radius-full);border:1px solid var(--rule2);background:transparent;color:var(--dim);font-weight:500;font-size:var(--t-chrome);display:inline-flex;align-items:center;justify-content:center;gap:7px;white-space:nowrap;transition:transform var(--d-micro) var(--ease-smooth),background var(--d-micro),border-color var(--d-micro),box-shadow var(--d-micro)}.btn:hover:not(:disabled){border-color:var(--highlight);color:var(--ink)}.btn:active:not(:disabled){transform:scale(.98)}.btn:disabled{opacity:.45;cursor:default}.btn.primary{height:var(--h-pill);border-color:var(--accent);background:var(--accent);color:var(--accent-ink);font-weight:600;font-size:var(--t-panel);box-shadow:var(--glow)}.btn.primary:hover:not(:disabled){background:var(--accent-hover);border-color:var(--accent-hover);color:var(--accent-ink);transform:translateY(-2px)}.btn.ghost{border-color:transparent}.btn.ghost:hover:not(:disabled){background:var(--surface)}.btn.compact{height:var(--h-compact);padding:0 11px;border-radius:var(--radius);font-size:var(--t-chrome)}.btn.icon{width:var(--h-control);padding:0}.btn.icon.compact{width:var(--h-compact)}.btn.danger{color:var(--bad)}.seg{display:inline-flex;gap:2px;padding:2px;background:var(--card);border:1px solid var(--rule2);border-radius:7px}.seg>button{font-size:var(--t-label);padding:4px 11px;border-radius:5px;color:var(--faint);white-space:nowrap;transition:background var(--d-micro),color var(--d-micro)}.seg>button:hover{color:var(--dim)}.seg>button[aria-pressed=true]{background:var(--highlight);color:var(--ink);font-weight:500}.seg>button:disabled{color:var(--muted);cursor:default}.toggle{font-size:var(--t-label);padding:3px 9px;border-radius:var(--radius-sm);border:1px solid transparent;color:var(--faint)}.toggle:hover{color:var(--dim)}.toggle[aria-pressed=true]{border-color:var(--highlight);background:var(--rule2);color:var(--ink)}.switch{width:32px;height:18px;flex:none;border-radius:var(--radius-full);background:var(--highlight);position:relative;transition:background var(--d-micro)}.switch:after{content:"";position:absolute;top:2px;left:2px;width:14px;height:14px;border-radius:50%;background:var(--faint);transition:transform var(--d-micro) var(--ease-smooth),background var(--d-micro)}.switch[aria-checked=true]{background:var(--accent)}.switch[aria-checked=true]:after{transform:translate(14px);background:var(--accent-ink)}.checkbox{width:15px;height:15px;flex:none;border-radius:4px;border:1px solid var(--highlight);display:grid;place-items:center;color:var(--accent-ink)}.checkbox[aria-checked=true]{background:var(--accent);border-color:var(--accent)}.checkbox:not([aria-checked=true])>svg{visibility:hidden}.appbar{height:var(--h-appbar);flex:none;display:flex;align-items:center;gap:12px;padding:0 12px;background:var(--stage);border-bottom:1px solid var(--rule2);-webkit-app-region:drag}.appbar button,.appbar .seg,.appbar .filechip,.appbar .nodrag{-webkit-app-region:no-drag}.brand{display:flex;align-items:center;gap:8px;font-size:var(--t-chrome);font-weight:600;letter-spacing:-.01em;padding-right:4px}.brand>svg{color:var(--accent)}.filechip{display:flex;align-items:center;gap:8px;height:var(--h-compact);padding:0 10px;background:var(--card);border:1px solid var(--rule2);border-radius:var(--radius-sm);font-size:var(--t-chrome);max-width:340px}.filechip .name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.wincontrols{display:flex;gap:2px;margin-left:4px}.wincontrols button{width:30px;height:26px;display:grid;place-items:center;border-radius:var(--radius-sm);color:var(--faint)}.wincontrols button:hover{background:var(--surface);color:var(--ink)}.wincontrols button.close:hover{background:var(--bad);color:#fff}.wincontrols i{display:block;background:currentColor}.wincontrols .min i{width:10px;height:1px}.wincontrols .max i{width:9px;height:9px;background:none;border:1px solid currentColor;border-radius:2px}.body{flex:1;min-height:0;display:flex}.stage{flex:1;min-width:0;display:flex;flex-direction:column;background:var(--stage);position:relative}.viewertools{min-height:38px;flex:none;display:flex;align-items:center;flex-wrap:wrap;gap:8px 12px;padding:5px 12px;border-bottom:1px solid var(--rule2);background:color-mix(in srgb,var(--stage) 92%,transparent)}.legend{display:flex;align-items:center;gap:10px;font-size:11px;color:var(--faint)}.legend span{display:flex;align-items:center;gap:5px}.legend i{display:block}.legend .anchor{width:7px;height:7px;border-radius:50%;background:var(--anchor)}.legend .handle{width:7px;height:7px;background:var(--handle);transform:rotate(45deg)}.legend .band{width:12px;height:6px;border-radius:2px}.legend .band.sure,.overlay .certainty .sure{--band: #30a46c}.legend .band.soft,.overlay .certainty .soft{--band: #f5a524}.legend .band.unsure,.overlay .certainty .unsure{--band: #e5484d}.legend .band{background:var(--band)}.overlay .certainty path{fill:var(--band);fill-rule:evenodd;fill-opacity:.45;stroke:var(--band);stroke-width:1;vector-effect:non-scaling-stroke}.legend .wire{width:12px;height:2px;background:var(--wire)}.panes{flex:1;min-height:0;display:flex;position:relative;overflow:hidden}.pane{flex:1;min-width:0;position:relative;overflow:hidden;display:grid;place-items:center;background-color:var(--stage);background-image:repeating-conic-gradient(var(--checker-a) 0% 25%,var(--checker-b) 0% 50%);background-size:16px 16px;cursor:grab;touch-action:none}.pane:active{cursor:grabbing}.pane.hidden{display:none}.panes.stacked .pane{position:absolute;inset:0}.panelabel{position:absolute;top:10px;left:12px;z-index:3;pointer-events:none}.chip{position:absolute;top:8px;right:12px;z-index:3;display:inline-flex;align-items:center;gap:6px;height:24px;padding:0 10px;border-radius:var(--radius-full);font-size:var(--t-label);border:1px solid;pointer-events:none}.chip i{width:6px;height:6px;border-radius:50%;background:currentColor;display:block}.chip.draft{color:var(--state-draft);border-color:color-mix(in srgb,var(--state-draft) 45%,transparent);background:color-mix(in srgb,var(--state-draft) 15%,transparent)}.chip.final{color:var(--state-final);border-color:color-mix(in srgb,var(--state-final) 40%,transparent);background:color-mix(in srgb,var(--state-final) 15%,transparent)}.chip.stale{color:var(--state-stale);border-color:color-mix(in srgb,var(--state-stale) 45%,transparent);background:color-mix(in srgb,var(--state-stale) 15%,transparent)}.chip.swapped:after{content:"";position:absolute;left:10px;right:10px;bottom:-3px;height:1.5px;background:var(--accent);border-radius:1px;transform-origin:left;animation:sweep var(--d-normal) var(--ease-out-expo)}@keyframes sweep{0%{transform:scaleX(0)}to{transform:scaleX(1)}}.art{position:absolute;left:0;top:0;transform-origin:0 0;will-change:transform;pointer-events:none}.art img{display:block;image-rendering:pixelated}.art svg{display:block;overflow:visible}.art.swapping{animation:crossfade var(--d-normal) var(--ease-out-expo)}@keyframes crossfade{0%{opacity:.25}to{opacity:1}}.art.centred{position:static;place-self:stretch;min-width:0;min-height:0;padding:44px 32px 32px}.panes.onlight{--stage: #f3f0ea;--checker-a: #ffffff;--checker-b: #ebe7df}.panes.ondark{--stage: #1a1816;--checker-a: #26231f;--checker-b: #211f1c}.overlay{position:absolute;left:0;top:0;overflow:visible;pointer-events:none}.overlay .wireframe path{fill:none;stroke:var(--wire);stroke-width:1px;vector-effect:non-scaling-stroke}.overlay .handles path{fill:none;stroke:var(--handle);vector-effect:non-scaling-stroke}.overlay .handles .lines{stroke-width:.8px}.overlay .handles .knobs{stroke-width:2.4px;stroke-linecap:square}.pane>canvas.anchors{position:absolute;left:0;top:0;pointer-events:none}.wipehandle{position:absolute;top:0;bottom:0;width:2px;background:var(--ink);z-index:4;cursor:ew-resize;opacity:.7}.wipehandle:after{content:"";position:absolute;left:50%;top:50%;width:26px;height:26px;margin:-13px 0 0 -13px;border-radius:50%;border:2px solid var(--ink);background:var(--stage)}.detailpane{position:absolute;inset:0;z-index:5;background:var(--stage);display:flex;flex-direction:column}.detailhead{flex:none;display:flex;align-items:center;gap:10px;padding:8px 12px;border-bottom:1px solid var(--rule2);font-size:var(--t-label);color:var(--dim)}.detailbody{flex:1;min-height:0;position:relative;overflow:hidden;display:grid;place-items:center}.detailfoot{flex:none;padding:8px 12px;border-top:1px solid var(--rule2);font-size:var(--t-label);color:var(--faint)}.pixelgrid{position:absolute;inset:0;background-image:linear-gradient(to right,rgba(250,248,245,.07) 1px,transparent 1px),linear-gradient(to bottom,rgba(250,248,245,.07) 1px,transparent 1px);pointer-events:none}:root[data-theme=light] .pixelgrid{background-image:linear-gradient(to right,rgba(26,24,22,.1) 1px,transparent 1px),linear-gradient(to bottom,rgba(26,24,22,.1) 1px,transparent 1px)}.stagestate{position:absolute;inset:0;z-index:6;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:11px;padding:32px;text-align:center;background:var(--stage)}.stagestate .title{font-size:16px;font-weight:500}.stagestate .body{display:block;font-size:var(--t-chrome);line-height:1.6;color:var(--faint);max-width:46ch}.stagestate .action{font-size:var(--t-chrome);color:var(--accent-text);margin-top:2px}.statusstrip{height:var(--h-status);flex:none;display:flex;align-items:center;gap:10px;padding:0 12px;border-top:1px solid var(--rule2);background:var(--stage);font-size:var(--t-label);color:var(--faint);font-variant-numeric:tabular-nums}.statusstrip .lead{color:var(--dim)}.statusstrip .privacy{margin-left:auto}.statusstrip .updatechip{display:inline-flex;align-items:center;gap:6px;color:var(--gold)}.statusstrip .updatechip i{width:6px;height:6px;border-radius:50%;background:currentColor;display:block}.rail{width:var(--rail-w);flex:none;border-left:1px solid var(--rule2);background:var(--paper);display:flex;flex-direction:column;position:relative}.railscroll{flex:1;min-height:0;overflow-y:auto;overflow-x:hidden;padding:var(--rail-pad);display:flex;flex-direction:column;gap:var(--rail-pad)}.railscroll::-webkit-scrollbar{width:10px}.railscroll::-webkit-scrollbar-thumb{background:var(--rule2);border-radius:5px;border:3px solid var(--paper)}.railscroll::-webkit-scrollbar-thumb:hover{background:var(--highlight)}.card{border:1px solid var(--rule2);background:var(--card);border-radius:var(--radius-lg);padding:13px;display:flex;flex-direction:column;gap:10px}.cardhead{display:flex;align-items:baseline;justify-content:space-between;gap:8px}.railfoot{flex:none;border-top:1px solid var(--rule2);background:var(--stage);padding:12px var(--rail-pad);display:flex;flex-direction:column;gap:8px}.railfoot .row{display:flex;gap:8px}.railfoot .note{display:flex;justify-content:space-between;font-size:var(--t-label);color:var(--muted)}.presets{display:flex;flex-wrap:wrap;gap:5px}.preset{display:inline-flex;align-items:center;padding:5px 10px;border-radius:999px;border:1px solid var(--rule2);background:var(--card);font-size:var(--t-chrome);color:var(--dim);transition:border-color var(--d-micro),background var(--d-micro),color var(--d-micro)}.preset:hover{border-color:var(--highlight);color:var(--ink)}.preset[aria-pressed=true]{border-color:var(--accent);background:var(--accent-soft);color:var(--ink);font-weight:500}.preset.saved{padding-right:22px}.preset{position:relative}.preset .forget{position:absolute;top:50%;margin-top:-8px;right:4px;width:16px;height:16px;border-radius:5px;opacity:0;transition:opacity var(--d-micro),background var(--d-micro)}.preset .forget:before,.preset .forget:after{content:"";position:absolute;left:4px;top:7.5px;width:8px;height:1.5px;border-radius:1px;background:var(--muted)}.preset .forget:before{transform:rotate(45deg)}.preset .forget:after{transform:rotate(-45deg)}.preset:hover .forget,.preset .forget:focus-visible{opacity:1}.preset .forget:hover{background:var(--highlight)}.preset .forget:hover:before,.preset .forget:hover:after{background:var(--bad)}.traybar{display:flex;align-items:baseline;justify-content:space-between;gap:8px;font-size:11px}.railmodes{flex:none;display:grid;grid-template-columns:1.75fr 1fr;gap:8px;padding:10px var(--rail-pad) 0}.mode{min-width:0;display:flex;flex-direction:column;gap:8px;padding:10px 11px 11px;border:1px solid var(--rule2);border-radius:var(--radius-lg);background:var(--card)}.modehead{display:flex;align-items:baseline;justify-content:space-between;gap:8px;min-height:16px}.modetitle{font-size:12.5px;font-weight:600;color:var(--ink)}.modestate{font-size:10.5px;color:var(--muted);white-space:nowrap}.seg.big{display:flex;width:100%;padding:3px}.seg.big>button{flex:1;padding:7px 0;font-size:12.5px;text-align:center}.seg.big>button[aria-pressed=true]{background:var(--accent);color:var(--accent-ink);font-weight:600}.bigswitch{display:flex;align-items:center;gap:8px;height:34px;padding:0 4px;border:1px solid var(--rule2);border-radius:999px;background:var(--paper);color:var(--dim);font-size:12.5px;font-weight:600;transition:background var(--d-micro),border-color var(--d-micro),color var(--d-micro)}.bigswitch .knob{flex:none;width:24px;height:24px;border-radius:50%;background:var(--highlight);transition:background var(--d-micro)}.bigswitch .state{flex:1;text-align:center;padding-right:4px}.bigswitch[aria-checked=true]{background:var(--accent);border-color:var(--accent);color:var(--accent-ink)}.bigswitch[aria-checked=true]{flex-direction:row-reverse}.bigswitch[aria-checked=true] .knob{background:var(--accent-ink)}.bigswitch[aria-checked=true] .state{padding-right:0;padding-left:4px}.railtabs{flex:none;background:var(--stage);border-bottom:1px solid var(--rule2)}.railseg{display:flex;width:100%}.railseg>button{position:relative;flex:1;display:flex;align-items:baseline;justify-content:center;gap:8px;height:46px;padding:14px 12px 0;color:var(--muted);transition:color var(--d-micro),background var(--d-micro)}.railseg>button:hover:not([aria-selected=true]){color:var(--dim);background:color-mix(in srgb,var(--ink) 4%,transparent)}.railseg>button[aria-selected=true]{color:var(--ink);background:var(--paper)}.railseg>button:after{content:"";position:absolute;left:0;right:0;bottom:-1px;height:2px;background:transparent;transition:background var(--d-micro)}.railseg>button[aria-selected=true]:after{background:var(--accent)}.railseg .tablabel{font-size:14px;font-weight:600;letter-spacing:-.005em}.railseg .tabnote{font-size:11px;font-weight:500;font-variant-numeric:tabular-nums;color:var(--faint)}.railseg .tabnote:empty{display:none}.railseg>button[aria-selected=true] .tabnote{color:var(--accent-text)}.tabcaption{margin:0;padding:7px var(--rail-pad) 9px;background:var(--paper);font-size:11.5px;color:var(--muted)}.presetblock{display:flex;flex-direction:column;gap:8px}.presetblock .traybar .faint{min-width:0}.rail section.group{gap:0;border:1px solid var(--rule2);background:var(--card);border-radius:var(--radius-lg)}.rail section.group .grouphead{align-items:center;padding-right:13px}.groupbtn{flex:1;display:flex;align-items:center;gap:7px;padding:11px 9px 11px 13px;color:var(--muted);text-align:left;border-radius:var(--radius-lg)}.groupbtn:hover{color:var(--dim)}.groupbtn .changed{font-size:10.5px;color:var(--accent-text)}.groupbody{display:flex;flex-direction:column;gap:13px;padding:2px 13px 14px}.control.changed .controlhead .label:before{content:"";display:inline-block;width:5px;height:5px;margin-right:6px;border-radius:50%;background:var(--accent);vertical-align:1px}.emptycard p{margin:0;font-size:var(--t-chrome);line-height:1.55;color:var(--dim)}.structure .srow{display:grid;grid-template-columns:1fr auto;column-gap:8px;row-gap:5px;align-items:baseline}.structure .srow .k{font-size:var(--t-chrome);color:var(--dim)}.structure .srow .v{font-size:var(--t-panel);font-variant-numeric:tabular-nums}.structure .srow .v .of{margin-left:6px;font-size:10.5px;color:var(--muted)}.sbar{grid-column:1 / -1;position:relative;height:4px;margin-bottom:4px;border-radius:2px;background:var(--highlight)}.sbar i{position:absolute;inset:0 auto 0 0;border-radius:2px;background:var(--accent);transition:width var(--d-normal) var(--ease-out-expo)}.sbar b{position:absolute;top:-3px;bottom:-3px;width:2px;margin-left:-1px;border-radius:1px;background:var(--dim)}.readout{display:grid;grid-template-columns:repeat(3,1fr);gap:1px;min-height:54px;background:var(--rule2);border:1px solid var(--rule2);border-radius:var(--radius);overflow:hidden}.readout .cell{min-width:0;display:flex;flex-direction:column;gap:1px;padding:6px 10px;background:var(--paper)}.readout .v{font-size:15px;font-weight:500;line-height:1.25}.readout .k{font-size:10px;color:var(--muted)}.delta{font-size:10.5px;font-variant-numeric:tabular-nums;color:var(--muted)}.delta.better{color:var(--good)}.delta.worse{color:var(--gold)}.readout.busy,.readout.idle{display:flex;align-items:center;gap:8px;padding:0 12px;background:var(--paper);font-size:var(--t-chrome)}.readout .stage{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--state-stale)}.pulse{flex:none;width:7px;height:7px;border-radius:50%;background:var(--state-stale);animation:pulse 1s ease-in-out infinite}@keyframes pulse{50%{opacity:.3}}.headline{display:flex;align-items:flex-end;gap:12px}.headline .figure{font-family:var(--font-display);font-size:52px;line-height:.85;letter-spacing:-.03em}.headline .of{display:flex;flex-direction:column;gap:2px;padding-bottom:4px}.meter{position:relative;height:6px;border-radius:3px;background:linear-gradient(90deg,var(--good),var(--gold) 55%,var(--bad))}.meter .threshold{position:absolute;top:-3px;bottom:-3px;width:1px;background:var(--dim)}.meter .needle{position:absolute;top:-4px;width:2px;height:14px;background:var(--ink);border-radius:1px;transition:left var(--d-normal) var(--ease-out-expo)}.meterscale{display:flex;justify-content:space-between;font-size:10.5px;color:var(--muted)}.stats{display:grid;grid-template-columns:1fr 1fr;gap:1px;background:var(--rule2);border:1px solid var(--rule2);border-radius:var(--radius);overflow:hidden;font-variant-numeric:tabular-nums}.stats>div{background:var(--paper);padding:8px 10px;display:flex;flex-direction:column;gap:2px}.stats .k{font-size:10.5px;color:var(--muted)}.stats .v{font-size:var(--t-panel)}.benchmark{display:flex;gap:8px;padding:8px 10px;border:1px dashed var(--highlight);border-radius:var(--radius);background:color-mix(in srgb,var(--ink) 2%,transparent)}.benchmark .label{flex:none;padding-top:1px}.benchmark p{margin:0;font-size:var(--t-label);line-height:1.45;color:var(--faint)}.loss{display:flex;gap:9px;padding-bottom:9px;border-bottom:1px solid var(--rule2)}.loss:last-of-type{border-bottom:none;padding-bottom:0}.loss .glyph{flex:none;padding-top:2px;color:var(--gold)}.loss .text{font-size:var(--t-chrome);line-height:1.45;color:var(--dim)}.loss .meta{font-size:var(--t-label);color:var(--muted);display:flex;gap:6px;flex-wrap:wrap}.loss .why{font-size:var(--t-label);line-height:1.5;color:var(--faint);margin-top:4px;padding-left:9px;border-left:1px solid var(--rule2)}.loss .expand{color:var(--muted)}.loss .expand:hover{color:var(--dim)}.nothinglost{display:flex;gap:9px;align-items:center;font-size:var(--t-chrome);color:var(--faint)}.nothinglost .glyph{color:var(--good);flex:none;display:flex}.ink{display:flex;align-items:center;gap:9px;padding:2px 0;border-radius:var(--radius-sm);width:100%;text-align:left}.ink:hover{background:var(--surface)}.swatch{width:22px;height:22px;flex:none;border-radius:5px;border:1px solid color-mix(in srgb,var(--ink) 12%,transparent);background-image:repeating-conic-gradient(var(--checker-a) 0% 25%,var(--checker-b) 0% 50%);background-size:8px 8px;position:relative;overflow:hidden}.swatch i{position:absolute;inset:0;display:block}.swatch.snapped{border:2px solid var(--accent)}.ink .hex{font-size:var(--t-chrome);font-variant-numeric:tabular-nums;color:var(--dim);width:72px}.ink .share{font-size:var(--t-label);color:var(--muted);font-variant-numeric:tabular-nums;width:44px}.ink .snap{margin-left:auto;font-size:var(--t-label);color:var(--muted)}.ink .snap.on{color:var(--accent-text)}.ink{gap:6px;touch-action:none}.ink.grouped .hex{color:var(--accent-text)}.inkpick{width:16px;height:16px;flex:none;border-radius:4px;border:1px solid var(--highlight);display:grid;place-items:center;color:var(--accent-ink)}.inkpick[aria-checked=true]{background:var(--accent);border-color:var(--accent)}.inkmain{display:flex;align-items:center;gap:9px;flex:1;min-width:0;text-align:left}.inkmain[aria-disabled=true]{cursor:grab}.swatch.mini{width:16px;height:16px;border-radius:4px}.swatch.mini.ramp{width:26px}.inklist{display:flex;flex-direction:column;gap:1px;border-radius:var(--radius-sm)}.inklist,.cgroup{transition:box-shadow var(--d-fast),background var(--d-fast)}.mergebar{display:flex;align-items:center;gap:10px;font-size:var(--t-label)}.cgroups{display:flex;flex-direction:column;gap:7px}.cgroupshead{display:flex;align-items:baseline;justify-content:space-between;gap:8px}.cgroup{border:1px solid var(--rule2);border-radius:var(--radius);background:var(--paper);padding:8px;display:flex;flex-direction:column;gap:7px}.cgroup.suggested{border-style:dashed;background:transparent}.cchips{display:flex;flex-wrap:wrap;gap:4px}.cchip{display:inline-flex;align-items:center;border:1px solid var(--highlight);border-radius:999px;background:var(--card);touch-action:none;font-size:var(--t-label);color:var(--dim)}.cchip.target{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent) inset}.cchip.unmatched{border-color:var(--gold);border-style:dashed}.cchipmain{display:inline-flex;align-items:center;gap:5px;padding:2px 4px 2px 3px;border-radius:999px}.cchipmain .swatch.mini{border-radius:999px}.cchipx{display:grid;place-items:center;width:18px;height:18px;margin-right:2px;border-radius:999px;color:var(--muted)}.cchipx:hover{color:var(--ink);background:var(--surface)}.cresult{display:flex;align-items:center;gap:6px;font-size:var(--t-label)}.cresult>span{white-space:nowrap}.cresult .arrow{color:var(--muted)}.cresultchip{display:inline-flex;align-items:center;gap:5px;color:var(--dim)}.cactions{display:flex;align-items:center;gap:6px}.creport{font-size:11px;line-height:1.4;display:inline-flex;align-items:center;gap:5px;min-width:0}.creport.warn{color:var(--gold)}.creport.warn svg{flex:none}.simplifyrow{display:flex;align-items:center;gap:5px;font-size:var(--t-label)}.numberfield.simplify{min-width:0;width:40px;text-align:center}.dragghost{position:fixed;z-index:60;pointer-events:none;filter:drop-shadow(0 2px 6px rgba(0,0,0,.45))}body.dragging-ink,body.dragging-ink *{cursor:grabbing!important}body.dragging-ink [data-drop=list]{box-shadow:0 0 0 1px var(--rule2)}.dropping{box-shadow:0 0 0 2px var(--accent)!important;background:var(--accent-subtle)!important}.control{display:flex;flex-direction:column;gap:5px}.controlhead{display:flex;align-items:baseline;gap:8px}.controlhead .label{flex:1;font-size:var(--t-chrome);color:var(--dim)}.controlhead .unit{font-size:var(--t-label);color:var(--muted)}.numberfield{height:24px;min-width:58px;padding:0 6px;border:1px solid var(--highlight);border-radius:var(--radius-sm);background:var(--paper);color:var(--ink);font:400 var(--t-chrome) var(--font-body);font-variant-numeric:tabular-nums;text-align:center}.numberfield:focus-visible{border-color:var(--accent)}.slider{appearance:none;-webkit-appearance:none;width:100%;height:14px;background:none;margin:2px 0 0;cursor:pointer}.slider::-webkit-slider-runnable-track{height:3px;border-radius:2px;background:var(--highlight)}.slider::-webkit-slider-thumb{-webkit-appearance:none;width:11px;height:11px;margin-top:-4px;border-radius:50%;background:var(--ink);border:none}.slider::-moz-range-track{height:3px;border-radius:2px;background:var(--highlight)}.slider::-moz-range-thumb{width:11px;height:11px;border:none;border-radius:50%;background:var(--ink)}.stops{display:flex;justify-content:space-between;gap:8px;font-size:10px;color:var(--muted);white-space:nowrap}.group{display:flex;flex-direction:column;gap:9px}.grouphead{display:flex;align-items:baseline;justify-content:space-between}.reset{font-size:var(--t-label);color:var(--accent-text)}.reset:disabled{color:var(--muted);cursor:default}.stagelist{display:flex;flex-direction:column;gap:5px;font-variant-numeric:tabular-nums}.stagerow{display:flex;align-items:baseline;gap:8px;font-size:var(--t-label);color:var(--muted)}.stagerow.done{color:var(--dim)}.stagerow .mark{width:10px;text-align:center;color:var(--muted)}.stagerow.done .mark{color:var(--good)}.stagerow .name{flex:1}.sweep{height:2px;background:var(--rule2);border-radius:2px;overflow:hidden}.sweep i{display:block;width:30%;height:100%;background:var(--accent);animation:sweepacross 1.6s var(--ease-out-expo) infinite}@keyframes sweepacross{0%{transform:translate(-100%)}to{transform:translate(340%)}}@media(prefers-reduced-motion:reduce){.sweep i{animation:none;width:100%;opacity:.4}.chip.swapped:after,.art.swapping,.callout,.pulse{animation:none}.dragging .drop{transition:none}}.firstrun{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:34px;background:var(--stage);padding:24px}.drop{width:min(620px,80%);padding:44px 24px;min-height:220px;justify-content:center;border:1.5px dashed var(--highlight);border-radius:var(--radius-xl);display:flex;flex-direction:column;align-items:center;gap:16px;background:color-mix(in srgb,var(--ink) 1.5%,transparent);text-align:center;transition:border-color var(--d-fast),background var(--d-fast)}.drop .glyph{color:var(--muted)}[hidden]{display:none!important}.drop.solid{background:var(--paper);border-style:solid;border-color:var(--rule2)}.panehint{max-width:34ch;text-align:center;font-size:12.5px;line-height:1.55}.drop .headline{font-family:var(--font-display);font-size:26px;letter-spacing:-.02em;display:block}.drop .release,.dragging .drop .resting{display:none}.dragging .drop .release{display:block}.dragging .drop{width:min(640px,90%);min-height:320px;transition:width var(--d-fast) var(--ease-out-expo),min-height var(--d-fast) var(--ease-out-expo),border-color var(--d-fast),background var(--d-fast)}.samples{display:flex;flex-direction:column;align-items:center;gap:12px}.samplerow{display:flex;gap:12px}.sample{width:112px;display:flex;flex-direction:column;gap:7px;align-items:center}.sample .thumb{width:112px;height:88px;border:1px solid var(--rule2);border-radius:10px;background-color:var(--card);background-image:repeating-conic-gradient(var(--checker-a) 0% 25%,var(--checker-b) 0% 50%);background-size:12px 12px;display:grid;place-items:center;overflow:hidden;transition:border-color var(--d-micro),transform var(--d-micro)}.sample:hover .thumb{border-color:var(--accent);transform:translateY(-2px)}.sample img{max-width:76px;max-height:68px}.sample .label{font-size:var(--t-label);color:var(--faint)}.dragging .drop{border-color:var(--accent);background:var(--accent-soft)}.dragging .drop .glyph{color:var(--accent)}.dragging:after{content:"";position:fixed;inset:0;border:2px solid var(--accent);pointer-events:none;z-index:60}.dragging .stage>*:not(.stagestate){opacity:.6}.callout{position:absolute;left:20px;bottom:20px;z-index:8;max-width:320px;display:flex;flex-direction:column;gap:5px;padding:10px 13px;border-radius:var(--radius-lg);border:1px solid var(--rule2);background:color-mix(in srgb,var(--card) 92%,transparent);backdrop-filter:blur(8px);font-size:11.5px;line-height:1.5;color:var(--dim);animation:rise var(--d-fast) var(--ease-out-expo)}.callout .head{display:flex;align-items:baseline;justify-content:space-between;gap:12px}#overlays:empty{display:none}.scrim{position:fixed;inset:0;z-index:40;background:color-mix(in srgb,var(--void) 62%,transparent);display:grid;place-items:center;padding:24px}.modal{width:min(460px,100%);max-height:86vh;overflow-y:auto;background:var(--card);border:1px solid var(--highlight);border-radius:var(--radius-xl);padding:22px;display:flex;flex-direction:column;gap:13px;box-shadow:var(--shadow-xl)}.modal h2{margin:0;font-family:var(--font-display);font-size:22px;font-weight:500;letter-spacing:-.02em}.modal p{margin:0;font-size:var(--t-panel);line-height:1.7;color:var(--dim)}.modal .actions{display:flex;gap:8px;justify-content:flex-end}.modal.composer{width:min(1040px,100%);max-height:92vh;padding:0;gap:0;overflow:hidden}.composerhead{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;padding:18px 22px 15px;border-bottom:1px solid var(--rule2)}.composerhead p{margin-top:3px;font-size:var(--t-chrome)}.composerbody{display:grid;grid-template-columns:minmax(0,1fr) 312px;min-height:0;overflow-y:auto}.composerpreview{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;padding:22px;min-width:0;background:var(--stage)}.cardstage{--ar: 1.7778;position:relative;width:min(100%,calc(58vh * var(--ar)));aspect-ratio:var(--ar);border-radius:var(--radius);overflow:hidden;background:var(--paper);box-shadow:var(--shadow-lg)}.cardstage img{display:block;width:100%;height:100%;object-fit:contain}.cardbusy{position:absolute;inset:0;display:grid;place-items:center;opacity:0;pointer-events:none;background:color-mix(in srgb,var(--paper) 45%,transparent);transition:opacity var(--d-micro)}.cardstage.busy .cardbusy{opacity:1;transition-delay:.26s}.cardspin{width:20px;height:20px;border-radius:50%;border:2px solid var(--highlight);border-top-color:var(--accent);animation:cardspin .8s linear infinite}@keyframes cardspin{to{transform:rotate(360deg)}}.composerside{display:flex;flex-direction:column;gap:18px;padding:20px 22px;border-left:1px solid var(--rule2)}.optgroup{display:flex;flex-direction:column;gap:8px}.cardseg{display:grid;grid-auto-flow:column;grid-auto-columns:1fr;gap:2px;padding:2px;border:1px solid var(--rule2);border-radius:var(--radius);background:var(--paper)}.cardseg button{display:flex;flex-direction:column;align-items:center;gap:1px;padding:6px 8px;border-radius:calc(var(--radius) - 2px);font-size:var(--t-chrome);color:var(--faint)}.cardseg button small{font-size:10px;color:var(--muted);font-variant-numeric:tabular-nums}.cardseg button:hover:not([aria-checked=true]){color:var(--ink)}.cardseg button[aria-checked=true]{color:var(--ink);background:var(--accent-soft);box-shadow:inset 0 0 0 1px var(--accent)}.optlist{display:flex;flex-direction:column;border:1px solid var(--rule2);border-radius:var(--radius);background:var(--paper)}.optlist>*+*{border-top:1px solid var(--rule2)}.optrow{display:flex;align-items:center;gap:12px;width:100%;padding:9px 11px;text-align:left}.optrow:hover{background:var(--rule2)}.optext{display:flex;flex:1;flex-direction:column;gap:1px;min-width:0}.optlabel{font-size:12.5px;color:var(--ink)}.optnote{font-size:11px;line-height:1.4;color:var(--muted)}.optslider{display:flex;flex-direction:column;gap:4px;padding:9px 11px 8px;transition:opacity var(--d-micro)}.optslider.off{opacity:.4}.optslider.off .slider{cursor:default}.optsliderhead{display:flex;justify-content:space-between;font-size:12px;color:var(--dim)}.composeractions{display:flex;flex-direction:column;gap:8px;margin-top:auto}.composeractions .btn{justify-content:center;gap:7px}@media(max-width:860px){.composerbody{grid-template-columns:minmax(0,1fr)}.composerside{border-left:0;border-top:1px solid var(--rule2)}}.popover{position:fixed;z-index:45;background:var(--card);border:1px solid var(--highlight);border-radius:var(--radius-lg);box-shadow:var(--shadow-lg);overflow:hidden}.menu{padding:6px;min-width:260px}.menu .item{display:flex;align-items:center;gap:9px;padding:7px 9px;border-radius:var(--radius);width:100%;text-align:left;font-size:var(--t-chrome);color:var(--dim)}.menu .item:hover{background:var(--surface);color:var(--ink)}.menu .item .when{margin-left:auto;font-size:var(--t-label);color:var(--muted)}.menu .rule{height:1px;background:var(--rule2);margin:5px 0}.sheet{position:absolute;inset:0;z-index:20;background:color-mix(in srgb,var(--void) 55%,transparent);display:flex;align-items:flex-end}.sheetbody{width:100%;max-height:100%;overflow-y:auto;background:var(--card);border-top:1px solid var(--highlight);border-radius:var(--radius-xl) var(--radius-xl) 0 0;padding:14px;display:flex;flex-direction:column;gap:11px;box-shadow:0 -20px 50px color-mix(in srgb,var(--void) 60%,transparent);animation:rise var(--d-fast) var(--ease-out-expo)}@keyframes rise{0%{transform:translateY(12px);opacity:0}}.format{display:flex;align-items:center;gap:9px;padding:7px 9px;border:1px solid var(--rule2);border-radius:var(--radius);background:var(--paper);width:100%;text-align:left;font-size:var(--t-chrome);color:var(--dim)}.format:hover{border-color:var(--highlight)}.format .size{margin-left:auto;font-size:var(--t-label);color:var(--muted);font-variant-numeric:tabular-nums}.tooltip{position:fixed;z-index:70;max-width:300px;background:var(--highlight);border:1px solid color-mix(in srgb,var(--ink) 10%,transparent);border-radius:var(--radius);padding:10px 12px;font-size:var(--t-chrome);line-height:1.6;color:var(--dim);box-shadow:var(--shadow);pointer-events:none}#toasts{position:fixed;left:50%;bottom:40px;transform:translate(-50%);z-index:80;display:flex;flex-direction:column;gap:8px;align-items:center}.toast{width:min(460px,90vw);background:var(--highlight);border:1px solid color-mix(in srgb,var(--ink) 10%,transparent);border-radius:10px;padding:11px 13px;display:flex;align-items:center;gap:11px;box-shadow:var(--shadow-lg);animation:risein var(--d-fast) var(--ease-out-expo)}@keyframes risein{0%{transform:translateY(8px);opacity:0}}.toast .text{flex:1;font-size:var(--t-chrome);color:var(--dim)}.toast .glyph{display:flex}.toast .glyph.good{color:var(--good)}.toast .glyph.bad{color:var(--bad)}.screen{position:absolute;inset:0;z-index:30;background:var(--paper);display:flex;flex-direction:column}.screenbar{height:var(--h-appbar);flex:none;display:flex;align-items:center;gap:12px;padding:0 12px;background:var(--stage);border-bottom:1px solid var(--rule2);-webkit-app-region:drag}.screenbar button{-webkit-app-region:no-drag}.screenbody{flex:1;min-height:0;overflow-y:auto;background:var(--stage)}.settingsgrid{width:min(1128px,100%);margin:0 auto;padding:24px;display:grid;grid-template-columns:repeat(auto-fit,minmax(420px,1fr));gap:16px;align-content:start}.settinggroup{border:1px solid var(--rule2);background:var(--card);border-radius:var(--radius-lg);overflow:hidden;align-self:start}.settinggroup>.head{padding:11px 14px;border-bottom:1px solid var(--rule2)}.settingrow{display:flex;align-items:center;gap:16px;padding:11px 14px;border-bottom:1px solid color-mix(in srgb,var(--ink) 5%,transparent)}.settingrow:last-child{border-bottom:none}.settingrow .about{flex:1;display:flex;flex-direction:column;gap:3px;min-width:0}.settingrow .label{font-size:var(--t-panel);color:var(--dim)}.settingrow .help{font-size:var(--t-label);line-height:1.5;color:var(--muted)}.settingrow .control{flex:none}.settingrow select,.settingrow input[type=text],.settingrow input[type=number]{height:var(--h-compact);padding:0 8px;border:1px solid var(--rule2);border-radius:7px;background:var(--paper);color:var(--ink);font:400 var(--t-chrome) var(--font-body);font-variant-numeric:tabular-nums;max-width:220px}.privacycard{border:1px solid var(--rule2);background:var(--card);border-radius:var(--radius-lg);padding:14px;display:flex;flex-direction:column;gap:8px;align-self:start}.privacycard .line{font-family:var(--font-display);font-size:19px;letter-spacing:-.02em}.about{display:flex;min-height:100%}.aboutleft{width:520px;flex:none;background:var(--stage);border-right:1px solid var(--rule2);display:flex;flex-direction:column;justify-content:center;gap:22px;padding:56px}.aboutleft .mark{color:var(--accent)}.aboutright{flex:1;padding:56px;display:flex;flex-direction:column;gap:20px;justify-content:center;background:var(--paper)}.credit{border:1px solid var(--highlight);border-radius:var(--radius-lg);padding:20px;display:flex;flex-direction:column;gap:10px;background:var(--gold-soft)}.notices{border:1px solid var(--rule2);border-radius:var(--radius-lg);overflow:hidden}.notices .head{padding:11px 14px;border-bottom:1px solid var(--rule2);font-size:var(--t-chrome);color:var(--dim);display:flex;justify-content:space-between}.notices pre{margin:0;height:220px;overflow:auto;padding:12px 14px;font:11.5px / 1.75 var(--font-mono);color:var(--muted);background:var(--stage);white-space:pre-wrap;user-select:text}.minifyrail{width:400px;flex:none;border-left:1px solid var(--rule2);background:var(--paper);display:flex;flex-direction:column}.minifyresult{display:flex;flex-direction:column;gap:5px}.minifyresult .figure{font-family:var(--font-display);font-size:66px;line-height:.85;letter-spacing:-.04em;color:var(--accent-text)}.sentence{font-size:var(--t-panel);line-height:2;color:var(--dim)}.sentence .inlinefield{display:inline-flex;align-items:center;height:24px;padding:0 4px;border:1px solid var(--rule2);border-radius:var(--radius-sm);background:var(--paper);color:var(--ink);font:400 var(--t-chrome) var(--font-body);font-variant-numeric:tabular-nums;width:58px;text-align:center}.sentence .inlinefield.accent{border-color:var(--accent);background:var(--accent-soft)}.batchbar{height:44px;flex:none;display:flex;align-items:center;gap:8px;padding:0 14px;border-bottom:1px solid var(--rule2);background:var(--stage)}.batchhead,.batchrow{display:grid;grid-template-columns:24px minmax(0,1.4fr) 120px 104px 84px 104px 96px minmax(0,1.2fr);align-items:center;gap:12px;padding:0 14px}.batchhead{height:34px;flex:none;border-bottom:1px solid var(--rule2)}.batchrow{height:var(--h-row);border-bottom:1px solid color-mix(in srgb,var(--ink) 4%,transparent);font-size:var(--t-chrome);font-variant-numeric:tabular-nums}.batchrow.failed{background:color-mix(in srgb,var(--bad) 8%,transparent)}.batchrow.running{background:var(--accent-subtle)}.batchrow .trunc,.batchhead .trunc{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.batchrow .right,.batchhead .right{text-align:right}.batchrow .state{display:flex;align-items:center;gap:6px}.batchrow .state i{width:6px;height:6px;border-radius:50%;background:currentColor;display:block;flex:none}.state.queued,.state.skipped{color:var(--muted)}.state.running{color:var(--accent-text)}.state.done{color:var(--good)}.state.failed{color:var(--bad)}.batchviewport{flex:1;min-height:0;overflow-y:auto;position:relative}.batchfoot{height:52px;flex:none;display:flex;align-items:center;gap:18px;padding:0 14px;border-top:1px solid var(--rule2);background:var(--stage);font-size:var(--t-chrome);font-variant-numeric:tabular-nums}.progress{height:3px;border-radius:2px;background:var(--highlight);overflow:hidden}.progress i{display:block;height:100%;background:var(--accent);transition:width var(--d-fast) var(--ease-smooth)}@media(max-width:1400px){:root{--rail-w: 330px}}@media(max-width:1180px){:root{--rail-w: 310px}.legend{display:none}}.chooser{position:absolute;left:50%;bottom:16px;transform:translate(-50%);z-index:8;width:min(560px,calc(100% - 32px));max-height:calc(100% - 70px);overflow-y:auto;display:flex;flex-direction:column;gap:9px;padding:10px 12px 11px;background:var(--card);border:1px solid var(--highlight);border-radius:var(--radius-xl);box-shadow:var(--shadow-lg);animation:rise var(--d-fast) var(--ease-out-expo)}.choosehead{display:flex;align-items:center;justify-content:space-between;gap:10px;min-width:0}.choosehead .eyebrow{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.chooseintro{font-size:var(--t-label);line-height:1.5;color:var(--dim);padding:7px 10px;border-radius:var(--radius);background:var(--accent-subtle);border:1px solid var(--accent-soft)}.choices{display:flex;flex-direction:column;gap:6px}.choice{display:grid;grid-template-columns:92px 1fr auto;align-items:center;gap:12px;padding:8px 8px 8px 12px;border:1px solid var(--rule2);border-radius:var(--radius-lg);background:var(--paper);min-width:0}.choice .btn{min-width:104px}.choicename{display:flex;align-items:center;gap:7px;font-family:var(--font-display);font-size:17px;letter-spacing:-.02em;color:var(--ink)}.choicename svg{color:var(--accent-text)}.choicebody{font-size:var(--t-chrome);line-height:1.5;color:var(--faint)}.choosenews{display:flex;flex-direction:column;gap:6px;padding-top:9px;border-top:1px solid var(--rule2)}.remember{display:flex;align-items:center;gap:7px;font-size:var(--t-label);color:var(--faint);cursor:pointer;align-self:flex-start}.remember input{accent-color:var(--accent);margin:0}.autochose{display:flex;flex-direction:column;gap:7px;font-size:var(--t-chrome)}.autolead{color:var(--dim);line-height:1.5}.autorow{display:flex;align-items:center;gap:10px;justify-content:space-between}.autorow.moved{padding-top:7px;border-top:1px dashed var(--rule2)}.autotext{display:flex;flex-direction:column;gap:2px;min-width:0}.autotitle{color:var(--ink)}.autotext .muted{font-size:var(--t-label)}.autobtns{display:flex;align-items:center;gap:10px;flex:none}.autorow .btn{flex:none}.autotaken{font-size:var(--t-label);color:var(--state-final);flex:none}.card.autocard{border-color:var(--accent-soft);background:color-mix(in srgb,var(--accent) 4%,var(--card))}.guideline{display:flex;align-items:baseline;justify-content:space-between;gap:10px;font-size:var(--t-label)}.rail.wizarding{width:max(var(--rail-w),400px)}.sheet.wizsheet{align-items:stretch;background:var(--paper)}.sheetbody.wizbody{height:100%;border-radius:0;border-top:none;box-shadow:none;padding:0;gap:0;overflow:hidden;background:var(--paper)}.wizhead{flex:none;display:flex;flex-direction:column;gap:9px;padding:14px 16px 12px;border-bottom:1px solid var(--rule2)}.wiztitle{font-size:24px;font-weight:500;line-height:1.1;color:var(--ink)}.wizdots{display:flex;gap:6px}.wizdots button{flex:1;height:4px;border-radius:2px;background:var(--highlight);transition:background var(--d-micro)}.wizdots button.done{background:color-mix(in srgb,var(--accent) 45%,var(--highlight))}.wizdots button[aria-selected=true]{background:var(--accent)}.wizdots button:hover{background:var(--accent-hover)}.wizcompare{display:flex;align-items:center;gap:8px;font-size:var(--t-label)}.wizcompare .seg>button{padding:3px 9px}.wizscroll{flex:1;min-height:0;overflow-y:auto;padding:14px 16px 18px;display:flex;flex-direction:column;gap:14px}.wizlead{font-size:var(--t-panel);line-height:1.55;color:var(--dim)}.wizguide{display:flex;flex-direction:column;gap:8px;padding-top:12px;border-top:1px solid var(--rule2)}.wizexplain{display:grid;grid-template-columns:auto 1fr;gap:5px 12px;margin:0;font-size:var(--t-chrome);line-height:1.5}.wizexplain dt{font-size:var(--t-eyebrow);letter-spacing:.12em;text-transform:uppercase;color:var(--muted);padding-top:3px;white-space:nowrap}.wizexplain dd{margin:0;color:var(--dim)}.wizlearn{font-size:var(--t-label);color:var(--accent-text);align-self:flex-start;text-decoration:none}.wizlearn:hover{text-decoration:underline}.wizcontrols{display:flex;flex-direction:column;gap:12px;padding:12px;border:1px solid var(--rule2);border-radius:var(--radius-lg);background:var(--card)}.wizlive{display:flex;flex-direction:column;gap:12px}.wizrow{display:flex;align-items:center;justify-content:space-between;gap:10px;font-size:var(--t-chrome)}.wizsmall{font-size:var(--t-label);line-height:1.5}.wizwhy{font-size:var(--t-chrome);line-height:1.5;color:var(--dim)}.wizchoices{display:grid;grid-template-columns:1fr 1fr;gap:8px}.wizchoice{display:flex;flex-direction:column;align-items:flex-start;gap:4px;padding:10px 11px;text-align:left;border:1px solid var(--rule2);border-radius:var(--radius);background:var(--paper);font-size:var(--t-label);line-height:1.45}.wizchoice .name{font-size:13px;font-weight:600;color:var(--ink)}.wizchoice:hover{border-color:var(--highlight)}.wizchoice[aria-pressed=true]{border-color:var(--accent);background:var(--accent-subtle)}.wiznote{display:flex;flex-direction:column;align-items:flex-start;gap:8px;padding:10px 11px;border-radius:var(--radius);background:var(--accent-subtle);font-size:var(--t-label);line-height:1.5;color:var(--dim)}.wiznav{flex:none;display:flex;align-items:center;gap:12px;padding:12px 16px;border-top:1px solid var(--rule2);background:var(--paper)}.kinds{display:grid;grid-template-columns:1fr 1fr;gap:9px}.kind{display:flex;flex-direction:column;align-items:stretch;gap:4px;padding:7px 7px 9px;text-align:left;border:1px solid var(--rule2);border-radius:var(--radius-lg);background:var(--card);min-width:0;transition:border-color var(--d-micro),background var(--d-micro)}.kind:hover{border-color:var(--highlight)}.kind[aria-checked=true]{border-color:var(--accent);background:color-mix(in srgb,var(--accent) 7%,var(--card));box-shadow:0 0 0 1px var(--accent)}.kthumb{position:relative;height:104px;border-radius:var(--radius);background-color:var(--stage);background-image:repeating-conic-gradient(var(--checker-a) 0% 25%,var(--checker-b) 0% 50%);background-size:10px 10px;display:grid;place-items:center;overflow:hidden;margin-bottom:3px}.kthumb img{max-width:92%;max-height:92%;object-fit:contain;image-rendering:auto}.kspin{position:absolute;inset:auto 0 0;height:2px;overflow:hidden;display:none}.kind.busy .kspin{display:block}.kspin:after{content:"";position:absolute;top:0;bottom:0;left:0;width:30%;background:var(--accent);animation:sweepacross 1.6s var(--ease-out-expo) infinite}@media(prefers-reduced-motion:reduce){.kspin:after{animation:none;width:100%;opacity:.5}}.kname{display:flex;align-items:baseline;justify-content:space-between;gap:6px;font-size:12.5px;font-weight:600;color:var(--ink);padding:0 2px}.ksugg{font-size:9.5px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:var(--accent-text)}.kwhy,.knote,.kstat{font-size:11px;line-height:1.4;padding:0 2px}.kwhy{color:var(--faint)}.knote{color:var(--muted);font-style:italic}.kstat{color:var(--muted);margin-top:2px}.wizreadout table{width:100%;border-collapse:collapse;font-size:var(--t-chrome)}.wizreadout th,.wizreadout td{padding:4px 0;text-align:right;font-weight:400}.wizreadout thead th{font-size:var(--t-eyebrow);letter-spacing:.12em;text-transform:uppercase;color:var(--muted)}.wizreadout tbody th{text-align:left;color:var(--faint)}.wizreadout td{color:var(--ink);padding-left:10px}.wizreadout td.delta{width:70px}.wizreadout td.delta.same{color:var(--muted)}.wizchanges .wizchange{display:flex;justify-content:space-between;gap:10px;font-size:var(--t-chrome);color:var(--faint)}.wizchanges .wizchange .num{color:var(--ink)}.pane.source.comparing .panelabel{color:var(--accent-text)}.intro{width:min(620px,80%);display:flex;flex-direction:column;gap:12px;padding:16px 18px;border:1px solid var(--accent-soft);border-radius:var(--radius-xl);background:color-mix(in srgb,var(--accent) 5%,var(--paper))}.intro ol{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:9px}.intro li{display:flex;gap:11px;align-items:baseline;font-size:var(--t-chrome);line-height:1.5}.intro li>div{display:flex;flex-direction:column}.introstep{width:20px;height:20px;flex:none;border-radius:50%;display:grid;place-items:center;font-size:11px;font-weight:600;background:var(--accent);color:var(--accent-ink)}.introtitle{color:var(--ink);font-weight:500}.introfoot{display:flex;align-items:center;justify-content:space-between;gap:12px;font-size:var(--t-label)}.boot{position:fixed;inset:0;z-index:50;display:grid;place-items:center;padding:16px;background:#0c0a09;transition:opacity .28s ease}.boot.leaving{opacity:0}.boot iframe{width:min(660px,100%);height:400px;border:0;border-radius:10px;box-shadow:0 30px 70px -20px #000c;background:#1a1816}html.framed:not(.fullscreen) body{padding-top:6px;background:var(--void)}html.framed:not(.fullscreen) #app{position:relative;border-top:1px solid rgba(201,117,74,.35)}.desktopnote{margin:14px 0 0;font-size:12.5px;text-align:center}.webcontrols{display:flex;gap:2px;margin-left:4px}.webcontrols .btn{gap:6px}.phonenote{position:fixed;inset:0;z-index:60;display:grid;place-items:center;padding:16px;background:color-mix(in srgb,var(--void) 82%,transparent)}.phonecard{max-width:420px;display:flex;flex-direction:column;gap:12px;padding:22px;border-radius:var(--radius-lg, 12px);background:var(--card);box-shadow:var(--shadow-lg)}.phonecard p{margin:0;font-size:13.5px;line-height:1.65;color:var(--dim)}.sc-loading{padding:40px;text-align:center}.sc-page{width:min(1320px,100%);margin:0 auto;padding:24px;display:flex;flex-direction:column;gap:18px}.sc-head h1{margin:0 0 6px;font-size:28px;font-weight:600}.sc-head p,.sc-results>p{margin:0;max-width:96ch;font-size:13px;line-height:1.6}.sc-main{display:grid;grid-template-columns:300px 1fr;gap:16px;min-height:0;align-items:start}.sc-list{height:560px;overflow-y:auto;overscroll-behavior:contain;padding:0 10px 10px;border:1px solid var(--rule2);border-radius:var(--radius-lg);background:var(--card);scrollbar-width:thin}.sc-sethead{position:sticky;top:0;z-index:1;display:flex;justify-content:space-between;padding:10px 2px 8px;background:var(--card);font-size:11px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:var(--dim)}.sc-sethead span{color:var(--faint);font-weight:600}.sc-tiles{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:6px;margin-bottom:6px}.sc-case{display:flex;flex-direction:column;align-items:stretch;gap:4px;min-width:0;overflow:hidden;padding:5px;border:1px solid var(--rule2);border-radius:var(--radius-sm);background:var(--stage);color:var(--dim);cursor:pointer;text-align:left}.sc-case[aria-pressed=true]{border-color:var(--accent);background:var(--accent-subtle);color:var(--accent)}.sc-thumb{aspect-ratio:1;width:100%;border-radius:5px;background:#fff;display:grid;place-items:center;overflow:hidden}.sc-thumb img{width:100%;height:100%;object-fit:contain}.sc-name{width:100%;font-size:11px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.sc-caption{margin:0;font-size:12.5px;color:var(--dim)}.sc-stagecol{display:flex;flex-direction:column;gap:10px;min-width:0}.sc-tools{display:flex;align-items:center;gap:8px;flex-wrap:nowrap;white-space:nowrap}.sc-tools .eyebrow{margin-left:6px}.sc-legend{margin-right:4px;display:inline-flex;align-items:center;gap:6px;font-size:11.5px}.sc-dot{display:inline-block;width:8px;height:8px;border-radius:50%}.sc-dot.a{background:var(--anchor)}.sc-dot.hd{background:var(--handle);margin-left:6px}.sc-viewer{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}.sc-pane{position:relative;aspect-ratio:1;overflow:hidden;border:1px solid var(--rule2);border-radius:var(--radius-sm);background-color:var(--stage);background-image:repeating-conic-gradient(var(--checker-a) 0% 25%,var(--checker-b) 0% 50%);background-size:16px 16px;cursor:grab;touch-action:none;user-select:none}.sc-pane:active{cursor:grabbing}.sc-cam{position:absolute;left:0;top:0;width:100%;height:100%;transform-origin:0 0}.sc-cam>img,.sc-layer,.sc-layer>svg{position:absolute;inset:0;width:100%;height:100%;display:block}.sc-cam>img{object-fit:contain}.sc-tag{position:absolute;top:8px;left:8px;padding:4px 7px;border-radius:6px;font-size:11px;font-weight:600;background:color-mix(in srgb,var(--paper) 85%,transparent);color:var(--ink);pointer-events:none}.sc-wire path,.sc-wire circle,.sc-wire ellipse,.sc-wire rect,.sc-wire polygon{fill:none!important;stroke:var(--wire);stroke-width:1px;vector-effect:non-scaling-stroke;stroke-opacity:.9}.sc-ov{pointer-events:none}.sc-ov .h{stroke:var(--handle);stroke-width:1px;vector-effect:non-scaling-stroke;fill:none;stroke-opacity:.85}.sc-ov .hd{stroke:var(--handle);stroke-width:3.5px;vector-effect:non-scaling-stroke;stroke-linecap:round;fill:none}.sc-ov .a{stroke:var(--anchor);stroke-width:5px;vector-effect:non-scaling-stroke;stroke-linecap:round;fill:none}.sc-chips{display:flex;flex-wrap:wrap;gap:6px}.sc-chip{font-size:12px;padding:4px 9px;border-radius:999px;border:1px solid var(--rule2);color:var(--dim);font-variant-numeric:tabular-nums}.sc-chip.win{border-color:transparent;background:var(--goodbg);color:var(--good)}.sc-results{display:flex;flex-direction:column;gap:12px;padding-top:8px;border-top:1px solid var(--rule2)}.sc-results h2{margin:8px 0 0;font-size:22px;font-weight:600}.sc-bignums{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}.sc-bignum{display:flex;flex-direction:column;gap:4px;padding:12px 14px;border:1px solid var(--rule2);border-radius:var(--radius-lg);background:var(--card);font-size:12px}.sc-bignum .v{font-size:28px;color:var(--accent);line-height:1.1}.sc-table{width:100%;border-collapse:collapse;font-size:12.5px;background:var(--card);border:1px solid var(--rule2);border-radius:var(--radius-lg);overflow:hidden}.sc-table th,.sc-table td{padding:8px 12px;text-align:right;border-bottom:1px solid var(--rule2);white-space:nowrap}.sc-table th:first-child,.sc-table td:first-child{text-align:left}.sc-table th{font-size:11px;font-weight:600;color:var(--faint);text-transform:uppercase;letter-spacing:.04em}.sc-table td.num{font-variant-numeric:tabular-nums;color:var(--dim)}.sc-table tr.win td{color:var(--ink)}.sc-table tr.win td:first-child{color:var(--accent);font-weight:600}.sc-note{font-size:12px!important}.sc-table caption{text-align:left;padding:10px 12px 4px;font-size:11px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--faint)}
 
 
studio/assets/main-DOUNaj7B.css ADDED
@@ -0,0 +1 @@
 
 
1
+ @font-face{font-family:Inter Studio;src:url(../fonts/inter-latin.woff2) format("woff2");font-weight:100 900;font-style:normal;font-display:swap;unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Playfair Studio;src:url(../fonts/playfair-latin.woff2) format("woff2");font-weight:400 900;font-style:normal;font-display:swap;unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}:root,:root[data-theme=dark]{color-scheme:dark;--void: #0c0a09;--stage: #141210;--paper: #1a1816;--card: #211f1c;--surface: #2a2724;--highlight: #35322e;--ink: #faf8f5;--dim: rgba(250, 248, 245, .75);--faint: rgba(250, 248, 245, .55);--muted: rgba(250, 248, 245, .4);--rule: rgba(250, 248, 245, .08);--rule2: #2a2724;--accent: #c9754a;--accent-text: #d4896a;--accent-hover: #d4896a;--accent-ink: #1a1816;--accent-soft: rgba(201, 117, 74, .12);--accent-subtle: rgba(201, 117, 74, .08);--gold: #b8976c;--gold-soft: rgba(184, 151, 108, .15);--good: #34d399;--goodbg: rgba(52, 211, 153, .15);--bad: #f87171;--badbg: rgba(248, 113, 113, .15);--anchor: #f87171;--handle: #b8976c;--wire: #faf8f5;--vt: #9a938c;--checker-a: #191715;--checker-b: #141210;--focus: rgba(201, 117, 74, .35);--focus-line: #c9754a;--state-draft: #b8976c;--state-final: #34d399;--state-stale: #c9754a;--shadow-sm: 0 4px 12px -2px rgba(12, 10, 9, .4);--shadow: 0 8px 24px -4px rgba(12, 10, 9, .5);--shadow-lg: 0 20px 50px -12px rgba(12, 10, 9, .6);--shadow-xl: 0 30px 70px -20px rgba(12, 10, 9, .7);--glow: 0 6px 20px rgba(201, 117, 74, .25)}:root[data-theme=light]{color-scheme:light;--void: #e6e1d9;--stage: #f0ece6;--paper: #faf8f5;--card: #ffffff;--surface: #eae5dd;--highlight: #ded8ce;--ink: #1a1816;--dim: rgba(26, 24, 22, .75);--faint: rgba(26, 24, 22, .58);--muted: rgba(26, 24, 22, .46);--rule: rgba(26, 24, 22, .1);--rule2: #d8d2c8;--accent: #c9754a;--accent-text: #9c5730;--accent-hover: #b3603a;--accent-ink: #faf8f5;--accent-soft: rgba(201, 117, 74, .14);--accent-subtle: rgba(201, 117, 74, .08);--gold: #8a6a3f;--gold-soft: rgba(138, 106, 63, .14);--good: #1c7a55;--goodbg: rgba(28, 122, 85, .14);--bad: #b83232;--badbg: rgba(184, 50, 50, .13);--anchor: #b83232;--handle: #8a6a3f;--wire: #1a1816;--vt: #6f6862;--checker-a: #ffffff;--checker-b: #e8e3db;--focus: rgba(201, 117, 74, .45);--focus-line: #9c5730;--state-draft: #8a6a3f;--state-final: #1c7a55;--state-stale: #9c5730;--shadow-sm: 0 4px 12px -2px rgba(90, 76, 60, .12);--shadow: 0 8px 24px -4px rgba(90, 76, 60, .16);--shadow-lg: 0 20px 50px -12px rgba(90, 76, 60, .2);--shadow-xl: 0 30px 70px -20px rgba(90, 76, 60, .24);--glow: 0 6px 20px rgba(201, 117, 74, .28)}:root{--font-display: "Playfair Studio", "Playfair Display", Georgia, serif;--font-body: "Inter Studio", Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;--font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;--h-compact: 28px;--h-control: 32px;--h-pill: 36px;--h-appbar: 46px;--h-status: 26px;--h-row: 30px;--t-chrome: 12.5px;--t-panel: 13px;--t-label: 11.5px;--t-eyebrow: 10px;--s1: 4px;--s2: 8px;--s3: 12px;--s4: 16px;--s5: 24px;--s6: 32px;--rail-pad: 14px;--rail-w: 352px;--radius-sm: 6px;--radius: 8px;--radius-lg: 12px;--radius-xl: 16px;--radius-full: 9999px;--ease-out-expo: cubic-bezier(.16, 1, .3, 1);--ease-smooth: cubic-bezier(.4, 0, .2, 1);--d-micro: .15s;--d-fast: .2s;--d-normal: .4s}@media(prefers-reduced-motion:reduce){:root{--d-micro: 0ms;--d-fast: 0ms;--d-normal: 0ms}}*,*:before,*:after{box-sizing:border-box}html,body{height:100%;margin:0;overflow:hidden}body{background:var(--paper);color:var(--ink);font:400 var(--t-panel) / 1.5 var(--font-body);-webkit-font-smoothing:antialiased;user-select:none;cursor:default}#app{height:100%;display:flex;flex-direction:column}:focus{outline:none}:focus-visible{outline:2px solid var(--focus-line);outline-offset:2px;border-radius:var(--radius-sm)}a{color:var(--accent-text);text-decoration:none}a:hover{text-decoration:underline}button{font:inherit;color:inherit;background:none;border:none;padding:0;cursor:pointer}.num{font-variant-numeric:tabular-nums}.mono{font-family:var(--font-mono);font-variant-numeric:tabular-nums}.serif{font-family:var(--font-display);letter-spacing:-.03em}.eyebrow{font-size:var(--t-eyebrow);letter-spacing:.14em;text-transform:uppercase;color:var(--muted)}.dim{color:var(--dim)}.faint{color:var(--faint)}.muted{color:var(--muted)}.sep{width:1px;height:18px;background:var(--rule2);flex:none}.spacer{flex:1}kbd{font:500 10.5px var(--font-mono);border:1px solid var(--highlight);border-radius:4px;padding:1px 5px;color:var(--faint)}.btn{height:var(--h-control);padding:0 16px;border-radius:var(--radius-full);border:1px solid var(--rule2);background:transparent;color:var(--dim);font-weight:500;font-size:var(--t-chrome);display:inline-flex;align-items:center;justify-content:center;gap:7px;white-space:nowrap;transition:transform var(--d-micro) var(--ease-smooth),background var(--d-micro),border-color var(--d-micro),box-shadow var(--d-micro)}.btn:hover:not(:disabled){border-color:var(--highlight);color:var(--ink)}.btn:active:not(:disabled){transform:scale(.98)}.btn:disabled{opacity:.45;cursor:default}.btn.primary{height:var(--h-pill);border-color:var(--accent);background:var(--accent);color:var(--accent-ink);font-weight:600;font-size:var(--t-panel);box-shadow:var(--glow)}.btn.primary:hover:not(:disabled){background:var(--accent-hover);border-color:var(--accent-hover);color:var(--accent-ink);transform:translateY(-2px)}.btn.ghost{border-color:transparent}.btn.ghost:hover:not(:disabled){background:var(--surface)}.btn.compact{height:var(--h-compact);padding:0 11px;border-radius:var(--radius);font-size:var(--t-chrome)}.btn.icon{width:var(--h-control);padding:0}.btn.icon.compact{width:var(--h-compact)}.btn.danger{color:var(--bad)}.seg{display:inline-flex;gap:2px;padding:2px;background:var(--card);border:1px solid var(--rule2);border-radius:7px}.seg>button{font-size:var(--t-label);padding:4px 11px;border-radius:5px;color:var(--faint);white-space:nowrap;transition:background var(--d-micro),color var(--d-micro)}.seg>button:hover{color:var(--dim)}.seg>button[aria-pressed=true]{background:var(--highlight);color:var(--ink);font-weight:500}.seg>button:disabled{color:var(--muted);cursor:default}.toggle{font-size:var(--t-label);padding:3px 9px;border-radius:var(--radius-sm);border:1px solid transparent;color:var(--faint)}.toggle:hover{color:var(--dim)}.toggle[aria-pressed=true]{border-color:var(--highlight);background:var(--rule2);color:var(--ink)}.switch{width:32px;height:18px;flex:none;border-radius:var(--radius-full);background:var(--highlight);position:relative;transition:background var(--d-micro)}.switch:after{content:"";position:absolute;top:2px;left:2px;width:14px;height:14px;border-radius:50%;background:var(--faint);transition:transform var(--d-micro) var(--ease-smooth),background var(--d-micro)}.switch[aria-checked=true]{background:var(--accent)}.switch[aria-checked=true]:after{transform:translate(14px);background:var(--accent-ink)}.checkbox{width:15px;height:15px;flex:none;border-radius:4px;border:1px solid var(--highlight);display:grid;place-items:center;color:var(--accent-ink)}.checkbox[aria-checked=true]{background:var(--accent);border-color:var(--accent)}.checkbox:not([aria-checked=true])>svg{visibility:hidden}.appbar{height:var(--h-appbar);flex:none;display:flex;align-items:center;gap:12px;padding:0 12px;background:var(--stage);border-bottom:1px solid var(--rule2);-webkit-app-region:drag}.appbar button,.appbar .seg,.appbar .filechip,.appbar .nodrag{-webkit-app-region:no-drag}.brand{display:flex;align-items:center;gap:8px;font-size:var(--t-chrome);font-weight:600;letter-spacing:-.01em;padding-right:4px}.brand>svg{color:var(--accent)}.filechip{display:flex;align-items:center;gap:8px;height:var(--h-compact);padding:0 10px;background:var(--card);border:1px solid var(--rule2);border-radius:var(--radius-sm);font-size:var(--t-chrome);max-width:340px}.filechip .name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.wincontrols{display:flex;gap:2px;margin-left:4px}.wincontrols button{width:30px;height:26px;display:grid;place-items:center;border-radius:var(--radius-sm);color:var(--faint)}.wincontrols button:hover{background:var(--surface);color:var(--ink)}.wincontrols button.close:hover{background:var(--bad);color:#fff}.wincontrols i{display:block;background:currentColor}.wincontrols .min i{width:10px;height:1px}.wincontrols .max i{width:9px;height:9px;background:none;border:1px solid currentColor;border-radius:2px}.body{flex:1;min-height:0;display:flex}.stage{flex:1;min-width:0;display:flex;flex-direction:column;background:var(--stage);position:relative}.viewertools{min-height:38px;flex:none;display:flex;align-items:center;flex-wrap:wrap;gap:8px 12px;padding:5px 12px;border-bottom:1px solid var(--rule2);background:color-mix(in srgb,var(--stage) 92%,transparent)}.legend{display:flex;align-items:center;gap:10px;font-size:11px;color:var(--faint)}.legend span{display:flex;align-items:center;gap:5px}.legend i{display:block}.legend .anchor{width:7px;height:7px;border-radius:50%;background:var(--anchor)}.legend .handle{width:7px;height:7px;background:var(--handle);transform:rotate(45deg)}.legend .band{width:12px;height:6px;border-radius:2px}.legend .band.sure,.overlay .certainty .sure{--band: #30a46c}.legend .band.soft,.overlay .certainty .soft{--band: #f5a524}.legend .band.unsure,.overlay .certainty .unsure{--band: #e5484d}.legend .band{background:var(--band)}.overlay .certainty path{fill:var(--band);fill-rule:evenodd;fill-opacity:.45;stroke:var(--band);stroke-width:1;vector-effect:non-scaling-stroke}.legend .wire{width:12px;height:2px;background:var(--wire)}.panes{flex:1;min-height:0;display:flex;position:relative;overflow:hidden}.pane{flex:1;min-width:0;position:relative;overflow:hidden;display:grid;place-items:center;background-color:var(--stage);background-image:repeating-conic-gradient(var(--checker-a) 0% 25%,var(--checker-b) 0% 50%);background-size:16px 16px;cursor:grab;touch-action:none}.pane:active{cursor:grabbing}.pane.hidden{display:none}.panes.stacked .pane{position:absolute;inset:0}.panelabel{position:absolute;top:10px;left:12px;z-index:3;pointer-events:none}.chip{position:absolute;top:8px;right:12px;z-index:3;display:inline-flex;align-items:center;gap:6px;height:24px;padding:0 10px;border-radius:var(--radius-full);font-size:var(--t-label);border:1px solid;pointer-events:none}.chip i{width:6px;height:6px;border-radius:50%;background:currentColor;display:block}.chip.draft{color:var(--state-draft);border-color:color-mix(in srgb,var(--state-draft) 45%,transparent);background:color-mix(in srgb,var(--state-draft) 15%,transparent)}.chip.final{color:var(--state-final);border-color:color-mix(in srgb,var(--state-final) 40%,transparent);background:color-mix(in srgb,var(--state-final) 15%,transparent)}.chip.stale{color:var(--state-stale);border-color:color-mix(in srgb,var(--state-stale) 45%,transparent);background:color-mix(in srgb,var(--state-stale) 15%,transparent)}.chip.swapped:after{content:"";position:absolute;left:10px;right:10px;bottom:-3px;height:1.5px;background:var(--accent);border-radius:1px;transform-origin:left;animation:sweep var(--d-normal) var(--ease-out-expo)}@keyframes sweep{0%{transform:scaleX(0)}to{transform:scaleX(1)}}.art{position:absolute;left:0;top:0;transform-origin:0 0;will-change:transform;pointer-events:none}.art img{display:block;image-rendering:pixelated}.art svg{display:block;overflow:visible}.art.swapping{animation:crossfade var(--d-normal) var(--ease-out-expo)}@keyframes crossfade{0%{opacity:.25}to{opacity:1}}.art.centred{position:static;place-self:stretch;min-width:0;min-height:0;padding:44px 32px 32px}.panes.onlight{--stage: #f3f0ea;--checker-a: #ffffff;--checker-b: #ebe7df}.panes.ondark{--stage: #1a1816;--checker-a: #26231f;--checker-b: #211f1c}.overlay{position:absolute;left:0;top:0;overflow:visible;pointer-events:none}.overlay .wireframe path{fill:none;stroke:var(--wire);stroke-width:1px;vector-effect:non-scaling-stroke}.overlay .handles path{fill:none;stroke:var(--handle);vector-effect:non-scaling-stroke}.overlay .handles .lines{stroke-width:.8px}.overlay .handles .knobs{stroke-width:2.4px;stroke-linecap:square}.pane>canvas.anchors{position:absolute;left:0;top:0;pointer-events:none}.wipehandle{position:absolute;top:0;bottom:0;width:2px;background:var(--ink);z-index:4;cursor:ew-resize;opacity:.7}.wipehandle:after{content:"";position:absolute;left:50%;top:50%;width:26px;height:26px;margin:-13px 0 0 -13px;border-radius:50%;border:2px solid var(--ink);background:var(--stage)}.detailpane{position:absolute;inset:0;z-index:5;background:var(--stage);display:flex;flex-direction:column}.detailhead{flex:none;display:flex;align-items:center;gap:10px;padding:8px 12px;border-bottom:1px solid var(--rule2);font-size:var(--t-label);color:var(--dim)}.detailbody{flex:1;min-height:0;position:relative;overflow:hidden;display:grid;place-items:center}.detailfoot{flex:none;padding:8px 12px;border-top:1px solid var(--rule2);font-size:var(--t-label);color:var(--faint)}.pixelgrid{position:absolute;inset:0;background-image:linear-gradient(to right,rgba(250,248,245,.07) 1px,transparent 1px),linear-gradient(to bottom,rgba(250,248,245,.07) 1px,transparent 1px);pointer-events:none}:root[data-theme=light] .pixelgrid{background-image:linear-gradient(to right,rgba(26,24,22,.1) 1px,transparent 1px),linear-gradient(to bottom,rgba(26,24,22,.1) 1px,transparent 1px)}.stagestate{position:absolute;inset:0;z-index:6;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:11px;padding:32px;text-align:center;background:var(--stage)}.stagestate .title{font-size:16px;font-weight:500}.stagestate .body{display:block;font-size:var(--t-chrome);line-height:1.6;color:var(--faint);max-width:46ch}.stagestate .action{font-size:var(--t-chrome);color:var(--accent-text);margin-top:2px}.statusstrip{height:var(--h-status);flex:none;display:flex;align-items:center;gap:10px;padding:0 12px;border-top:1px solid var(--rule2);background:var(--stage);font-size:var(--t-label);color:var(--faint);font-variant-numeric:tabular-nums}.statusstrip .lead{color:var(--dim)}.statusstrip .privacy{margin-left:auto}.statusstrip .updatechip{display:inline-flex;align-items:center;gap:6px;color:var(--gold)}.statusstrip .updatechip i{width:6px;height:6px;border-radius:50%;background:currentColor;display:block}.statusstrip .fetchchip{display:inline-flex;align-items:center;gap:8px;margin-left:12px;color:var(--faint);white-space:nowrap}.statusstrip .fetchchip.bad{color:var(--bad)}.fetchbar{width:64px;flex:none}.fetchnote{display:flex;flex-direction:column;gap:6px;font-size:11.5px;line-height:1.45;color:var(--faint)}.fetchnote .fetchbar{width:100%}.fetchnote .fetchline{color:var(--dim);font-variant-numeric:tabular-nums}.rail{width:var(--rail-w);flex:none;border-left:1px solid var(--rule2);background:var(--paper);display:flex;flex-direction:column;position:relative}.railscroll{flex:1;min-height:0;overflow-y:auto;overflow-x:hidden;padding:var(--rail-pad);display:flex;flex-direction:column;gap:var(--rail-pad)}.railscroll::-webkit-scrollbar{width:10px}.railscroll::-webkit-scrollbar-thumb{background:var(--rule2);border-radius:5px;border:3px solid var(--paper)}.railscroll::-webkit-scrollbar-thumb:hover{background:var(--highlight)}.card{border:1px solid var(--rule2);background:var(--card);border-radius:var(--radius-lg);padding:13px;display:flex;flex-direction:column;gap:10px}.cardhead{display:flex;align-items:baseline;justify-content:space-between;gap:8px}.railfoot{flex:none;border-top:1px solid var(--rule2);background:var(--stage);padding:12px var(--rail-pad);display:flex;flex-direction:column;gap:8px}.railfoot .row{display:flex;gap:8px}.railfoot .note{display:flex;justify-content:space-between;font-size:var(--t-label);color:var(--muted)}.presets{display:flex;flex-wrap:wrap;gap:5px}.preset{display:inline-flex;align-items:center;padding:5px 10px;border-radius:999px;border:1px solid var(--rule2);background:var(--card);font-size:var(--t-chrome);color:var(--dim);transition:border-color var(--d-micro),background var(--d-micro),color var(--d-micro)}.preset:hover{border-color:var(--highlight);color:var(--ink)}.preset[aria-pressed=true]{border-color:var(--accent);background:var(--accent-soft);color:var(--ink);font-weight:500}.preset.saved{padding-right:22px}.preset{position:relative}.preset .forget{position:absolute;top:50%;margin-top:-8px;right:4px;width:16px;height:16px;border-radius:5px;opacity:0;transition:opacity var(--d-micro),background var(--d-micro)}.preset .forget:before,.preset .forget:after{content:"";position:absolute;left:4px;top:7.5px;width:8px;height:1.5px;border-radius:1px;background:var(--muted)}.preset .forget:before{transform:rotate(45deg)}.preset .forget:after{transform:rotate(-45deg)}.preset:hover .forget,.preset .forget:focus-visible{opacity:1}.preset .forget:hover{background:var(--highlight)}.preset .forget:hover:before,.preset .forget:hover:after{background:var(--bad)}.traybar{display:flex;align-items:baseline;justify-content:space-between;gap:8px;font-size:11px}.railmodes{flex:none;display:grid;grid-template-columns:1.75fr 1fr;gap:8px;padding:10px var(--rail-pad) 0}.mode{min-width:0;display:flex;flex-direction:column;gap:8px;padding:10px 11px 11px;border:1px solid var(--rule2);border-radius:var(--radius-lg);background:var(--card)}.modehead{display:flex;align-items:baseline;justify-content:space-between;gap:8px;min-height:16px}.modetitle{font-size:12.5px;font-weight:600;color:var(--ink)}.modestate{font-size:10.5px;color:var(--muted);white-space:nowrap}.seg.big{display:flex;width:100%;padding:3px}.seg.big>button{flex:1;padding:7px 0;font-size:12.5px;text-align:center}.seg.big>button[aria-pressed=true]{background:var(--accent);color:var(--accent-ink);font-weight:600}.bigswitch{display:flex;align-items:center;gap:8px;height:34px;padding:0 4px;border:1px solid var(--rule2);border-radius:999px;background:var(--paper);color:var(--dim);font-size:12.5px;font-weight:600;transition:background var(--d-micro),border-color var(--d-micro),color var(--d-micro)}.bigswitch .knob{flex:none;width:24px;height:24px;border-radius:50%;background:var(--highlight);transition:background var(--d-micro)}.bigswitch .state{flex:1;text-align:center;padding-right:4px}.bigswitch[aria-checked=true]{background:var(--accent);border-color:var(--accent);color:var(--accent-ink)}.bigswitch[aria-checked=true]{flex-direction:row-reverse}.bigswitch[aria-checked=true] .knob{background:var(--accent-ink)}.bigswitch[aria-checked=true] .state{padding-right:0;padding-left:4px}.railtabs{flex:none;background:var(--stage);border-bottom:1px solid var(--rule2)}.railseg{display:flex;width:100%}.railseg>button{position:relative;flex:1;display:flex;align-items:baseline;justify-content:center;gap:8px;height:46px;padding:14px 12px 0;color:var(--muted);transition:color var(--d-micro),background var(--d-micro)}.railseg>button:hover:not([aria-selected=true]){color:var(--dim);background:color-mix(in srgb,var(--ink) 4%,transparent)}.railseg>button[aria-selected=true]{color:var(--ink);background:var(--paper)}.railseg>button:after{content:"";position:absolute;left:0;right:0;bottom:-1px;height:2px;background:transparent;transition:background var(--d-micro)}.railseg>button[aria-selected=true]:after{background:var(--accent)}.railseg .tablabel{font-size:14px;font-weight:600;letter-spacing:-.005em}.railseg .tabnote{font-size:11px;font-weight:500;font-variant-numeric:tabular-nums;color:var(--faint)}.railseg .tabnote:empty{display:none}.railseg>button[aria-selected=true] .tabnote{color:var(--accent-text)}.tabcaption{margin:0;padding:7px var(--rail-pad) 9px;background:var(--paper);font-size:11.5px;color:var(--muted)}.presetblock{display:flex;flex-direction:column;gap:8px}.presetblock .traybar .faint{min-width:0}.rail section.group{gap:0;border:1px solid var(--rule2);background:var(--card);border-radius:var(--radius-lg)}.rail section.group .grouphead{align-items:center;padding-right:13px}.groupbtn{flex:1;display:flex;align-items:center;gap:7px;padding:11px 9px 11px 13px;color:var(--muted);text-align:left;border-radius:var(--radius-lg)}.groupbtn:hover{color:var(--dim)}.groupbtn .changed{font-size:10.5px;color:var(--accent-text)}.groupbody{display:flex;flex-direction:column;gap:13px;padding:2px 13px 14px}.control.changed .controlhead .label:before{content:"";display:inline-block;width:5px;height:5px;margin-right:6px;border-radius:50%;background:var(--accent);vertical-align:1px}.emptycard p{margin:0;font-size:var(--t-chrome);line-height:1.55;color:var(--dim)}.structure .srow{display:grid;grid-template-columns:1fr auto;column-gap:8px;row-gap:5px;align-items:baseline}.structure .srow .k{font-size:var(--t-chrome);color:var(--dim)}.structure .srow .v{font-size:var(--t-panel);font-variant-numeric:tabular-nums}.structure .srow .v .of{margin-left:6px;font-size:10.5px;color:var(--muted)}.sbar{grid-column:1 / -1;position:relative;height:4px;margin-bottom:4px;border-radius:2px;background:var(--highlight)}.sbar i{position:absolute;inset:0 auto 0 0;border-radius:2px;background:var(--accent);transition:width var(--d-normal) var(--ease-out-expo)}.sbar b{position:absolute;top:-3px;bottom:-3px;width:2px;margin-left:-1px;border-radius:1px;background:var(--dim)}.readout{display:grid;grid-template-columns:repeat(3,1fr);gap:1px;min-height:54px;background:var(--rule2);border:1px solid var(--rule2);border-radius:var(--radius);overflow:hidden}.readout .cell{min-width:0;display:flex;flex-direction:column;gap:1px;padding:6px 10px;background:var(--paper)}.readout .v{font-size:15px;font-weight:500;line-height:1.25}.readout .k{font-size:10px;color:var(--muted)}.delta{font-size:10.5px;font-variant-numeric:tabular-nums;color:var(--muted)}.delta.better{color:var(--good)}.delta.worse{color:var(--gold)}.readout.busy,.readout.idle{display:flex;align-items:center;gap:8px;padding:0 12px;background:var(--paper);font-size:var(--t-chrome)}.readout .stage{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--state-stale)}.pulse{flex:none;width:7px;height:7px;border-radius:50%;background:var(--state-stale);animation:pulse 1s ease-in-out infinite}@keyframes pulse{50%{opacity:.3}}.headline{display:flex;align-items:flex-end;gap:12px}.headline .figure{font-family:var(--font-display);font-size:52px;line-height:.85;letter-spacing:-.03em}.headline .of{display:flex;flex-direction:column;gap:2px;padding-bottom:4px}.meter{position:relative;height:6px;border-radius:3px;background:linear-gradient(90deg,var(--good),var(--gold) 55%,var(--bad))}.meter .threshold{position:absolute;top:-3px;bottom:-3px;width:1px;background:var(--dim)}.meter .needle{position:absolute;top:-4px;width:2px;height:14px;background:var(--ink);border-radius:1px;transition:left var(--d-normal) var(--ease-out-expo)}.meterscale{display:flex;justify-content:space-between;font-size:10.5px;color:var(--muted)}.stats{display:grid;grid-template-columns:1fr 1fr;gap:1px;background:var(--rule2);border:1px solid var(--rule2);border-radius:var(--radius);overflow:hidden;font-variant-numeric:tabular-nums}.stats>div{background:var(--paper);padding:8px 10px;display:flex;flex-direction:column;gap:2px}.stats .k{font-size:10.5px;color:var(--muted)}.stats .v{font-size:var(--t-panel)}.benchmark{display:flex;gap:8px;padding:8px 10px;border:1px dashed var(--highlight);border-radius:var(--radius);background:color-mix(in srgb,var(--ink) 2%,transparent)}.benchmark .label{flex:none;padding-top:1px}.benchmark p{margin:0;font-size:var(--t-label);line-height:1.45;color:var(--faint)}.loss{display:flex;gap:9px;padding-bottom:9px;border-bottom:1px solid var(--rule2)}.loss:last-of-type{border-bottom:none;padding-bottom:0}.loss .glyph{flex:none;padding-top:2px;color:var(--gold)}.loss .text{font-size:var(--t-chrome);line-height:1.45;color:var(--dim)}.loss .meta{font-size:var(--t-label);color:var(--muted);display:flex;gap:6px;flex-wrap:wrap}.loss .why{font-size:var(--t-label);line-height:1.5;color:var(--faint);margin-top:4px;padding-left:9px;border-left:1px solid var(--rule2)}.loss .expand{color:var(--muted)}.loss .expand:hover{color:var(--dim)}.nothinglost{display:flex;gap:9px;align-items:center;font-size:var(--t-chrome);color:var(--faint)}.nothinglost .glyph{color:var(--good);flex:none;display:flex}.ink{display:flex;align-items:center;gap:9px;padding:2px 0;border-radius:var(--radius-sm);width:100%;text-align:left}.ink:hover{background:var(--surface)}.swatch{width:22px;height:22px;flex:none;border-radius:5px;border:1px solid color-mix(in srgb,var(--ink) 12%,transparent);background-image:repeating-conic-gradient(var(--checker-a) 0% 25%,var(--checker-b) 0% 50%);background-size:8px 8px;position:relative;overflow:hidden}.swatch i{position:absolute;inset:0;display:block}.swatch.snapped{border:2px solid var(--accent)}.ink .hex{font-size:var(--t-chrome);font-variant-numeric:tabular-nums;color:var(--dim);width:72px}.ink .share{font-size:var(--t-label);color:var(--muted);font-variant-numeric:tabular-nums;width:44px}.ink .snap{margin-left:auto;font-size:var(--t-label);color:var(--muted)}.ink .snap.on{color:var(--accent-text)}.ink{gap:6px;touch-action:none}.ink.grouped .hex{color:var(--accent-text)}.inkpick{width:16px;height:16px;flex:none;border-radius:4px;border:1px solid var(--highlight);display:grid;place-items:center;color:var(--accent-ink)}.inkpick[aria-checked=true]{background:var(--accent);border-color:var(--accent)}.inkmain{display:flex;align-items:center;gap:9px;flex:1;min-width:0;text-align:left}.inkmain[aria-disabled=true]{cursor:grab}.swatch.mini{width:16px;height:16px;border-radius:4px}.swatch.mini.ramp{width:26px}.inklist{display:flex;flex-direction:column;gap:1px;border-radius:var(--radius-sm)}.inklist,.cgroup{transition:box-shadow var(--d-fast),background var(--d-fast)}.mergebar{display:flex;align-items:center;gap:10px;font-size:var(--t-label)}.cgroups{display:flex;flex-direction:column;gap:7px}.cgroupshead{display:flex;align-items:baseline;justify-content:space-between;gap:8px}.cgroup{border:1px solid var(--rule2);border-radius:var(--radius);background:var(--paper);padding:8px;display:flex;flex-direction:column;gap:7px}.cgroup.suggested{border-style:dashed;background:transparent}.cchips{display:flex;flex-wrap:wrap;gap:4px}.cchip{display:inline-flex;align-items:center;border:1px solid var(--highlight);border-radius:999px;background:var(--card);touch-action:none;font-size:var(--t-label);color:var(--dim)}.cchip.target{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent) inset}.cchip.unmatched{border-color:var(--gold);border-style:dashed}.cchipmain{display:inline-flex;align-items:center;gap:5px;padding:2px 4px 2px 3px;border-radius:999px}.cchipmain .swatch.mini{border-radius:999px}.cchipx{display:grid;place-items:center;width:18px;height:18px;margin-right:2px;border-radius:999px;color:var(--muted)}.cchipx:hover{color:var(--ink);background:var(--surface)}.cresult{display:flex;align-items:center;gap:6px;font-size:var(--t-label)}.cresult>span{white-space:nowrap}.cresult .arrow{color:var(--muted)}.cresultchip{display:inline-flex;align-items:center;gap:5px;color:var(--dim)}.cactions{display:flex;align-items:center;gap:6px}.creport{font-size:11px;line-height:1.4;display:inline-flex;align-items:center;gap:5px;min-width:0}.creport.warn{color:var(--gold)}.creport.warn svg{flex:none}.simplifyrow{display:flex;align-items:center;gap:5px;font-size:var(--t-label)}.numberfield.simplify{min-width:0;width:40px;text-align:center}.dragghost{position:fixed;z-index:60;pointer-events:none;filter:drop-shadow(0 2px 6px rgba(0,0,0,.45))}body.dragging-ink,body.dragging-ink *{cursor:grabbing!important}body.dragging-ink [data-drop=list]{box-shadow:0 0 0 1px var(--rule2)}.dropping{box-shadow:0 0 0 2px var(--accent)!important;background:var(--accent-subtle)!important}.control{display:flex;flex-direction:column;gap:5px}.controlhead{display:flex;align-items:baseline;gap:8px}.controlhead .label{flex:1;font-size:var(--t-chrome);color:var(--dim)}.controlhead .unit{font-size:var(--t-label);color:var(--muted)}.numberfield{height:24px;min-width:58px;padding:0 6px;border:1px solid var(--highlight);border-radius:var(--radius-sm);background:var(--paper);color:var(--ink);font:400 var(--t-chrome) var(--font-body);font-variant-numeric:tabular-nums;text-align:center}.numberfield:focus-visible{border-color:var(--accent)}.slider{appearance:none;-webkit-appearance:none;width:100%;height:14px;background:none;margin:2px 0 0;cursor:pointer}.slider::-webkit-slider-runnable-track{height:3px;border-radius:2px;background:var(--highlight)}.slider::-webkit-slider-thumb{-webkit-appearance:none;width:11px;height:11px;margin-top:-4px;border-radius:50%;background:var(--ink);border:none}.slider::-moz-range-track{height:3px;border-radius:2px;background:var(--highlight)}.slider::-moz-range-thumb{width:11px;height:11px;border:none;border-radius:50%;background:var(--ink)}.stops{display:flex;justify-content:space-between;gap:8px;font-size:10px;color:var(--muted);white-space:nowrap}.group{display:flex;flex-direction:column;gap:9px}.grouphead{display:flex;align-items:baseline;justify-content:space-between}.reset{font-size:var(--t-label);color:var(--accent-text)}.reset:disabled{color:var(--muted);cursor:default}.stagelist{display:flex;flex-direction:column;gap:5px;font-variant-numeric:tabular-nums}.stagerow{display:flex;align-items:baseline;gap:8px;font-size:var(--t-label);color:var(--muted)}.stagerow.done{color:var(--dim)}.stagerow .mark{width:10px;text-align:center;color:var(--muted)}.stagerow.done .mark{color:var(--good)}.stagerow .name{flex:1}.sweep{height:2px;background:var(--rule2);border-radius:2px;overflow:hidden}.sweep i{display:block;width:30%;height:100%;background:var(--accent);animation:sweepacross 1.6s var(--ease-out-expo) infinite}@keyframes sweepacross{0%{transform:translate(-100%)}to{transform:translate(340%)}}@media(prefers-reduced-motion:reduce){.sweep i{animation:none;width:100%;opacity:.4}.chip.swapped:after,.art.swapping,.callout,.pulse{animation:none}.dragging .drop{transition:none}}.firstrun{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:34px;background:var(--stage);padding:24px}.drop{width:min(620px,80%);padding:44px 24px;min-height:220px;justify-content:center;border:1.5px dashed var(--highlight);border-radius:var(--radius-xl);display:flex;flex-direction:column;align-items:center;gap:16px;background:color-mix(in srgb,var(--ink) 1.5%,transparent);text-align:center;transition:border-color var(--d-fast),background var(--d-fast)}.drop .glyph{color:var(--muted)}[hidden]{display:none!important}.drop.solid{background:var(--paper);border-style:solid;border-color:var(--rule2)}.panehint{max-width:34ch;text-align:center;font-size:12.5px;line-height:1.55}.drop .headline{font-family:var(--font-display);font-size:26px;letter-spacing:-.02em;display:block}.drop .release,.dragging .drop .resting{display:none}.dragging .drop .release{display:block}.dragging .drop{width:min(640px,90%);min-height:320px;transition:width var(--d-fast) var(--ease-out-expo),min-height var(--d-fast) var(--ease-out-expo),border-color var(--d-fast),background var(--d-fast)}.samples{display:flex;flex-direction:column;align-items:center;gap:12px}.samplerow{display:flex;gap:12px}.sample{width:112px;display:flex;flex-direction:column;gap:7px;align-items:center}.sample .thumb{width:112px;height:88px;border:1px solid var(--rule2);border-radius:10px;background-color:var(--card);background-image:repeating-conic-gradient(var(--checker-a) 0% 25%,var(--checker-b) 0% 50%);background-size:12px 12px;display:grid;place-items:center;overflow:hidden;transition:border-color var(--d-micro),transform var(--d-micro)}.sample:hover .thumb{border-color:var(--accent);transform:translateY(-2px)}.sample img{max-width:76px;max-height:68px}.sample .label{font-size:var(--t-label);color:var(--faint)}.dragging .drop{border-color:var(--accent);background:var(--accent-soft)}.dragging .drop .glyph{color:var(--accent)}.dragging:after{content:"";position:fixed;inset:0;border:2px solid var(--accent);pointer-events:none;z-index:60}.dragging .stage>*:not(.stagestate){opacity:.6}.callout{position:absolute;left:20px;bottom:20px;z-index:8;max-width:320px;display:flex;flex-direction:column;gap:5px;padding:10px 13px;border-radius:var(--radius-lg);border:1px solid var(--rule2);background:color-mix(in srgb,var(--card) 92%,transparent);backdrop-filter:blur(8px);font-size:11.5px;line-height:1.5;color:var(--dim);animation:rise var(--d-fast) var(--ease-out-expo)}.callout .head{display:flex;align-items:baseline;justify-content:space-between;gap:12px}#overlays:empty{display:none}.scrim{position:fixed;inset:0;z-index:40;background:color-mix(in srgb,var(--void) 62%,transparent);display:grid;place-items:center;padding:24px}.modal{width:min(460px,100%);max-height:86vh;overflow-y:auto;background:var(--card);border:1px solid var(--highlight);border-radius:var(--radius-xl);padding:22px;display:flex;flex-direction:column;gap:13px;box-shadow:var(--shadow-xl)}.modal h2{margin:0;font-family:var(--font-display);font-size:22px;font-weight:500;letter-spacing:-.02em}.modal p{margin:0;font-size:var(--t-panel);line-height:1.7;color:var(--dim)}.modal .actions{display:flex;gap:8px;justify-content:flex-end}.modal.composer{width:min(1040px,100%);max-height:92vh;padding:0;gap:0;overflow:hidden}.composerhead{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;padding:18px 22px 15px;border-bottom:1px solid var(--rule2)}.composerhead p{margin-top:3px;font-size:var(--t-chrome)}.composerbody{display:grid;grid-template-columns:minmax(0,1fr) 312px;min-height:0;overflow-y:auto}.composerpreview{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;padding:22px;min-width:0;background:var(--stage)}.cardstage{--ar: 1.7778;position:relative;width:min(100%,calc(58vh * var(--ar)));aspect-ratio:var(--ar);border-radius:var(--radius);overflow:hidden;background:var(--paper);box-shadow:var(--shadow-lg)}.cardstage img{display:block;width:100%;height:100%;object-fit:contain}.cardbusy{position:absolute;inset:0;display:grid;place-items:center;opacity:0;pointer-events:none;background:color-mix(in srgb,var(--paper) 45%,transparent);transition:opacity var(--d-micro)}.cardstage.busy .cardbusy{opacity:1;transition-delay:.26s}.cardspin{width:20px;height:20px;border-radius:50%;border:2px solid var(--highlight);border-top-color:var(--accent);animation:cardspin .8s linear infinite}@keyframes cardspin{to{transform:rotate(360deg)}}.composerside{display:flex;flex-direction:column;gap:18px;padding:20px 22px;border-left:1px solid var(--rule2)}.optgroup{display:flex;flex-direction:column;gap:8px}.cardseg{display:grid;grid-auto-flow:column;grid-auto-columns:1fr;gap:2px;padding:2px;border:1px solid var(--rule2);border-radius:var(--radius);background:var(--paper)}.cardseg button{display:flex;flex-direction:column;align-items:center;gap:1px;padding:6px 8px;border-radius:calc(var(--radius) - 2px);font-size:var(--t-chrome);color:var(--faint)}.cardseg button small{font-size:10px;color:var(--muted);font-variant-numeric:tabular-nums}.cardseg button:hover:not([aria-checked=true]){color:var(--ink)}.cardseg button[aria-checked=true]{color:var(--ink);background:var(--accent-soft);box-shadow:inset 0 0 0 1px var(--accent)}.optlist{display:flex;flex-direction:column;border:1px solid var(--rule2);border-radius:var(--radius);background:var(--paper)}.optlist>*+*{border-top:1px solid var(--rule2)}.optrow{display:flex;align-items:center;gap:12px;width:100%;padding:9px 11px;text-align:left}.optrow:hover{background:var(--rule2)}.optext{display:flex;flex:1;flex-direction:column;gap:1px;min-width:0}.optlabel{font-size:12.5px;color:var(--ink)}.optnote{font-size:11px;line-height:1.4;color:var(--muted)}.optslider{display:flex;flex-direction:column;gap:4px;padding:9px 11px 8px;transition:opacity var(--d-micro)}.optslider.off{opacity:.4}.optslider.off .slider{cursor:default}.optsliderhead{display:flex;justify-content:space-between;font-size:12px;color:var(--dim)}.composeractions{display:flex;flex-direction:column;gap:8px;margin-top:auto}.composeractions .btn{justify-content:center;gap:7px}@media(max-width:860px){.composerbody{grid-template-columns:minmax(0,1fr)}.composerside{border-left:0;border-top:1px solid var(--rule2)}}.popover{position:fixed;z-index:45;background:var(--card);border:1px solid var(--highlight);border-radius:var(--radius-lg);box-shadow:var(--shadow-lg);overflow:hidden}.menu{padding:6px;min-width:260px}.menu .item{display:flex;align-items:center;gap:9px;padding:7px 9px;border-radius:var(--radius);width:100%;text-align:left;font-size:var(--t-chrome);color:var(--dim)}.menu .item:hover{background:var(--surface);color:var(--ink)}.menu .item .when{margin-left:auto;font-size:var(--t-label);color:var(--muted)}.menu .rule{height:1px;background:var(--rule2);margin:5px 0}.sheet{position:absolute;inset:0;z-index:20;background:color-mix(in srgb,var(--void) 55%,transparent);display:flex;align-items:flex-end}.sheetbody{width:100%;max-height:100%;overflow-y:auto;background:var(--card);border-top:1px solid var(--highlight);border-radius:var(--radius-xl) var(--radius-xl) 0 0;padding:14px;display:flex;flex-direction:column;gap:11px;box-shadow:0 -20px 50px color-mix(in srgb,var(--void) 60%,transparent);animation:rise var(--d-fast) var(--ease-out-expo)}@keyframes rise{0%{transform:translateY(12px);opacity:0}}.format{display:flex;align-items:center;gap:9px;padding:7px 9px;border:1px solid var(--rule2);border-radius:var(--radius);background:var(--paper);width:100%;text-align:left;font-size:var(--t-chrome);color:var(--dim)}.format:hover{border-color:var(--highlight)}.format .size{margin-left:auto;font-size:var(--t-label);color:var(--muted);font-variant-numeric:tabular-nums}.tooltip{position:fixed;z-index:70;max-width:300px;background:var(--highlight);border:1px solid color-mix(in srgb,var(--ink) 10%,transparent);border-radius:var(--radius);padding:10px 12px;font-size:var(--t-chrome);line-height:1.6;color:var(--dim);box-shadow:var(--shadow);pointer-events:none}#toasts{position:fixed;left:50%;bottom:40px;transform:translate(-50%);z-index:80;display:flex;flex-direction:column;gap:8px;align-items:center}.toast{width:min(460px,90vw);background:var(--highlight);border:1px solid color-mix(in srgb,var(--ink) 10%,transparent);border-radius:10px;padding:11px 13px;display:flex;align-items:center;gap:11px;box-shadow:var(--shadow-lg);animation:risein var(--d-fast) var(--ease-out-expo)}@keyframes risein{0%{transform:translateY(8px);opacity:0}}.toast .text{flex:1;font-size:var(--t-chrome);color:var(--dim)}.toast .glyph{display:flex}.toast .glyph.good{color:var(--good)}.toast .glyph.bad{color:var(--bad)}.screen{position:absolute;inset:0;z-index:30;background:var(--paper);display:flex;flex-direction:column}.screenbar{height:var(--h-appbar);flex:none;display:flex;align-items:center;gap:12px;padding:0 12px;background:var(--stage);border-bottom:1px solid var(--rule2);-webkit-app-region:drag}.screenbar button{-webkit-app-region:no-drag}.screenbody{flex:1;min-height:0;overflow-y:auto;background:var(--stage)}.settingsgrid{width:min(1128px,100%);margin:0 auto;padding:24px;display:grid;grid-template-columns:repeat(auto-fit,minmax(420px,1fr));gap:16px;align-content:start}.settinggroup{border:1px solid var(--rule2);background:var(--card);border-radius:var(--radius-lg);overflow:hidden;align-self:start}.settinggroup>.head{padding:11px 14px;border-bottom:1px solid var(--rule2)}.settingrow{display:flex;align-items:center;gap:16px;padding:11px 14px;border-bottom:1px solid color-mix(in srgb,var(--ink) 5%,transparent)}.settingrow:last-child{border-bottom:none}.settingrow .about{flex:1;display:flex;flex-direction:column;gap:3px;min-width:0}.settingrow .label{font-size:var(--t-panel);color:var(--dim)}.settingrow .help{font-size:var(--t-label);line-height:1.5;color:var(--muted)}.settingrow .control{flex:none}.settingrow select,.settingrow input[type=text],.settingrow input[type=number]{height:var(--h-compact);padding:0 8px;border:1px solid var(--rule2);border-radius:7px;background:var(--paper);color:var(--ink);font:400 var(--t-chrome) var(--font-body);font-variant-numeric:tabular-nums;max-width:220px}.privacycard{border:1px solid var(--rule2);background:var(--card);border-radius:var(--radius-lg);padding:14px;display:flex;flex-direction:column;gap:8px;align-self:start}.privacycard .line{font-family:var(--font-display);font-size:19px;letter-spacing:-.02em}.about{display:flex;min-height:100%}.aboutleft{width:520px;flex:none;background:var(--stage);border-right:1px solid var(--rule2);display:flex;flex-direction:column;justify-content:center;gap:22px;padding:56px}.aboutleft .mark{color:var(--accent)}.aboutright{flex:1;padding:56px;display:flex;flex-direction:column;gap:20px;justify-content:center;background:var(--paper)}.credit{border:1px solid var(--highlight);border-radius:var(--radius-lg);padding:20px;display:flex;flex-direction:column;gap:10px;background:var(--gold-soft)}.notices{border:1px solid var(--rule2);border-radius:var(--radius-lg);overflow:hidden}.notices .head{padding:11px 14px;border-bottom:1px solid var(--rule2);font-size:var(--t-chrome);color:var(--dim);display:flex;justify-content:space-between}.notices pre{margin:0;height:220px;overflow:auto;padding:12px 14px;font:11.5px / 1.75 var(--font-mono);color:var(--muted);background:var(--stage);white-space:pre-wrap;user-select:text}.minifyrail{width:400px;flex:none;border-left:1px solid var(--rule2);background:var(--paper);display:flex;flex-direction:column}.minifyresult{display:flex;flex-direction:column;gap:5px}.minifyresult .figure{font-family:var(--font-display);font-size:66px;line-height:.85;letter-spacing:-.04em;color:var(--accent-text)}.sentence{font-size:var(--t-panel);line-height:2;color:var(--dim)}.sentence .inlinefield{display:inline-flex;align-items:center;height:24px;padding:0 4px;border:1px solid var(--rule2);border-radius:var(--radius-sm);background:var(--paper);color:var(--ink);font:400 var(--t-chrome) var(--font-body);font-variant-numeric:tabular-nums;width:58px;text-align:center}.sentence .inlinefield.accent{border-color:var(--accent);background:var(--accent-soft)}.batchbar{height:44px;flex:none;display:flex;align-items:center;gap:8px;padding:0 14px;border-bottom:1px solid var(--rule2);background:var(--stage)}.batchhead,.batchrow{display:grid;grid-template-columns:24px minmax(0,1.4fr) 120px 104px 84px 104px 96px minmax(0,1.2fr);align-items:center;gap:12px;padding:0 14px}.batchhead{height:34px;flex:none;border-bottom:1px solid var(--rule2)}.batchrow{height:var(--h-row);border-bottom:1px solid color-mix(in srgb,var(--ink) 4%,transparent);font-size:var(--t-chrome);font-variant-numeric:tabular-nums}.batchrow.failed{background:color-mix(in srgb,var(--bad) 8%,transparent)}.batchrow.running{background:var(--accent-subtle)}.batchrow .trunc,.batchhead .trunc{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.batchrow .right,.batchhead .right{text-align:right}.batchrow .state{display:flex;align-items:center;gap:6px}.batchrow .state i{width:6px;height:6px;border-radius:50%;background:currentColor;display:block;flex:none}.state.queued,.state.skipped{color:var(--muted)}.state.running{color:var(--accent-text)}.state.done{color:var(--good)}.state.failed{color:var(--bad)}.batchviewport{flex:1;min-height:0;overflow-y:auto;position:relative}.batchfoot{height:52px;flex:none;display:flex;align-items:center;gap:18px;padding:0 14px;border-top:1px solid var(--rule2);background:var(--stage);font-size:var(--t-chrome);font-variant-numeric:tabular-nums}.progress{height:3px;border-radius:2px;background:var(--highlight);overflow:hidden}.progress i{display:block;height:100%;background:var(--accent);transition:width var(--d-fast) var(--ease-smooth)}@media(max-width:1400px){:root{--rail-w: 330px}}@media(max-width:1180px){:root{--rail-w: 310px}.legend{display:none}}.chooser{position:absolute;left:50%;bottom:16px;transform:translate(-50%);z-index:8;width:min(560px,calc(100% - 32px));max-height:calc(100% - 70px);overflow-y:auto;display:flex;flex-direction:column;gap:9px;padding:10px 12px 11px;background:var(--card);border:1px solid var(--highlight);border-radius:var(--radius-xl);box-shadow:var(--shadow-lg);animation:rise var(--d-fast) var(--ease-out-expo)}.choosehead{display:flex;align-items:center;justify-content:space-between;gap:10px;min-width:0}.choosehead .eyebrow{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.chooseintro{font-size:var(--t-label);line-height:1.5;color:var(--dim);padding:7px 10px;border-radius:var(--radius);background:var(--accent-subtle);border:1px solid var(--accent-soft)}.choices{display:flex;flex-direction:column;gap:6px}.choice{display:grid;grid-template-columns:92px 1fr auto;align-items:center;gap:12px;padding:8px 8px 8px 12px;border:1px solid var(--rule2);border-radius:var(--radius-lg);background:var(--paper);min-width:0}.choice .btn{min-width:104px}.choicename{display:flex;align-items:center;gap:7px;font-family:var(--font-display);font-size:17px;letter-spacing:-.02em;color:var(--ink)}.choicename svg{color:var(--accent-text)}.choicebody{font-size:var(--t-chrome);line-height:1.5;color:var(--faint)}.choosenews{display:flex;flex-direction:column;gap:6px;padding-top:9px;border-top:1px solid var(--rule2)}.remember{display:flex;align-items:center;gap:7px;font-size:var(--t-label);color:var(--faint);cursor:pointer;align-self:flex-start}.remember input{accent-color:var(--accent);margin:0}.autochose{display:flex;flex-direction:column;gap:7px;font-size:var(--t-chrome)}.autolead{color:var(--dim);line-height:1.5}.autorow{display:flex;align-items:center;gap:10px;justify-content:space-between}.autorow.moved{padding-top:7px;border-top:1px dashed var(--rule2)}.autotext{display:flex;flex-direction:column;gap:2px;min-width:0}.autotitle{color:var(--ink)}.autotext .muted{font-size:var(--t-label)}.autobtns{display:flex;align-items:center;gap:10px;flex:none}.autorow .btn{flex:none}.autotaken{font-size:var(--t-label);color:var(--state-final);flex:none}.card.autocard{border-color:var(--accent-soft);background:color-mix(in srgb,var(--accent) 4%,var(--card))}.guideline{display:flex;align-items:baseline;justify-content:space-between;gap:10px;font-size:var(--t-label)}.rail.wizarding{width:max(var(--rail-w),400px)}.sheet.wizsheet{align-items:stretch;background:var(--paper)}.sheetbody.wizbody{height:100%;border-radius:0;border-top:none;box-shadow:none;padding:0;gap:0;overflow:hidden;background:var(--paper)}.wizhead{flex:none;display:flex;flex-direction:column;gap:9px;padding:14px 16px 12px;border-bottom:1px solid var(--rule2)}.wiztitle{font-size:24px;font-weight:500;line-height:1.1;color:var(--ink)}.wizdots{display:flex;gap:6px}.wizdots button{flex:1;height:4px;border-radius:2px;background:var(--highlight);transition:background var(--d-micro)}.wizdots button.done{background:color-mix(in srgb,var(--accent) 45%,var(--highlight))}.wizdots button[aria-selected=true]{background:var(--accent)}.wizdots button:hover{background:var(--accent-hover)}.wizcompare{display:flex;align-items:center;gap:8px;font-size:var(--t-label)}.wizcompare .seg>button{padding:3px 9px}.wizscroll{flex:1;min-height:0;overflow-y:auto;padding:14px 16px 18px;display:flex;flex-direction:column;gap:14px}.wizlead{font-size:var(--t-panel);line-height:1.55;color:var(--dim)}.wizguide{display:flex;flex-direction:column;gap:8px;padding-top:12px;border-top:1px solid var(--rule2)}.wizexplain{display:grid;grid-template-columns:auto 1fr;gap:5px 12px;margin:0;font-size:var(--t-chrome);line-height:1.5}.wizexplain dt{font-size:var(--t-eyebrow);letter-spacing:.12em;text-transform:uppercase;color:var(--muted);padding-top:3px;white-space:nowrap}.wizexplain dd{margin:0;color:var(--dim)}.wizlearn{font-size:var(--t-label);color:var(--accent-text);align-self:flex-start;text-decoration:none}.wizlearn:hover{text-decoration:underline}.wizcontrols{display:flex;flex-direction:column;gap:12px;padding:12px;border:1px solid var(--rule2);border-radius:var(--radius-lg);background:var(--card)}.wizlive{display:flex;flex-direction:column;gap:12px}.wizrow{display:flex;align-items:center;justify-content:space-between;gap:10px;font-size:var(--t-chrome)}.wizsmall{font-size:var(--t-label);line-height:1.5}.wizwhy{font-size:var(--t-chrome);line-height:1.5;color:var(--dim)}.wizchoices{display:grid;grid-template-columns:1fr 1fr;gap:8px}.wizchoice{display:flex;flex-direction:column;align-items:flex-start;gap:4px;padding:10px 11px;text-align:left;border:1px solid var(--rule2);border-radius:var(--radius);background:var(--paper);font-size:var(--t-label);line-height:1.45}.wizchoice .name{font-size:13px;font-weight:600;color:var(--ink)}.wizchoice:hover{border-color:var(--highlight)}.wizchoice[aria-pressed=true]{border-color:var(--accent);background:var(--accent-subtle)}.wiznote{display:flex;flex-direction:column;align-items:flex-start;gap:8px;padding:10px 11px;border-radius:var(--radius);background:var(--accent-subtle);font-size:var(--t-label);line-height:1.5;color:var(--dim)}.wiznav{flex:none;display:flex;align-items:center;gap:12px;padding:12px 16px;border-top:1px solid var(--rule2);background:var(--paper)}.kinds{display:grid;grid-template-columns:1fr 1fr;gap:9px}.kind{display:flex;flex-direction:column;align-items:stretch;gap:4px;padding:7px 7px 9px;text-align:left;border:1px solid var(--rule2);border-radius:var(--radius-lg);background:var(--card);min-width:0;transition:border-color var(--d-micro),background var(--d-micro)}.kind:hover{border-color:var(--highlight)}.kind[aria-checked=true]{border-color:var(--accent);background:color-mix(in srgb,var(--accent) 7%,var(--card));box-shadow:0 0 0 1px var(--accent)}.kthumb{position:relative;height:104px;border-radius:var(--radius);background-color:var(--stage);background-image:repeating-conic-gradient(var(--checker-a) 0% 25%,var(--checker-b) 0% 50%);background-size:10px 10px;display:grid;place-items:center;overflow:hidden;margin-bottom:3px}.kthumb img{max-width:92%;max-height:92%;object-fit:contain;image-rendering:auto}.kspin{position:absolute;inset:auto 0 0;height:2px;overflow:hidden;display:none}.kind.busy .kspin{display:block}.kspin:after{content:"";position:absolute;top:0;bottom:0;left:0;width:30%;background:var(--accent);animation:sweepacross 1.6s var(--ease-out-expo) infinite}@media(prefers-reduced-motion:reduce){.kspin:after{animation:none;width:100%;opacity:.5}}.kname{display:flex;align-items:baseline;justify-content:space-between;gap:6px;font-size:12.5px;font-weight:600;color:var(--ink);padding:0 2px}.ksugg{font-size:9.5px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:var(--accent-text)}.kwhy,.knote,.kstat{font-size:11px;line-height:1.4;padding:0 2px}.kwhy{color:var(--faint)}.knote{color:var(--muted);font-style:italic}.kstat{color:var(--muted);margin-top:2px}.wizreadout table{width:100%;border-collapse:collapse;font-size:var(--t-chrome)}.wizreadout th,.wizreadout td{padding:4px 0;text-align:right;font-weight:400}.wizreadout thead th{font-size:var(--t-eyebrow);letter-spacing:.12em;text-transform:uppercase;color:var(--muted)}.wizreadout tbody th{text-align:left;color:var(--faint)}.wizreadout td{color:var(--ink);padding-left:10px}.wizreadout td.delta{width:70px}.wizreadout td.delta.same{color:var(--muted)}.wizchanges .wizchange{display:flex;justify-content:space-between;gap:10px;font-size:var(--t-chrome);color:var(--faint)}.wizchanges .wizchange .num{color:var(--ink)}.pane.source.comparing .panelabel{color:var(--accent-text)}.intro{width:min(620px,80%);display:flex;flex-direction:column;gap:12px;padding:16px 18px;border:1px solid var(--accent-soft);border-radius:var(--radius-xl);background:color-mix(in srgb,var(--accent) 5%,var(--paper))}.intro ol{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:9px}.intro li{display:flex;gap:11px;align-items:baseline;font-size:var(--t-chrome);line-height:1.5}.intro li>div{display:flex;flex-direction:column}.introstep{width:20px;height:20px;flex:none;border-radius:50%;display:grid;place-items:center;font-size:11px;font-weight:600;background:var(--accent);color:var(--accent-ink)}.introtitle{color:var(--ink);font-weight:500}.introfoot{display:flex;align-items:center;justify-content:space-between;gap:12px;font-size:var(--t-label)}html.framed:not(.fullscreen) body{padding-top:6px;background:var(--void)}html.framed:not(.fullscreen) #app{position:relative;border-top:1px solid rgba(201,117,74,.35)}.desktopnote{margin:14px 0 0;font-size:12.5px;text-align:center}.webcontrols{display:flex;gap:2px;margin-left:4px}.webcontrols .btn{gap:6px}.phonenote{position:fixed;inset:0;z-index:60;display:grid;place-items:center;padding:16px;background:color-mix(in srgb,var(--void) 82%,transparent)}.phonecard{max-width:420px;display:flex;flex-direction:column;gap:12px;padding:22px;border-radius:var(--radius-lg, 12px);background:var(--card);box-shadow:var(--shadow-lg)}.phonecard p{margin:0;font-size:13.5px;line-height:1.65;color:var(--dim)}.sc-loading{padding:40px;text-align:center}.sc-page{width:min(1320px,100%);margin:0 auto;padding:24px;display:flex;flex-direction:column;gap:18px}.sc-head h1{margin:0 0 6px;font-size:28px;font-weight:600}.sc-head p,.sc-results>p{margin:0;max-width:96ch;font-size:13px;line-height:1.6}.sc-main{display:grid;grid-template-columns:300px 1fr;gap:16px;min-height:0;align-items:start}.sc-list{height:560px;overflow-y:auto;overscroll-behavior:contain;padding:0 10px 10px;border:1px solid var(--rule2);border-radius:var(--radius-lg);background:var(--card);scrollbar-width:thin}.sc-sethead{position:sticky;top:0;z-index:1;display:flex;justify-content:space-between;padding:10px 2px 8px;background:var(--card);font-size:11px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:var(--dim)}.sc-sethead span{color:var(--faint);font-weight:600}.sc-tiles{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:6px;margin-bottom:6px}.sc-case{display:flex;flex-direction:column;align-items:stretch;gap:4px;min-width:0;overflow:hidden;padding:5px;border:1px solid var(--rule2);border-radius:var(--radius-sm);background:var(--stage);color:var(--dim);cursor:pointer;text-align:left}.sc-case[aria-pressed=true]{border-color:var(--accent);background:var(--accent-subtle);color:var(--accent)}.sc-thumb{aspect-ratio:1;width:100%;border-radius:5px;background:#fff;display:grid;place-items:center;overflow:hidden}.sc-thumb img{width:100%;height:100%;object-fit:contain}.sc-name{width:100%;font-size:11px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.sc-caption{margin:0;font-size:12.5px;color:var(--dim)}.sc-stagecol{display:flex;flex-direction:column;gap:10px;min-width:0}.sc-tools{display:flex;align-items:center;gap:8px;flex-wrap:nowrap;white-space:nowrap}.sc-tools .eyebrow{margin-left:6px}.sc-legend{margin-right:4px;display:inline-flex;align-items:center;gap:6px;font-size:11.5px}.sc-dot{display:inline-block;width:8px;height:8px;border-radius:50%}.sc-dot.a{background:var(--anchor)}.sc-dot.hd{background:var(--handle);margin-left:6px}.sc-viewer{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}.sc-pane{position:relative;aspect-ratio:1;overflow:hidden;border:1px solid var(--rule2);border-radius:var(--radius-sm);background-color:var(--stage);background-image:repeating-conic-gradient(var(--checker-a) 0% 25%,var(--checker-b) 0% 50%);background-size:16px 16px;cursor:grab;touch-action:none;user-select:none}.sc-pane:active{cursor:grabbing}.sc-cam{position:absolute;left:0;top:0;width:100%;height:100%;transform-origin:0 0}.sc-cam>img,.sc-layer,.sc-layer>svg{position:absolute;inset:0;width:100%;height:100%;display:block}.sc-cam>img{object-fit:contain}.sc-tag{position:absolute;top:8px;left:8px;padding:4px 7px;border-radius:6px;font-size:11px;font-weight:600;background:color-mix(in srgb,var(--paper) 85%,transparent);color:var(--ink);pointer-events:none}.sc-wire path,.sc-wire circle,.sc-wire ellipse,.sc-wire rect,.sc-wire polygon{fill:none!important;stroke:var(--wire);stroke-width:1px;vector-effect:non-scaling-stroke;stroke-opacity:.9}.sc-ov{pointer-events:none}.sc-ov .h{stroke:var(--handle);stroke-width:1px;vector-effect:non-scaling-stroke;fill:none;stroke-opacity:.85}.sc-ov .hd{stroke:var(--handle);stroke-width:3.5px;vector-effect:non-scaling-stroke;stroke-linecap:round;fill:none}.sc-ov .a{stroke:var(--anchor);stroke-width:5px;vector-effect:non-scaling-stroke;stroke-linecap:round;fill:none}.sc-chips{display:flex;flex-wrap:wrap;gap:6px}.sc-chip{font-size:12px;padding:4px 9px;border-radius:999px;border:1px solid var(--rule2);color:var(--dim);font-variant-numeric:tabular-nums}.sc-chip.win{border-color:transparent;background:var(--goodbg);color:var(--good)}.sc-results{display:flex;flex-direction:column;gap:12px;padding-top:8px;border-top:1px solid var(--rule2)}.sc-results h2{margin:8px 0 0;font-size:22px;font-weight:600}.sc-bignums{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}.sc-bignum{display:flex;flex-direction:column;gap:4px;padding:12px 14px;border:1px solid var(--rule2);border-radius:var(--radius-lg);background:var(--card);font-size:12px}.sc-bignum .v{font-size:28px;color:var(--accent);line-height:1.1}.sc-table{width:100%;border-collapse:collapse;font-size:12.5px;background:var(--card);border:1px solid var(--rule2);border-radius:var(--radius-lg);overflow:hidden}.sc-table th,.sc-table td{padding:8px 12px;text-align:right;border-bottom:1px solid var(--rule2);white-space:nowrap}.sc-table th:first-child,.sc-table td:first-child{text-align:left}.sc-table th{font-size:11px;font-weight:600;color:var(--faint);text-transform:uppercase;letter-spacing:.04em}.sc-table td.num{font-variant-numeric:tabular-nums;color:var(--dim)}.sc-table tr.win td{color:var(--ink)}.sc-table tr.win td:first-child{color:var(--accent);font-weight:600}.sc-note{font-size:12px!important}.sc-table caption{text-align:left;padding:10px 12px 4px;font-size:11px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--faint)}
studio/assets/main-Dn0s9OJH.js ADDED
The diff for this file is too large to render. See raw diff
 
studio/assets/mark-DZ7OzD-p.js DELETED
@@ -1,3 +0,0 @@
1
- (function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const r of document.querySelectorAll('link[rel="modulepreload"]'))n(r);new MutationObserver(r=>{for(const o of r)if(o.type==="childList")for(const i of o.addedNodes)i.tagName==="LINK"&&i.rel==="modulepreload"&&n(i)}).observe(document,{childList:!0,subtree:!0});function s(r){const o={};return r.integrity&&(o.integrity=r.integrity),r.referrerPolicy&&(o.referrerPolicy=r.referrerPolicy),r.crossOrigin==="use-credentials"?o.credentials="include":r.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function n(r){if(r.ep)return;r.ep=!0;const o=s(r);fetch(r.href,o)}})();function c(e,t=!1){return window.__TAURI_INTERNALS__.transformCallback(e,t)}async function a(e,t={},s){return window.__TAURI_INTERNALS__.invoke(e,t,s)}function d(){return!!(globalThis||window).isTauri}var u;(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"})(u||(u={}));async function l(e,t){window.__TAURI_EVENT_PLUGIN_INTERNALS__.unregisterListener(e,t),await a("plugin:event|unlisten",{event:e,eventId:t})}async function f(e,t,s){var n;const r=(n=void 0)!==null&&n!==void 0?n:{kind:"Any"};return a("plugin:event|listen",{event:e,target:r,handler:c(t)}).then(o=>async()=>l(e,o))}async function _(e,t){await a("plugin:event|emit",{event:e,payload:t})}const p=`<!-- Generated by studio/tools/make_assets.py from web/logo.svg. Do not edit. -->
2
- <svg color="#c9754a" xmlns="http://www.w3.org/2000/svg" viewBox="254.5 140.5 514.25 742.25"><path d="M511.3 140.6c-23.3 79.8-103 178.5-144.9 232.75L335.45 415.4a467.73 467.73 0 00-64.8 115.4c-65.5 193.1 80.8 351.3 237.85 351.7 21.26 1.1 42.56-1.3 63.06-7 90.8-18.87 211.87-120.04 195.47-280.9-17.9-164.8-187.55-272.65-251.8-443.84Zm10.2 374.9a61.05 61.05 0 00-67.1 81.6 61.05 61.05 0 00104.3 17.26 61.05 61.05 0 00-37.2-98.86ZM511.4 817.65l177.16-201.3-43-.15a44.56 44.56 0 00-36.2 18.47l-97.8 111.67-102.2-116.67c-16.06-18.94-38.06-12-74.9-13.4 5.7 7.3 159.75 179.73 176.93 201.4Z" fill="currentColor" fill-rule="evenodd"/></svg>
3
- `;export{d as a,_ as e,a as i,f as l,p as m};
 
 
 
 
studio/assets/mark-DxrDvgRD.css DELETED
@@ -1 +0,0 @@
1
- @font-face{font-family:Inter Studio;src:url(../fonts/inter-latin.woff2) format("woff2");font-weight:100 900;font-style:normal;font-display:swap;unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Playfair Studio;src:url(../fonts/playfair-latin.woff2) format("woff2");font-weight:400 900;font-style:normal;font-display:swap;unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}:root,:root[data-theme=dark]{color-scheme:dark;--void: #0c0a09;--stage: #141210;--paper: #1a1816;--card: #211f1c;--surface: #2a2724;--highlight: #35322e;--ink: #faf8f5;--dim: rgba(250, 248, 245, .75);--faint: rgba(250, 248, 245, .55);--muted: rgba(250, 248, 245, .4);--rule: rgba(250, 248, 245, .08);--rule2: #2a2724;--accent: #c9754a;--accent-text: #d4896a;--accent-hover: #d4896a;--accent-ink: #1a1816;--accent-soft: rgba(201, 117, 74, .12);--accent-subtle: rgba(201, 117, 74, .08);--gold: #b8976c;--gold-soft: rgba(184, 151, 108, .15);--good: #34d399;--goodbg: rgba(52, 211, 153, .15);--bad: #f87171;--badbg: rgba(248, 113, 113, .15);--anchor: #f87171;--handle: #b8976c;--wire: #faf8f5;--vt: #9a938c;--checker-a: #191715;--checker-b: #141210;--focus: rgba(201, 117, 74, .35);--focus-line: #c9754a;--state-draft: #b8976c;--state-final: #34d399;--state-stale: #c9754a;--shadow-sm: 0 4px 12px -2px rgba(12, 10, 9, .4);--shadow: 0 8px 24px -4px rgba(12, 10, 9, .5);--shadow-lg: 0 20px 50px -12px rgba(12, 10, 9, .6);--shadow-xl: 0 30px 70px -20px rgba(12, 10, 9, .7);--glow: 0 6px 20px rgba(201, 117, 74, .25)}:root[data-theme=light]{color-scheme:light;--void: #e6e1d9;--stage: #f0ece6;--paper: #faf8f5;--card: #ffffff;--surface: #eae5dd;--highlight: #ded8ce;--ink: #1a1816;--dim: rgba(26, 24, 22, .75);--faint: rgba(26, 24, 22, .58);--muted: rgba(26, 24, 22, .46);--rule: rgba(26, 24, 22, .1);--rule2: #d8d2c8;--accent: #c9754a;--accent-text: #9c5730;--accent-hover: #b3603a;--accent-ink: #faf8f5;--accent-soft: rgba(201, 117, 74, .14);--accent-subtle: rgba(201, 117, 74, .08);--gold: #8a6a3f;--gold-soft: rgba(138, 106, 63, .14);--good: #1c7a55;--goodbg: rgba(28, 122, 85, .14);--bad: #b83232;--badbg: rgba(184, 50, 50, .13);--anchor: #b83232;--handle: #8a6a3f;--wire: #1a1816;--vt: #6f6862;--checker-a: #ffffff;--checker-b: #e8e3db;--focus: rgba(201, 117, 74, .45);--focus-line: #9c5730;--state-draft: #8a6a3f;--state-final: #1c7a55;--state-stale: #9c5730;--shadow-sm: 0 4px 12px -2px rgba(90, 76, 60, .12);--shadow: 0 8px 24px -4px rgba(90, 76, 60, .16);--shadow-lg: 0 20px 50px -12px rgba(90, 76, 60, .2);--shadow-xl: 0 30px 70px -20px rgba(90, 76, 60, .24);--glow: 0 6px 20px rgba(201, 117, 74, .28)}:root{--font-display: "Playfair Studio", "Playfair Display", Georgia, serif;--font-body: "Inter Studio", Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;--font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;--h-compact: 28px;--h-control: 32px;--h-pill: 36px;--h-appbar: 46px;--h-status: 26px;--h-row: 30px;--t-chrome: 12.5px;--t-panel: 13px;--t-label: 11.5px;--t-eyebrow: 10px;--s1: 4px;--s2: 8px;--s3: 12px;--s4: 16px;--s5: 24px;--s6: 32px;--rail-pad: 14px;--rail-w: 352px;--radius-sm: 6px;--radius: 8px;--radius-lg: 12px;--radius-xl: 16px;--radius-full: 9999px;--ease-out-expo: cubic-bezier(.16, 1, .3, 1);--ease-smooth: cubic-bezier(.4, 0, .2, 1);--d-micro: .15s;--d-fast: .2s;--d-normal: .4s}@media(prefers-reduced-motion:reduce){:root{--d-micro: 0ms;--d-fast: 0ms;--d-normal: 0ms}}
 
 
studio/assets/splash-DU7ZsOCg.js DELETED
@@ -1 +0,0 @@
1
- import{l as f,m as y,e as v}from"./mark-DZ7OzD-p.js";const h="http://www.w3.org/2000/svg",r=document.getElementById("splash"),S=document.getElementById("bar"),b=document.getElementById("status");function x(){const t=new DOMParser().parseFromString(y,"image/svg+xml").documentElement,e=t.querySelector("path"),n=document.getElementById("mark");if(!e||!n)return;const o=document.createElementNS(h,"svg");o.setAttribute("viewBox",t.getAttribute("viewBox")??"0 0 1 1");for(const s of["outline","body"]){const i=document.createElementNS(h,"path");i.setAttribute("d",e.getAttribute("d")??""),i.setAttribute("class",s),s==="outline"&&i.setAttribute("pathLength","1"),o.append(i)}n.append(o)}function P(){const t=navigator.userAgent;return/Windows/i.test(t)?"Windows":/Mac/i.test(t)?"macOS":"Linux"}function c(t){b.textContent=t.text,S.style.width=`${Math.round(Math.min(1,Math.max(0,t.progress))*100)}%`}const d=window.parent!==window;async function E(){if(x(),document.getElementById("version").textContent=d?"Version 0.2.0 · in your browser":`Version 0.2.0 · ${P()}`,d){const t=document.querySelector(".edition");t&&(t.textContent="Studio Lite"),r.setAttribute("aria-label","Inkvec Studio Lite is starting"),window.addEventListener("message",e=>{if(e.origin!==window.location.origin)return;const n=e.data;n?.type==="splash-status"&&c({text:n.text??"",progress:n.progress??0}),n?.type==="splash-done"&&r.classList.add("done","leaving")})}await Promise.race([Promise.all([document.fonts.load('500 68px "Playfair Studio"'),document.fonts.load('400 12px "Inter Studio"')]),new Promise(t=>setTimeout(t,900))]).catch(()=>{}),c({text:"Starting the engine…",progress:.12}),document.body.classList.add("go"),A();try{await f("splash-status",t=>c(t.payload)),await f("splash-done",()=>{r.classList.add("done","leaving")})}catch{}}async function A(){await new Promise(e=>requestAnimationFrame(()=>e(void 0)));const t=I();if(t&&await Promise.race([new Promise(e=>{t.el.addEventListener("animationend",n=>{n.animationName===t.name&&e()})}),new Promise(e=>setTimeout(e,1e4))]),await new Promise(e=>setTimeout(e,450)),d){window.parent.postMessage({type:"splash-animation-done"},window.location.origin);return}try{await v("splash-animation-done")}catch{}}function I(){const t=o=>o.trim().endsWith("ms")?parseFloat(o)/1e3:parseFloat(o),e=o=>o.split(",").map(s=>s.trim());let n=null;for(const o of document.querySelectorAll("*")){const s=getComputedStyle(o),i=e(s.animationName),m=e(s.animationDuration),u=e(s.animationDelay),l=e(s.animationIterationCount);i.forEach((g,a)=>{const p=l[a%l.length];if(g==="none"||p==="infinite")return;const w=t(u[a%u.length])+t(m[a%m.length])*Number(p);(!n||w>n.end)&&(n={el:o,name:g,end:w})})}return n}E();
 
 
studio/assets/splash-Qg7cNZqj.css DELETED
@@ -1 +0,0 @@
1
- *{box-sizing:border-box;margin:0}html,body{height:100%;overflow:hidden;background:#1a1816;color:#faf8f5;font-family:var(--font-body, "Inter Studio", Inter, system-ui, sans-serif);-webkit-font-smoothing:antialiased;user-select:none;cursor:default}.splash *{pointer-events:none}.splash{position:fixed;inset:0;display:grid;grid-template-rows:1fr auto;padding:34px 40px 20px;overflow:hidden;background:#1a1816;box-shadow:inset 0 0 0 1px #faf8f517;transition:opacity .26s ease}.splash.leaving{opacity:0}.grid{position:absolute;inset:0;background-image:linear-gradient(to right,rgba(250,248,245,.045) 1px,transparent 1px),linear-gradient(to bottom,rgba(250,248,245,.045) 1px,transparent 1px);background-size:14px 14px;-webkit-mask-image:radial-gradient(ellipse 60% 75% at 16% 46%,#000 0%,transparent 72%);mask-image:radial-gradient(ellipse 60% 75% at 16% 46%,#000 0%,transparent 72%);opacity:0;animation:fadein 1.2s ease .1s forwards}.glow{position:absolute;inset:0;background:radial-gradient(circle at 12% -8%,rgba(201,117,74,.22),transparent 55%)}.stage{position:relative;display:flex;align-items:center;gap:42px;min-height:0}.mark{flex:none;height:212px;filter:drop-shadow(0 14px 30px rgba(201,117,74,.22))}.mark svg{display:block;height:100%;width:auto;overflow:visible}.mark .outline,.mark .body{fill-rule:evenodd}.mark .outline{fill:none;stroke:#c9754a;stroke-width:5;stroke-linejoin:round;stroke-dasharray:1;stroke-dashoffset:1}.mark .body{fill:#c9754a;opacity:0}.go .mark .outline{animation:draw 1.55s cubic-bezier(.6,0,.2,1) .15s forwards,fadeout .5s ease 1.55s forwards}.go .mark .body{animation:fadein .7s ease 1.3s forwards}.titles{display:flex;flex-direction:column;align-items:flex-start}.titles>*{opacity:0;transform:translateY(8px)}.go .titles>*{animation:rise .7s cubic-bezier(.2,.7,.2,1) forwards;animation-delay:var(--d, .4s)}.eyebrow.by{--d: .35s;font-size:10.5px;font-weight:500;letter-spacing:.22em;text-transform:uppercase;color:#faf8f580}h1{--d: .5s;margin-top:8px;font-family:var(--font-display, "Playfair Studio", Georgia, serif);font-size:68px;font-weight:500;line-height:1;letter-spacing:-.025em}.edition{--d: .65s;margin-top:10px;font-size:12px;font-weight:600;letter-spacing:.3em;text-transform:uppercase;color:#c9754a}.tag{--d: .8s;margin-top:20px;font-size:14px;color:#faf8f59e}.foot{position:relative;display:flex;flex-direction:column;gap:9px}.progress{height:2px;border-radius:1px;background:#faf8f51a;overflow:hidden}.progress i{position:relative;display:block;height:100%;width:0;background:#c9754a;transition:width .55s cubic-bezier(.3,.7,.2,1)}.progress i:after{content:"";position:absolute;inset:0;background:linear-gradient(90deg,transparent,rgba(255,255,255,.55),transparent);transform:translate(-100%);animation:sweep 1.4s ease-in-out infinite}.splash.done .progress i:after{animation:none;opacity:0}.row{display:flex;justify-content:space-between;gap:16px;font-size:12px;color:#faf8f5b8}.version{font-variant-numeric:tabular-nums;color:#faf8f580}.fine{display:flex;justify-content:space-between;gap:16px;font-size:10px;color:#faf8f557}@keyframes draw{to{stroke-dashoffset:0}}@keyframes fadeout{to{opacity:0}}@keyframes fadein{to{opacity:1}}@keyframes rise{to{opacity:1;transform:none}}@keyframes sweep{to{transform:translate(100%)}}@media(prefers-reduced-motion:reduce){.grid,.mark .body,.titles>*{animation:none!important;opacity:1;transform:none}.mark .outline,.progress i:after{animation:none!important;opacity:0}.progress i{transition:none}}
 
 
studio/denoise.js CHANGED
@@ -15,9 +15,13 @@
15
  // (`denoiser_input` / `take_denoiser_output`). This module's whole job is to turn one
16
  // Float32Array into another.
17
  //
18
- // Two things are fetched the first time the denoiser runs, and cached in the browser
19
- // afterwards: ONNX Runtime Web (~28 MB with its WebGPU kernels) and the weights (~80 MB).
20
- // The image is not one of them — it never leaves the machine, the same as the tracer.
 
 
 
 
21
 
22
  // Pinned, both of them. A runtime that silently moved under the page would be a second,
23
  // untested set of kernels for a network whose agreement with the native one is a measured
@@ -26,6 +30,14 @@
26
  const ORT_VERSION = "1.30.0";
27
  const ORT_BASE = `https://cdn.jsdelivr.net/npm/onnxruntime-web@${ORT_VERSION}/dist/`;
28
  const ORT_ENTRY = `${ORT_BASE}ort.webgpu.min.mjs`;
 
 
 
 
 
 
 
 
29
 
30
  // Where a verified copy of the weights lives between visits. Bump the suffix if the model
31
  // the page asks for ever changes, so an old entry cannot be served for a new URL.
@@ -49,12 +61,14 @@ export function activeBackend() {
49
 
50
  async function ort() {
51
  if (!ortPromise) {
52
- ortPromise = import(/* @vite-ignore */ ORT_ENTRY).then((mod) => {
 
53
  const ort = mod.default ?? mod;
54
- // The WebGPU build still loads WebAssembly: the GPU kernels live in a wasm module
55
- // (`ort-wasm-simd-threaded.jsep.wasm`) and everything WebGPU has no kernel for falls
56
- // back to CPU inside the same session.
57
  ort.env.wasm.wasmPaths = ORT_BASE;
 
58
  // Threads need `SharedArrayBuffer`, which needs the cross-origin isolation the Space
59
  // asks for in its README. Where it is not granted this is one thread, and the CPU
60
  // fallback is correspondingly slower — the same trade the tracer makes next door.
@@ -63,7 +77,7 @@ async function ort() {
63
  : 1;
64
  ort.env.logLevel = "error";
65
  return ort;
66
- });
67
  ortPromise.catch(() => { ortPromise = null; });
68
  }
69
  return ortPromise;
@@ -72,61 +86,196 @@ async function ort() {
72
  const hex = (buf) =>
73
  [...new Uint8Array(buf)].map((b) => b.toString(16).padStart(2, "0")).join("");
74
 
75
- /**
76
- * The weights, from the cache if they are there and from Hugging Face if they are not,
77
- * verified either way against the SHA-256 the tracer reports.
78
- *
79
- * Verified either way on purpose: `inkvec_restore::pull_onnx_weights` re-checks a file it
80
- * finds already present rather than trusting it, because a half-written or tampered copy
81
- * would otherwise fail much later, deep inside the runtime, with an unrelated message. The
82
- * hash costs about a tenth of a second against the minute the download costs once.
83
- */
84
- async function weights(url, sha256, onProgress) {
85
- let cache = null;
86
  try {
87
- cache = await caches.open(CACHE);
88
  } catch (e) {
89
  // Private windows and pages with site data blocked have no Cache Storage. Downloading
90
  // every time is worse than caching, and better than not working.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  }
 
 
 
 
 
 
92
 
93
- let bytes = null;
94
- const hit = cache && (await cache.match(url));
95
- if (hit) {
96
- bytes = new Uint8Array(await hit.arrayBuffer());
97
- onProgress?.({ received: bytes.length, total: bytes.length, cached: true });
98
- } else {
99
- const res = await fetch(url);
100
- if (!res.ok) throw new Error(`the denoiser weights could not be fetched: HTTP ${res.status}`);
101
- const total = Number(res.headers.get("content-length")) || 0;
102
- const chunks = [];
 
 
 
 
 
 
 
 
 
 
 
103
  let received = 0;
104
- const reader = res.body.getReader();
105
- for (;;) {
106
- const { done, value } = await reader.read();
107
- if (done) break;
108
- chunks.push(value);
109
- received += value.length;
110
- onProgress?.({ received, total, cached: false });
111
  }
112
- bytes = new Uint8Array(received);
113
- let at = 0;
114
- for (const c of chunks) { bytes.set(c, at); at += c.length; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  }
 
 
 
 
 
 
 
 
116
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  const digest = hex(await crypto.subtle.digest("SHA-256", bytes));
118
  if (digest !== sha256) {
 
119
  if (cache) await cache.delete(url);
120
- throw new Error(
121
- `the denoiser weights failed verification: SHA-256 ${digest} is not ${sha256}`,
122
- );
123
- }
124
- if (cache && !hit) {
125
- // A failure here is a full disk or a quota, not a reason to refuse to denoise.
126
- try {
127
- const headers = { "content-type": "application/octet-stream" };
128
- await cache.put(url, new Response(bytes, { headers }));
129
- } catch (e) { /* the next visit downloads again */ }
130
  }
131
  return bytes;
132
  }
@@ -187,10 +336,12 @@ export async function load({ url, sha256, onProgress, onStage } = {}) {
187
  if (!sessionPromise) {
188
  source = { url, sha256 };
189
  sessionPromise = (async () => {
190
- onStage?.("runtime");
191
- const ortMod = await ort();
192
  onStage?.("weights");
193
  const model = await weights(url, sha256, onProgress);
 
 
194
  onStage?.("session");
195
  if (await realGpu()) {
196
  try {
 
15
  // (`denoiser_input` / `take_denoiser_output`). This module's whole job is to turn one
16
  // Float32Array into another.
17
  //
18
+ // Two things are fetched before the denoiser can run, and kept in the browser's Cache Storage
19
+ // afterwards: ONNX Runtime Web's WebAssembly (~26 MB; its two small JavaScript files ride the
20
+ // HTTP cache, jsDelivr marks them immutable) and the weights (~76 MB). `prefetch` fetches
21
+ // both ahead of need -- Inkvec Studio Lite starts it as soon as its engine has arrived -- and
22
+ // `load` fetches whatever is still missing. One download at a time per file however many ask,
23
+ // and one that breaks off resumes where it stopped (an HTTP range) when it is asked again.
24
+ // The image is not among them: it never leaves the machine, the same as the tracer.
25
 
26
  // Pinned, both of them. A runtime that silently moved under the page would be a second,
27
  // untested set of kernels for a network whose agreement with the native one is a measured
 
30
  const ORT_VERSION = "1.30.0";
31
  const ORT_BASE = `https://cdn.jsdelivr.net/npm/onnxruntime-web@${ORT_VERSION}/dist/`;
32
  const ORT_ENTRY = `${ORT_BASE}ort.webgpu.min.mjs`;
33
+ // What the WebGPU build loads at run time (1.30 builds its GPU and CPU kernels with
34
+ // asyncify): the WebAssembly, handed over as bytes from the cache (`env.wasm.wasmBinary`),
35
+ // and its loader, which only the HTTP cache keeps.
36
+ const ORT_WASM = `${ORT_BASE}ort-wasm-simd-threaded.asyncify.wasm`;
37
+ const ORT_LOADER = `${ORT_BASE}ort-wasm-simd-threaded.asyncify.mjs`;
38
+ // Its size, for progress: jsDelivr compresses it, so its Content-Length is not this.
39
+ const ORT_WASM_BYTES = 26781914;
40
+ const ORT_CACHE = `inkvec-ort-${ORT_VERSION}`;
41
 
42
  // Where a verified copy of the weights lives between visits. Bump the suffix if the model
43
  // the page asks for ever changes, so an old entry cannot be served for a new URL.
 
61
 
62
  async function ort() {
63
  if (!ortPromise) {
64
+ ortPromise = (async () => {
65
+ const [mod, binary] = await Promise.all([import(/* @vite-ignore */ ORT_ENTRY), runtimeBinary()]);
66
  const ort = mod.default ?? mod;
67
+ // The WebGPU build still loads WebAssembly: the GPU kernels live in a wasm module and
68
+ // everything WebGPU has no kernel for falls back to CPU inside the same session. Its
69
+ // bytes come from the cache; its loader from the same folder, the same version.
70
  ort.env.wasm.wasmPaths = ORT_BASE;
71
+ if (binary) ort.env.wasm.wasmBinary = binary;
72
  // Threads need `SharedArrayBuffer`, which needs the cross-origin isolation the Space
73
  // asks for in its README. Where it is not granted this is one thread, and the CPU
74
  // fallback is correspondingly slower — the same trade the tracer makes next door.
 
77
  : 1;
78
  ort.env.logLevel = "error";
79
  return ort;
80
+ })();
81
  ortPromise.catch(() => { ortPromise = null; });
82
  }
83
  return ortPromise;
 
86
  const hex = (buf) =>
87
  [...new Uint8Array(buf)].map((b) => b.toString(16).padStart(2, "0")).join("");
88
 
89
+ async function openCache(name) {
 
 
 
 
 
 
 
 
 
 
90
  try {
91
+ return await caches.open(name);
92
  } catch (e) {
93
  // Private windows and pages with site data blocked have no Cache Storage. Downloading
94
  // every time is worse than caching, and better than not working.
95
+ return null;
96
+ }
97
+ }
98
+
99
+ /** Bytes of one file already fetched in this page, by URL, while its download is incomplete. */
100
+ const partial = new Map();
101
+ /** Downloads in flight, by URL: a second caller joins the first instead of fetching again. */
102
+ const inflight = new Map();
103
+ let aborter = null;
104
+
105
+ /**
106
+ * One file, streamed with progress. If an earlier attempt in this page broke off, the bytes it
107
+ * got are kept and only the rest is asked for (`Range`); a server that answers with the whole
108
+ * file instead simply starts it over.
109
+ */
110
+ async function download(url, { onBytes, priority, knownBytes, signal }) {
111
+ const have = partial.get(url) ?? { chunks: [], received: 0 };
112
+ const headers = have.received > 0 ? { Range: `bytes=${have.received}-` } : undefined;
113
+ // `no-store`: the bytes go into Cache Storage below, and a second copy in the HTTP cache
114
+ // would only double what is written to disk while the page is starting.
115
+ const res = await fetch(url, { headers, priority, signal, cache: "no-store" });
116
+ if (!(res.ok || res.status === 206)) throw new Error(`HTTP ${res.status} for ${url.split("?")[0]}`);
117
+ if (res.status !== 206) {
118
+ have.chunks = [];
119
+ have.received = 0;
120
+ }
121
+ partial.set(url, have);
122
+ const length = Number(res.headers.get("content-length")) || 0;
123
+ const encoded = Boolean(res.headers.get("content-encoding"));
124
+ const total = knownBytes || (!encoded && length ? have.received + length : 0);
125
+ onBytes(have.received, total);
126
+ const reader = res.body.getReader();
127
+ for (;;) {
128
+ const { done, value } = await reader.read();
129
+ if (done) break;
130
+ have.chunks.push(value);
131
+ have.received += value.length;
132
+ onBytes(have.received, total);
133
  }
134
+ const bytes = new Uint8Array(have.received);
135
+ let at = 0;
136
+ for (const c of have.chunks) { bytes.set(c, at); at += c.length; }
137
+ partial.delete(url);
138
+ return bytes;
139
+ }
140
 
141
+ /**
142
+ * What is still to be fetched of `files` ([{ url, cache, sha256?, knownBytes? }]), fetched
143
+ * together with one combined progress report, verified where a hash is known, and stored.
144
+ * Returns each file's bytes (from the cache or the network), in order.
145
+ */
146
+ async function ensure(files, { onProgress, priority = "auto", read = true } = {}) {
147
+ const found = await Promise.all(
148
+ files.map(async (f) => {
149
+ const cache = await openCache(f.cache);
150
+ const hit = cache && (await cache.match(f.url));
151
+ return { ...f, cache, hit };
152
+ }),
153
+ );
154
+ const missing = found.filter((f) => !f.hit);
155
+ if (!missing.length) {
156
+ onProgress?.({ received: 0, total: 0, cached: true });
157
+ return read ? Promise.all(found.map(async (f) => new Uint8Array(await f.hit.arrayBuffer()))) : [];
158
+ }
159
+ const got = new Map();
160
+ const totals = new Map();
161
+ const report = () => {
162
  let received = 0;
163
+ let total = 0;
164
+ for (const f of missing) {
165
+ received += got.get(f.url) ?? 0;
166
+ total += totals.get(f.url) || f.knownBytes || 0;
 
 
 
167
  }
168
+ onProgress?.({ received, total: Math.max(total, received), cached: false });
169
+ };
170
+ if (!aborter) aborter = new AbortController();
171
+ const signal = aborter.signal;
172
+ const fetched = await Promise.all(
173
+ missing.map((f) => {
174
+ let job = inflight.get(f.url);
175
+ if (!job) {
176
+ job = (async () => {
177
+ const bytes = await download(f.url, {
178
+ priority,
179
+ knownBytes: f.knownBytes,
180
+ signal,
181
+ onBytes: (n, t) => {
182
+ got.set(f.url, n);
183
+ if (t) totals.set(f.url, t);
184
+ report();
185
+ },
186
+ });
187
+ if (f.sha256) {
188
+ const digest = hex(await crypto.subtle.digest("SHA-256", bytes));
189
+ if (digest !== f.sha256) {
190
+ throw new Error(`the denoiser weights failed verification: SHA-256 ${digest} is not ${f.sha256}`);
191
+ }
192
+ }
193
+ if (f.cache) {
194
+ // A failure here is a full disk or a quota, not a reason to refuse to denoise.
195
+ try {
196
+ const headers = { "content-type": "application/octet-stream", "content-length": String(bytes.length) };
197
+ await f.cache.put(f.url, new Response(bytes, { headers }));
198
+ } catch (e) { /* the next visit downloads again */ }
199
+ }
200
+ return bytes;
201
+ })();
202
+ inflight.set(f.url, job);
203
+ job.then(() => inflight.delete(f.url), () => inflight.delete(f.url));
204
+ } else {
205
+ // Joined a download another caller started: its progress is reported there, and
206
+ // here the file counts as arriving all at once.
207
+ totals.set(f.url, f.knownBytes || 0);
208
+ job.then((b) => { got.set(f.url, b.length); totals.set(f.url, b.length); report(); }, () => {});
209
+ }
210
+ return job;
211
+ }),
212
+ ).finally(() => {
213
+ if (!inflight.size) aborter = null;
214
+ });
215
+ if (!read) return [];
216
+ const out = [];
217
+ let k = 0;
218
+ for (const f of found) out.push(f.hit ? new Uint8Array(await f.hit.arrayBuffer()) : fetched[k++]);
219
+ return out;
220
+ }
221
+
222
+ const runtimeFile = () => ({ url: ORT_WASM, cache: ORT_CACHE, knownBytes: ORT_WASM_BYTES });
223
+ const weightsFile = (url, sha256) => ({ url, cache: CACHE, sha256 });
224
+
225
+ /** ONNX Runtime Web's WebAssembly, from the cache, or fetched and kept there. */
226
+ async function runtimeBinary() {
227
+ const [bytes] = await ensure([runtimeFile()]);
228
+ return bytes;
229
+ }
230
+
231
+ /**
232
+ * Fetch everything the denoiser needs and keep it, without starting it: the weights
233
+ * (verified against `sha256`) and ONNX Runtime Web. `priority` is the fetch priority hint
234
+ * ("low" for a download nobody is waiting for yet). Resolves to `{ cached }`: whether
235
+ * everything was already here. Nothing is read back out of the cache to answer that.
236
+ */
237
+ export async function prefetch({ url, sha256, onProgress, priority = "low" } = {}) {
238
+ let cached = true;
239
+ await ensure([weightsFile(url, sha256), runtimeFile()], {
240
+ priority,
241
+ read: false,
242
+ onProgress: (p) => {
243
+ if (!p.cached) cached = false;
244
+ onProgress?.(p);
245
+ },
246
+ });
247
+ // The small loaders as well, so a later visit's first run needs nothing from the network.
248
+ if (!cached) {
249
+ await Promise.all(
250
+ [ORT_ENTRY, ORT_LOADER].map((u) => fetch(u, { priority }).then((r) => r.arrayBuffer(), () => null)),
251
+ );
252
  }
253
+ return { cached };
254
+ }
255
+
256
+ /** Stop any download in flight; what arrived is kept, and the next request resumes it. */
257
+ export function abort() {
258
+ aborter?.abort();
259
+ aborter = null;
260
+ }
261
 
262
+ /**
263
+ * The weights, from the cache if they are there and from Hugging Face if they are not,
264
+ * verified either way against the SHA-256 the tracer reports.
265
+ *
266
+ * Verified either way on purpose: `inkvec_restore::pull_onnx_weights` re-checks a file it
267
+ * finds already present rather than trusting it, because a half-written or tampered copy
268
+ * would otherwise fail much later, deep inside the runtime, with an unrelated message. The
269
+ * hash costs about a tenth of a second against the minute the download costs once.
270
+ */
271
+ async function weights(url, sha256, onProgress) {
272
+ // The runtime is fetched alongside, so the progress reported is for everything missing.
273
+ const [bytes] = await ensure([weightsFile(url, sha256), runtimeFile()], { onProgress, priority: "high" });
274
  const digest = hex(await crypto.subtle.digest("SHA-256", bytes));
275
  if (digest !== sha256) {
276
+ const cache = await openCache(CACHE);
277
  if (cache) await cache.delete(url);
278
+ throw new Error(`the denoiser weights failed verification: SHA-256 ${digest} is not ${sha256}`);
 
 
 
 
 
 
 
 
 
279
  }
280
  return bytes;
281
  }
 
336
  if (!sessionPromise) {
337
  source = { url, sha256 };
338
  sessionPromise = (async () => {
339
+ // The weights first: that fetches whatever is missing of both files, with progress,
340
+ // so the runtime after it comes out of the cache.
341
  onStage?.("weights");
342
  const model = await weights(url, sha256, onProgress);
343
+ onStage?.("runtime");
344
+ const ortMod = await ort();
345
  onStage?.("session");
346
  if (await realGpu()) {
347
  try {
studio/guide/getting-started.html CHANGED
@@ -137,7 +137,8 @@ right pane is yours.</p>
137
  measure noisy, and only in builds that include the denoiser. It says why it was offered.
138
  <strong>Off</strong> traces the pixels as they are; <strong>Auto</strong> cleans compression damage first, where it finds
139
  some. If the denoiser is not downloaded yet, the step offers the download (see
140
- <a href="settings.html#denoiser">the denoiser</a>).</li>
 
141
  <li><strong>Colours.</strong> The palette, with suggested colour groups, and <strong>Max colours</strong>. For an image with
142
  transparency, also <strong>Trace transparency</strong>. Hover an ink or a group to single it out in the
143
  drawing.</li>
 
137
  measure noisy, and only in builds that include the denoiser. It says why it was offered.
138
  <strong>Off</strong> traces the pixels as they are; <strong>Auto</strong> cleans compression damage first, where it finds
139
  some. If the denoiser is not downloaded yet, the step offers the download (see
140
+ <a href="settings.html#denoiser">the denoiser</a>); in Studio Lite it is already downloading, and the
141
+ step shows how far it has got.</li>
142
  <li><strong>Colours.</strong> The palette, with suggested colour groups, and <strong>Max colours</strong>. For an image with
143
  transparency, also <strong>Trace transparency</strong>. Hover an ink or a group to single it out in the
144
  drawing.</li>
studio/guide/index.html CHANGED
@@ -80,6 +80,9 @@ in a few places:</p>
80
  <li>It has no folder batch: the <strong>Batch</strong> tab needs to read and write folders on disk.</li>
81
  <li>Saving works through your browser's downloads: where this guide says a file is written to a
82
  folder, Studio Lite downloads it instead.</li>
 
 
 
83
  </ul>
84
  <p>Where a page describes something Studio Lite does differently, it says so.</p>
85
  <h2 id="contents">Contents</h2>
 
80
  <li>It has no folder batch: the <strong>Batch</strong> tab needs to read and write folders on disk.</li>
81
  <li>Saving works through your browser's downloads: where this guide says a file is written to a
82
  folder, Studio Lite downloads it instead.</li>
83
+ <li>The denoiser downloads by itself, in the background, the first time Studio Lite opens, and is
84
+ kept in the browser for later visits (see <a href="settings.html#denoiser">the denoiser</a>).</li>
85
+ <li>Preferences and the choices you leave the app with are kept in the browser, not in a file.</li>
86
  </ul>
87
  <p>Where a page describes something Studio Lite does differently, it says so.</p>
88
  <h2 id="contents">Contents</h2>
studio/guide/install.html CHANGED
@@ -72,7 +72,9 @@ through the same thing with a sample.</p>
72
  <li><strong>The update check</strong>, when the app starts. It asks GitHub for the latest release and sends the
73
  app's version and your operating system (in the request's user agent), nothing else. Turn it off
74
  in <a href="settings.html#updates">Settings</a> and the app does not contact the network at all.</li>
75
- <li><strong>The denoiser download</strong>, only when you ask for it. See <a href="settings.html#denoiser">the denoiser</a>.</li>
 
 
76
  </ul>
77
  <p>Your images, traces and exported files never leave your computer.</p>
78
  <h2 id="updates">Updates</h2>
 
72
  <li><strong>The update check</strong>, when the app starts. It asks GitHub for the latest release and sends the
73
  app's version and your operating system (in the request's user agent), nothing else. Turn it off
74
  in <a href="settings.html#updates">Settings</a> and the app does not contact the network at all.</li>
75
+ <li><strong>The denoiser download</strong>, only when you ask for it. See <a href="settings.html#denoiser">the denoiser</a>.
76
+ (Studio Lite, in the browser, downloads it in the background when it opens, unless the browser
77
+ asks to save data.)</li>
78
  </ul>
79
  <p>Your images, traces and exported files never leave your computer.</p>
80
  <h2 id="updates">Updates</h2>
studio/guide/settings.html CHANGED
@@ -53,6 +53,21 @@ included in the installer (it is about 80 MB).</p>
53
  Once it is installed, the command-line <code>inkvec --restore</code> uses the same file.</p>
54
  <p>The Intel macOS build does not include the denoiser, and says <em>Not in this build</em>. The Windows,
55
  Linux and Apple-silicon macOS builds do.</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  <h2 id="updates">Updates</h2>
57
  <ul>
58
  <li><strong>Check on start</strong> (on): looks for a newer version when the app starts, sending your app version
@@ -116,9 +131,22 @@ is one).</p>
116
  </tbody>
117
  </table>
118
  <p>On Windows, uninstalling the app also removes both entries.</p>
119
- <p><strong>Reset settings</strong> puts every preference and the trace controls back to their defaults and clears
120
- the recent files. <strong>Your saved presets are kept</strong> (forget one from the preset tray), nothing you
121
- have traced or exported is touched, and the denoiser stays installed.</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  <h2 id="privacy">Privacy</h2>
123
  <p>The last card says what the rest of this guide says too: tracing runs on your own processor, there
124
  is no account, no upload and no telemetry, and the update check is the only request the app makes
@@ -135,7 +163,7 @@ on its own.</p>
135
  </thead>
136
  <tbody>
137
  <tr>
138
- <td>Preferences, recent files, saved presets</td>
139
  <td><code>%APPDATA%\inkvec-studio\preferences.json</code></td>
140
  <td><code>~/Library/Application Support/inkvec-studio/preferences.json</code></td>
141
  <td><code>~/.config/inkvec-studio/preferences.json</code></td>
@@ -153,9 +181,13 @@ on its own.</p>
153
  guide, the source and the online demo, the compute acknowledgement for the denoiser's training, and
154
  the licence and third-party notices (<strong>Open</strong> shows them in full).</p>
155
  <h2 id="studio-lite">Studio Lite</h2>
156
- <p>Studio Lite keeps its preferences in the browser. It has no <strong>Advanced</strong> entries (no command on
157
- the PATH, no right-click menu) and no default output folder, since files are downloaded.</p><nav class="pagenav"><a class="prev" href="batch.html"><span class="dir">Previous</span><span class="lbl">Batch</span></a><a class="next" href="shortcuts.html"><span class="dir">Next</span><span class="lbl">Keyboard and mouse</span></a></nav></article></main>
158
- <aside class="rail"><h5>On this page</h5><ul><ul><li class="l2"><a href="#general">General</a></li><li class="l2"><a href="#performance">Performance</a></li><li class="l2"><a href="#denoiser">Denoiser</a></li><li class="l2"><a href="#updates">Updates</a></li><li class="l2"><a href="#advanced">Advanced</a></li><li class="l2"><a href="#privacy">Privacy</a></li><li class="l2"><a href="#where-things-are-kept">Where things are kept</a></li><li class="l2"><a href="#about">About</a></li><li class="l2"><a href="#studio-lite">Studio Lite</a></li></ul></ul></aside>
 
 
 
 
159
  </div>
160
  </body>
161
  </html>
 
53
  Once it is installed, the command-line <code>inkvec --restore</code> uses the same file.</p>
54
  <p>The Intel macOS build does not include the denoiser, and says <em>Not in this build</em>. The Windows,
55
  Linux and Apple-silicon macOS builds do.</p>
56
+ <p><strong>In Studio Lite</strong> there is nothing to press. The denoiser (about 76 MB of weights and 26 MB of
57
+ ONNX Runtime Web, the runtime that runs it in the browser) starts downloading in the background
58
+ as soon as the page has loaded its engine, at low priority, and is kept in the browser's storage,
59
+ so later visits start with it already there. While it downloads, the status strip at the bottom
60
+ left shows how far it has got (<em>Denoiser 34 of 102 MB · 33%</em>) and then goes away. If you turn the
61
+ denoiser <strong>On</strong> (or pick <strong>Auto</strong>, or a preset that uses it) before it has arrived, the trace you
62
+ see is made without it for the moment, the <strong>Denoiser</strong> control shows the download (<em>Downloading
63
+ the denoiser, 34 of 102 MB, 33%</em>, then <em>Preparing the denoiser…</em>), and the image is traced again
64
+ with it, by itself, once it is ready. If the download fails, the control says why and offers
65
+ <strong>Retry</strong>; a download that breaks off is also retried once by itself, from where it stopped.</p>
66
+ <p>The background download is skipped when your browser asks sites to save data (Chrome's and
67
+ Edge's data saver, or a metered connection set to save data). Turning the denoiser on then
68
+ downloads it, with the same progress. <strong>Remove</strong> clears the browser's copy. It only works on the
69
+ Space's own tab (a page that is <em>cross-origin isolated</em>); inside the Hugging Face frame the
70
+ control says <em>needs its own tab</em>, and nothing is downloaded there.</p>
71
  <h2 id="updates">Updates</h2>
72
  <ul>
73
  <li><strong>Check on start</strong> (on): looks for a newer version when the app starts, sending your app version
 
131
  </tbody>
132
  </table>
133
  <p>On Windows, uninstalling the app also removes both entries.</p>
134
+ <p><strong>Reset settings</strong> puts every preference, the trace controls and the remembered choices (see
135
+ below) back to their defaults and clears the recent files. <strong>Your saved presets are kept</strong> (forget
136
+ one from the preset tray), nothing you have traced or exported is touched, the denoiser stays
137
+ installed, and the window stays where it is.</p>
138
+ <h2 id="what-the-app-remembers">What the app remembers</h2>
139
+ <p>Besides the settings on this screen, the app remembers how you left it, and opens that way next
140
+ time: the trace controls and the selected preset, the <strong>Denoiser</strong> and <strong>Editable</strong> switches, the
141
+ tab you were on, the half of the rail (<strong>Result</strong> or <strong>Tune</strong>) and which Tune groups were open, the
142
+ viewer's arrangement (<strong>Side by side</strong>, <strong>Wipe</strong>, <strong>A/B</strong>), its layers (<strong>Fill</strong>, <strong>Wireframe</strong>,
143
+ <strong>Anchors</strong>, <strong>Handles</strong>, <strong>Certainty</strong>) and <strong>Detail</strong>, the formats and sizes ticked in the
144
+ export sheet, the Minify tab's controls and backdrop, the Fabricate tab's settings (without the
145
+ colours of one drawing), the Batch tab's switches, and, on the desktop, the window's size and
146
+ position (on a monitor that is no longer connected, it opens centred instead). There is no save
147
+ button: a change is kept about half a second after you make it.</p>
148
+ <p>What belongs to one image is not kept: its colour groups, snapped colours, and the zoom and pan.
149
+ They start fresh with the next image.</p>
150
  <h2 id="privacy">Privacy</h2>
151
  <p>The last card says what the rest of this guide says too: tracing runs on your own processor, there
152
  is no account, no upload and no telemetry, and the update check is the only request the app makes
 
163
  </thead>
164
  <tbody>
165
  <tr>
166
+ <td>Preferences, remembered choices, recent files, saved presets</td>
167
  <td><code>%APPDATA%\inkvec-studio\preferences.json</code></td>
168
  <td><code>~/Library/Application Support/inkvec-studio/preferences.json</code></td>
169
  <td><code>~/.config/inkvec-studio/preferences.json</code></td>
 
181
  guide, the source and the online demo, the compute acknowledgement for the denoiser's training, and
182
  the licence and third-party notices (<strong>Open</strong> shows them in full).</p>
183
  <h2 id="studio-lite">Studio Lite</h2>
184
+ <p>Studio Lite keeps its preferences, remembered choices and saved presets in the browser's local
185
+ storage for the Space's site, and the denoiser in its Cache Storage; clearing the site's data in
186
+ the browser removes both. In a private window, or with site data blocked, nothing can be kept:
187
+ the app works the same and starts from the defaults each time. It has no <strong>Advanced</strong> entries (no
188
+ command on the PATH, no right-click menu) and no default output folder, since files are
189
+ downloaded.</p><nav class="pagenav"><a class="prev" href="batch.html"><span class="dir">Previous</span><span class="lbl">Batch</span></a><a class="next" href="shortcuts.html"><span class="dir">Next</span><span class="lbl">Keyboard and mouse</span></a></nav></article></main>
190
+ <aside class="rail"><h5>On this page</h5><ul><ul><li class="l2"><a href="#general">General</a></li><li class="l2"><a href="#performance">Performance</a></li><li class="l2"><a href="#denoiser">Denoiser</a></li><li class="l2"><a href="#updates">Updates</a></li><li class="l2"><a href="#advanced">Advanced</a></li><li class="l2"><a href="#what-the-app-remembers">What the app remembers</a></li><li class="l2"><a href="#privacy">Privacy</a></li><li class="l2"><a href="#where-things-are-kept">Where things are kept</a></li><li class="l2"><a href="#about">About</a></li><li class="l2"><a href="#studio-lite">Studio Lite</a></li></ul></ul></aside>
191
  </div>
192
  </body>
193
  </html>
studio/guide/troubleshooting.html CHANGED
@@ -107,6 +107,18 @@ clears the recent files, and keeps your saved presets, your files and the denois
107
  folder.</p>
108
  <p><strong>Where are Batch, the command line and the right-click menu?</strong> They need the desktop app. See
109
  <a href="index.html#inkvec-studio-and-inkvec-studio-lite">Inkvec Studio and Inkvec Studio Lite</a>.</p>
 
 
 
 
 
 
 
 
 
 
 
 
110
  <h2 id="reporting-a-problem">Reporting a problem</h2>
111
  <p>Report bugs on <a href="https://github.com/logolabs/inkvec/issues">GitHub</a>. Include the app version (from
112
  About), your operating system, and, if you can share it, the image. Security problems go through
 
107
  folder.</p>
108
  <p><strong>Where are Batch, the command line and the right-click menu?</strong> They need the desktop app. See
109
  <a href="index.html#inkvec-studio-and-inkvec-studio-lite">Inkvec Studio and Inkvec Studio Lite</a>.</p>
110
+ <p><strong>The first visit takes a few seconds to start.</strong> The loading screen is the engine arriving
111
+ (about 5 MB) and starting; its bar follows the real download. Later visits start from the
112
+ browser's cache.</p>
113
+ <p><strong>The denoiser is on but the trace looks the same, and the rail says it is downloading.</strong> The
114
+ trace shown was made without it while it downloads (about 100 MB, once). It traces again by
115
+ itself when the denoiser is ready. If it failed, press <strong>Retry</strong> under the Denoiser buttons or in
116
+ the status strip.</p>
117
+ <p><strong>Nothing is downloaded in the background.</strong> Your browser asks sites to save data, so Studio
118
+ Lite waits until you turn the denoiser on. Inside the Hugging Face frame the denoiser cannot run
119
+ at all: use <strong>Open in its own tab</strong>.</p>
120
+ <p><strong>My settings are not remembered.</strong> A private window, or a browser set to block site data, gives
121
+ the page nowhere to keep them. The app still works, from the defaults each time.</p>
122
  <h2 id="reporting-a-problem">Reporting a problem</h2>
123
  <p>Report bugs on <a href="https://github.com/logolabs/inkvec/issues">GitHub</a>. Include the app version (from
124
  About), your operating system, and, if you can share it, the image. Security problems go through
studio/index.html CHANGED
@@ -2,22 +2,363 @@
2
  <html lang="en" data-theme="dark">
3
  <head>
4
  <meta charset="UTF-8" />
5
- <!-- A web page: its own origin, plus the denoiser's runtime and weights when asked for. -->
6
  <meta
7
  http-equiv="Content-Security-Policy"
8
- content="default-src 'self'; script-src 'self' 'wasm-unsafe-eval' https://cdn.jsdelivr.net; worker-src 'self' blob:; img-src 'self' blob: data:; style-src 'self' 'unsafe-inline'; font-src 'self'; connect-src 'self' https://huggingface.co https://*.huggingface.co https://*.hf.co https://cdn.jsdelivr.net"
9
  />
10
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
11
  <meta name="description" content="Turn a raster logo into an exact SVG, in your browser. Nothing is uploaded." />
12
  <link rel="icon" href="./favicon.svg" type="image/svg+xml" />
13
  <title>Inkvec Studio Lite</title>
14
- <script type="module" crossorigin src="./assets/main-Bt_M4BWC.js"></script>
15
- <link rel="modulepreload" crossorigin href="./assets/mark-DZ7OzD-p.js">
16
- <link rel="stylesheet" crossorigin href="./assets/mark-DxrDvgRD.css">
17
- <link rel="stylesheet" crossorigin href="./assets/main-CTlNDZmC.css">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  </head>
19
  <body>
20
- <div id="boot" class="boot"><iframe src="./splash.html" title="Inkvec Studio Lite is starting"></iframe></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  <div id="app"></div>
22
  <div id="overlays"></div>
23
  <div id="toasts" role="status" aria-live="polite"></div>
 
2
  <html lang="en" data-theme="dark">
3
  <head>
4
  <meta charset="UTF-8" />
5
+ <!-- A web page: its own origin, plus the denoiser's runtime and weights. -->
6
  <meta
7
  http-equiv="Content-Security-Policy"
8
+ content="default-src 'self'; script-src 'self' 'wasm-unsafe-eval' 'sha256-+dTfo4HcxEndAo5tPy8N4VZ1j7HyojWz4m6mwWx2wGw=' https://cdn.jsdelivr.net; worker-src 'self' blob:; img-src 'self' blob: data:; style-src 'self' 'unsafe-inline'; font-src 'self'; connect-src 'self' https://huggingface.co https://*.huggingface.co https://*.hf.co https://cdn.jsdelivr.net"
9
  />
10
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
11
  <meta name="description" content="Turn a raster logo into an exact SVG, in your browser. Nothing is uploaded." />
12
  <link rel="icon" href="./favicon.svg" type="image/svg+xml" />
13
  <title>Inkvec Studio Lite</title>
14
+ <script type="module" crossorigin src="./assets/main-Dn0s9OJH.js"></script>
15
+ <link rel="stylesheet" crossorigin href="./assets/main-DOUNaj7B.css">
16
+ <link rel="preload" href="./fonts/playfair-latin.woff2" as="font" type="font/woff2" crossorigin />
17
+ <link rel="preload" href="./fonts/inter-latin.woff2" as="font" type="font/woff2" crossorigin />
18
+ <style>
19
+ /* Inkvec Studio Lite's loading screen: the whole page, not the desktop's splash card.
20
+
21
+ Inlined into the browser build's index.html (vite.config.ts), so it paints with the first
22
+ frame, before the app's bundle has been read. Always the dark charcoal, whatever the theme:
23
+ it is a brand moment of a second or three. One animation on the bar and one only: its
24
+ scale follows real progress (bytes of the engine, then its start), through a single
25
+ transition. The mark draws itself once, beside it, and that is all that moves. */
26
+
27
+ .boot {
28
+ position: fixed;
29
+ inset: 0;
30
+ z-index: 50;
31
+ display: grid;
32
+ grid-template-rows: 1fr auto;
33
+ overflow: hidden;
34
+ background: #1a1816;
35
+ color: #faf8f5;
36
+ font-family: "Inter Studio", Inter, system-ui, sans-serif;
37
+ -webkit-font-smoothing: antialiased;
38
+ user-select: none;
39
+ cursor: progress;
40
+ transition: opacity 0.22s ease;
41
+ }
42
+ .boot.leaving {
43
+ opacity: 0;
44
+ pointer-events: none;
45
+ }
46
+ /* Inside Hugging Face's frame: the same gap and copper hairline the app has there, so the
47
+ hand-over does not move anything. */
48
+ html.framed:not(.fullscreen) .boot {
49
+ top: 6px;
50
+ border-top: 1px solid rgba(201, 117, 74, 0.35);
51
+ }
52
+
53
+ /* The raster the product reads, fading out from the middle; the copper light above it. */
54
+ .boot-grid {
55
+ position: absolute;
56
+ inset: 0;
57
+ background-image:
58
+ linear-gradient(to right, rgba(250, 248, 245, 0.04) 1px, transparent 1px),
59
+ linear-gradient(to bottom, rgba(250, 248, 245, 0.04) 1px, transparent 1px);
60
+ background-size: 16px 16px;
61
+ -webkit-mask-image: radial-gradient(ellipse 55% 60% at 50% 42%, #000 0%, transparent 75%);
62
+ mask-image: radial-gradient(ellipse 55% 60% at 50% 42%, #000 0%, transparent 75%);
63
+ }
64
+ .boot-glow {
65
+ position: absolute;
66
+ inset: 0;
67
+ background: radial-gradient(ellipse 50% 45% at 50% 30%, rgba(201, 117, 74, 0.16), transparent 70%);
68
+ }
69
+
70
+ .boot-stage {
71
+ position: relative;
72
+ display: flex;
73
+ flex-direction: column;
74
+ align-items: center;
75
+ justify-content: center;
76
+ padding: 24px 16px 8px;
77
+ min-height: 0;
78
+ text-align: center;
79
+ }
80
+
81
+ .boot-mark {
82
+ display: block;
83
+ height: clamp(64px, 13vh, 124px);
84
+ width: auto;
85
+ overflow: visible;
86
+ filter: drop-shadow(0 12px 28px rgba(201, 117, 74, 0.25));
87
+ }
88
+ .boot-mark path {
89
+ fill-rule: evenodd;
90
+ }
91
+ .boot-mark .o {
92
+ fill: none;
93
+ stroke: #c9754a;
94
+ stroke-width: 6;
95
+ stroke-linejoin: round;
96
+ stroke-dasharray: 1;
97
+ stroke-dashoffset: 1;
98
+ }
99
+ .boot-mark .b {
100
+ fill: #c9754a;
101
+ opacity: 0;
102
+ }
103
+
104
+ .boot-by {
105
+ margin: clamp(16px, 3.2vh, 28px) 0 0;
106
+ font-size: 10.5px;
107
+ font-weight: 500;
108
+ letter-spacing: 0.24em;
109
+ text-transform: uppercase;
110
+ color: rgba(250, 248, 245, 0.5);
111
+ }
112
+ .boot-word {
113
+ margin: 6px 0 0;
114
+ font-family: "Playfair Studio", Georgia, serif;
115
+ font-size: clamp(40px, 7.5vh, 66px);
116
+ font-weight: 500;
117
+ line-height: 1;
118
+ letter-spacing: -0.025em;
119
+ }
120
+ .boot-edition {
121
+ margin: 10px 0 0;
122
+ font-size: 12px;
123
+ font-weight: 600;
124
+ letter-spacing: 0.3em;
125
+ text-transform: uppercase;
126
+ color: #c9754a;
127
+ }
128
+
129
+ .boot-progress {
130
+ width: min(440px, 100%);
131
+ margin-top: clamp(24px, 5vh, 44px);
132
+ text-align: left;
133
+ }
134
+ .boot-bar {
135
+ height: 3px;
136
+ border-radius: 2px;
137
+ background: rgba(250, 248, 245, 0.1);
138
+ overflow: hidden;
139
+ }
140
+ .boot-bar i {
141
+ display: block;
142
+ height: 100%;
143
+ background: #c9754a;
144
+ transform: scaleX(0);
145
+ transform-origin: left center;
146
+ transition: transform 0.3s cubic-bezier(0.25, 0.7, 0.25, 1);
147
+ will-change: transform;
148
+ }
149
+ .boot-row {
150
+ display: flex;
151
+ justify-content: space-between;
152
+ gap: 16px;
153
+ margin-top: 10px;
154
+ font-size: 12.5px;
155
+ color: rgba(250, 248, 245, 0.78);
156
+ }
157
+ .boot-detail {
158
+ min-height: 1.4em;
159
+ margin-top: 2px;
160
+ font-size: 11.5px;
161
+ color: rgba(250, 248, 245, 0.45);
162
+ }
163
+ .boot-num {
164
+ font-variant-numeric: tabular-nums;
165
+ }
166
+ .boot-progress .boot-row span:first-child {
167
+ overflow: hidden;
168
+ text-overflow: ellipsis;
169
+ white-space: nowrap;
170
+ }
171
+
172
+ .boot-foot {
173
+ position: relative;
174
+ display: flex;
175
+ flex-wrap: wrap;
176
+ justify-content: space-between;
177
+ gap: 4px 16px;
178
+ padding: 14px 20px 16px;
179
+ font-size: 11px;
180
+ color: rgba(250, 248, 245, 0.36);
181
+ }
182
+
183
+ /* The entrance plays once the faces are in (boot.js), so it never starts on a fallback font. */
184
+ .boot-by,
185
+ .boot-word,
186
+ .boot-edition,
187
+ .boot-progress {
188
+ opacity: 0;
189
+ transform: translateY(6px);
190
+ }
191
+ html.boot-go .boot-mark .o {
192
+ animation:
193
+ boot-draw 1.1s cubic-bezier(0.6, 0, 0.2, 1) forwards,
194
+ boot-fade 0.4s ease 1.05s forwards;
195
+ }
196
+ html.boot-go .boot-mark .b {
197
+ animation: boot-show 0.5s ease 0.85s forwards;
198
+ }
199
+ html.boot-go .boot-by,
200
+ html.boot-go .boot-word,
201
+ html.boot-go .boot-edition,
202
+ html.boot-go .boot-progress {
203
+ animation: boot-rise 0.55s cubic-bezier(0.2, 0.7, 0.2, 1) forwards;
204
+ }
205
+ html.boot-go .boot-word {
206
+ animation-delay: 0.08s;
207
+ }
208
+ html.boot-go .boot-edition {
209
+ animation-delay: 0.16s;
210
+ }
211
+ html.boot-go .boot-progress {
212
+ animation-delay: 0.22s;
213
+ }
214
+
215
+ /* A start-up that could not finish says so, in place of the progress. */
216
+ .boot-failed .boot-bar i {
217
+ background: #d9534f;
218
+ }
219
+ .boot-failed .boot-detail {
220
+ color: rgba(250, 248, 245, 0.7);
221
+ }
222
+ .boot-reload {
223
+ margin-top: 12px;
224
+ padding: 6px 14px;
225
+ border: 1px solid rgba(250, 248, 245, 0.25);
226
+ border-radius: 6px;
227
+ background: transparent;
228
+ color: #faf8f5;
229
+ font: inherit;
230
+ font-size: 12.5px;
231
+ cursor: pointer;
232
+ }
233
+
234
+ @keyframes boot-draw {
235
+ to {
236
+ stroke-dashoffset: 0;
237
+ }
238
+ }
239
+ @keyframes boot-fade {
240
+ to {
241
+ opacity: 0;
242
+ }
243
+ }
244
+ @keyframes boot-show {
245
+ to {
246
+ opacity: 1;
247
+ }
248
+ }
249
+ @keyframes boot-rise {
250
+ to {
251
+ opacity: 1;
252
+ transform: none;
253
+ }
254
+ }
255
+
256
+ @media (max-width: 520px) {
257
+ .boot-foot {
258
+ justify-content: center;
259
+ text-align: center;
260
+ }
261
+ }
262
+
263
+ /* Nothing moves for anyone who asked for that: the finished picture, and a bar that steps. */
264
+ @media (prefers-reduced-motion: reduce) {
265
+ .boot-by,
266
+ .boot-word,
267
+ .boot-edition,
268
+ .boot-progress,
269
+ .boot-mark .b {
270
+ animation: none !important;
271
+ opacity: 1;
272
+ transform: none;
273
+ }
274
+ .boot-mark .o {
275
+ animation: none !important;
276
+ opacity: 0;
277
+ }
278
+ .boot-bar i,
279
+ .boot {
280
+ transition: none;
281
+ }
282
+ }
283
+ </style>
284
+ <script>
285
+ // Inkvec Studio Lite's loading screen, before the app's bundle arrives. Inlined into the
286
+ // head of index.html (and allowed by its own hash in the page's content policy), so it runs
287
+ // before the first paint:
288
+ //
289
+ // - `framed` when the page is inside another page's frame (Hugging Face shows the Space in
290
+ // one), so the loading screen sits below the same gap and hairline the app will;
291
+ // - `boot-go` once the two faces have loaded (or 700 ms have passed), which starts the
292
+ // entrance, so it never plays on a fallback font;
293
+ // - a failsafe: if the app's own script has not taken the screen over in 30 s (blocked, or
294
+ // a network that dropped it), say so and offer a reload instead of a bar that never moves.
295
+ //
296
+ // Everything after that is lib/web/chrome.ts: real progress, and the hand-over.
297
+ (function () {
298
+ var root = document.documentElement;
299
+ var framed = false;
300
+ try {
301
+ framed = window.self !== window.top;
302
+ } catch (e) {
303
+ framed = true;
304
+ }
305
+ if (framed) root.classList.add("framed");
306
+
307
+ var go = function () {
308
+ root.classList.add("boot-go");
309
+ };
310
+ try {
311
+ Promise.race([
312
+ Promise.all([document.fonts.load('500 60px "Playfair Studio"'), document.fonts.load('400 13px "Inter Studio"')]),
313
+ new Promise(function (resolve) {
314
+ setTimeout(resolve, 700);
315
+ }),
316
+ ]).then(go, go);
317
+ } catch (e) {
318
+ go();
319
+ }
320
+
321
+ setTimeout(function () {
322
+ var boot = document.getElementById("boot");
323
+ if (!boot || window.__inkvecBoot) return;
324
+ boot.classList.add("boot-failed");
325
+ var status = document.getElementById("boot-status");
326
+ var detail = document.getElementById("boot-detail");
327
+ if (status) status.textContent = "The Studio did not load";
328
+ if (detail) {
329
+ detail.textContent = "Its script never arrived. Check the connection, then reload.";
330
+ var b = document.createElement("button");
331
+ b.className = "boot-reload";
332
+ b.textContent = "Reload";
333
+ b.onclick = function () {
334
+ location.reload();
335
+ };
336
+ detail.parentNode.appendChild(b);
337
+ }
338
+ }, 30000);
339
+ })();
340
+ </script>
341
  </head>
342
  <body>
343
+ <div id="boot" class="boot" role="status" aria-live="polite" aria-label="Inkvec Studio Lite is starting">
344
+ <div class="boot-grid" aria-hidden="true"></div>
345
+ <div class="boot-glow" aria-hidden="true"></div>
346
+ <div class="boot-stage">
347
+ <svg class="boot-mark" viewBox="254.5 140.5 514.25 742.25" aria-hidden="true"><path class="o" pathLength="1" d="M511.3 140.6c-23.3 79.8-103 178.5-144.9 232.75L335.45 415.4a467.73 467.73 0 00-64.8 115.4c-65.5 193.1 80.8 351.3 237.85 351.7 21.26 1.1 42.56-1.3 63.06-7 90.8-18.87 211.87-120.04 195.47-280.9-17.9-164.8-187.55-272.65-251.8-443.84Zm10.2 374.9a61.05 61.05 0 00-67.1 81.6 61.05 61.05 0 00104.3 17.26 61.05 61.05 0 00-37.2-98.86ZM511.4 817.65l177.16-201.3-43-.15a44.56 44.56 0 00-36.2 18.47l-97.8 111.67-102.2-116.67c-16.06-18.94-38.06-12-74.9-13.4 5.7 7.3 159.75 179.73 176.93 201.4Z" /><path class="b" d="M511.3 140.6c-23.3 79.8-103 178.5-144.9 232.75L335.45 415.4a467.73 467.73 0 00-64.8 115.4c-65.5 193.1 80.8 351.3 237.85 351.7 21.26 1.1 42.56-1.3 63.06-7 90.8-18.87 211.87-120.04 195.47-280.9-17.9-164.8-187.55-272.65-251.8-443.84Zm10.2 374.9a61.05 61.05 0 00-67.1 81.6 61.05 61.05 0 00104.3 17.26 61.05 61.05 0 00-37.2-98.86ZM511.4 817.65l177.16-201.3-43-.15a44.56 44.56 0 00-36.2 18.47l-97.8 111.67-102.2-116.67c-16.06-18.94-38.06-12-74.9-13.4 5.7 7.3 159.75 179.73 176.93 201.4Z" /></svg>
348
+ <p class="boot-by">LogoLabs</p>
349
+ <h1 class="boot-word">Inkvec</h1>
350
+ <p class="boot-edition">Studio Lite</p>
351
+ <div class="boot-progress">
352
+ <div class="boot-bar" id="boot-bar" role="progressbar" aria-label="Loading Inkvec Studio Lite" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0"><i id="boot-fill"></i></div>
353
+ <div class="boot-row"><span id="boot-status">Loading the Studio</span><span id="boot-pct" class="boot-num">0%</span></div>
354
+ <div class="boot-detail boot-num" id="boot-detail"></div>
355
+ </div>
356
+ </div>
357
+ <footer class="boot-foot">
358
+ <span>Runs in your browser. Your image never leaves this computer.</span>
359
+ <span>Version 0.2.0 · Apache-2.0 · logolabs.org</span>
360
+ </footer>
361
+ </div>
362
  <div id="app"></div>
363
  <div id="overlays"></div>
364
  <div id="toasts" role="status" aria-live="polite"></div>
studio/pkg-threads/inkvec_studio_wasm_bg.wasm CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:613a726a3a9d95c48eb42d6dcff4d2a7ef5397f3d14efe3285a2ff8e13a16059
3
- size 5273012
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:85f3c9cc9cda6a5fb18856686d80c5c6b5dbd606925ec3abe1d58633d8341340
3
+ size 5366604
studio/pkg-threads/snippets/wasm-bindgen-rayon-38edf6e439f6d70d/src/workerHelpers.js CHANGED
@@ -51,7 +51,7 @@ waitForMsgType(self, 'wasm_bindgen_worker_init').then(async ({ init, receiver })
51
  // OTOH, even though it can't be inlined, it should be still reasonably
52
  // cheap since the requested file is already in cache (it was loaded by
53
  // the main thread).
54
- const pkg = await import('../../../inkvec_studio_wasm.js?v=385495021c9e');
55
  await pkg.default(init);
56
  postMessage({ type: 'wasm_bindgen_worker_ready' });
57
  pkg.wbg_rayon_start_worker(receiver);
 
51
  // OTOH, even though it can't be inlined, it should be still reasonably
52
  // cheap since the requested file is already in cache (it was loaded by
53
  // the main thread).
54
+ const pkg = await import('../../../inkvec_studio_wasm.js?v=bb166903cf5e');
55
  await pkg.default(init);
56
  postMessage({ type: 'wasm_bindgen_worker_ready' });
57
  pkg.wbg_rayon_start_worker(receiver);
studio/pkg/inkvec_studio_wasm_bg.wasm CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:00782312b170600499d26c01cae77d8ad44e57bffa86737c6e83ab2e13aa986e
3
- size 5237361
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:73dd59e6e152d9e48d628c025900b5c64ad425c4bb7ac21c084c35fafdca7120
3
+ size 5329722
studio/splash.html DELETED
@@ -1,46 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en" data-theme="dark">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <!-- The splash is its own window and its own page: it has to paint before the app's
6
- bundle has been read, so it carries nothing from it but the fonts and tokens. -->
7
- <meta
8
- http-equiv="Content-Security-Policy"
9
- content="default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; font-src 'self'; connect-src 'self' ipc: http://ipc.localhost"
10
- />
11
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
12
- <title>Inkvec Studio</title>
13
- <script type="module" crossorigin src="./assets/splash-DU7ZsOCg.js"></script>
14
- <link rel="modulepreload" crossorigin href="./assets/mark-DZ7OzD-p.js">
15
- <link rel="stylesheet" crossorigin href="./assets/mark-DxrDvgRD.css">
16
- <link rel="stylesheet" crossorigin href="./assets/splash-Qg7cNZqj.css">
17
- </head>
18
- <body>
19
- <main class="splash" id="splash" data-tauri-drag-region role="status" aria-live="polite" aria-label="Inkvec Studio is starting">
20
- <div class="grid" aria-hidden="true"></div>
21
- <div class="glow" aria-hidden="true"></div>
22
-
23
- <section class="stage">
24
- <div class="mark" id="mark" aria-hidden="true"></div>
25
- <div class="titles">
26
- <span class="eyebrow by">LogoLabs presents</span>
27
- <h1>Inkvec</h1>
28
- <span class="edition">Studio</span>
29
- <p class="tag">Raster to SVG, exactly.</p>
30
- </div>
31
- </section>
32
-
33
- <footer class="foot">
34
- <div class="progress" aria-hidden="true"><i id="bar"></i></div>
35
- <div class="row">
36
- <span id="status" class="status">Starting the engine…</span>
37
- <span id="version" class="version"></span>
38
- </div>
39
- <div class="fine">
40
- <span>Copyright logolabs.org · Apache-2.0</span>
41
- <span>Denoiser trained on Arrhenius at NAISS · EuroHPC JU</span>
42
- </div>
43
- </footer>
44
- </main>
45
- </body>
46
- </html>