Inkvec 0.2: the presentation page, and Inkvec Studio Lite under studio/
Browse files- README.md +2 -1
- index.html +1 -1
- studio/assets/denoiser.worker-DjPuHhk4.js +1 -0
- studio/assets/denoiser.worker-ER2QG0XM.js +0 -1
- studio/assets/engine.worker-Cn2MnOae.js +0 -1
- studio/assets/engine.worker-DxLbviFI.js +1 -0
- studio/assets/main-Bt_M4BWC.js +0 -0
- studio/assets/main-CTlNDZmC.css +0 -1
- studio/assets/main-DOUNaj7B.css +1 -0
- studio/assets/main-Dn0s9OJH.js +0 -0
- studio/assets/mark-DZ7OzD-p.js +0 -3
- studio/assets/mark-DxrDvgRD.css +0 -1
- studio/assets/splash-DU7ZsOCg.js +0 -1
- studio/assets/splash-Qg7cNZqj.css +0 -1
- studio/denoise.js +203 -52
- studio/guide/getting-started.html +2 -1
- studio/guide/index.html +3 -0
- studio/guide/install.html +3 -1
- studio/guide/settings.html +39 -7
- studio/guide/troubleshooting.html +12 -0
- studio/index.html +348 -7
- studio/pkg-threads/inkvec_studio_wasm_bg.wasm +2 -2
- studio/pkg-threads/snippets/wasm-bindgen-rayon-38edf6e439f6d70d/src/workerHelpers.js +1 -1
- studio/pkg/inkvec_studio_wasm_bg.wasm +2 -2
- studio/splash.html +0 -46
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
|
|
|
|
| 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><circle></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,
|
| 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><circle></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
|
| 19 |
-
// afterwards: ONNX Runtime Web (~
|
| 20 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 =
|
|
|
|
| 53 |
const ort = mod.default ?? mod;
|
| 54 |
-
// The WebGPU build still loads WebAssembly: the GPU kernels live in a wasm module
|
| 55 |
-
//
|
| 56 |
-
//
|
| 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 |
-
|
| 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 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
let received = 0;
|
| 104 |
-
|
| 105 |
-
for (
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
chunks.push(value);
|
| 109 |
-
received += value.length;
|
| 110 |
-
onProgress?.({ received, total, cached: false });
|
| 111 |
}
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 191 |
-
|
| 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>)
|
|
|
|
| 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>.
|
|
|
|
|
|
|
| 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
|
| 120 |
-
the recent files. <strong>Your saved presets are kept</strong> (forget
|
| 121 |
-
have traced or exported is touched,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 157 |
-
|
| 158 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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-
|
| 15 |
-
<link rel="
|
| 16 |
-
<link rel="
|
| 17 |
-
<link rel="
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
</head>
|
| 19 |
<body>
|
| 20 |
-
<div id="boot" class="boot"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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:
|
| 3 |
-
size
|
|
|
|
| 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=
|
| 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:
|
| 3 |
-
size
|
|
|
|
| 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>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|