@feelgxxd/textel 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -29,6 +29,17 @@ Install it once with `npm install -g @feelgxxd/textel` and the command is just `
29
29
 
30
30
  Reads `.glb`, `.gltf`, `.obj` and `.stl`. Everything runs locally; nothing is uploaded.
31
31
 
32
+ ## Playground
33
+
34
+ **[feelgxxd.github.io/textel](https://feelgxxd.github.io/textel/)**: drop a model into the page, tune the
35
+ look with a live preview, and take it with you. Nothing to install, and your files never leave the browser.
36
+
37
+ - Start from a preset (terminal green, amber CRT, blueprint, ink on paper, blocks) or build your own look:
38
+ characters, color and gradients, outlines, shading, light, camera and motion, and per-material styles
39
+ - **Copy link** puts the whole look in the URL, so a link opens the same look (the model itself stays with you)
40
+ - Export it as a viewer embed for a page, a recorded `.textel.json` loop with a player embed, the matching
41
+ `textel` command for the terminal, or a preset JSON file
42
+
32
43
  ## Terminal
33
44
 
34
45
  ```
@@ -95,7 +106,7 @@ textel render logo.glb --aspect 1.5 -o logo.textel.json
95
106
  ```
96
107
 
97
108
  ```html
98
- <script type="module" src="https://cdn.jsdelivr.net/npm/@feelgxxd/textel@0.2/dist/textel-player.js"></script>
109
+ <script type="module" src="https://cdn.jsdelivr.net/npm/@feelgxxd/textel@0.4/dist/textel-player.js"></script>
99
110
  <textel-player src="logo.textel.json"></textel-player>
100
111
  ```
101
112
 
@@ -121,7 +132,7 @@ or `::part(screen)`. From JavaScript: `player.play()`, `player.pause()`, `player
121
132
  ### `<textel-viewer>`
122
133
 
123
134
  ```html
124
- <script type="module" src="https://cdn.jsdelivr.net/npm/@feelgxxd/textel@0.3/dist/textel-viewer.js"></script>
135
+ <script type="module" src="https://cdn.jsdelivr.net/npm/@feelgxxd/textel@0.4/dist/textel-viewer.js"></script>
125
136
  <textel-viewer src="model.glb" charset="hard" color="#301000,#ffb000" edges></textel-viewer>
126
137
  ```
127
138
 
@@ -141,7 +152,7 @@ background tabs and respects reduced motion.
141
152
  | `cols` | grid width in characters (default 96) |
142
153
  | `paused` | start without spinning |
143
154
  | `nodrag` | turn off dragging |
144
- | `zoomable` | wheel and `+` / `-` zoom |
155
+ | `zoomable` | wheel, pinch and `+` / `-` zoom; on touch screens it takes all touches, so the page does not scroll over it |
145
156
  | `label` | accessible name |
146
157
 
147
158
  A preset is applied first, then the attributes, then `viewer.look = { ... }` from JavaScript.
@@ -9,6 +9,7 @@ import { mergeStyles } from "../style.js";
9
9
  import { drag, gridFor, step, styleFromAttributes } from "./viewer-core.js";
10
10
  const CSS = `:host{display:block;position:relative;overflow:hidden;aspect-ratio:var(--textel-ratio,1);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;outline:none}
11
11
  :host(:not([nodrag])){cursor:grab;touch-action:pan-y}
12
+ :host([zoomable]:not([nodrag])){touch-action:none}
12
13
  :host(:not([nodrag]):active){cursor:grabbing}
13
14
  :host(:focus-visible){box-shadow:0 0 0 2px currentColor}
14
15
  pre{position:absolute;left:0;top:0;margin:0;transform-origin:0 0;font:inherit;font-size:16px;line-height:1;letter-spacing:0;white-space:pre;color:inherit;text-shadow:var(--textel-glow,none);-webkit-text-size-adjust:none;text-size-adjust:none;user-select:none;pointer-events:none}
@@ -60,6 +61,9 @@ export class TextelViewer extends Base {
60
61
  #last = -1;
61
62
  #dirty = true;
62
63
  #pointer = null;
64
+ /** every finger or pen on the element, for pinch zoom */
65
+ #touches = new Map();
66
+ #pinch = null;
63
67
  #load = null;
64
68
  #io;
65
69
  #ro;
@@ -280,21 +284,49 @@ export class TextelViewer extends Base {
280
284
  #onDown = (e) => {
281
285
  if (this.hasAttribute('nodrag') || !this.#renderer || e.button !== 0)
282
286
  return;
283
- this.#pointer = { x: e.clientX, y: e.clientY, t: performance.now() };
284
287
  this.setPointerCapture(e.pointerId);
288
+ this.#touches.set(e.pointerId, { x: e.clientX, y: e.clientY });
289
+ if (this.#touches.size === 2 && this.hasAttribute('zoomable')) {
290
+ // a second finger: stop turning and pinch instead
291
+ this.#pinch = { dist: this.#spread(), zoom: this.#zoom };
292
+ this.#pointer = null;
293
+ this.#motion = { ...this.#motion, vyaw: 0, vpitch: 0 };
294
+ }
295
+ else if (this.#touches.size === 1) {
296
+ this.#pointer = { id: e.pointerId, x: e.clientX, y: e.clientY, t: performance.now() };
297
+ }
285
298
  this.#kick();
286
299
  };
287
300
  #onMove = (e) => {
301
+ if (!this.#touches.has(e.pointerId))
302
+ return;
303
+ this.#touches.set(e.pointerId, { x: e.clientX, y: e.clientY });
304
+ if (this.#pinch) {
305
+ if (this.#pinch.dist > 0)
306
+ this.#setZoom(this.#pinch.zoom * (this.#spread() / this.#pinch.dist));
307
+ return;
308
+ }
288
309
  const p = this.#pointer;
289
- if (!p)
310
+ if (!p || p.id !== e.pointerId)
290
311
  return;
291
312
  const now = performance.now();
292
313
  this.#motion = drag(this.#motion, e.clientX - p.x, e.clientY - p.y, (now - p.t) / 1000, this.clientWidth, MAX_PITCH);
293
- this.#pointer = { x: e.clientX, y: e.clientY, t: now };
314
+ this.#pointer = { ...p, x: e.clientX, y: e.clientY, t: now };
294
315
  this.#kick();
295
316
  };
296
- #onUp = () => {
297
- if (!this.#pointer)
317
+ #onUp = (e) => {
318
+ if (!this.#touches.delete(e.pointerId))
319
+ return;
320
+ if (this.#pinch) {
321
+ // lifting a finger ends the pinch; a finger still down turns the model again, without a flick
322
+ if (this.#touches.size < 2)
323
+ this.#pinch = null;
324
+ const [rest] = [...this.#touches];
325
+ this.#pointer = rest && !this.#pinch ? { id: rest[0], ...rest[1], t: performance.now() } : null;
326
+ this.#kick();
327
+ return;
328
+ }
329
+ if (!this.#pointer || this.#pointer.id !== e.pointerId)
298
330
  return;
299
331
  // a pause before letting go means no flick
300
332
  if (performance.now() - this.#pointer.t > 80)
@@ -302,6 +334,11 @@ export class TextelViewer extends Base {
302
334
  this.#pointer = null;
303
335
  this.#kick();
304
336
  };
337
+ /** distance between the first two fingers, in pixels */
338
+ #spread() {
339
+ const [a, b] = [...this.#touches.values()];
340
+ return b ? Math.hypot(a.x - b.x, a.y - b.y) : 0;
341
+ }
305
342
  #onKey = (e) => {
306
343
  const m = this.#motion, step = 0.15;
307
344
  const moves = {
package/dist/examples.js CHANGED
@@ -41,18 +41,32 @@ function* dice() {
41
41
  }
42
42
  return false;
43
43
  };
44
- // pips are shallow dimples pressed into each face
44
+ // pips are shallow dimples pressed into each face: spheres of radius r centered just outside it
45
+ const r = pip + 0.06, centers = [];
46
+ for (let f = 0; f < 6; f++) {
47
+ const [ax, sg, u, v] = SIDES[f];
48
+ const list = [];
49
+ for (const [pu, pv] of FACES[f]) {
50
+ const c = [0, 0, 0];
51
+ c[ax] = sg * (s + 0.05);
52
+ c[u] = pu;
53
+ c[v] = pv;
54
+ list.push(...c);
55
+ }
56
+ centers.push(list);
57
+ }
45
58
  const field = (x, y, z) => {
46
59
  let d = sdBox(x, y, z, s, s, s, 0.18);
47
60
  const p = [x, y, z];
48
61
  for (let f = 0; f < 6; f++) {
49
- const [ax, sg, u, v] = SIDES[f];
50
- for (const [pu, pv] of FACES[f]) {
51
- const c = [0, 0, 0];
52
- c[ax] = sg * (s + 0.05);
53
- c[u] = pu;
54
- c[v] = pv;
55
- d = Math.max(d, -(Math.hypot(p[0] - c[0], p[1] - c[1], p[2] - c[2]) - pip - 0.06));
62
+ const [ax, sg] = SIDES[f];
63
+ // a dimple only shapes the surface near its own face; farther in, the box is deeper anyway
64
+ if (p[ax] * sg < s - 0.3)
65
+ continue;
66
+ const c = centers[f];
67
+ for (let i = 0; i < c.length; i += 3) {
68
+ const dx = x - c[i], dy = y - c[i + 1], dz = z - c[i + 2];
69
+ d = Math.max(d, r - Math.sqrt(dx * dx + dy * dy + dz * dz));
56
70
  }
57
71
  }
58
72
  return d;
@@ -1,10 +1,11 @@
1
- var yt=class{positions=[];normals=[];indices=[];triMaterial=[];materials=[];hasNormals=!0;matIndex=new Map;material(t){let n=this.matIndex.get(t);return n===void 0&&(n=this.materials.length,this.materials.push(t),this.matIndex.set(t,n)),n}vertex(t,n,r,s,i,o){let h=this.positions.length/3;return this.positions.push(t,n,r),s===void 0||i===void 0||o===void 0?(this.hasNormals=!1,this.normals.push(0,0,0)):this.normals.push(s,i,o),h}triangle(t,n,r,s){this.indices.push(t,n,r),this.triMaterial.push(s)}build(){return this.materials.length===0&&this.material("default"),{positions:new Float32Array(this.positions),normals:this.hasNormals&&this.positions.length?qe(new Float32Array(this.normals)):null,indices:new Uint32Array(this.indices),triMaterial:new Uint16Array(this.triMaterial),materials:this.materials}}};function qe(e){for(let t=0;t<e.length;t+=3){let n=Math.hypot(e[t],e[t+1],e[t+2])||1;e[t]/=n,e[t+1]/=n,e[t+2]/=n}return e}function xe(e){let t=e.positions,n=e.indices,r=t.length/3,s=new Uint32Array(r),i=new Map,o=[1/0,-1/0];for(let l=0;l<t.length;l++)o[0]=Math.min(o[0],t[l]),o[1]=Math.max(o[1],t[l]);let h=1e5/Math.max(o[1]-o[0],1e-9);for(let l=0;l<r;l++){let m=`${Math.round(t[l*3]*h)},${Math.round(t[l*3+1]*h)},${Math.round(t[l*3+2]*h)}`,v=i.get(m);v===void 0&&(v=i.size,i.set(m,v)),s[l]=v}let c=new Float64Array(i.size*3);for(let l=0;l<n.length;l+=3){let m=n[l]*3,v=n[l+1]*3,S=n[l+2]*3,u=t[v]-t[m],f=t[v+1]-t[m+1],a=t[v+2]-t[m+2],g=t[S]-t[m],p=t[S+1]-t[m+1],b=t[S+2]-t[m+2],A=f*b-a*p,G=a*g-u*b,O=u*p-f*g;for(let H of[n[l],n[l+1],n[l+2]]){let $=s[H]*3;c[$]+=A,c[$+1]+=G,c[$+2]+=O}}let y=new Float32Array(r*3);for(let l=0;l<r;l++){let m=s[l]*3,v=Math.hypot(c[m],c[m+1],c[m+2])||1;y[l*3]=c[m]/v,y[l*3+1]=c[m+1]/v,y[l*3+2]=c[m+2]/v}return y}var Ke={SCALAR:1,VEC2:2,VEC3:3,VEC4:4,MAT2:4,MAT3:9,MAT4:16};function ve(e,t={}){let n,r=null,s=new DataView(e.buffer,e.byteOffset,e.byteLength);if(e.length>=12&&s.getUint32(0,!0)===1179937895){let f=12;for(n={};f+8<=e.length;){let a=s.getUint32(f,!0),g=s.getUint32(f+4,!0),p=e.subarray(f+8,f+8+a);g===1313821514?n=JSON.parse(new TextDecoder().decode(p)):g===5130562&&(r=p),f+=8+a}}else n=JSON.parse(new TextDecoder().decode(e));let i=(n.extensionsRequired??[]).filter(f=>f==="KHR_draco_mesh_compression"||f==="EXT_meshopt_compression");if(i.length)throw new Error(`compressed glTF (${i.join(", ")}) is not supported yet; export it without compression`);let o=(n.buffers??[]).map((f,a)=>{if(f.uri===void 0){if(!r)throw new Error(`buffer ${a} has no data`);return r}let g=/^data:[^,]*;base64,(.*)$/.exec(f.uri);if(g)return tn(g[1]);if(!t.resolve)throw new Error(`external buffer "${f.uri}" needs a resolver`);return t.resolve(decodeURIComponent(f.uri))}),h=f=>{let a=n.accessors?.[f];if(!a)throw new Error(`missing accessor ${f}`);let g=Ke[a.type]??1,p=new Float32Array(a.count*g);if(a.bufferView===void 0)return{data:p,size:g,count:a.count};let b=n.bufferViews[a.bufferView],A=o[b.buffer],G=new DataView(A.buffer,A.byteOffset,A.byteLength),O=a.componentType,H=O===5126||O===5125?4:O===5123||O===5122?2:1,$=b.byteStride||H*g,J=(b.byteOffset??0)+(a.byteOffset??0),k=!!a.normalized;for(let d=0;d<a.count;d++)for(let et=0;et<g;et++){let Y=J+d*$+et*H,L;switch(O){case 5126:L=G.getFloat32(Y,!0);break;case 5125:L=G.getUint32(Y,!0);break;case 5123:L=G.getUint16(Y,!0),k&&(L/=65535);break;case 5122:L=G.getInt16(Y,!0),k&&(L=Math.max(L/32767,-1));break;case 5121:L=G.getUint8(Y),k&&(L/=255);break;case 5120:L=G.getInt8(Y),k&&(L=Math.max(L/127,-1));break;default:throw new Error(`unknown component type ${O}`)}p[d*g+et]=L}return{data:p,size:g,count:a.count}},c=new yt,y=f=>f===void 0?"default":n.materials?.[f]?.name||`material${f}`,l=(f,a,g)=>{if(g>64)throw new Error("node hierarchy too deep");let p=n.nodes?.[f];if(!p)return;let b=We(a,Ze(p));p.mesh!==void 0&&m(p.mesh,b);for(let A of p.children??[])l(A,b,g+1)},m=(f,a)=>{let g=Qe(a);for(let p of n.meshes?.[f]?.primitives??[]){let b=p.mode??4;if(b!==4&&b!==5&&b!==6||p.attributes.POSITION===void 0)continue;let A=h(p.attributes.POSITION),G=p.attributes.NORMAL!==void 0?h(p.attributes.NORMAL):null,O=c.material(y(p.material)),H=c.positions.length/3;for(let d=0;d<A.count;d++){let et=A.data[d*3],Y=A.data[d*3+1],L=A.data[d*3+2],w=a[0]*et+a[4]*Y+a[8]*L+a[12],I=a[1]*et+a[5]*Y+a[9]*L+a[13],C=a[2]*et+a[6]*Y+a[10]*L+a[14];if(G){let U=G.data[d*3],B=G.data[d*3+1],X=G.data[d*3+2];c.vertex(w,I,C,g[0]*U+g[3]*B+g[6]*X,g[1]*U+g[4]*B+g[7]*X,g[2]*U+g[5]*B+g[8]*X)}else c.vertex(w,I,C)}let $=p.indices!==void 0?h(p.indices).data:null,J=$?$.length:A.count,k=d=>H+($?$[d]:d);if(b===4)for(let d=0;d+2<J;d+=3)c.triangle(k(d),k(d+1),k(d+2),O);else if(b===5)for(let d=0;d+2<J;d++)d%2?c.triangle(k(d+1),k(d),k(d+2),O):c.triangle(k(d),k(d+1),k(d+2),O);else for(let d=1;d+1<J;d++)c.triangle(k(0),k(d),k(d+1),O)}},v=[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],u=n.scenes?.[n.scene??0]?.nodes;if(!u){let f=new Set((n.nodes??[]).flatMap(a=>a.children??[]));u=(n.nodes??[]).map((a,g)=>g).filter(a=>!f.has(a))}for(let f of u)l(f,v,0);if(!c.indices.length)throw new Error("no triangle meshes found in the glTF");return c.build()}function We(e,t){let n=new Array(16).fill(0);for(let r=0;r<4;r++)for(let s=0;s<4;s++)for(let i=0;i<4;i++)n[r*4+s]+=e[i*4+s]*t[r*4+i];return n}function Ze(e){if(e.matrix)return e.matrix;let[t,n,r]=e.translation??[0,0,0],[s,i,o,h]=e.rotation??[0,0,0,1],[c,y,l]=e.scale??[1,1,1];return[(1-2*(i*i+o*o))*c,2*(s*i+o*h)*c,2*(s*o-i*h)*c,0,2*(s*i-o*h)*y,(1-2*(s*s+o*o))*y,2*(i*o+s*h)*y,0,2*(s*o+i*h)*l,2*(i*o-s*h)*l,(1-2*(s*s+i*i))*l,0,t,n,r,1]}function Qe(e){let t=e[0],n=e[1],r=e[2],s=e[4],i=e[5],o=e[6],h=e[8],c=e[9],y=e[10],l=i*y-o*c,m=-(s*y-o*h),v=s*c-i*h,S=t*l+n*m+r*v||1;return[l/S,m/S,v/S,-(n*y-r*c)/S,(t*y-r*h)/S,-(t*c-n*h)/S,(n*o-r*i)/S,-(t*o-r*s)/S,(t*i-n*s)/S]}function tn(e){if(typeof atob=="function")return Uint8Array.from(atob(e),t=>t.charCodeAt(0));throw new Error("base64 decoding is not available")}function*Ae(e,t=4e3){let n=[],r=[],s=new yt,i=new Map,o=-1;for(let h=0,c=0;h<e.length;c++){let y=e.indexOf(`
2
- `,h);y<0&&(y=e.length);let l=e.slice(h,y).trim();if(h=y+1,c%t===t-1&&(yield),!l||l[0]==="#")continue;let m=l.split(/\s+/),v=m[0];if(v==="v")n.push(+m[1],+m[2],+m[3]);else if(v==="vn")r.push(+m[1],+m[2],+m[3]);else if(v==="usemtl")o=s.material(m.slice(1).join(" ")||"default"),i.clear();else if(v==="f"){o<0&&(o=s.material("default"));let S=[];for(let u=1;u<m.length;u++){let f=m[u],a=i.get(f);if(a===void 0){let[g,,p]=f.split("/"),b=parseInt(g,10);if(b=b<0?n.length/3+b:b-1,!(b>=0&&b<n.length/3))throw new Error(`obj line ${c+1}: bad vertex index "${f}"`);if(p){let A=parseInt(p,10);A=A<0?r.length/3+A:A-1,a=s.vertex(n[b*3],n[b*3+1],n[b*3+2],r[A*3],r[A*3+1],r[A*3+2])}else a=s.vertex(n[b*3],n[b*3+1],n[b*3+2]);i.set(f,a)}S.push(a)}for(let u=1;u+1<S.length;u++)s.triangle(S[0],S[u],S[u+1],o)}}return s.build()}function Se(e){let t=new DataView(e.buffer,e.byteOffset,e.byteLength);if(e.length>=84){let s=t.getUint32(80,!0);if(84+s*50===e.length){let i=new Float32Array(s*9);for(let o=0;o<s;o++){let h=84+o*50+12;for(let c=0;c<9;c++)i[o*9+c]=t.getFloat32(h+c*4,!0)}return Ie(i)}}let n=new TextDecoder().decode(e),r=[];for(let s of n.matchAll(/vertex\s+(\S+)\s+(\S+)\s+(\S+)/g))r.push(+s[1],+s[2],+s[3]);if(!r.length||r.length%9)throw new Error("not a valid STL file");return Ie(new Float32Array(r))}function Ie(e){let t=e.length/3,n=new Uint32Array(t);for(let r=0;r<t;r++)n[r]=r;return{positions:e,normals:null,indices:n,triMaterial:new Uint16Array(t/3),materials:["default"]}}function en(e,t){let n=t.toLowerCase().match(/\.[a-z0-9]+$/)?.[0]??"";if(n===".glb"||n===".gltf")return"gltf";if(n===".obj")return"obj";if(n===".stl")return"stl";if(e.length>=4&&e[0]===103&&e[1]===108&&e[2]===84&&e[3]===70)return"gltf";let r=new TextDecoder().decode(e.subarray(0,512)).trimStart();return r.startsWith("{")?"gltf":/^(v|vn|vt|o|g|mtllib|#)\s/m.test(r)?"obj":"stl"}function*nn(e,t="",n={}){let r=en(e,t);return r==="obj"?yield*Ae(new TextDecoder().decode(e)):r==="gltf"?ve(e,n):Se(e)}async function Bt(e,t="",n={},r=12){let s=nn(e,t,n),i=performance.now();for(;;){let o=s.next();if(o.done)return o.value;performance.now()-i>r&&(await new Promise(h=>setTimeout(h,0)),i=performance.now())}}function Ee(e){let{width:t,height:n,chars:r}=e,s=[];for(let i=0;i<n;i++)s.push(r.slice(i*t,i*t+t).join(""));return s.join(`
3
- `)}var Fe=(e,t,n)=>[e[0]+(t[0]-e[0])*n,e[1]+(t[1]-e[1])*n,e[2]+(t[2]-e[2])*n];function rn(e,t){if(e.length===1)return e[0];let n=Math.min(1,Math.max(0,t))*(e.length-1),r=Math.min(e.length-2,Math.floor(n));return Fe(e[r],e[r+1],n-r)}function sn(e,t,n){let r=e.style.colorSteps,s=r<0?(t??e.style.chars).length:r;return s<=1?s===1?1:n:Math.min(s-1,Math.floor(n*s))/(s-1)}function on(e,t){let n=e.mat[t];if(n<0)return{rgb:null,faint:!1};let r=e.materials[n],s=e.style.color,i=r.color??(s.mode==="gradient"?rn(s.stops,sn(e,r.chars,e.lum[t])):s.mode==="mono"?s.fg:null);return i?(r.opacity<1&&(i=Fe(s.bg??[0,0,0],i,r.opacity)),{rgb:[Math.round(i[0]),Math.round(i[1]),Math.round(i[2])],faint:!1}):{rgb:null,faint:r.opacity<1}}var an=e=>e==="<"?"&lt;":e===">"?"&gt;":e==="&"?"&amp;":e,ln=e=>"#"+e.map(t=>t.toString(16).padStart(2,"0")).join("");function Re(e){let{width:t,height:n,chars:r}=e,s="";for(let i=0;i<n;i++){let o="";for(let h=0;h<t;h++){let c=i*t+h,y=r[c];if(y!==" "){let{rgb:l,faint:m}=on(e,c),v=e.mat[c],S=v>=0?e.materials[v].opacity:1,u=l?`color:${ln(l)}`:m?`opacity:${S}`:"";u!==o&&(o&&(s+="</span>"),u&&(s+=`<span style="${u}">`),o=u)}s+=an(y)}o&&(s+="</span>"),i<n-1&&(s+=`
4
- `)}return s}var cn=[..."$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\\|()1{}[]?-_+~<>i!lI;:,\"^`'. "].reverse().join(""),Te={classic:".,-~:;=!*#$@",hard:" .:-=+*#%@",detailed:cn,blocks:" \u2591\u2592\u2593\u2588",dots:" \xB7\u2022\u25CF",minimal:" .:-="},V={width:80,height:40,aspect:2,charset:"classic",pitch:.3,spin:.66,distance:5,zoom:1,light:[-.45,.6,.65],ambient:0,shading:"auto",supersample:2,coverage:.5,edgeChars:"-/|\\",edgeThreshold:.03};function Ut(...e){let t={};for(let n of e){if(!n)continue;let{materials:r,...s}=n;for(let[i,o]of Object.entries(s))o!==void 0&&(t[i]=o);r&&(t.materials={...t.materials,...r})}return t}function Ce(e,t){if(e!==void 0&&e.length)return[...e];let n=t??V.charset,r=Te[n];if(!r)throw new Error(`unknown charset "${n}" (have: ${Object.keys(Te).join(", ")})`);return[...r]}function Nt(e={}){let t=(i,o,h=-1/0,c=1/0)=>{let y=i??o;if(!Number.isFinite(y))throw new Error(`not a number: ${i}`);return Math.min(c,Math.max(h,y))},n=e.light??V.light,r=Math.hypot(n[0],n[1],n[2])||1,s=[...e.edgeChars??V.edgeChars];if(s.length!==4)throw new Error("edgeChars needs exactly 4 glyphs: horizontal, rising, vertical, falling");return{width:Math.round(t(e.width,V.width,1,2e3)),height:Math.round(t(e.height,V.height,1,2e3)),aspect:t(e.aspect,V.aspect,.25,8),chars:Ce(e.chars,e.charset),pitch:t(e.pitch,V.pitch),fitPitch:Math.abs(t(e.fitPitch,0,0,Math.PI/2)),spin:t(e.spin,V.spin),distance:t(e.distance,V.distance,1.5,1e3),zoom:t(e.zoom,V.zoom,.01,100),light:[n[0]/r,n[1]/r,n[2]/r],ambient:t(e.ambient,V.ambient,0,1),shading:e.shading??V.shading,supersample:Math.round(t(e.supersample,V.supersample,1,4)),coverage:t(e.coverage,V.coverage,0,1),edges:e.edges===!0?"outline":e.edges||!1,edgeChars:s,edgeThreshold:t(e.edgeThreshold,V.edgeThreshold,0),color:hn(e.color),colorSteps:Math.round(t(e.colorSteps,-1,-1,4096)),materials:e.materials??{}}}function ut(e){let t=/^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(e.trim());if(!t)throw new Error(`not a hex color: ${e}`);let n=t[1];return n.length===3&&(n=n.replace(/./g,r=>r+r)),[parseInt(n.slice(0,2),16),parseInt(n.slice(2,4),16),parseInt(n.slice(4,6),16)]}function hn(e){let t=[255,255,255];if(e===void 0||e==="none")return{mode:"none",fg:t,stops:[],bg:null};if(typeof e=="string")return{mode:"mono",fg:ut(e),stops:[],bg:null};if(Array.isArray(e)){if(e.length===1)return{mode:"mono",fg:ut(e[0]),stops:[],bg:null};let s=e.map(ut);return{mode:"gradient",fg:s[s.length-1],stops:s,bg:null}}let n=(e.stops??[]).map(ut);return{mode:e.mode??(n.length>1?"gradient":e.fg?"mono":"none"),fg:e.fg?ut(e.fg):n[n.length-1]??t,stops:n,bg:e.bg?ut(e.bg):null}}function ze(e,t){let n={name:e,chars:null,color:null,opacity:1,carved:!1};return t===void 0||t==="solid"?n:t==="carved"?{...n,carved:!0}:t==="dim"?{...n,opacity:.6}:((t.chars!==void 0||t.charset!==void 0)&&(n.chars=Ce(t.chars,t.charset)),t.color&&(n.color=ut(t.color)),t.opacity!==void 0&&(n.opacity=Math.min(1,Math.max(0,t.opacity))),n.carved=!!t.carved,n)}var wt=-1;function ke(e,t={}){let n=e.positions.length/3,r=e.indices.length/3;if(!r)throw new Error("the model has no triangles");let s=e.indices,i=e.triMaterial,o=new Float32Array(n*3);{let w=e.positions,I=[1/0,1/0,1/0],C=[-1/0,-1/0,-1/0];for(let R=0;R<n;R++)for(let x=0;x<3;x++){let q=w[R*3+x];q<I[x]&&(I[x]=q),q>C[x]&&(C[x]=q)}let U=[0,1,2].map(R=>(I[R]+C[R])/2),B=0;for(let R=0;R<n;R++)B=Math.max(B,Math.hypot(w[R*3]-U[0],w[R*3+1]-U[1],w[R*3+2]-U[2]));let X=1/(B||1);for(let R=0;R<n;R++)for(let x=0;x<3;x++)o[R*3+x]=(w[R*3+x]-U[x])*X}let h=0,c=0;for(let w=0;w<n;w++)h=Math.max(h,Math.hypot(o[w*3],o[w*3+2])),c=Math.max(c,Math.abs(o[w*3+1]));let y=null,l=new Float32Array(n*3),m=new Float32Array(n),v=new Float32Array(n),S=new Float32Array(n),u=null,f=t,a=Nt(f),g=[],p=0,b=0,A=new Float32Array(0),G=new Float32Array(0),O=new Int16Array(0),H=new Float32Array(0),$=new Float32Array(0),J=new Int16Array(0);function k(){g=e.materials.map(C=>ze(C,a.materials[C]));let w=a.supersample;(p!==a.width*w||b!==a.height*w)&&(p=a.width*w,b=a.height*w,A=new Float32Array(p*b),G=new Float32Array(p*b),O=new Int16Array(p*b));let I=a.width*a.height;H.length!==I&&(H=new Float32Array(I),$=new Float32Array(I),J=new Int16Array(I))}k();function d(){return a.shading==="flat"?null:e.normals?e.normals:a.shading==="smooth"?y??=xe(e):null}function et(){let w=a.distance,I=Math.abs(a.pitch),C=Math.max(I,a.fitPitch),U=Math.atan2(h,c),B=nt=>c*Math.cos(nt)+h*Math.sin(nt),X=Math.max(h,.001),R=Math.max(U>I&&U<C?Math.hypot(c,h):Math.max(B(I),B(C)),.001),x=X/Math.sqrt(w*w-Math.min(X*X,w*w*.99)),q=R/Math.sqrt(w*w-1);return a.zoom*Math.min(p/2/x,b/2*a.aspect/q)*.98}function Y(w={}){let I=a.supersample,C=a.width,U=a.height,B=a.distance,X=a.aspect,R=w.yaw??0,x=w.pitch??a.pitch,q=w.roll??0,nt=Math.cos(R),ft=Math.sin(R),Mt=Math.cos(x),xt=Math.sin(x),ot=Math.cos(q),at=Math.sin(q),Tt=nt,Ct=0,E=ft,F=xt*ft,lt=Mt,mt=-xt*nt,zt=-Mt*ft,Vt=xt,kt=Mt*nt,Z=ot*Tt-at*F,vt=ot*Ct-at*lt,At=ot*E-at*mt,Ht=at*Tt+ot*F,Jt=at*Ct+ot*lt,Xt=at*E+ot*mt,_t=zt,Yt=Vt,qt=kt,Kt=et(),Ge=Kt/X,$e=p/2,Be=b/2;for(let M=0,T=0;M<n;M++,T+=3){let z=o[T],P=o[T+1],_=o[T+2],N=Z*z+vt*P+At*_,Q=Ht*z+Jt*P+Xt*_,tt=_t*z+Yt*P+qt*_;l[T]=N,l[T+1]=Q,l[T+2]=tt;let D=1/(B-tt);S[M]=D,m[M]=$e+Kt*N*D,v[M]=Be-Ge*Q*D}let ht=d();if(ht){(!u||u.length!==ht.length)&&(u=new Float32Array(ht.length));for(let M=0;M<ht.length;M+=3){let T=ht[M],z=ht[M+1],P=ht[M+2];u[M]=Z*T+vt*z+At*P,u[M+1]=Ht*T+Jt*z+Xt*P,u[M+2]=_t*T+Yt*z+qt*P}}A.fill(0),O.fill(wt);let[Wt,Zt,Qt]=a.light,Pt=a.ambient,te=1-Pt;for(let M=0;M<r;M++){let T=s[M*3],z=s[M*3+1],P=s[M*3+2],_=m[T],N=v[T],Q=m[z],tt=v[z],D=m[P],St=v[P],$t=(Q-_)*(St-N)-(tt-N)*(D-_);if($t===0||!Number.isFinite($t))continue;let ne=Math.max(0,Math.ceil(Math.min(_,Q,D)-.5)),re=Math.min(p-1,Math.floor(Math.max(_,Q,D)-.5)),se=Math.max(0,Math.ceil(Math.min(N,tt,St)-.5)),ie=Math.min(b-1,Math.floor(Math.max(N,tt,St)-.5));if(ne>re||se>ie)continue;let j=T*3,K=z*3,W=P*3,oe=l[K]-l[j],ae=l[K+1]-l[j+1],le=l[K+2]-l[j+2],ce=l[W]-l[j],he=l[W+1]-l[j+1],ue=l[W+2]-l[j+2],rt=ae*ue-le*he,st=le*ce-oe*ue,it=oe*he-ae*ce,De=(l[j]+l[K]+l[W])/3,je=(l[j+1]+l[K+1]+l[W+1])/3,Ve=(l[j+2]+l[K+2]+l[W+2])/3,fe=-De,me=-je,de=B-Ve,Ot=1,pe=0;if(u){let ct=u[j]+u[K]+u[W],dt=u[j+1]+u[K+1]+u[W+1],pt=u[j+2]+u[K+2]+u[W+2];rt*ct+st*dt+it*pt<0&&(rt=-rt,st=-st,it=-it),rt*fe+st*me+it*de<0&&(Ot=-1)}else{rt*fe+st*me+it*de<0&&(rt=-rt,st=-st,it=-it);let ct=Math.hypot(rt,st,it)||1;pe=Pt+te*Math.max(0,(rt*Wt+st*Zt+it*Qt)/ct)}let ge=1/$t,He=i[M],Je=S[T],Xe=S[z],_e=S[P];for(let ct=se;ct<=ie;ct++){let dt=ct+.5;for(let pt=ne;pt<=re;pt++){let Lt=pt+.5,gt=((Q-Lt)*(St-dt)-(tt-dt)*(D-Lt))*ge,bt=((D-Lt)*(N-dt)-(St-dt)*(_-Lt))*ge,Et=1-gt-bt;if(gt<-1e-7||bt<-1e-7||Et<-1e-7)continue;let be=gt*Je+bt*Xe+Et*_e,Ft=ct*p+pt;if(!(be<=A[Ft]))if(A[Ft]=be,O[Ft]=He,u){let ye=(gt*u[j]+bt*u[K]+Et*u[W])*Ot,we=(gt*u[j+1]+bt*u[K+1]+Et*u[W+1])*Ot,Me=(gt*u[j+2]+bt*u[K+2]+Et*u[W+2])*Ot,Ye=Math.hypot(ye,we,Me)||1;G[Ft]=Pt+te*Math.max(0,(ye*Wt+we*Zt+Me*Qt)/Ye)}else G[Ft]=pe}}}let ee=I*I,Ne=Math.max(1,Math.ceil(a.coverage*ee-1e-9)),Gt=new Int16Array(ee);for(let M=0;M<U;M++)for(let T=0;T<C;T++){let z=0,P=0,_=0;for(let Q=0;Q<I;Q++)for(let tt=0;tt<I;tt++){let D=(M*I+Q)*p+T*I+tt;O[D]!==wt&&(Gt[z++]=O[D],P+=G[D],A[D]>_&&(_=A[D]))}let N=M*C+T;if(z<Ne){J[N]=wt,$[N]=0,H[N]=0;continue}J[N]=z===1?Gt[0]:un(Gt,z),$[N]=P/z,H[N]=_}let It=new Array(C*U);for(let M=0;M<C*U;M++){let T=J[M];if(T===wt){It[M]=" ";continue}let z=g[T];if(z.carved){It[M]=" ";continue}let P=z.chars??a.chars;It[M]=P[Math.min(P.length-1,Math.floor($[M]*P.length))]}return a.edges&&L(It,C,U),{width:C,height:U,chars:It,lum:$.slice(),mat:J.slice(),materials:g,style:a}}function L(w,I,C){let U=a.edges==="depth",B=a.edgeThreshold,X=a.aspect,R=(E,F)=>E>=0&&F>=0&&E<I&&F<C&&J[F*I+E]!==wt,x=(E,F)=>R(E,F)?H[F*I+E]:0,q=Math.atan(X),nt=q/2,ft=(q+Math.PI/2)/2,Mt=Math.PI-ft,xt=Math.PI-nt,[ot,at,Tt,Ct]=a.edgeChars;for(let E=0;E<C;E++)for(let F=0;F<I;F++){let lt=E*I+F;if(J[lt]===wt||g[J[lt]].carved)continue;let mt=!1;for(let[vt,At]of[[1,0],[-1,0],[0,1],[0,-1]]){if(!R(F+vt,E+At)){mt=!0;break}if(U&&H[lt]-x(F+vt,E+At)>B*H[lt]){mt=!0;break}}if(!mt)continue;let zt=x(F+1,E-1)+2*x(F+1,E)+x(F+1,E+1)-x(F-1,E-1)-2*x(F-1,E)-x(F-1,E+1),kt=-(x(F-1,E+1)+2*x(F,E+1)+x(F+1,E+1)-x(F-1,E-1)-2*x(F,E-1)-x(F+1,E-1))/X;if(zt===0&&kt===0)continue;let Z=Math.atan2(zt,-kt);Z<0&&(Z+=Math.PI),Z>=Math.PI&&(Z-=Math.PI),w[lt]=Z<nt||Z>=xt?ot:Z<ft?at:Z<Mt?Tt:Ct}}return{mesh:e,get style(){return a},setStyle(w){f=Ut(f,w),a=Nt(f),k()},render:Y}}function un(e,t){let n=e[0],r=0;for(let s=0;s<t;s++){let i=0;for(let o=0;o<t;o++)e[o]===e[s]&&i++;i>r&&(r=i,n=e[s])}return n}function Oe(e,t,n,r,s){let i=Math.max(8,Math.round(e)),o=t>0?t:.6,h=n>0?n:1,c=r>0&&s>0?r/s:1,y=Math.max(4,Math.min(400,Math.round(i*o/(h*c)))),l=i*o,m=y*h;return{cols:i,rows:y,aspect:h/o,scale:r>0?r/l:1,ratio:l/m}}var Dt=(e,t)=>1-Math.exp(-e*t);function Le(e,t,n){let r=Math.min(Math.max(n,0),.1),s=e.vyaw+(t.spin-e.vyaw)*Dt(2,r),i=e.vpitch*(1-Dt(4,r)),o=e.pitch+i*r;return o+=(t.pitch-o)*Dt(1.5,r),o=Math.max(-t.maxPitch,Math.min(t.maxPitch,o)),{yaw:e.yaw+s*r,pitch:o,vyaw:s,vpitch:i}}function Ue(e,t,n,r,s,i){let o=s>0?Math.PI*2/s:.01,h=t*o,c=n*o,y=Math.max(-i,Math.min(i,e.pitch+c)),l=r>.001?1/r:0;return{yaw:e.yaw+h,pitch:y,vyaw:h*l,vpitch:c*l}}function Pe(e){let t={},n=h=>{let c=e(h);if(c===null||c.trim()==="")return;let y=Number(c);return Number.isFinite(y)?y:void 0},r=e("charset");r&&(t.charset=r);let s=e("chars");s&&(t.chars=s);let i=e("color");i&&(t.color=i.includes(",")?i.split(",").map(h=>h.trim()):i.trim());let o=e("edges");o!==null&&(t.edges=o==="depth"?"depth":o==="off"||o==="false"?!1:"outline");for(let h of["spin","pitch","zoom","distance","ambient"]){let c=n(h);c!==void 0&&(t[h]=c)}return t}var fn=`:host{display:block;position:relative;overflow:hidden;aspect-ratio:var(--textel-ratio,1);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;outline:none}
1
+ var yt=class{positions=[];normals=[];indices=[];triMaterial=[];materials=[];hasNormals=!0;matIndex=new Map;material(t){let n=this.matIndex.get(t);return n===void 0&&(n=this.materials.length,this.materials.push(t),this.matIndex.set(t,n)),n}vertex(t,n,r,i,s,o){let h=this.positions.length/3;return this.positions.push(t,n,r),i===void 0||s===void 0||o===void 0?(this.hasNormals=!1,this.normals.push(0,0,0)):this.normals.push(i,s,o),h}triangle(t,n,r,i){this.indices.push(t,n,r),this.triMaterial.push(i)}build(){return this.materials.length===0&&this.material("default"),{positions:new Float32Array(this.positions),normals:this.hasNormals&&this.positions.length?qe(new Float32Array(this.normals)):null,indices:new Uint32Array(this.indices),triMaterial:new Uint16Array(this.triMaterial),materials:this.materials}}};function qe(e){for(let t=0;t<e.length;t+=3){let n=Math.hypot(e[t],e[t+1],e[t+2])||1;e[t]/=n,e[t+1]/=n,e[t+2]/=n}return e}function xe(e){let t=e.positions,n=e.indices,r=t.length/3,i=new Uint32Array(r),s=new Map,o=[1/0,-1/0];for(let l=0;l<t.length;l++)o[0]=Math.min(o[0],t[l]),o[1]=Math.max(o[1],t[l]);let h=1e5/Math.max(o[1]-o[0],1e-9);for(let l=0;l<r;l++){let m=`${Math.round(t[l*3]*h)},${Math.round(t[l*3+1]*h)},${Math.round(t[l*3+2]*h)}`,v=s.get(m);v===void 0&&(v=s.size,s.set(m,v)),i[l]=v}let c=new Float64Array(s.size*3);for(let l=0;l<n.length;l+=3){let m=n[l]*3,v=n[l+1]*3,S=n[l+2]*3,u=t[v]-t[m],f=t[v+1]-t[m+1],a=t[v+2]-t[m+2],b=t[S]-t[m],p=t[S+1]-t[m+1],g=t[S+2]-t[m+2],A=f*g-a*p,G=a*b-u*g,O=u*p-f*b;for(let H of[n[l],n[l+1],n[l+2]]){let $=i[H]*3;c[$]+=A,c[$+1]+=G,c[$+2]+=O}}let y=new Float32Array(r*3);for(let l=0;l<r;l++){let m=i[l]*3,v=Math.hypot(c[m],c[m+1],c[m+2])||1;y[l*3]=c[m]/v,y[l*3+1]=c[m+1]/v,y[l*3+2]=c[m+2]/v}return y}var Ke={SCALAR:1,VEC2:2,VEC3:3,VEC4:4,MAT2:4,MAT3:9,MAT4:16};function ve(e,t={}){let n,r=null,i=new DataView(e.buffer,e.byteOffset,e.byteLength);if(e.length>=12&&i.getUint32(0,!0)===1179937895){let f=12;for(n={};f+8<=e.length;){let a=i.getUint32(f,!0),b=i.getUint32(f+4,!0),p=e.subarray(f+8,f+8+a);b===1313821514?n=JSON.parse(new TextDecoder().decode(p)):b===5130562&&(r=p),f+=8+a}}else n=JSON.parse(new TextDecoder().decode(e));let s=(n.extensionsRequired??[]).filter(f=>f==="KHR_draco_mesh_compression"||f==="EXT_meshopt_compression");if(s.length)throw new Error(`compressed glTF (${s.join(", ")}) is not supported yet; export it without compression`);let o=(n.buffers??[]).map((f,a)=>{if(f.uri===void 0){if(!r)throw new Error(`buffer ${a} has no data`);return r}let b=/^data:[^,]*;base64,(.*)$/.exec(f.uri);if(b)return tn(b[1]);if(!t.resolve)throw new Error(`external buffer "${f.uri}" needs a resolver`);return t.resolve(decodeURIComponent(f.uri))}),h=f=>{let a=n.accessors?.[f];if(!a)throw new Error(`missing accessor ${f}`);let b=Ke[a.type]??1,p=new Float32Array(a.count*b);if(a.bufferView===void 0)return{data:p,size:b,count:a.count};let g=n.bufferViews[a.bufferView],A=o[g.buffer],G=new DataView(A.buffer,A.byteOffset,A.byteLength),O=a.componentType,H=O===5126||O===5125?4:O===5123||O===5122?2:1,$=g.byteStride||H*b,X=(g.byteOffset??0)+(a.byteOffset??0),k=!!a.normalized;for(let d=0;d<a.count;d++)for(let et=0;et<b;et++){let _=X+d*$+et*H,L;switch(O){case 5126:L=G.getFloat32(_,!0);break;case 5125:L=G.getUint32(_,!0);break;case 5123:L=G.getUint16(_,!0),k&&(L/=65535);break;case 5122:L=G.getInt16(_,!0),k&&(L=Math.max(L/32767,-1));break;case 5121:L=G.getUint8(_),k&&(L/=255);break;case 5120:L=G.getInt8(_),k&&(L=Math.max(L/127,-1));break;default:throw new Error(`unknown component type ${O}`)}p[d*b+et]=L}return{data:p,size:b,count:a.count}},c=new yt,y=f=>f===void 0?"default":n.materials?.[f]?.name||`material${f}`,l=(f,a,b)=>{if(b>64)throw new Error("node hierarchy too deep");let p=n.nodes?.[f];if(!p)return;let g=We(a,Ze(p));p.mesh!==void 0&&m(p.mesh,g);for(let A of p.children??[])l(A,g,b+1)},m=(f,a)=>{let b=Qe(a);for(let p of n.meshes?.[f]?.primitives??[]){let g=p.mode??4;if(g!==4&&g!==5&&g!==6||p.attributes.POSITION===void 0)continue;let A=h(p.attributes.POSITION),G=p.attributes.NORMAL!==void 0?h(p.attributes.NORMAL):null,O=c.material(y(p.material)),H=c.positions.length/3;for(let d=0;d<A.count;d++){let et=A.data[d*3],_=A.data[d*3+1],L=A.data[d*3+2],w=a[0]*et+a[4]*_+a[8]*L+a[12],I=a[1]*et+a[5]*_+a[9]*L+a[13],T=a[2]*et+a[6]*_+a[10]*L+a[14];if(G){let U=G.data[d*3],B=G.data[d*3+1],J=G.data[d*3+2];c.vertex(w,I,T,b[0]*U+b[3]*B+b[6]*J,b[1]*U+b[4]*B+b[7]*J,b[2]*U+b[5]*B+b[8]*J)}else c.vertex(w,I,T)}let $=p.indices!==void 0?h(p.indices).data:null,X=$?$.length:A.count,k=d=>H+($?$[d]:d);if(g===4)for(let d=0;d+2<X;d+=3)c.triangle(k(d),k(d+1),k(d+2),O);else if(g===5)for(let d=0;d+2<X;d++)d%2?c.triangle(k(d+1),k(d),k(d+2),O):c.triangle(k(d),k(d+1),k(d+2),O);else for(let d=1;d+1<X;d++)c.triangle(k(0),k(d),k(d+1),O)}},v=[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],u=n.scenes?.[n.scene??0]?.nodes;if(!u){let f=new Set((n.nodes??[]).flatMap(a=>a.children??[]));u=(n.nodes??[]).map((a,b)=>b).filter(a=>!f.has(a))}for(let f of u)l(f,v,0);if(!c.indices.length)throw new Error("no triangle meshes found in the glTF");return c.build()}function We(e,t){let n=new Array(16).fill(0);for(let r=0;r<4;r++)for(let i=0;i<4;i++)for(let s=0;s<4;s++)n[r*4+i]+=e[s*4+i]*t[r*4+s];return n}function Ze(e){if(e.matrix)return e.matrix;let[t,n,r]=e.translation??[0,0,0],[i,s,o,h]=e.rotation??[0,0,0,1],[c,y,l]=e.scale??[1,1,1];return[(1-2*(s*s+o*o))*c,2*(i*s+o*h)*c,2*(i*o-s*h)*c,0,2*(i*s-o*h)*y,(1-2*(i*i+o*o))*y,2*(s*o+i*h)*y,0,2*(i*o+s*h)*l,2*(s*o-i*h)*l,(1-2*(i*i+s*s))*l,0,t,n,r,1]}function Qe(e){let t=e[0],n=e[1],r=e[2],i=e[4],s=e[5],o=e[6],h=e[8],c=e[9],y=e[10],l=s*y-o*c,m=-(i*y-o*h),v=i*c-s*h,S=t*l+n*m+r*v||1;return[l/S,m/S,v/S,-(n*y-r*c)/S,(t*y-r*h)/S,-(t*c-n*h)/S,(n*o-r*s)/S,-(t*o-r*i)/S,(t*s-n*i)/S]}function tn(e){if(typeof atob=="function")return Uint8Array.from(atob(e),t=>t.charCodeAt(0));throw new Error("base64 decoding is not available")}function*Ae(e,t=4e3){let n=[],r=[],i=new yt,s=new Map,o=-1;for(let h=0,c=0;h<e.length;c++){let y=e.indexOf(`
2
+ `,h);y<0&&(y=e.length);let l=e.slice(h,y).trim();if(h=y+1,c%t===t-1&&(yield),!l||l[0]==="#")continue;let m=l.split(/\s+/),v=m[0];if(v==="v")n.push(+m[1],+m[2],+m[3]);else if(v==="vn")r.push(+m[1],+m[2],+m[3]);else if(v==="usemtl")o=i.material(m.slice(1).join(" ")||"default"),s.clear();else if(v==="f"){o<0&&(o=i.material("default"));let S=[];for(let u=1;u<m.length;u++){let f=m[u],a=s.get(f);if(a===void 0){let[b,,p]=f.split("/"),g=parseInt(b,10);if(g=g<0?n.length/3+g:g-1,!(g>=0&&g<n.length/3))throw new Error(`obj line ${c+1}: bad vertex index "${f}"`);if(p){let A=parseInt(p,10);A=A<0?r.length/3+A:A-1,a=i.vertex(n[g*3],n[g*3+1],n[g*3+2],r[A*3],r[A*3+1],r[A*3+2])}else a=i.vertex(n[g*3],n[g*3+1],n[g*3+2]);s.set(f,a)}S.push(a)}for(let u=1;u+1<S.length;u++)i.triangle(S[0],S[u],S[u+1],o)}}return i.build()}function Se(e){let t=new DataView(e.buffer,e.byteOffset,e.byteLength);if(e.length>=84){let i=t.getUint32(80,!0);if(84+i*50===e.length){let s=new Float32Array(i*9);for(let o=0;o<i;o++){let h=84+o*50+12;for(let c=0;c<9;c++)s[o*9+c]=t.getFloat32(h+c*4,!0)}return Ie(s)}}let n=new TextDecoder().decode(e),r=[];for(let i of n.matchAll(/vertex\s+(\S+)\s+(\S+)\s+(\S+)/g))r.push(+i[1],+i[2],+i[3]);if(!r.length||r.length%9)throw new Error("not a valid STL file");return Ie(new Float32Array(r))}function Ie(e){let t=e.length/3,n=new Uint32Array(t);for(let r=0;r<t;r++)n[r]=r;return{positions:e,normals:null,indices:n,triMaterial:new Uint16Array(t/3),materials:["default"]}}function en(e,t){let n=t.toLowerCase().match(/\.[a-z0-9]+$/)?.[0]??"";if(n===".glb"||n===".gltf")return"gltf";if(n===".obj")return"obj";if(n===".stl")return"stl";if(e.length>=4&&e[0]===103&&e[1]===108&&e[2]===84&&e[3]===70)return"gltf";let r=new TextDecoder().decode(e.subarray(0,512)).trimStart();return r.startsWith("{")?"gltf":/^(v|vn|vt|o|g|mtllib|#)\s/m.test(r)?"obj":"stl"}function*nn(e,t="",n={}){let r=en(e,t);return r==="obj"?yield*Ae(new TextDecoder().decode(e)):r==="gltf"?ve(e,n):Se(e)}async function Bt(e,t="",n={},r=12){let i=nn(e,t,n),s=performance.now();for(;;){let o=i.next();if(o.done)return o.value;performance.now()-s>r&&(await new Promise(h=>setTimeout(h,0)),s=performance.now())}}function Ee(e){let{width:t,height:n,chars:r}=e,i=[];for(let s=0;s<n;s++)i.push(r.slice(s*t,s*t+t).join(""));return i.join(`
3
+ `)}var Fe=(e,t,n)=>[e[0]+(t[0]-e[0])*n,e[1]+(t[1]-e[1])*n,e[2]+(t[2]-e[2])*n];function rn(e,t){if(e.length===1)return e[0];let n=Math.min(1,Math.max(0,t))*(e.length-1),r=Math.min(e.length-2,Math.floor(n));return Fe(e[r],e[r+1],n-r)}function sn(e,t,n){let r=e.style.colorSteps,i=r<0?(t??e.style.chars).length:r;return i<=1?i===1?1:n:Math.min(i-1,Math.floor(n*i))/(i-1)}function on(e,t){let n=e.mat[t];if(n<0)return{rgb:null,faint:!1};let r=e.materials[n],i=e.style.color,s=r.color??(i.mode==="gradient"?rn(i.stops,sn(e,r.chars,e.lum[t])):i.mode==="mono"?i.fg:null);return s?(r.opacity<1&&(s=Fe(i.bg??[0,0,0],s,r.opacity)),{rgb:[Math.round(s[0]),Math.round(s[1]),Math.round(s[2])],faint:!1}):{rgb:null,faint:r.opacity<1}}var an=e=>e==="<"?"&lt;":e===">"?"&gt;":e==="&"?"&amp;":e,ln=e=>"#"+e.map(t=>t.toString(16).padStart(2,"0")).join("");function ze(e){let{width:t,height:n,chars:r}=e,i="";for(let s=0;s<n;s++){let o="";for(let h=0;h<t;h++){let c=s*t+h,y=r[c];if(y!==" "){let{rgb:l,faint:m}=on(e,c),v=e.mat[c],S=v>=0?e.materials[v].opacity:1,u=l?`color:${ln(l)}`:m?`opacity:${S}`:"";u!==o&&(o&&(i+="</span>"),u&&(i+=`<span style="${u}">`),o=u)}i+=an(y)}o&&(i+="</span>"),s<n-1&&(i+=`
4
+ `)}return i}var cn=[..."$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\\|()1{}[]?-_+~<>i!lI;:,\"^`'. "].reverse().join(""),Re={classic:".,-~:;=!*#$@",hard:" .:-=+*#%@",detailed:cn,blocks:" \u2591\u2592\u2593\u2588",dots:" \xB7\u2022\u25CF",minimal:" .:-="},V={width:80,height:40,aspect:2,charset:"classic",pitch:.3,spin:.66,distance:5,zoom:1,light:[-.45,.6,.65],ambient:0,shading:"auto",supersample:2,coverage:.5,edgeChars:"-/|\\",edgeThreshold:.03};function Ut(...e){let t={};for(let n of e){if(!n)continue;let{materials:r,...i}=n;for(let[s,o]of Object.entries(i))o!==void 0&&(t[s]=o);r&&(t.materials={...t.materials,...r})}return t}function Te(e,t){if(e!==void 0&&e.length)return[...e];let n=t??V.charset,r=Re[n];if(!r)throw new Error(`unknown charset "${n}" (have: ${Object.keys(Re).join(", ")})`);return[...r]}function Nt(e={}){let t=(s,o,h=-1/0,c=1/0)=>{let y=s??o;if(!Number.isFinite(y))throw new Error(`not a number: ${s}`);return Math.min(c,Math.max(h,y))},n=e.light??V.light,r=Math.hypot(n[0],n[1],n[2])||1,i=[...e.edgeChars??V.edgeChars];if(i.length!==4)throw new Error("edgeChars needs exactly 4 glyphs: horizontal, rising, vertical, falling");return{width:Math.round(t(e.width,V.width,1,2e3)),height:Math.round(t(e.height,V.height,1,2e3)),aspect:t(e.aspect,V.aspect,.25,8),chars:Te(e.chars,e.charset),pitch:t(e.pitch,V.pitch),fitPitch:Math.abs(t(e.fitPitch,0,0,Math.PI/2)),spin:t(e.spin,V.spin),distance:t(e.distance,V.distance,1.5,1e3),zoom:t(e.zoom,V.zoom,.01,100),light:[n[0]/r,n[1]/r,n[2]/r],ambient:t(e.ambient,V.ambient,0,1),shading:e.shading??V.shading,supersample:Math.round(t(e.supersample,V.supersample,1,4)),coverage:t(e.coverage,V.coverage,0,1),edges:e.edges===!0?"outline":e.edges||!1,edgeChars:i,edgeThreshold:t(e.edgeThreshold,V.edgeThreshold,0),color:hn(e.color),colorSteps:Math.round(t(e.colorSteps,-1,-1,4096)),materials:e.materials??{}}}function ut(e){let t=/^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(e.trim());if(!t)throw new Error(`not a hex color: ${e}`);let n=t[1];return n.length===3&&(n=n.replace(/./g,r=>r+r)),[parseInt(n.slice(0,2),16),parseInt(n.slice(2,4),16),parseInt(n.slice(4,6),16)]}function hn(e){let t=[255,255,255];if(e===void 0||e==="none")return{mode:"none",fg:t,stops:[],bg:null};if(typeof e=="string")return{mode:"mono",fg:ut(e),stops:[],bg:null};if(Array.isArray(e)){if(e.length===1)return{mode:"mono",fg:ut(e[0]),stops:[],bg:null};let i=e.map(ut);return{mode:"gradient",fg:i[i.length-1],stops:i,bg:null}}let n=(e.stops??[]).map(ut);return{mode:e.mode??(n.length>1?"gradient":e.fg?"mono":"none"),fg:e.fg?ut(e.fg):n[n.length-1]??t,stops:n,bg:e.bg?ut(e.bg):null}}function Ce(e,t){let n={name:e,chars:null,color:null,opacity:1,carved:!1};return t===void 0||t==="solid"?n:t==="carved"?{...n,carved:!0}:t==="dim"?{...n,opacity:.6}:((t.chars!==void 0||t.charset!==void 0)&&(n.chars=Te(t.chars,t.charset)),t.color&&(n.color=ut(t.color)),t.opacity!==void 0&&(n.opacity=Math.min(1,Math.max(0,t.opacity))),n.carved=!!t.carved,n)}var wt=-1;function ke(e,t={}){let n=e.positions.length/3,r=e.indices.length/3;if(!r)throw new Error("the model has no triangles");let i=e.indices,s=e.triMaterial,o=new Float32Array(n*3);{let w=e.positions,I=[1/0,1/0,1/0],T=[-1/0,-1/0,-1/0];for(let z=0;z<n;z++)for(let x=0;x<3;x++){let q=w[z*3+x];q<I[x]&&(I[x]=q),q>T[x]&&(T[x]=q)}let U=[0,1,2].map(z=>(I[z]+T[z])/2),B=0;for(let z=0;z<n;z++)B=Math.max(B,Math.hypot(w[z*3]-U[0],w[z*3+1]-U[1],w[z*3+2]-U[2]));let J=1/(B||1);for(let z=0;z<n;z++)for(let x=0;x<3;x++)o[z*3+x]=(w[z*3+x]-U[x])*J}let h=0,c=0;for(let w=0;w<n;w++)h=Math.max(h,Math.hypot(o[w*3],o[w*3+2])),c=Math.max(c,Math.abs(o[w*3+1]));let y=null,l=new Float32Array(n*3),m=new Float32Array(n),v=new Float32Array(n),S=new Float32Array(n),u=null,f=t,a=Nt(f),b=[],p=0,g=0,A=new Float32Array(0),G=new Float32Array(0),O=new Int16Array(0),H=new Float32Array(0),$=new Float32Array(0),X=new Int16Array(0);function k(){b=e.materials.map(T=>Ce(T,a.materials[T]));let w=a.supersample;(p!==a.width*w||g!==a.height*w)&&(p=a.width*w,g=a.height*w,A=new Float32Array(p*g),G=new Float32Array(p*g),O=new Int16Array(p*g));let I=a.width*a.height;H.length!==I&&(H=new Float32Array(I),$=new Float32Array(I),X=new Int16Array(I))}k();function d(){return a.shading==="flat"?null:e.normals?e.normals:a.shading==="smooth"?y??=xe(e):null}function et(){let w=a.distance,I=Math.abs(a.pitch),T=Math.max(I,a.fitPitch),U=Math.atan2(h,c),B=nt=>c*Math.cos(nt)+h*Math.sin(nt),J=Math.max(h,.001),z=Math.max(U>I&&U<T?Math.hypot(c,h):Math.max(B(I),B(T)),.001),x=J/Math.sqrt(w*w-Math.min(J*J,w*w*.99)),q=z/Math.sqrt(w*w-1);return a.zoom*Math.min(p/2/x,g/2*a.aspect/q)*.98}function _(w={}){let I=a.supersample,T=a.width,U=a.height,B=a.distance,J=a.aspect,z=w.yaw??0,x=w.pitch??a.pitch,q=w.roll??0,nt=Math.cos(z),ft=Math.sin(z),Mt=Math.cos(x),xt=Math.sin(x),ot=Math.cos(q),at=Math.sin(q),Rt=nt,Tt=0,E=ft,F=xt*ft,lt=Mt,mt=-xt*nt,Ct=-Mt*ft,Vt=xt,kt=Mt*nt,Z=ot*Rt-at*F,vt=ot*Tt-at*lt,At=ot*E-at*mt,Ht=at*Rt+ot*F,Xt=at*Tt+ot*lt,Jt=at*E+ot*mt,Yt=Ct,_t=Vt,qt=kt,Kt=et(),Ge=Kt/J,$e=p/2,Be=g/2;for(let M=0,R=0;M<n;M++,R+=3){let C=o[R],P=o[R+1],Y=o[R+2],N=Z*C+vt*P+At*Y,Q=Ht*C+Xt*P+Jt*Y,tt=Yt*C+_t*P+qt*Y;l[R]=N,l[R+1]=Q,l[R+2]=tt;let D=1/(B-tt);S[M]=D,m[M]=$e+Kt*N*D,v[M]=Be-Ge*Q*D}let ht=d();if(ht){(!u||u.length!==ht.length)&&(u=new Float32Array(ht.length));for(let M=0;M<ht.length;M+=3){let R=ht[M],C=ht[M+1],P=ht[M+2];u[M]=Z*R+vt*C+At*P,u[M+1]=Ht*R+Xt*C+Jt*P,u[M+2]=Yt*R+_t*C+qt*P}}A.fill(0),O.fill(wt);let[Wt,Zt,Qt]=a.light,Pt=a.ambient,te=1-Pt;for(let M=0;M<r;M++){let R=i[M*3],C=i[M*3+1],P=i[M*3+2],Y=m[R],N=v[R],Q=m[C],tt=v[C],D=m[P],St=v[P],$t=(Q-Y)*(St-N)-(tt-N)*(D-Y);if($t===0||!Number.isFinite($t))continue;let ne=Math.max(0,Math.ceil(Math.min(Y,Q,D)-.5)),re=Math.min(p-1,Math.floor(Math.max(Y,Q,D)-.5)),ie=Math.max(0,Math.ceil(Math.min(N,tt,St)-.5)),se=Math.min(g-1,Math.floor(Math.max(N,tt,St)-.5));if(ne>re||ie>se)continue;let j=R*3,K=C*3,W=P*3,oe=l[K]-l[j],ae=l[K+1]-l[j+1],le=l[K+2]-l[j+2],ce=l[W]-l[j],he=l[W+1]-l[j+1],ue=l[W+2]-l[j+2],rt=ae*ue-le*he,it=le*ce-oe*ue,st=oe*he-ae*ce,De=(l[j]+l[K]+l[W])/3,je=(l[j+1]+l[K+1]+l[W+1])/3,Ve=(l[j+2]+l[K+2]+l[W+2])/3,fe=-De,me=-je,de=B-Ve,Ot=1,pe=0;if(u){let ct=u[j]+u[K]+u[W],dt=u[j+1]+u[K+1]+u[W+1],pt=u[j+2]+u[K+2]+u[W+2];rt*ct+it*dt+st*pt<0&&(rt=-rt,it=-it,st=-st),rt*fe+it*me+st*de<0&&(Ot=-1)}else{rt*fe+it*me+st*de<0&&(rt=-rt,it=-it,st=-st);let ct=Math.hypot(rt,it,st)||1;pe=Pt+te*Math.max(0,(rt*Wt+it*Zt+st*Qt)/ct)}let be=1/$t,He=s[M],Xe=S[R],Je=S[C],Ye=S[P];for(let ct=ie;ct<=se;ct++){let dt=ct+.5;for(let pt=ne;pt<=re;pt++){let Lt=pt+.5,bt=((Q-Lt)*(St-dt)-(tt-dt)*(D-Lt))*be,gt=((D-Lt)*(N-dt)-(St-dt)*(Y-Lt))*be,Et=1-bt-gt;if(bt<-1e-7||gt<-1e-7||Et<-1e-7)continue;let ge=bt*Xe+gt*Je+Et*Ye,Ft=ct*p+pt;if(!(ge<=A[Ft]))if(A[Ft]=ge,O[Ft]=He,u){let ye=(bt*u[j]+gt*u[K]+Et*u[W])*Ot,we=(bt*u[j+1]+gt*u[K+1]+Et*u[W+1])*Ot,Me=(bt*u[j+2]+gt*u[K+2]+Et*u[W+2])*Ot,_e=Math.hypot(ye,we,Me)||1;G[Ft]=Pt+te*Math.max(0,(ye*Wt+we*Zt+Me*Qt)/_e)}else G[Ft]=pe}}}let ee=I*I,Ne=Math.max(1,Math.ceil(a.coverage*ee-1e-9)),Gt=new Int16Array(ee);for(let M=0;M<U;M++)for(let R=0;R<T;R++){let C=0,P=0,Y=0;for(let Q=0;Q<I;Q++)for(let tt=0;tt<I;tt++){let D=(M*I+Q)*p+R*I+tt;O[D]!==wt&&(Gt[C++]=O[D],P+=G[D],A[D]>Y&&(Y=A[D]))}let N=M*T+R;if(C<Ne){X[N]=wt,$[N]=0,H[N]=0;continue}X[N]=C===1?Gt[0]:un(Gt,C),$[N]=P/C,H[N]=Y}let It=new Array(T*U);for(let M=0;M<T*U;M++){let R=X[M];if(R===wt){It[M]=" ";continue}let C=b[R];if(C.carved){It[M]=" ";continue}let P=C.chars??a.chars;It[M]=P[Math.min(P.length-1,Math.floor($[M]*P.length))]}return a.edges&&L(It,T,U),{width:T,height:U,chars:It,lum:$.slice(),mat:X.slice(),materials:b,style:a}}function L(w,I,T){let U=a.edges==="depth",B=a.edgeThreshold,J=a.aspect,z=(E,F)=>E>=0&&F>=0&&E<I&&F<T&&X[F*I+E]!==wt,x=(E,F)=>z(E,F)?H[F*I+E]:0,q=Math.atan(J),nt=q/2,ft=(q+Math.PI/2)/2,Mt=Math.PI-ft,xt=Math.PI-nt,[ot,at,Rt,Tt]=a.edgeChars;for(let E=0;E<T;E++)for(let F=0;F<I;F++){let lt=E*I+F;if(X[lt]===wt||b[X[lt]].carved)continue;let mt=!1;for(let[vt,At]of[[1,0],[-1,0],[0,1],[0,-1]]){if(!z(F+vt,E+At)){mt=!0;break}if(U&&H[lt]-x(F+vt,E+At)>B*H[lt]){mt=!0;break}}if(!mt)continue;let Ct=x(F+1,E-1)+2*x(F+1,E)+x(F+1,E+1)-x(F-1,E-1)-2*x(F-1,E)-x(F-1,E+1),kt=-(x(F-1,E+1)+2*x(F,E+1)+x(F+1,E+1)-x(F-1,E-1)-2*x(F,E-1)-x(F+1,E-1))/J;if(Ct===0&&kt===0)continue;let Z=Math.atan2(Ct,-kt);Z<0&&(Z+=Math.PI),Z>=Math.PI&&(Z-=Math.PI),w[lt]=Z<nt||Z>=xt?ot:Z<ft?at:Z<Mt?Rt:Tt}}return{mesh:e,get style(){return a},setStyle(w){f=Ut(f,w),a=Nt(f),k()},render:_}}function un(e,t){let n=e[0],r=0;for(let i=0;i<t;i++){let s=0;for(let o=0;o<t;o++)e[o]===e[i]&&s++;s>r&&(r=s,n=e[i])}return n}function Oe(e,t,n,r,i){let s=Math.max(8,Math.round(e)),o=t>0?t:.6,h=n>0?n:1,c=r>0&&i>0?r/i:1,y=Math.max(4,Math.min(400,Math.round(s*o/(h*c)))),l=s*o,m=y*h;return{cols:s,rows:y,aspect:h/o,scale:r>0?r/l:1,ratio:l/m}}var Dt=(e,t)=>1-Math.exp(-e*t);function Le(e,t,n){let r=Math.min(Math.max(n,0),.1),i=e.vyaw+(t.spin-e.vyaw)*Dt(2,r),s=e.vpitch*(1-Dt(4,r)),o=e.pitch+s*r;return o+=(t.pitch-o)*Dt(1.5,r),o=Math.max(-t.maxPitch,Math.min(t.maxPitch,o)),{yaw:e.yaw+i*r,pitch:o,vyaw:i,vpitch:s}}function Ue(e,t,n,r,i,s){let o=i>0?Math.PI*2/i:.01,h=t*o,c=n*o,y=Math.max(-s,Math.min(s,e.pitch+c)),l=r>.001?1/r:0;return{yaw:e.yaw+h,pitch:y,vyaw:h*l,vpitch:c*l}}function Pe(e){let t={},n=h=>{let c=e(h);if(c===null||c.trim()==="")return;let y=Number(c);return Number.isFinite(y)?y:void 0},r=e("charset");r&&(t.charset=r);let i=e("chars");i&&(t.chars=i);let s=e("color");s&&(t.color=s.includes(",")?s.split(",").map(h=>h.trim()):s.trim());let o=e("edges");o!==null&&(t.edges=o==="depth"?"depth":o==="off"||o==="false"?!1:"outline");for(let h of["spin","pitch","zoom","distance","ambient"]){let c=n(h);c!==void 0&&(t[h]=c)}return t}var fn=`:host{display:block;position:relative;overflow:hidden;aspect-ratio:var(--textel-ratio,1);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;outline:none}
5
5
  :host(:not([nodrag])){cursor:grab;touch-action:pan-y}
6
+ :host([zoomable]:not([nodrag])){touch-action:none}
6
7
  :host(:not([nodrag]):active){cursor:grabbing}
7
8
  :host(:focus-visible){box-shadow:0 0 0 2px currentColor}
8
9
  pre{position:absolute;left:0;top:0;margin:0;transform-origin:0 0;font:inherit;font-size:16px;line-height:1;letter-spacing:0;white-space:pre;color:inherit;text-shadow:var(--textel-glow,none);-webkit-text-size-adjust:none;text-size-adjust:none;user-select:none;pointer-events:none}
9
- .probe{visibility:hidden}`,Rt=1.2,mn=["charset","chars","color","edges","spin","pitch","zoom","distance","ambient"],dn=typeof HTMLElement>"u"?class{}:HTMLElement;async function pn(e,t){let n=async i=>{let o=await fetch(i,{signal:t});if(!o.ok)throw new Error(`${o.status} ${o.statusText} for ${i}`);return new Uint8Array(await o.arrayBuffer())},r=await n(e),s=new Map;if(/\.gltf(\?|#|$)/i.test(e)){let i=JSON.parse(new TextDecoder().decode(r));for(let o of i.buffers??[])o.uri&&!o.uri.startsWith("data:")&&s.set(decodeURIComponent(o.uri),await n(new URL(o.uri,new URL(e,location.href)).href))}return Bt(r,e.split(/[?#]/)[0],{resolve:i=>{let o=s.get(i);if(!o)throw new Error(`missing buffer ${i}`);return o}})}var jt=class extends dn{static observedAttributes=["src","preset","cols","paused","label",...mn];#i;#l;#f=null;#n=null;#A={};#g={};#c=null;#t={yaw:0,pitch:0,vyaw:0,vpitch:0};#h=1;#o=!0;#I=!0;#r=0;#m=-1;#d=!0;#s=null;#S=null;#b;#y;#T=typeof matchMedia=="function"&&matchMedia("(prefers-reduced-motion: reduce)").matches;constructor(){super();let t=this.attachShadow({mode:"open"}),n=document.createElement("style");n.textContent=fn,this.#i=document.createElement("pre"),this.#i.setAttribute("part","screen"),this.#l=document.createElement("pre"),this.#l.className="probe",this.#l.textContent=Array(4).fill("M".repeat(40)).join(`
10
- `),t.append(n,this.#l,this.#i),this.addEventListener("pointerdown",this.#z),this.addEventListener("pointermove",this.#k),this.addEventListener("pointerup",this.#R),this.addEventListener("pointercancel",this.#R),this.addEventListener("keydown",this.#O),this.addEventListener("wheel",this.#L,{passive:!1})}connectedCallback(){this.setAttribute("role","img"),this.hasAttribute("tabindex")||(this.tabIndex=0),this.#E(),this.#o=!this.hasAttribute("paused"),typeof IntersectionObserver=="function"&&(this.#b=new IntersectionObserver(([t])=>{this.#I=t.isIntersecting,this.#e()}),this.#b.observe(this)),typeof ResizeObserver=="function"&&(this.#y=new ResizeObserver(()=>this.#a()),this.#y.observe(this)),this.ownerDocument.addEventListener("visibilitychange",this.#e),this.ownerDocument.fonts?.ready.then(()=>this.#a()),this.#a(),this.#e()}disconnectedCallback(){this.#b?.disconnect(),this.#y?.disconnect(),this.ownerDocument.removeEventListener("visibilitychange",this.#e),cancelAnimationFrame(this.#r),this.#r=0}attributeChangedCallback(t,n,r){n!==r&&(t==="src"?r&&this.load(r):t==="preset"?r&&this.#C(r):t==="paused"?r===null?this.play():this.pause():t==="label"?this.#E():t==="cols"?this.#a():this.#p())}get look(){return this.#g}set look(t){this.#g=t??{},this.#p()}get mesh(){return this.#f}set mesh(t){this.#f=t,this.#n=null,this.#t={yaw:0,pitch:this.#u().pitch??.3,vyaw:0,vpitch:0},this.#a(),this.#e()}get paused(){return!this.#o}play(){this.#o=!0,this.#e()}pause(){this.#o=!1,this.#t={...this.#t,vyaw:0,vpitch:0},this.#e()}async load(t,n=""){this.#S?.abort();let r=this.#S=new AbortController;try{let s;if(typeof t=="string")s=await pn(t,r.signal);else{let i=t instanceof Uint8Array?t:new Uint8Array(t instanceof Blob?await t.arrayBuffer():t);s=await Bt(i,n||(t instanceof File?t.name:""))}if(r.signal.aborted)return;this.mesh=s,this.dispatchEvent(new CustomEvent("load",{detail:{triangles:s.indices.length/3,materials:s.materials}}))}catch(s){if(r.signal.aborted)return;this.dispatchEvent(new CustomEvent("error",{detail:{error:s}}))}}async#C(t){try{let n=await fetch(t);if(!n.ok)throw new Error(`${n.status} ${n.statusText} for ${t}`);this.#A=await n.json(),this.#p()}catch(n){this.dispatchEvent(new CustomEvent("error",{detail:{error:n}}))}}#u(){return Ut(this.#A,Pe(t=>this.getAttribute(t)),this.#g)}#p(){if(!this.#n||!this.#c)return this.#a();let t=this.#u();this.#n.setStyle({...t,...this.#w(t)}),this.#M(),this.#e()}#w(t){let n=this.#c;return{width:n.cols,height:n.rows,aspect:n.aspect,zoom:(t.zoom??1)*this.#h,fitPitch:this.hasAttribute("nodrag")?0:Rt}}#a(){if(!this.isConnected)return;let t=this.#l,n=t.offsetWidth/40,r=t.offsetHeight/4,s=Number(this.getAttribute("cols"))||96;if(this.#c=Oe(s,n,r,this.clientWidth,this.clientHeight),this.style.setProperty("--textel-ratio",String(this.#c.ratio)),this.#i.style.transform=`scale(${this.#c.scale})`,!this.#f)return;let i=this.#u();try{this.#n?this.#n.setStyle({...i,...this.#w(i)}):this.#n=ke(this.#f,{...i,...this.#w(i)})}catch(o){this.dispatchEvent(new CustomEvent("error",{detail:{error:o}}));return}this.#d=!0,this.#M()}#M(){if(!this.#n)return;let t=this.#n.render({yaw:this.#t.yaw,pitch:this.#t.pitch});t.style.color.mode==="none"&&t.materials.every(r=>!r.color&&r.opacity===1)?this.#i.textContent=Ee(t):this.#i.innerHTML=Re(t),this.#d=!1}#E(){this.isConnected&&this.setAttribute("aria-label",this.getAttribute("label")||"3D model drawn in ASCII")}#x(){return this.#o&&!this.#T?this.#u().spin??.66:0}#e=()=>{let t=this.#t,n=this.#x()!==0||Math.abs(t.vyaw)>.001||Math.abs(t.vpitch)>.001||!!this.#s,r=!!this.#n&&this.#I&&!this.ownerDocument.hidden&&this.isConnected&&(n||this.#d);r&&!this.#r?(this.#m=-1,this.#r=requestAnimationFrame(this.#F)):!r&&this.#r&&(cancelAnimationFrame(this.#r),this.#r=0)};#F=t=>{this.#r=0;let n=this.#m<0?0:(t-this.#m)/1e3;this.#m=t,this.#s||(this.#t=Le(this.#t,{spin:this.#x(),pitch:this.#u().pitch??.3,maxPitch:Rt},n)),this.#M(),this.#r=requestAnimationFrame(this.#F);let r=this.#t;this.#x()===0&&!this.#s&&Math.abs(r.vyaw)<.001&&Math.abs(r.vpitch)<.001&&(cancelAnimationFrame(this.#r),this.#r=0)};#z=t=>{this.hasAttribute("nodrag")||!this.#n||t.button!==0||(this.#s={x:t.clientX,y:t.clientY,t:performance.now()},this.setPointerCapture(t.pointerId),this.#e())};#k=t=>{let n=this.#s;if(!n)return;let r=performance.now();this.#t=Ue(this.#t,t.clientX-n.x,t.clientY-n.y,(r-n.t)/1e3,this.clientWidth,Rt),this.#s={x:t.clientX,y:t.clientY,t:r},this.#e()};#R=()=>{this.#s&&(performance.now()-this.#s.t>80&&(this.#t={...this.#t,vyaw:0,vpitch:0}),this.#s=null,this.#e())};#O=t=>{let n=this.#t,r=.15,s={ArrowLeft:()=>n.yaw-=r,ArrowRight:()=>n.yaw+=r,ArrowUp:()=>n.pitch=Math.max(-Rt,n.pitch-r),ArrowDown:()=>n.pitch=Math.min(Rt,n.pitch+r)," ":()=>this.#o?this.pause():this.play()};this.hasAttribute("zoomable")&&(s["+"]=s["="]=()=>this.#v(this.#h*1.1),s["-"]=()=>this.#v(this.#h/1.1));let i=s[t.key];i&&(t.preventDefault(),i(),this.#d=!0,this.#e())};#L=t=>{!this.hasAttribute("zoomable")||!this.#n||(t.preventDefault(),this.#v(this.#h*Math.exp(-t.deltaY*.001)))};#v(t){this.#h=Math.max(.3,Math.min(4,t)),this.#p()}};typeof customElements<"u"&&!customElements.get("textel-viewer")&&customElements.define("textel-viewer",jt);export{jt as TextelViewer};
10
+ .probe{visibility:hidden}`,zt=1.2,mn=["charset","chars","color","edges","spin","pitch","zoom","distance","ambient"],dn=typeof HTMLElement>"u"?class{}:HTMLElement;async function pn(e,t){let n=async s=>{let o=await fetch(s,{signal:t});if(!o.ok)throw new Error(`${o.status} ${o.statusText} for ${s}`);return new Uint8Array(await o.arrayBuffer())},r=await n(e),i=new Map;if(/\.gltf(\?|#|$)/i.test(e)){let s=JSON.parse(new TextDecoder().decode(r));for(let o of s.buffers??[])o.uri&&!o.uri.startsWith("data:")&&i.set(decodeURIComponent(o.uri),await n(new URL(o.uri,new URL(e,location.href)).href))}return Bt(r,e.split(/[?#]/)[0],{resolve:s=>{let o=i.get(s);if(!o)throw new Error(`missing buffer ${s}`);return o}})}var jt=class extends dn{static observedAttributes=["src","preset","cols","paused","label",...mn];#a;#u;#d=null;#r=null;#S={};#w={};#f=null;#t={yaw:0,pitch:0,vyaw:0,vpitch:0};#l=1;#c=!0;#E=!0;#i=0;#p=-1;#b=!0;#n=null;#s=new Map;#o=null;#F=null;#M;#x;#k=typeof matchMedia=="function"&&matchMedia("(prefers-reduced-motion: reduce)").matches;constructor(){super();let t=this.attachShadow({mode:"open"}),n=document.createElement("style");n.textContent=fn,this.#a=document.createElement("pre"),this.#a.setAttribute("part","screen"),this.#u=document.createElement("pre"),this.#u.className="probe",this.#u.textContent=Array(4).fill("M".repeat(40)).join(`
11
+ `),t.append(n,this.#u,this.#a),this.addEventListener("pointerdown",this.#L),this.addEventListener("pointermove",this.#U),this.addEventListener("pointerup",this.#T),this.addEventListener("pointercancel",this.#T),this.addEventListener("keydown",this.#P),this.addEventListener("wheel",this.#G,{passive:!1})}connectedCallback(){this.setAttribute("role","img"),this.hasAttribute("tabindex")||(this.tabIndex=0),this.#z(),this.#c=!this.hasAttribute("paused"),typeof IntersectionObserver=="function"&&(this.#M=new IntersectionObserver(([t])=>{this.#E=t.isIntersecting,this.#e()}),this.#M.observe(this)),typeof ResizeObserver=="function"&&(this.#x=new ResizeObserver(()=>this.#h()),this.#x.observe(this)),this.ownerDocument.addEventListener("visibilitychange",this.#e),this.ownerDocument.fonts?.ready.then(()=>this.#h()),this.#h(),this.#e()}disconnectedCallback(){this.#M?.disconnect(),this.#x?.disconnect(),this.ownerDocument.removeEventListener("visibilitychange",this.#e),cancelAnimationFrame(this.#i),this.#i=0}attributeChangedCallback(t,n,r){n!==r&&(t==="src"?r&&this.load(r):t==="preset"?r&&this.#O(r):t==="paused"?r===null?this.play():this.pause():t==="label"?this.#z():t==="cols"?this.#h():this.#g())}get look(){return this.#w}set look(t){this.#w=t??{},this.#g()}get mesh(){return this.#d}set mesh(t){this.#d=t,this.#r=null,this.#t={yaw:0,pitch:this.#m().pitch??.3,vyaw:0,vpitch:0},this.#h(),this.#e()}get paused(){return!this.#c}play(){this.#c=!0,this.#e()}pause(){this.#c=!1,this.#t={...this.#t,vyaw:0,vpitch:0},this.#e()}async load(t,n=""){this.#F?.abort();let r=this.#F=new AbortController;try{let i;if(typeof t=="string")i=await pn(t,r.signal);else{let s=t instanceof Uint8Array?t:new Uint8Array(t instanceof Blob?await t.arrayBuffer():t);i=await Bt(s,n||(t instanceof File?t.name:""))}if(r.signal.aborted)return;this.mesh=i,this.dispatchEvent(new CustomEvent("load",{detail:{triangles:i.indices.length/3,materials:i.materials}}))}catch(i){if(r.signal.aborted)return;this.dispatchEvent(new CustomEvent("error",{detail:{error:i}}))}}async#O(t){try{let n=await fetch(t);if(!n.ok)throw new Error(`${n.status} ${n.statusText} for ${t}`);this.#S=await n.json(),this.#g()}catch(n){this.dispatchEvent(new CustomEvent("error",{detail:{error:n}}))}}#m(){return Ut(this.#S,Pe(t=>this.getAttribute(t)),this.#w)}#g(){if(!this.#r||!this.#f)return this.#h();let t=this.#m();this.#r.setStyle({...t,...this.#v(t)}),this.#A(),this.#e()}#v(t){let n=this.#f;return{width:n.cols,height:n.rows,aspect:n.aspect,zoom:(t.zoom??1)*this.#l,fitPitch:this.hasAttribute("nodrag")?0:zt}}#h(){if(!this.isConnected)return;let t=this.#u,n=t.offsetWidth/40,r=t.offsetHeight/4,i=Number(this.getAttribute("cols"))||96;if(this.#f=Oe(i,n,r,this.clientWidth,this.clientHeight),this.style.setProperty("--textel-ratio",String(this.#f.ratio)),this.#a.style.transform=`scale(${this.#f.scale})`,!this.#d)return;let s=this.#m();try{this.#r?this.#r.setStyle({...s,...this.#v(s)}):this.#r=ke(this.#d,{...s,...this.#v(s)})}catch(o){this.dispatchEvent(new CustomEvent("error",{detail:{error:o}}));return}this.#b=!0,this.#A()}#A(){if(!this.#r)return;let t=this.#r.render({yaw:this.#t.yaw,pitch:this.#t.pitch});t.style.color.mode==="none"&&t.materials.every(r=>!r.color&&r.opacity===1)?this.#a.textContent=Ee(t):this.#a.innerHTML=ze(t),this.#b=!1}#z(){this.isConnected&&this.setAttribute("aria-label",this.getAttribute("label")||"3D model drawn in ASCII")}#I(){return this.#c&&!this.#k?this.#m().spin??.66:0}#e=()=>{let t=this.#t,n=this.#I()!==0||Math.abs(t.vyaw)>.001||Math.abs(t.vpitch)>.001||!!this.#n,r=!!this.#r&&this.#E&&!this.ownerDocument.hidden&&this.isConnected&&(n||this.#b);r&&!this.#i?(this.#p=-1,this.#i=requestAnimationFrame(this.#R)):!r&&this.#i&&(cancelAnimationFrame(this.#i),this.#i=0)};#R=t=>{this.#i=0;let n=this.#p<0?0:(t-this.#p)/1e3;this.#p=t,this.#n||(this.#t=Le(this.#t,{spin:this.#I(),pitch:this.#m().pitch??.3,maxPitch:zt},n)),this.#A(),this.#i=requestAnimationFrame(this.#R);let r=this.#t;this.#I()===0&&!this.#n&&Math.abs(r.vyaw)<.001&&Math.abs(r.vpitch)<.001&&(cancelAnimationFrame(this.#i),this.#i=0)};#L=t=>{this.hasAttribute("nodrag")||!this.#r||t.button!==0||(this.setPointerCapture(t.pointerId),this.#s.set(t.pointerId,{x:t.clientX,y:t.clientY}),this.#s.size===2&&this.hasAttribute("zoomable")?(this.#o={dist:this.#C(),zoom:this.#l},this.#n=null,this.#t={...this.#t,vyaw:0,vpitch:0}):this.#s.size===1&&(this.#n={id:t.pointerId,x:t.clientX,y:t.clientY,t:performance.now()}),this.#e())};#U=t=>{if(!this.#s.has(t.pointerId))return;if(this.#s.set(t.pointerId,{x:t.clientX,y:t.clientY}),this.#o){this.#o.dist>0&&this.#y(this.#o.zoom*(this.#C()/this.#o.dist));return}let n=this.#n;if(!n||n.id!==t.pointerId)return;let r=performance.now();this.#t=Ue(this.#t,t.clientX-n.x,t.clientY-n.y,(r-n.t)/1e3,this.clientWidth,zt),this.#n={...n,x:t.clientX,y:t.clientY,t:r},this.#e()};#T=t=>{if(this.#s.delete(t.pointerId)){if(this.#o){this.#s.size<2&&(this.#o=null);let[n]=[...this.#s];this.#n=n&&!this.#o?{id:n[0],...n[1],t:performance.now()}:null,this.#e();return}!this.#n||this.#n.id!==t.pointerId||(performance.now()-this.#n.t>80&&(this.#t={...this.#t,vyaw:0,vpitch:0}),this.#n=null,this.#e())}};#C(){let[t,n]=[...this.#s.values()];return n?Math.hypot(t.x-n.x,t.y-n.y):0}#P=t=>{let n=this.#t,r=.15,i={ArrowLeft:()=>n.yaw-=r,ArrowRight:()=>n.yaw+=r,ArrowUp:()=>n.pitch=Math.max(-zt,n.pitch-r),ArrowDown:()=>n.pitch=Math.min(zt,n.pitch+r)," ":()=>this.#c?this.pause():this.play()};this.hasAttribute("zoomable")&&(i["+"]=i["="]=()=>this.#y(this.#l*1.1),i["-"]=()=>this.#y(this.#l/1.1));let s=i[t.key];s&&(t.preventDefault(),s(),this.#b=!0,this.#e())};#G=t=>{!this.hasAttribute("zoomable")||!this.#r||(t.preventDefault(),this.#y(this.#l*Math.exp(-t.deltaY*.001)))};#y(t){this.#l=Math.max(.3,Math.min(4,t)),this.#g()}};typeof customElements<"u"&&!customElements.get("textel-viewer")&&customElements.define("textel-viewer",jt);export{jt as TextelViewer};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@feelgxxd/textel",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Turn 3D models into animated ASCII art for the terminal and the web.",
5
5
  "keywords": [
6
6
  "ascii",
@@ -61,7 +61,9 @@
61
61
  "./dist/browser/player.js",
62
62
  "./dist/textel-player.js",
63
63
  "./dist/browser/viewer.js",
64
- "./dist/textel-viewer.js"
64
+ "./dist/textel-viewer.js",
65
+ "./src/browser/player.ts",
66
+ "./src/browser/viewer.ts"
65
67
  ],
66
68
  "files": [
67
69
  "dist"
@@ -77,14 +79,17 @@
77
79
  "snapshots": "node test/update-snapshots.ts",
78
80
  "size": "node scripts/build-browser.ts --check",
79
81
  "demo": "node scripts/build-browser.ts && node scripts/demo.ts",
80
- "prepublishOnly": "npm run typecheck && npm test && npm run build"
82
+ "prepublishOnly": "npm run typecheck && npm test && npm run build",
83
+ "site": "vite --config site/vite.config.ts",
84
+ "site:build": "vite build --config site/vite.config.ts"
81
85
  },
82
86
  "devDependencies": {
83
87
  "@happy-dom/global-registrator": "^21.0.4",
84
88
  "@types/node": "^24.0.0",
85
89
  "esbuild": "^0.28.2",
86
90
  "happy-dom": "^21.0.4",
87
- "typescript": "^5.9.0"
91
+ "typescript": "^5.9.0",
92
+ "vite": "^8.3.6"
88
93
  },
89
94
  "publishConfig": {
90
95
  "access": "public"