moodi 0.0.0-stage → 0.1.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 itsk0rn3l
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,94 @@
1
- # Temporary Holding Version
1
+ # moodi
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **Tiny 3D mood buddies for your UI.**
4
+
5
+ A soft little 3D character you drop into any website with one line. Its face shows
6
+ what is happening — thinking, loading, happy, error, sleeping. Shape, color and
7
+ expression are all customizable. Everything is generated in code: no models, no
8
+ textures, nothing to download besides three.js.
9
+
10
+ ![moodi](https://raw.githubusercontent.com/korneXD/moodi/main/.github/assets/readme.png)
11
+
12
+ **Playground:** <https://moodi-two-tau.vercel.app> · by [@itsk0rn3l](https://x.com/itsk0rn3l)
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ npm i moodi three
18
+ ```
19
+
20
+ `three` is a peer dependency (>= 0.160). `react` is optional and only needed for `moodi/react`.
21
+
22
+ ## Usage
23
+
24
+ ### Web Component
25
+
26
+ ```html
27
+ <moodi-buddy shape="ghost" mood="happy" color="#6c5ce7" size="160"></moodi-buddy>
28
+ <script type="module">import 'moodi';</script>
29
+ ```
30
+
31
+ ### React
32
+
33
+ ```tsx
34
+ import { Moodi } from 'moodi/react';
35
+
36
+ <Moodi shape="blob" mood={isLoading ? 'loading' : 'idle'} color="#8fb996" />
37
+ ```
38
+
39
+ `moodi/react` is SSR-safe (Next.js, Remix, …): three.js is loaded lazily in the
40
+ browser, so nothing WebGL-related runs on the server.
41
+
42
+ ### CDN, no build step
43
+
44
+ ```html
45
+ <script type="module" src="https://esm.sh/moodi"></script>
46
+ <moodi-buddy mood="thinking"></moodi-buddy>
47
+ ```
48
+
49
+ ## Props
50
+
51
+ | Prop (attribute) | Type | Default | Notes |
52
+ | ----------------------------- | ------------------------------------------------------------ | ----------- | --------------------------------------------- |
53
+ | `shape` | `'ghost' \| 'pebble' \| 'blob' \| 'drop' \| 'bean' \| 'cube'` | `'ghost'` | Body shape |
54
+ | `mood` | `Mood` (see below) | `'idle'` | Changes are interpolated, never instant |
55
+ | `color` | any CSS color | `'#6c5ce7'` | Body color |
56
+ | `faceColor` (`face-color`) | any CSS color | auto | Eyes & mouth, auto-contrast with the body |
57
+ | `cheekColor` (`cheek-color`) | any CSS color | auto | Blush, derived from the body color |
58
+ | `size` | `number \| string` | `160` | px, or any CSS length (`"10rem"`, `"40vw"`) |
59
+ | `speed` | `number` | `1` | `0` pauses |
60
+ | `followCursor` (`follow-cursor`) | `boolean` | `true` | Eyes (and slightly the body) follow the pointer |
61
+ | `interactive` | `boolean` | `true` | Hover lift, click reaction |
62
+ | `shadow` | `boolean` | `true` | Soft ground shadow |
63
+
64
+ Boolean attributes on the Web Component take `"true"` / `"false"`.
65
+
66
+ ## Moods
67
+
68
+ `idle` · `thinking` · `loading` · `listening` · `speaking` · `happy` · `surprised` · `sad` · `error` · `sleeping` · `love`
69
+
70
+ ## Imperative API
71
+
72
+ ```js
73
+ const el = document.querySelector('moodi-buddy');
74
+ el.mood = 'thinking';
75
+ el.react('happy', { duration: 1200 }); // temporary reaction, then back to the current mood
76
+ el.addEventListener('moodchange', (e) => console.log(e.detail)); // { mood, previous, reaction }
77
+ ```
78
+
79
+ In React, pass a `ref` (it points at the element) or use `onMoodChange`.
80
+
81
+ Also exported: `SHAPES`, `MOODS`, `PALETTES`, `MOOD_INFO`, `DEFAULTS`, `defineMoodi(tag?)`.
82
+
83
+ ## Behaviour
84
+
85
+ - All buddies on a page share **one WebGL context**; dozens are fine.
86
+ - Rendering stops while a buddy is off-screen or the tab is hidden; paused buddies (`speed={0}`) only redraw when something changes.
87
+ - `prefers-reduced-motion`: only blinking and soft fades.
88
+ - Transparent background, pixel ratio capped at 2.
89
+ - `role="img"` and `aria-label="moodi: <mood>"` (set your own `aria-label` to override).
90
+ - ~11 kB gzip, three.js excluded.
91
+
92
+ ## License
93
+
94
+ [MIT](./LICENSE) © itsk0rn3l
package/dist/index.cjs ADDED
@@ -0,0 +1,25 @@
1
+ 'use strict';var three=require('three');var Ne=["ghost","pebble","blob","drop","bean","cube"],Xe=["idle","thinking","loading","listening","speaking","happy","surprised","sad","error","sleeping","love"],M={shape:"ghost",mood:"idle",color:"#6c5ce7",size:160,speed:1,followCursor:true,interactive:true,shadow:true},wt={Iris:"#6c5ce7",Peach:"#f2a07b",Sage:"#8fb996",Sky:"#8ec5e8",Lilac:"#b9a3e3",Butter:"#f3d36b",Coral:"#f07167",Ink:"#2b2d42",Cloud:"#ecebe8"},St={idle:{label:"Idle",description:"Breathing softly. Blinks now and then."},thinking:{label:"Thinking",description:"Eyes drift up. Little thoughts pop."},loading:{label:"Loading",description:"Half-lidded, wobbling patiently."},listening:{label:"Listening",description:"All eyes. Nods along to every word."},speaking:{label:"Speaking",description:"Chatty mouth, bouncing to the rhythm."},happy:{label:"Happy",description:"Bounces with joy. Cheeks glow."},surprised:{label:"Surprised",description:"Wide eyes, tiny o. Did not see that coming."},sad:{label:"Sad",description:"Slumps a little. Needs a minute."},error:{label:"Error",description:"Something broke. Shakes it off."},sleeping:{label:"Sleeping",description:"Deep, slow breaths. Do not disturb."},love:{label:"Love",description:"Heart eyes, floating on air."}},Ct={pebble:"Pebble",blob:"Blob",drop:"Drop",bean:"Bean",cube:"Cube",ghost:"Ghost"},re=t=>Ne.includes(t),ae=t=>Xe.includes(t);var z=null;function _(t,o){if(!t)return o;let e=/^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(t.trim());if(e)return Se(e[1]);if(z||(z=document.createElement("canvas").getContext("2d")),!z)return o;z.fillStyle="#010203",z.fillStyle=t;let s=String(z.fillStyle);if(s==="#010203"&&t.trim()!=="#010203")return o;if(s.startsWith("#"))return Se(s.slice(1));z.canvas.width=z.canvas.height=1,z.clearRect(0,0,1,1),z.fillRect(0,0,1,1);let i=z.getImageData(0,0,1,1).data;return [i[0]/255,i[1]/255,i[2]/255]}function Se(t){t.length===3&&(t=t[0]+t[0]+t[1]+t[1]+t[2]+t[2]);let o=parseInt(t,16);return [(o>>16&255)/255,(o>>8&255)/255,(o&255)/255]}var O=t=>`rgb(${Math.round(t[0]*255)},${Math.round(t[1]*255)},${Math.round(t[2]*255)})`,P=(t,o,e)=>[t[0]+(o[0]-t[0])*e,t[1]+(o[1]-t[1])*e,t[2]+(o[2]-t[2])*e],ne=t=>t<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4),he=t=>.2126*ne(t[0])+.7152*ne(t[1])+.0722*ne(t[2]),Ve=[.07,.06,.08],He=[1,.97,.93],$e=[1,.42,.48],U=t=>he(t)>.06?P(Ve,t,.06):He,J=t=>P(t,$e,.78);var $=["eyeOpen","eyeSize","lookX","lookY","lidCover","lidTilt","happyEye","closedEye","xEye","heartEye","mouthW","mouthCurve","mouthOpen","mouthRound","mouthSkew","mouthWave","blush"],L={eyeOpen:1,eyeSize:1,lookX:0,lookY:0,lidCover:0,lidTilt:0,happyEye:0,closedEye:0,xEye:0,heartEye:0,mouthW:.1,mouthCurve:.35,mouthOpen:0,mouthRound:0,mouthSkew:0,mouthWave:0,blush:.45},Z={idle:{...L},thinking:{...L,eyeOpen:.92,lookX:.7,lookY:-0.8,lidCover:.12,mouthW:.065,mouthCurve:0,mouthSkew:.8,blush:.3},loading:{...L,lidCover:.48,mouthW:.06,mouthCurve:.12,blush:.3},listening:{...L,eyeOpen:1.12,eyeSize:1.16,mouthW:.06,mouthCurve:.2,mouthOpen:.02},speaking:{...L,mouthW:.09,mouthCurve:.25},happy:{...L,happyEye:1,mouthW:.13,mouthCurve:1,mouthOpen:.08,blush:1},surprised:{...L,eyeOpen:1.25,eyeSize:1.22,mouthW:.06,mouthCurve:0,mouthOpen:.12,mouthRound:1,blush:.35},sad:{...L,eyeOpen:.95,lidCover:.32,lidTilt:.5,lookY:.45,mouthW:.08,mouthCurve:-0.75,blush:.2},error:{...L,xEye:1,mouthW:.1,mouthCurve:-0.2,mouthWave:1,blush:.15},sleeping:{...L,closedEye:1,mouthW:.04,mouthCurve:0,mouthOpen:.035,mouthRound:.7,blush:.55},love:{...L,heartEye:1,mouthW:.11,mouthCurve:.9,mouthOpen:.03,blush:1}},Ce={idle:1,thinking:.15,loading:0,listening:1,speaking:.9,happy:.6,surprised:.8,sad:.3,error:.3,sleeping:0,love:.7},de=t=>({...t}),je=.31,Ke=-0.04,Ue=.098,le=.128,Je=.2,ce=.05;function Te(t,o,e,s,i,a,l,d,u,p){let r=t.canvas.width;t.setTransform(1,0,0,1,0,0),t.clearRect(0,0,r,r),t.setTransform(r/2,0,0,r/2,r/2,r/2),t.lineCap="round",t.lineJoin="round";let h=o.lookX*.075,c=o.lookY*.06;if(o.blush>.01){let m=r*.018;t.save(),"filter"in t&&(t.filter=`blur(${m}px)`);for(let y of [-1,1]){let v=y*.52+h*.4,n=.16+c*.4,f=t.createRadialGradient(v,n,0,v,n,.16);f.addColorStop(0,i),f.addColorStop(1,"rgba(0,0,0,0)"),t.globalAlpha=Math.min(1,o.blush)*.62,t.fillStyle=f,t.beginPath(),t.ellipse(v,n,.17,.12,0,0,Math.PI*2),t.fill();}t.restore();let b=Math.min(1,Math.max(0,(o.blush-.6)/.4));if(b>.01){t.save(),t.globalAlpha=b*.55,t.strokeStyle=i,t.lineWidth=.016,t.lineCap="round";for(let y of [-1,1]){let v=y*.52+h*.4,n=.16+c*.4;for(let f=-1;f<=1;f++){let F=v+f*.045;t.beginPath(),t.moveTo(F+.018,n-.035),t.lineTo(F-.018,n+.035),t.stroke();}}t.restore();}}t.fillStyle=s,t.strokeStyle=s,t.lineWidth=ce;let A=1-Math.min(1,o.happyEye+o.closedEye+o.xEye+o.heartEye);for(let m of [-1,1]){let b=m*je+h,y=Ke+c,v=Ue*o.eyeSize;if(A>.01){t.globalAlpha=A;let n=Math.max(.06,o.eyeOpen*e),f=le*o.eyeSize*n;if(t.save(),o.lidCover>.01||o.lidTilt>.01){let W=y-le*o.eyeSize+2*le*o.eyeSize*o.lidCover;t.translate(b,W),t.rotate(m*o.lidTilt*.6),t.beginPath(),t.rect(-1,0,2,2),t.clip(),t.rotate(-m*o.lidTilt*.6),t.translate(-b,-W);}t.shadowColor=s,t.shadowBlur=r*.035*a;let F=t.createRadialGradient(b,y-f*.2,0,b,y,Math.max(v,f));if(F.addColorStop(0,l),F.addColorStop(1,s),t.fillStyle=F,t.beginPath(),t.ellipse(b,y,v,f,0,0,Math.PI*2),t.fill(),t.shadowBlur=0,t.fillStyle=s,n>.35){let x=Math.max(-1,Math.min(1,o.lookX)),W=Math.max(-1,Math.min(1,o.lookY)),ie=d==="#fff"?.3:.34;t.globalAlpha=A*Math.min(1,(n-.35)*3),t.fillStyle=d,t.beginPath(),t.arc(b-v*.1+x*v*.5,y-f*.16+W*f*.42,v*ie,0,Math.PI*2),t.fill(),t.fillStyle=s;}t.restore();}if(t.shadowColor=s,t.shadowBlur=r*.03*a,o.happyEye>.01){let n=.6+.4*o.happyEye;t.globalAlpha=Math.min(1,o.happyEye),t.beginPath(),t.arc(b,y+.045,v*1.15*n,Math.PI*1.12,Math.PI*1.88),t.stroke();}if(o.closedEye>.01){let n=.6+.4*o.closedEye;t.globalAlpha=Math.min(1,o.closedEye),t.beginPath(),t.moveTo(b-v*1.15*n,y+.01),t.quadraticCurveTo(b,y+.07*n,b+v*1.15*n,y+.01),t.stroke();}if(o.xEye>.01){let n=v*.95*(.6+.4*o.xEye);t.globalAlpha=Math.min(1,o.xEye),t.beginPath(),t.moveTo(b-n,y-n),t.lineTo(b+n,y+n),t.moveTo(b+n,y-n),t.lineTo(b-n,y+n),t.stroke();}o.heartEye>.01&&(t.globalAlpha=Math.min(1,o.heartEye),ue(t,b,y,v*1.35*(.5+.5*o.heartEye)),t.fill());}t.shadowBlur=0;let k=h*.6+o.mouthSkew*.07,w=Je+c*.6,T=Math.min(1,Math.max(0,o.mouthRound)),S=o.mouthW,R=t.createLinearGradient(k-S-.02,w,k+S+.02,w);R.addColorStop(0,p),R.addColorStop(.5,l),R.addColorStop(1,p),t.shadowColor=s,t.shadowBlur=r*.02*a;let B=(m,b,y,v)=>{t.fillStyle=u,t.fill(m),t.shadowBlur=0,b>.02&&(t.save(),t.clip(m),t.fillStyle=i,t.globalAlpha*=Math.min(1,(b-.02)*12),t.beginPath(),t.ellipse(k,y,v,b*.55,0,0,Math.PI*2),t.fill(),t.restore()),t.strokeStyle=R;let n=t.lineWidth;t.lineWidth=n*.6,t.stroke(m),t.lineWidth=n,t.shadowBlur=r*.02*a;};if(T<.99){t.globalAlpha=1-T;let m=18,b=f=>o.mouthCurve*.075*(1-f*f),y=(f,F)=>{let x=f/m*2-1,W=k+x*S,ie=w+b(x)+o.mouthSkew*x*.03+o.mouthWave*.018*Math.sin((x+1)*Math.PI*2)+F*(1-x*x);return [W,ie]},v=Math.max(0,o.mouthOpen),n=new Path2D;for(let f=0;f<=m;f++){let[F,x]=y(f,-v*.15);f?n.lineTo(F,x):n.moveTo(F,x);}if(v>.003){for(let f=m;f>=0;f--){let[F,x]=y(f,v);n.lineTo(F,x);}n.closePath(),B(n,v,y(m/2,v)[1],S*.55);}else t.strokeStyle=R,t.stroke(n);}if(T>.01){t.globalAlpha=T;let m=Math.max(.02,o.mouthOpen*.55+.01),b=new Path2D;b.ellipse(k,w+.01,m*.85,m,0,0,Math.PI*2),t.lineWidth=ce*.8,B(b,o.mouthOpen,w+.01+m*.8,m*.6),t.lineWidth=ce;}t.shadowBlur=0,t.globalAlpha=1;}function ue(t,o,e,s){t.beginPath(),t.moveTo(o,e+s*.85),t.bezierCurveTo(o-s*1.25,e+s*.05,o-s*.75,e-s*.95,o,e-s*.35),t.bezierCurveTo(o+s*.75,e-s*.95,o+s*1.25,e+s*.05,o,e+s*.85),t.closePath();}var te=()=>({y:0,rx:0,ry:0,rz:0,st:0,mouth:0,lookX:0,lookY:0}),{sin:g,cos:Ze,abs:Qe,max:Q,exp:et,PI:ee}=Math;function pe(t,o,e,s){switch(s.y=s.rx=s.ry=s.rz=s.st=s.mouth=s.lookX=s.lookY=0,t){case "idle":{let i=g(o*1.6);s.y=.015*i,s.st=.018*i;break}case "thinking":{s.rz=.07*g(o*1.1),s.y=.02*g(o*2.2),s.ry=.15,s.rx=-0.06;break}case "loading":{let i=o*2.4;s.rx=.12*g(i),s.rz=.12*Ze(i),s.ry=.22*g(i),s.y=.02*g(i*2),s.lookX=.55*g(i);break}case "listening":{let i=Q(0,g(o*3.2));s.rx=.09*i*i;let a=Qe(g(o*7.1)*.5+g(o*11.3)*.3+g(o*3.7)*.2);s.st=.03*a;break}case "speaking":{let i=Q(0,g(o*9)*.6+g(o*14.3)*.4)*(.65+.35*g(o*1.7));s.mouth=.11*i,s.st=.04*i,s.rz=.03*g(o*2.1);break}case "happy":{let i=o*1.35%1;s.y=.2*g(ee*i);let a=Q(0,1-i/.14)+Q(0,(i-.9)/.1);s.st=.08*g(ee*i)-.14*a,s.rz=.05*g(o*1.35*ee);break}case "surprised":{if(e<.45){let i=g(ee*e/.45);s.y=.16*i,s.st=.12*i;}s.rz=.01*g(o*38)*et(-e*2),s.rx=-0.06;break}case "sad":{let i=g(o*1.1);s.y=-0.06,s.rx=.2,s.st=-0.04+.015*i,s.rz=-0.04;break}case "error":{let i=e%2.8;if(i<.55){let a=1-i/.55;s.ry=.26*g(i*42)*a,s.rz=.05*g(i*42+1)*a;}s.st=-0.02;break}case "sleeping":{let i=g(o*1);s.st=.04*i,s.y=-0.03,s.rz=.1,s.rx=.1;break}case "love":{s.y=.07+.06*g(o*1.6),s.rz=.05*g(o*.8),s.st=.015*g(o*1.6+1);break}}return s}function Ae(t,o,e,s){for(let i of Object.keys(s))s[i]=t[i]+(o[i]-t[i])*e;return s}var Ee={thinking:"dots",sleeping:"zz",love:"hearts",listening:"waves",surprised:"bang"};var X=new Set,E=null,q=0,Y=0,oe=0,be,Pe=false,N="idle",I={x:0,y:0,lastMove:-1e9,known:false},G={reducedMotion:false};function ot(){if(Pe)return;Pe=true,addEventListener("pointermove",o=>{I.x=o.clientX,I.y=o.clientY,I.lastMove=performance.now(),I.known=true;},{passive:true}),document.addEventListener("visibilitychange",D);let t=matchMedia("(prefers-reduced-motion: reduce)");G.reducedMotion=t.matches,t.addEventListener?.("change",o=>G.reducedMotion=o.matches);}function Fe(t,o){return E||(E=new three.WebGLRenderer({antialias:true,alpha:true,powerPreference:"low-power",preserveDrawingBuffer:true}),E.setPixelRatio(1),E.setClearColor(0,0),E.setScissorTest(true),E.domElement.addEventListener("webglcontextlost",e=>{e.preventDefault(),E?.dispose(),E=null,q=Y=0,N="idle";})),(t>q||o>Y)&&(q=Math.max(q,t),Y=Math.max(Y,o),E.setSize(q,Y,false)),E}var Re=()=>Y;function Be(t,o){if(N==="ready")return true;if(N==="idle"&&E){N="compiling";let e=E;e.compileAsync(t,o).catch(()=>{}).then(()=>{e===E&&(N="ready"),D();});}return false}function ze(t){ot(),clearTimeout(be),X.add(t),D();}function Oe(t){X.delete(t),X.size===0&&(clearTimeout(be),be=setTimeout(()=>{X.size||!E||(E.dispose(),E.forceContextLoss(),E=null,q=Y=0,N="idle");},1500));}function D(){if(!oe){for(let t of X)if(t.active){oe=requestAnimationFrame(Le);return}}}var st=10,xe=new WeakMap,me=0;function Le(t){oe=0;let o=[...X].filter(a=>a.active);if(!o.length)return;let e=performance.now(),s=o.length,i=0;for(;i<s;i++){let a=o[(me+i)%s],l=xe.get(a)??t-16;if(xe.set(a,t),a.tick(Math.min(.05,Math.max(0,(t-l)/1e3)),t),performance.now()-e>st){i++;break}}me=(me+i)%s,oe=requestAnimationFrame(Le);}var j={pebble:{faceY:.02,faceScale:1.3,animated:false},blob:{faceY:.02,faceScale:1.28,animated:true},drop:{faceY:-0.24,faceScale:1.22,animated:false},bean:{faceY:.14,faceScale:.92,animated:false},cube:{faceY:0,faceScale:1.22,animated:false},ghost:{faceY:.22,faceScale:1.24,animated:true}};function Ie(t){let o=new three.SphereGeometry(1,t*2,Math.round(t*1.5)),e=o.getAttribute("position"),s=o.getIndex(),i=new Map,a=[],l=new Uint32Array(e.count);for(let r=0;r<e.count;r++){let h=e.getX(r),c=e.getY(r),C=e.getZ(r),A=`${Math.round(h*1e4)},${Math.round(c*1e4)},${Math.round(C*1e4)}`,k=i.get(A);if(k===void 0){k=a.length/3,i.set(A,k);let w=Math.hypot(h,c,C)||1;a.push(h/w,c/w,C/w);}l[r]=k;}let d=[];for(let r=0;r<s.count;r+=3){let h=l[s.getX(r)],c=l[s.getX(r+1)],C=l[s.getX(r+2)];h!==c&&c!==C&&h!==C&&d.push(h,c,C);}o.dispose();let u=new three.BufferGeometry,p=new Float32Array(a);return u.setAttribute("position",new three.BufferAttribute(new Float32Array(p),3)),u.setIndex(d),{geometry:u,dirs:p}}var nt=(t,o,e)=>{let s=Math.min(1,Math.max(0,(e-t)/(o-t)));return s*s*(3-2*s)};function fe(t,o,e,s,i,a,l){let d=o,u=e,p=s;switch(t){case "pebble":{d=o*1.1+.04*e,u=e*.92*(.95+.05*Math.tanh(3*e))+.03*o,p=s*.94;break}case "blob":{let r=.97+.045*Math.sin(2.1*o+1.3*i)*Math.sin(2.4*e+1.7*i+1)+.035*Math.sin(2.7*s+2.3*o+1.1*i);d=o*r,u=e*r,p=s*r;break}case "drop":{let r=Math.max(e,0),h=1-.8*Math.pow(r,1.6);d=o*h*.98+.1*r*r*r,u=(e<0?e*.9:e*1.32)-.12,p=s*h*.98;break}case "bean":{let r=e*.78+.42*Math.tanh(e*3);d=(o*.8+.14*(r*r/1.44)-.07)*.9,u=r*.88,p=s*.8*.9;break}case "cube":{let h=Math.pow(Math.pow(Math.abs(o),4.5)+Math.pow(Math.abs(e),4.5)+Math.pow(Math.abs(s),4.5),-0.2222222222222222);d=o*h*.84,u=e*h*.82,p=s*h*.84;break}case "ghost":{if(e>=0)d=o,u=e,p=s;else {let h=(Math.acos(Math.max(-1,e))-Math.PI/2)/(Math.PI/2),c=Math.hypot(o,s)||1,C=o/c,A=s/c,k=Math.atan2(s,o),w=.085*Math.sin(5*k+2.2*i),T=1.04,S=.13,R=-1.05+w,B;if(h<.45){let m=h/.45;B=1+.04*m,u=-m*(1.05-S)+w*nt(.45,1,m);}else if(h<.75){let m=(h-.45)/.3*(Math.PI/2);B=T-S+S*Math.cos(m),u=R+S-S*Math.sin(m);}else {let m=(h-.75)/.25;B=(T-S)*(1-m),u=R+.22*m*m*(3-2*m);}d=C*B,p=A*B;}d*=.9,u=(u+.1)*.9,p*=.9;break}}a[l]=d,a[l+1]=u,a[l+2]=p;}var ve=-0.85,De=new Map;function gt(t){let o=De.get(t);if(!o){let{geometry:e,dirs:s}=Ie(t);o={dirs:s,index:new Uint32Array(e.getIndex().array)},e.dispose(),De.set(t,o);}return o}var V=(t,o)=>1-Math.exp(-t*o),We=t=>1-Math.pow(1-t,3),se=class{constructor(){this.scene=new three.Scene;this.camera=new three.PerspectiveCamera(30,1,.1,50);this.group=new three.Group;this.v=new three.Vector3;this.geometry=new three.BufferGeometry;this.faceCanvas=document.createElement("canvas");this.uniforms={uFace:{value:null},uFaceY:{value:0},uFaceScale:{value:1.5}};this.state={...M};this.visible=false;this.connected=false;this.w=0;this.h=0;this.t=Math.random()*100;this.shapeFrom=M.shape;this.shapeTo=M.shape;this.morph=1;this.shapeDirty=true;this.shown=M.mood;this.prevShown=M.mood;this.tau=10;this.prevTau=10;this.blend=1;this.reaction=null;this.pose=te();this.poseA=te();this.poseB=te();this.face=de(Z.idle);this.target=de(Z.idle);this.drawn=new Float32Array($.length+7).fill(NaN);this.blink=1;this.blinkT=-1;this.nextBlink=2+Math.random()*3;this.body=_(M.color,[.95,.63,.48]);this.bodyT=this.body;this.faceC=U(this.body);this.faceCT=this.faceC;this.cheek=J(this.body);this.cheekT=this.cheek;this.look={x:0,y:0};this.wander={x:0,y:0,next:0};this.squash=0;this.squashV=0;this.pop=.6;this.popV=0;this.hover=0;this.hovering=false;this.overlay={dots:0,zz:0,hearts:0,waves:0,bang:0};this.needsFrame=true;this.css=[0,0];this.quality=1;this.cost=0;this.costFrames=0;this.lastFrame=[];this.canvas=document.createElement("canvas"),this.ctx=this.canvas.getContext("2d"),this.faceCtx=this.faceCanvas.getContext("2d"),this.faceCanvas.width=this.faceCanvas.height=256,this.faceTex=new three.CanvasTexture(this.faceCanvas),this.faceTex.colorSpace=three.SRGBColorSpace,this.faceTex.minFilter=three.LinearMipmapLinearFilter,this.faceTex.anisotropy=4,this.uniforms.uFace.value=this.faceTex,this.setSegments(24),this.material=this.createMaterial(),this.mesh=new three.Mesh(this.geometry,this.material),this.mesh.frustumCulled=false,this.mesh.position.y=-ve,this.group.add(this.mesh),this.scene.add(this.group),this.scene.add(new three.HemisphereLight(16774120,7234140,1.7));let o=new three.DirectionalLight(16777215,1.9);o.position.set(2.5,3.5,4);let e=new three.DirectionalLight(16770770,1.4);e.position.set(-3.5,2,-3);let s=new three.DirectionalLight(16773350,.45);s.position.set(-2,-1,3),this.scene.add(o,e,s),this.camera.position.set(0,.3,6.6),this.camera.lookAt(0,.12,0);}get active(){return this.visible&&this.connected&&this.w>0}createMaterial(){let o=new three.MeshStandardMaterial({roughness:.62,metalness:0});return o.color.setRGB(...this.body,three.SRGBColorSpace),o.customProgramCacheKey=()=>"moodi-1",o.onBeforeCompile=e=>{Object.assign(e.uniforms,this.uniforms),e.vertexShader=e.vertexShader.replace("#include <common>",`#include <common>
2
+ varying vec3 vFaceP;
3
+ varying vec3 vFaceN;`).replace("#include <begin_vertex>",`#include <begin_vertex>
4
+ vFaceP = position;
5
+ vFaceN = objectNormal;`),e.fragmentShader=e.fragmentShader.replace("#include <common>",`#include <common>
6
+ varying vec3 vFaceP;
7
+ varying vec3 vFaceN;
8
+ uniform sampler2D uFace;
9
+ uniform float uFaceY;
10
+ uniform float uFaceScale;`).replace("#include <map_fragment>",`#include <map_fragment>
11
+ vec2 fuv = vec2(vFaceP.x, vFaceP.y - uFaceY) / uFaceScale + 0.5;
12
+ float inb = step(0.0, fuv.x) * step(fuv.x, 1.0) * step(0.0, fuv.y) * step(fuv.y, 1.0);
13
+ vec4 fc = texture2D(uFace, fuv);
14
+ float faceMask = fc.a * inb * smoothstep(0.12, 0.45, normalize(vFaceN).z);
15
+ diffuseColor.rgb = mix(diffuseColor.rgb, fc.rgb, faceMask);`).replace("#include <roughnessmap_fragment>",`#include <roughnessmap_fragment>
16
+ roughnessFactor = mix(roughnessFactor, 0.35, faceMask * 0.7);`).replace("#include <opaque_fragment>",`float rimK = 1.0 - saturate(dot(normal, normalize(vViewPosition)));
17
+ outgoingLight += diffuseColor.rgb * 0.24 * rimK * rimK + vec3(0.05) * pow(rimK, 4.0);
18
+ // soft contact shading on the underside
19
+ outgoingLight *= mix(0.62, 1.0, smoothstep(-0.95, 0.35, normalize(vFaceN).y));
20
+ // eyes and mouth glow a little: mostly unlit, so they read as light, not paint
21
+ float emis = smoothstep(0.75, 1.0, fc.a) * inb * smoothstep(0.12, 0.45, normalize(vFaceN).z);
22
+ outgoingLight = mix(outgoingLight, diffuseColor.rgb * 1.12, emis * 0.65);
23
+ #include <opaque_fragment>`);},o}setSegments(o){let e=gt(o);this.dirs!==e.dirs&&(this.dirs=e.dirs,this.geometry.dispose(),this.geometry=new three.BufferGeometry,this.geometry.setAttribute("position",new three.BufferAttribute(new Float32Array(e.dirs.length),3)),this.geometry.setIndex(new three.BufferAttribute(e.index,1)),this.mesh&&(this.mesh.geometry=this.geometry),this.shapeDirty=true);}connect(){this.connected=true,ze(this);}disconnect(){this.connected=false,Oe(this);}setVisible(o){this.visible=o,o&&D();}resize(o,e){this.css=[o,e];let s=Math.min(2,devicePixelRatio||1)*this.quality,i=Math.max(1,Math.round(o*s)),a=Math.max(1,Math.round(e*s));if(i===this.w&&a===this.h)return;this.w=this.canvas.width=i,this.h=this.canvas.height=a;let l=Math.min(i,a),d=Math.min(o,e);this.camera.position.set(0,.3,d<90?5.1:6.1),this.camera.lookAt(0,d<90?.02:.12,0),this.camera.aspect=i/a,this.camera.updateProjectionMatrix(),this.setSegments(l<160?16:l<400?24:32);let u=l<220?256:512;this.faceCanvas.width!==u&&(this.faceCanvas.width=this.faceCanvas.height=u,this.faceTex.dispose(),this.drawn.fill(NaN)),this.needsFrame=true,D();}update(o){let e=this.state;this.state={...e,...o};let s=this.state;s.shape!==this.shapeTo&&(this.shapeFrom=this.shapeTo,this.shapeTo=s.shape,this.morph=0,G.reducedMotion||(this.squashV-=2.2)),(s.color!==e.color||s.faceColor!==e.faceColor||s.cheekColor!==e.cheekColor)&&(this.bodyT=_(s.color,_(M.color,this.bodyT)),this.faceCT=s.faceColor?_(s.faceColor,U(this.bodyT)):U(this.bodyT),this.cheekT=s.cheekColor?_(s.cheekColor,J(this.bodyT)):J(this.bodyT)),s.mood!==e.mood&&!this.reaction&&this.show(s.mood),this.needsFrame=true,D();}react(o,e=1200){this.reaction={mood:o,left:e/1e3},this.show(o);}get currentMood(){return this.shown}dispose(){this.disconnect(),this.geometry.dispose(),this.material.dispose(),this.faceTex.dispose(),this.onMoodChange=void 0;}show(o){if(o===this.shown)return;let e=this.shown;this.prevShown=e,this.prevTau=this.tau,this.shown=o,this.tau=0,this.blend=0,G.reducedMotion||(this.squashV-=1.1),this.onMoodChange?.({mood:o,previous:e,reaction:!!this.reaction});}tick(o,e){let s=this.state,i=G.reducedMotion,a=o*Math.max(0,s.speed);this.t+=a,this.tau+=a,this.prevTau+=a,this.reaction&&(this.reaction.left-=o,this.reaction.left<=0&&(this.reaction=null,this.show(s.mood))),pe(this.shown,this.t,this.tau,this.poseB),this.blend<1?(this.blend=Math.min(1,this.blend+o/.45),pe(this.prevShown,this.t,this.prevTau,this.poseA),Ae(this.poseA,this.poseB,We(this.blend),this.pose)):Object.assign(this.pose,this.poseB);let l=this.pose;i&&(l.y=l.rx=l.ry=l.rz=l.st=l.lookX=0);let d=s.followCursor&&!i&&s.speed>0?Ce[this.shown]:0,u=0,p=0;if(d>0&&I.known){let n=this.canvas.getBoundingClientRect(),f=Math.max(200,n.width*.85);u=Math.max(-1,Math.min(1,(I.x-(n.left+n.width/2))/f)),p=Math.max(-1,Math.min(1,(I.y-(n.top+n.height*.45))/f)),u*=d,p*=d;}else if(this.shown==="idle"&&!i&&s.speed>0){if(this.t>this.wander.next){let n=Math.random()<.4;this.wander.x=n?0:(Math.random()*2-1)*.6,this.wander.y=n?0:(Math.random()*2-1)*.35,this.wander.next=this.t+1.8+Math.random()*2.6;}u=this.wander.x,p=this.wander.y;}let r=V(7,o);this.look.x+=(u-this.look.x)*r,this.look.y+=(p-this.look.y)*r;let h=Z[this.shown],c=this.target;for(let n of $)c[n]=h[n];c.lookX+=this.look.x+l.lookX,c.lookY+=this.look.y+l.lookY,c.mouthOpen+=l.mouth,c.eyeOpen+=this.hover*.08;let C=V(11,o);for(let n of $)this.face[n]+=(c[n]-this.face[n])*C;let A=V(16,o);if(this.face.lookX+=(c.lookX-this.face.lookX)*A,this.face.lookY+=(c.lookY-this.face.lookY)*A,this.face.mouthOpen+=(c.mouthOpen-this.face.mouthOpen)*A,this.nextBlink-=a,this.nextBlink<=0&&this.blinkT<0&&(this.blinkT=0,this.nextBlink=Math.random()<.2?.25:2.2+Math.random()*3.8),this.blinkT>=0){this.blinkT+=a;let n=this.blinkT/.16;this.blink=n>=1?1:1-Math.sin(Math.PI*n),n>=1&&(this.blinkT=-1);}let k=Math.min(o,1/30);this.squashV+=(-170*this.squash-11*this.squashV)*k,this.squash+=this.squashV*k,i&&(this.squash=this.squashV=0),i?this.pop=1:(this.popV+=(-140*(this.pop-1)-13*this.popV)*k,this.pop+=this.popV*k),this.hover+=((this.hovering&&s.interactive?1:0)-this.hover)*V(12,o);let w=V(9,o);this.body=P(this.body,this.bodyT,w),this.faceC=P(this.faceC,this.faceCT,w),this.cheek=P(this.cheek,this.cheekT,w),this.material.color.setRGB(this.body[0],this.body[1],this.body[2],three.SRGBColorSpace);let T=Ee[this.shown],S=V(6,o);for(let n of Object.keys(this.overlay))this.overlay[n]+=((T===n?1:0)-this.overlay[n])*S;this.morph<1&&(this.morph=Math.min(1,this.morph+o/(i?.2:.38)),this.shapeDirty=true);let R=j[this.shapeTo].animated||this.morph<1&&j[this.shapeFrom].animated;(this.shapeDirty||R&&a>0)&&this.rebuild();let B=this.group,m=(1+l.st+this.squash)*(1+this.hover*.04),b=1/Math.sqrt(Math.max(.3,1+l.st+this.squash))*(1+this.hover*.04);B.position.y=ve+l.y,B.rotation.set(l.rx+this.look.y*.22*(d>0?1:0),l.ry+this.look.x*.42,l.rz),B.scale.set(b*this.pop,m*this.pop,b*this.pop);let y=this.drawFaceIfNeeded();if(s.speed===0&&!y&&!this.needsFrame&&this.settled())return;let v=performance.now();this.render(l)&&(this.needsFrame=false,this.adapt(performance.now()-v));}adapt(o){this.cost=this.costFrames?this.cost*.9+o*.1:o,!(++this.costFrames<20||this.cost<9||this.quality<=.5)&&(this.quality=Math.max(.5,this.quality-.25),this.costFrames=0,this.resize(this.css[0],this.css[1]));}settled(){let o=this.group,e=this.overlay,s=[e.bang,o.position.y,o.rotation.x,o.rotation.y,o.rotation.z,o.scale.x,o.scale.y,...this.body,this.morph,e.dots,e.zz,e.hearts,e.waves],i=this.lastFrame,a=i.length===s.length;for(let l=0;a&&l<s.length;l++)a=Math.abs(s[l]-i[l])<1e-4;return this.lastFrame=s,a}rebuild(){let o=this.geometry.getAttribute("position"),e=o.array,s=this.dirs,i=We(this.morph),a=this.shapeFrom,l=this.shapeTo,d=new Float32Array(3);for(let r=0;r<s.length;r+=3)fe(l,s[r],s[r+1],s[r+2],this.t,e,r),i<1&&(fe(a,s[r],s[r+1],s[r+2],this.t,d,0),e[r]=d[0]+(e[r]-d[0])*i,e[r+1]=d[1]+(e[r+1]-d[1])*i,e[r+2]=d[2]+(e[r+2]-d[2])*i);o.needsUpdate=true,this.geometry.computeVertexNormals();let u=j[a],p=j[l];this.uniforms.uFaceY.value=u.faceY+(p.faceY-u.faceY)*i,this.uniforms.uFaceScale.value=u.faceScale+(p.faceScale-u.faceScale)*i,this.shapeDirty=false;}drawFaceIfNeeded(){let o=this.face,e=this.drawn,s=false,i=0;for(let d of $)Math.abs(e[i]-o[d])<.002||(s=true,e[i]=o[d]),i++;let a=[this.blink,...this.faceC,...this.cheek];for(let d of a)Math.abs(e[i]-d)<.002||(s=true,e[i]=d),i++;if(!s)return false;let l=he(this.faceC)>.45;return Te(this.faceCtx,o,this.blink,O(this.faceC),O(this.cheek),l?1:.3,O(P(this.faceC,[1,1,1],l?.6:.16)),l?O(P(this.body,[.04,.03,.06],.86)):"#fff",O(l?P(this.body,[.03,.02,.05],.78):P(this.faceC,[0,0,0],.35)),O(this.faceC)),this.faceTex.needsUpdate=true,true}project(o,e,s){let i=this.v.set(o,e,s).applyMatrix4(this.group.matrixWorld).project(this.camera);return [(i.x*.5+.5)*this.w,(-i.y*.5+.5)*this.h]}render(o){let{w:e,h:s,ctx:i}=this,a=Fe(e,s);if(!Be(this.scene,this.camera))return false;if(i.clearRect(0,0,e,s),this.group.updateMatrixWorld(),this.state.shadow){let l=this.v.set(0,ve-.08,0).project(this.camera),d=(l.x*.5+.5)*e,u=(-l.y*.5+.5)*s,p=Math.max(0,o.y)*3,r=Math.min(e,s)*.26*(1-p*.25)*this.pop,h=i.createRadialGradient(d,u,0,d,u,r),c=P(this.body,[.05,.03,.02],.75);h.addColorStop(0,`rgba(${c[0]*255|0},${c[1]*255|0},${c[2]*255|0},${.42*(1-p*.4)})`),h.addColorStop(1,"rgba(0,0,0,0)"),i.fillStyle=h,i.save(),i.translate(d,u),i.scale(1,.22),i.translate(-d,-u),i.beginPath(),i.arc(d,u,r,0,Math.PI*2),i.fill(),i.restore();}return a.setViewport(0,0,e,s),a.setScissor(0,0,e,s),a.render(this.scene,this.camera),i.drawImage(a.domElement,0,Re()-s,e,s,0,0,e,s),this.drawOverlays(),true}drawOverlays(){let o=this.overlay;if(o.dots+o.zz+o.hearts+o.waves+o.bang<.01)return;let{ctx:e}=this,s=this.t,i=G.reducedMotion,a=Math.min(this.w,this.h),l=P(this.body,[1,1,1],.22),d=O(l);if(e.save(),o.dots>.01){let[u,p]=this.project(.6,.92,.2),r=i?2:s*.55%1.6,h=[.014,.022,.052];for(let c=0;c<3;c++){let C=i?1:we(Me((r-c*.18)/.32)),A=i?0:Math.sin(s*2.2+c)*a*.006,k=u+[0,.05,.12][c]*a,w=p-[0,.05,.12][c]*a+A,T=h[c]*a*C;if(!(T<=.2)&&(e.globalAlpha=o.dots*.95,e.fillStyle=d,e.beginPath(),c<2?e.arc(k,w,T,0,Math.PI*2):yt(e,k,w,T),e.fill(),c===2&&C>.6)){e.fillStyle=O(P(this.body,[0,0,0],.35));for(let S=0;S<3;S++){let R=i?0:Math.max(0,Math.sin(s*7-S*.9))*T*.16;e.beginPath(),e.arc(k+(S-1)*T*.34,w-R,T*.1,0,Math.PI*2),e.fill();}}}}if(o.zz>.01){let[u,p]=this.project(.45,.85,.2);e.textAlign="center",e.textBaseline="middle",e.fillStyle=d;for(let r=0;r<3;r++){let h=i?.25+r*.25:(s*.33+r/3)%1,c=u+h*a*.17+Math.sin(h*7+r)*a*.018,C=p-ke(h)*a*.26;e.globalAlpha=o.zz*Math.min(1,h*5)*(1-h)*1.3,e.save(),e.translate(c,C),e.rotate(-0.25+Math.sin(h*5+r)*.22),e.font=`800 ${Math.round(a*(.05+.06*h))}px ui-rounded, system-ui, sans-serif`,e.fillText("z",0,0),e.restore();}}if(o.hearts>.01){let[u,p]=this.project(0,.8,.2),r=P(this.cheek,[1,.3,.45],.55);for(let h=0;h<4;h++){let c=i?.4:(s*.3+h/4)%1,C=h%2?1:-1,A=u+C*a*(.16+.07*(h>>1))+Math.sin(c*6+h)*a*.025,k=p-a*.02-ke(c)*a*.28,w=i?1:we(Me(c/.22)),T=a*(.026+.008*(h%3))*w;e.globalAlpha=o.hearts*(1-Math.max(0,(c-.6)/.4)),e.save(),e.translate(A,k),e.rotate(Math.sin(s*3+h)*.25);let S=e.createLinearGradient(0,-T,0,T);S.addColorStop(0,O(P(r,[1,1,1],.35))),S.addColorStop(1,O(r)),e.fillStyle=S,ue(e,0,0,T),e.fill(),e.restore();}}if(o.waves>.01){e.lineCap="round",e.strokeStyle=d;for(let u of [-1,1]){let[p,r]=this.project(u*1,.12,0);for(let h=0;h<3;h++){let c=i?.3+h*.2:(s*.8+h/3)%1,C=ke(c);e.globalAlpha=o.waves*Math.sin(Math.PI*c)*.9,e.lineWidth=a*(.016-.008*c),e.beginPath();let A=a*(.03+.11*C);u>0?e.arc(p-a*.03,r,A,-0.55,.55):e.arc(p+a*.03,r,A,Math.PI-.55,Math.PI+.55),e.stroke();}}}if(o.bang>.01){let[u,p]=this.project(.55,1,.2),r=i?1:we(Me(this.tau/.35));e.globalAlpha=o.bang,e.fillStyle=d,e.save(),e.translate(u,p),e.rotate(.18),e.scale(r,r),e.beginPath(),e.roundRect(-a*.016,-a*.1,a*.032,a*.07,a*.016),e.arc(0,a*.005,a*.018,0,Math.PI*2),e.fill(),e.restore();}e.restore();}},Me=t=>t<0?0:t>1?1:t,ke=t=>1-(1-t)*(1-t),we=t=>1+(1.9+1)*Math.pow(t-1,3)+1.9*Math.pow(t-1,2);function yt(t,o,e,s){t.ellipse(o,e,s,s*.72,0,0,Math.PI*2),t.moveTo(o-s*.35+s*.45,e-s*.38),t.arc(o-s*.35,e-s*.38,s*.45,0,Math.PI*2),t.moveTo(o+s*.3+s*.5,e-s*.32),t.arc(o+s*.3,e-s*.32,s*.5,0,Math.PI*2);}var vt=["shape","mood","color","face-color","cheek-color","size","speed","follow-cursor","interactive","shadow"],H=(t,o)=>t===null?o:t!=="false"&&t!=="0",_e=t=>t==null||t===""?`${M.size}px`:typeof t=="number"||/^\d+(\.\d+)?$/.test(String(t))?`${t}px`:String(t),Mt=`:host{display:inline-block;position:relative;cursor:pointer;width:var(--moodi-size);height:var(--moodi-size);vertical-align:middle;contain:layout paint style;-webkit-tap-highlight-color:transparent}
24
+ :host([interactive="false"]){pointer-events:none}
25
+ canvas{display:block;width:100%;height:100%}`,kt=typeof HTMLElement>"u"?class{}:HTMLElement,K=class extends kt{constructor(){super();this.buddy=null;this.onEnter=()=>{this.buddy&&(this.buddy.hovering=true);};this.onLeave=()=>{this.buddy&&(this.buddy.hovering=false);};this.onClick=()=>{this.buddy?.state.interactive&&this.react(Math.random()<.5?"happy":"surprised",{duration:1100});};this.root=this.attachShadow({mode:"open"});let e=document.createElement("style");e.textContent=Mt,this.root.append(e),this.style.setProperty("--moodi-size",_e(this.getAttribute("size")));}connectedCallback(){if(this.hasAttribute("role")||this.setAttribute("role","img"),!this.buddy){let e=this.buddy=new se;e.onMoodChange=s=>{this.syncLabel(s.mood),this.dispatchEvent(new CustomEvent("moodchange",{detail:s,bubbles:true}));},this.root.append(e.canvas),e.update(this.readState()),this.addEventListener("pointerenter",this.onEnter),this.addEventListener("pointerleave",this.onLeave),this.addEventListener("click",this.onClick);}this.syncLabel(this.buddy.currentMood),this.buddy.connect(),this.io=new IntersectionObserver(([e])=>this.buddy?.setVisible(e.isIntersecting),{rootMargin:"64px"}),this.io.observe(this),this.ro=new ResizeObserver(([e])=>{let s=e.contentRect;this.buddy?.resize(s.width,s.height);}),this.ro.observe(this);}disconnectedCallback(){this.io?.disconnect(),this.ro?.disconnect(),this.buddy&&(this.buddy.canvas.remove(),this.buddy.dispose(),this.buddy=null),this.removeEventListener("pointerenter",this.onEnter),this.removeEventListener("pointerleave",this.onLeave),this.removeEventListener("click",this.onClick);}attributeChangedCallback(e,s,i){s!==i&&(e==="size"&&this.style.setProperty("--moodi-size",_e(i)),e!=="aria-label"&&(this.buddy?.update(this.readState()),e==="mood"&&this.buddy&&this.syncLabel(this.buddy.currentMood)));}readState(){let e=this.getAttribute("shape"),s=this.getAttribute("mood"),i=parseFloat(this.getAttribute("speed")??"");return {shape:re(e)?e:M.shape,mood:ae(s)?s:M.mood,color:this.getAttribute("color")||M.color,faceColor:this.getAttribute("face-color")||void 0,cheekColor:this.getAttribute("cheek-color")||void 0,speed:Number.isFinite(i)?Math.max(0,i):M.speed,followCursor:H(this.getAttribute("follow-cursor"),M.followCursor),interactive:H(this.getAttribute("interactive"),M.interactive),shadow:H(this.getAttribute("shadow"),M.shadow)}}syncLabel(e){let s=this.getAttribute("aria-label");s&&!s.startsWith("moodi: ")||this.setAttribute("aria-label",`moodi: ${e}`);}react(e,s={}){this.buddy?.react(e,s.duration??1200);}get shape(){let e=this.getAttribute("shape");return re(e)?e:M.shape}set shape(e){this.setAttribute("shape",e);}get mood(){let e=this.getAttribute("mood");return ae(e)?e:M.mood}set mood(e){this.setAttribute("mood",e);}get color(){return this.getAttribute("color")||M.color}set color(e){this.setAttribute("color",e);}get faceColor(){return this.getAttribute("face-color")||void 0}set faceColor(e){e?this.setAttribute("face-color",e):this.removeAttribute("face-color");}get cheekColor(){return this.getAttribute("cheek-color")||void 0}set cheekColor(e){e?this.setAttribute("cheek-color",e):this.removeAttribute("cheek-color");}get size(){let e=this.getAttribute("size");return e?/^\d+(\.\d+)?$/.test(e)?Number(e):e:M.size}set size(e){this.setAttribute("size",String(e));}get speed(){return this.readState().speed}set speed(e){this.setAttribute("speed",String(e));}get followCursor(){return H(this.getAttribute("follow-cursor"),M.followCursor)}set followCursor(e){this.setAttribute("follow-cursor",String(e));}get interactive(){return H(this.getAttribute("interactive"),M.interactive)}set interactive(e){this.setAttribute("interactive",String(e));}get shadow(){return H(this.getAttribute("shadow"),M.shadow)}set shadow(e){this.setAttribute("shadow",String(e));}};K.observedAttributes=vt;function qe(t="moodi-buddy"){typeof window>"u"||!("customElements"in window)||customElements.get(t)||customElements.define(t,class extends K{});}qe();exports.DEFAULTS=M;exports.MOODS=Xe;exports.MOOD_INFO=St;exports.MoodiBuddyElement=K;exports.PALETTES=wt;exports.SHAPES=Ne;exports.SHAPE_LABELS=Ct;exports.defineMoodi=qe;exports.isMood=ae;exports.isShape=re;
@@ -0,0 +1,105 @@
1
+ type Shape = 'pebble' | 'blob' | 'drop' | 'bean' | 'cube' | 'ghost';
2
+ type Mood = 'idle' | 'thinking' | 'loading' | 'listening' | 'speaking' | 'happy' | 'surprised' | 'sad' | 'error' | 'sleeping' | 'love';
3
+ interface MoodiProps {
4
+ /** Body shape. Default `'ghost'`. */
5
+ shape?: Shape;
6
+ /** Current mood / state. Default `'idle'`. */
7
+ mood?: Mood;
8
+ /** Body color, any CSS color. Default `'#6c5ce7'`. */
9
+ color?: string;
10
+ /** Eyes / mouth color. Auto-contrast by default. */
11
+ faceColor?: string;
12
+ /** Blush color. Derived from the body color by default. */
13
+ cheekColor?: string;
14
+ /** px number or any CSS length. Default `160`. */
15
+ size?: number | string;
16
+ /** Animation speed multiplier, `0` pauses. Default `1`. */
17
+ speed?: number;
18
+ /** Eyes (and slightly the body) follow the pointer. Default `true`. */
19
+ followCursor?: boolean;
20
+ /** Reacts to hover and click. Default `true`. */
21
+ interactive?: boolean;
22
+ /** Soft ground shadow. Default `true`. */
23
+ shadow?: boolean;
24
+ }
25
+ interface MoodChangeDetail {
26
+ mood: Mood;
27
+ previous: Mood;
28
+ /** true while a temporary `react()` reaction is showing */
29
+ reaction: boolean;
30
+ }
31
+ declare const SHAPES: readonly Shape[];
32
+ declare const MOODS: readonly Mood[];
33
+ declare const DEFAULTS: {
34
+ shape: Shape;
35
+ mood: Mood;
36
+ color: string;
37
+ size: number | string;
38
+ speed: number;
39
+ followCursor: boolean;
40
+ interactive: boolean;
41
+ shadow: boolean;
42
+ };
43
+ declare const PALETTES: Readonly<Record<string, string>>;
44
+ declare const MOOD_INFO: Readonly<Record<Mood, {
45
+ label: string;
46
+ description: string;
47
+ }>>;
48
+ declare const SHAPE_LABELS: Readonly<Record<Shape, string>>;
49
+ declare const isShape: (v: unknown) => v is Shape;
50
+ declare const isMood: (v: unknown) => v is Mood;
51
+
52
+ declare const Base: typeof HTMLElement;
53
+ declare class MoodiBuddyElement extends Base {
54
+ static observedAttributes: readonly ["shape", "mood", "color", "face-color", "cheek-color", "size", "speed", "follow-cursor", "interactive", "shadow"];
55
+ private buddy;
56
+ private io?;
57
+ private ro?;
58
+ private root;
59
+ constructor();
60
+ connectedCallback(): void;
61
+ disconnectedCallback(): void;
62
+ attributeChangedCallback(name: string, old: string | null, val: string | null): void;
63
+ private readState;
64
+ private syncLabel;
65
+ private onEnter;
66
+ private onLeave;
67
+ private onClick;
68
+ /** Show a mood for a moment, then return to the current `mood`. */
69
+ react(mood: Mood, opts?: {
70
+ duration?: number;
71
+ }): void;
72
+ get shape(): Shape;
73
+ set shape(v: Shape);
74
+ get mood(): Mood;
75
+ set mood(v: Mood);
76
+ get color(): string;
77
+ set color(v: string);
78
+ get faceColor(): string | undefined;
79
+ set faceColor(v: string | undefined);
80
+ get cheekColor(): string | undefined;
81
+ set cheekColor(v: string | undefined);
82
+ get size(): number | string;
83
+ set size(v: number | string);
84
+ get speed(): number;
85
+ set speed(v: number);
86
+ get followCursor(): boolean;
87
+ set followCursor(v: boolean);
88
+ get interactive(): boolean;
89
+ set interactive(v: boolean);
90
+ get shadow(): boolean;
91
+ set shadow(v: boolean);
92
+ }
93
+ /** Registers `<moodi-buddy>`. Safe to call more than once, no-op on the server. */
94
+ declare function defineMoodi(tag?: string): void;
95
+
96
+ declare global {
97
+ interface HTMLElementTagNameMap {
98
+ 'moodi-buddy': MoodiBuddyElement;
99
+ }
100
+ interface HTMLElementEventMap {
101
+ moodchange: CustomEvent<MoodChangeDetail>;
102
+ }
103
+ }
104
+
105
+ export { DEFAULTS, MOODS, MOOD_INFO, type Mood, type MoodChangeDetail, MoodiBuddyElement, type MoodiProps, PALETTES, SHAPES, SHAPE_LABELS, type Shape, defineMoodi, isMood, isShape };
@@ -0,0 +1,105 @@
1
+ type Shape = 'pebble' | 'blob' | 'drop' | 'bean' | 'cube' | 'ghost';
2
+ type Mood = 'idle' | 'thinking' | 'loading' | 'listening' | 'speaking' | 'happy' | 'surprised' | 'sad' | 'error' | 'sleeping' | 'love';
3
+ interface MoodiProps {
4
+ /** Body shape. Default `'ghost'`. */
5
+ shape?: Shape;
6
+ /** Current mood / state. Default `'idle'`. */
7
+ mood?: Mood;
8
+ /** Body color, any CSS color. Default `'#6c5ce7'`. */
9
+ color?: string;
10
+ /** Eyes / mouth color. Auto-contrast by default. */
11
+ faceColor?: string;
12
+ /** Blush color. Derived from the body color by default. */
13
+ cheekColor?: string;
14
+ /** px number or any CSS length. Default `160`. */
15
+ size?: number | string;
16
+ /** Animation speed multiplier, `0` pauses. Default `1`. */
17
+ speed?: number;
18
+ /** Eyes (and slightly the body) follow the pointer. Default `true`. */
19
+ followCursor?: boolean;
20
+ /** Reacts to hover and click. Default `true`. */
21
+ interactive?: boolean;
22
+ /** Soft ground shadow. Default `true`. */
23
+ shadow?: boolean;
24
+ }
25
+ interface MoodChangeDetail {
26
+ mood: Mood;
27
+ previous: Mood;
28
+ /** true while a temporary `react()` reaction is showing */
29
+ reaction: boolean;
30
+ }
31
+ declare const SHAPES: readonly Shape[];
32
+ declare const MOODS: readonly Mood[];
33
+ declare const DEFAULTS: {
34
+ shape: Shape;
35
+ mood: Mood;
36
+ color: string;
37
+ size: number | string;
38
+ speed: number;
39
+ followCursor: boolean;
40
+ interactive: boolean;
41
+ shadow: boolean;
42
+ };
43
+ declare const PALETTES: Readonly<Record<string, string>>;
44
+ declare const MOOD_INFO: Readonly<Record<Mood, {
45
+ label: string;
46
+ description: string;
47
+ }>>;
48
+ declare const SHAPE_LABELS: Readonly<Record<Shape, string>>;
49
+ declare const isShape: (v: unknown) => v is Shape;
50
+ declare const isMood: (v: unknown) => v is Mood;
51
+
52
+ declare const Base: typeof HTMLElement;
53
+ declare class MoodiBuddyElement extends Base {
54
+ static observedAttributes: readonly ["shape", "mood", "color", "face-color", "cheek-color", "size", "speed", "follow-cursor", "interactive", "shadow"];
55
+ private buddy;
56
+ private io?;
57
+ private ro?;
58
+ private root;
59
+ constructor();
60
+ connectedCallback(): void;
61
+ disconnectedCallback(): void;
62
+ attributeChangedCallback(name: string, old: string | null, val: string | null): void;
63
+ private readState;
64
+ private syncLabel;
65
+ private onEnter;
66
+ private onLeave;
67
+ private onClick;
68
+ /** Show a mood for a moment, then return to the current `mood`. */
69
+ react(mood: Mood, opts?: {
70
+ duration?: number;
71
+ }): void;
72
+ get shape(): Shape;
73
+ set shape(v: Shape);
74
+ get mood(): Mood;
75
+ set mood(v: Mood);
76
+ get color(): string;
77
+ set color(v: string);
78
+ get faceColor(): string | undefined;
79
+ set faceColor(v: string | undefined);
80
+ get cheekColor(): string | undefined;
81
+ set cheekColor(v: string | undefined);
82
+ get size(): number | string;
83
+ set size(v: number | string);
84
+ get speed(): number;
85
+ set speed(v: number);
86
+ get followCursor(): boolean;
87
+ set followCursor(v: boolean);
88
+ get interactive(): boolean;
89
+ set interactive(v: boolean);
90
+ get shadow(): boolean;
91
+ set shadow(v: boolean);
92
+ }
93
+ /** Registers `<moodi-buddy>`. Safe to call more than once, no-op on the server. */
94
+ declare function defineMoodi(tag?: string): void;
95
+
96
+ declare global {
97
+ interface HTMLElementTagNameMap {
98
+ 'moodi-buddy': MoodiBuddyElement;
99
+ }
100
+ interface HTMLElementEventMap {
101
+ moodchange: CustomEvent<MoodChangeDetail>;
102
+ }
103
+ }
104
+
105
+ export { DEFAULTS, MOODS, MOOD_INFO, type Mood, type MoodChangeDetail, MoodiBuddyElement, type MoodiProps, PALETTES, SHAPES, SHAPE_LABELS, type Shape, defineMoodi, isMood, isShape };
package/dist/index.js ADDED
@@ -0,0 +1,25 @@
1
+ import {Scene,PerspectiveCamera,Group,Vector3,BufferGeometry,CanvasTexture,SRGBColorSpace,LinearMipmapLinearFilter,Mesh,HemisphereLight,DirectionalLight,MeshStandardMaterial,BufferAttribute,WebGLRenderer,SphereGeometry}from'three';var Ne=["ghost","pebble","blob","drop","bean","cube"],Xe=["idle","thinking","loading","listening","speaking","happy","surprised","sad","error","sleeping","love"],M={shape:"ghost",mood:"idle",color:"#6c5ce7",size:160,speed:1,followCursor:true,interactive:true,shadow:true},wt={Iris:"#6c5ce7",Peach:"#f2a07b",Sage:"#8fb996",Sky:"#8ec5e8",Lilac:"#b9a3e3",Butter:"#f3d36b",Coral:"#f07167",Ink:"#2b2d42",Cloud:"#ecebe8"},St={idle:{label:"Idle",description:"Breathing softly. Blinks now and then."},thinking:{label:"Thinking",description:"Eyes drift up. Little thoughts pop."},loading:{label:"Loading",description:"Half-lidded, wobbling patiently."},listening:{label:"Listening",description:"All eyes. Nods along to every word."},speaking:{label:"Speaking",description:"Chatty mouth, bouncing to the rhythm."},happy:{label:"Happy",description:"Bounces with joy. Cheeks glow."},surprised:{label:"Surprised",description:"Wide eyes, tiny o. Did not see that coming."},sad:{label:"Sad",description:"Slumps a little. Needs a minute."},error:{label:"Error",description:"Something broke. Shakes it off."},sleeping:{label:"Sleeping",description:"Deep, slow breaths. Do not disturb."},love:{label:"Love",description:"Heart eyes, floating on air."}},Ct={pebble:"Pebble",blob:"Blob",drop:"Drop",bean:"Bean",cube:"Cube",ghost:"Ghost"},re=t=>Ne.includes(t),ae=t=>Xe.includes(t);var z=null;function _(t,o){if(!t)return o;let e=/^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(t.trim());if(e)return Se(e[1]);if(z||(z=document.createElement("canvas").getContext("2d")),!z)return o;z.fillStyle="#010203",z.fillStyle=t;let s=String(z.fillStyle);if(s==="#010203"&&t.trim()!=="#010203")return o;if(s.startsWith("#"))return Se(s.slice(1));z.canvas.width=z.canvas.height=1,z.clearRect(0,0,1,1),z.fillRect(0,0,1,1);let i=z.getImageData(0,0,1,1).data;return [i[0]/255,i[1]/255,i[2]/255]}function Se(t){t.length===3&&(t=t[0]+t[0]+t[1]+t[1]+t[2]+t[2]);let o=parseInt(t,16);return [(o>>16&255)/255,(o>>8&255)/255,(o&255)/255]}var O=t=>`rgb(${Math.round(t[0]*255)},${Math.round(t[1]*255)},${Math.round(t[2]*255)})`,P=(t,o,e)=>[t[0]+(o[0]-t[0])*e,t[1]+(o[1]-t[1])*e,t[2]+(o[2]-t[2])*e],ne=t=>t<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4),he=t=>.2126*ne(t[0])+.7152*ne(t[1])+.0722*ne(t[2]),Ve=[.07,.06,.08],He=[1,.97,.93],$e=[1,.42,.48],U=t=>he(t)>.06?P(Ve,t,.06):He,J=t=>P(t,$e,.78);var $=["eyeOpen","eyeSize","lookX","lookY","lidCover","lidTilt","happyEye","closedEye","xEye","heartEye","mouthW","mouthCurve","mouthOpen","mouthRound","mouthSkew","mouthWave","blush"],L={eyeOpen:1,eyeSize:1,lookX:0,lookY:0,lidCover:0,lidTilt:0,happyEye:0,closedEye:0,xEye:0,heartEye:0,mouthW:.1,mouthCurve:.35,mouthOpen:0,mouthRound:0,mouthSkew:0,mouthWave:0,blush:.45},Z={idle:{...L},thinking:{...L,eyeOpen:.92,lookX:.7,lookY:-0.8,lidCover:.12,mouthW:.065,mouthCurve:0,mouthSkew:.8,blush:.3},loading:{...L,lidCover:.48,mouthW:.06,mouthCurve:.12,blush:.3},listening:{...L,eyeOpen:1.12,eyeSize:1.16,mouthW:.06,mouthCurve:.2,mouthOpen:.02},speaking:{...L,mouthW:.09,mouthCurve:.25},happy:{...L,happyEye:1,mouthW:.13,mouthCurve:1,mouthOpen:.08,blush:1},surprised:{...L,eyeOpen:1.25,eyeSize:1.22,mouthW:.06,mouthCurve:0,mouthOpen:.12,mouthRound:1,blush:.35},sad:{...L,eyeOpen:.95,lidCover:.32,lidTilt:.5,lookY:.45,mouthW:.08,mouthCurve:-0.75,blush:.2},error:{...L,xEye:1,mouthW:.1,mouthCurve:-0.2,mouthWave:1,blush:.15},sleeping:{...L,closedEye:1,mouthW:.04,mouthCurve:0,mouthOpen:.035,mouthRound:.7,blush:.55},love:{...L,heartEye:1,mouthW:.11,mouthCurve:.9,mouthOpen:.03,blush:1}},Ce={idle:1,thinking:.15,loading:0,listening:1,speaking:.9,happy:.6,surprised:.8,sad:.3,error:.3,sleeping:0,love:.7},de=t=>({...t}),je=.31,Ke=-0.04,Ue=.098,le=.128,Je=.2,ce=.05;function Te(t,o,e,s,i,a,l,d,u,p){let r=t.canvas.width;t.setTransform(1,0,0,1,0,0),t.clearRect(0,0,r,r),t.setTransform(r/2,0,0,r/2,r/2,r/2),t.lineCap="round",t.lineJoin="round";let h=o.lookX*.075,c=o.lookY*.06;if(o.blush>.01){let m=r*.018;t.save(),"filter"in t&&(t.filter=`blur(${m}px)`);for(let y of [-1,1]){let v=y*.52+h*.4,n=.16+c*.4,f=t.createRadialGradient(v,n,0,v,n,.16);f.addColorStop(0,i),f.addColorStop(1,"rgba(0,0,0,0)"),t.globalAlpha=Math.min(1,o.blush)*.62,t.fillStyle=f,t.beginPath(),t.ellipse(v,n,.17,.12,0,0,Math.PI*2),t.fill();}t.restore();let b=Math.min(1,Math.max(0,(o.blush-.6)/.4));if(b>.01){t.save(),t.globalAlpha=b*.55,t.strokeStyle=i,t.lineWidth=.016,t.lineCap="round";for(let y of [-1,1]){let v=y*.52+h*.4,n=.16+c*.4;for(let f=-1;f<=1;f++){let F=v+f*.045;t.beginPath(),t.moveTo(F+.018,n-.035),t.lineTo(F-.018,n+.035),t.stroke();}}t.restore();}}t.fillStyle=s,t.strokeStyle=s,t.lineWidth=ce;let A=1-Math.min(1,o.happyEye+o.closedEye+o.xEye+o.heartEye);for(let m of [-1,1]){let b=m*je+h,y=Ke+c,v=Ue*o.eyeSize;if(A>.01){t.globalAlpha=A;let n=Math.max(.06,o.eyeOpen*e),f=le*o.eyeSize*n;if(t.save(),o.lidCover>.01||o.lidTilt>.01){let W=y-le*o.eyeSize+2*le*o.eyeSize*o.lidCover;t.translate(b,W),t.rotate(m*o.lidTilt*.6),t.beginPath(),t.rect(-1,0,2,2),t.clip(),t.rotate(-m*o.lidTilt*.6),t.translate(-b,-W);}t.shadowColor=s,t.shadowBlur=r*.035*a;let F=t.createRadialGradient(b,y-f*.2,0,b,y,Math.max(v,f));if(F.addColorStop(0,l),F.addColorStop(1,s),t.fillStyle=F,t.beginPath(),t.ellipse(b,y,v,f,0,0,Math.PI*2),t.fill(),t.shadowBlur=0,t.fillStyle=s,n>.35){let x=Math.max(-1,Math.min(1,o.lookX)),W=Math.max(-1,Math.min(1,o.lookY)),ie=d==="#fff"?.3:.34;t.globalAlpha=A*Math.min(1,(n-.35)*3),t.fillStyle=d,t.beginPath(),t.arc(b-v*.1+x*v*.5,y-f*.16+W*f*.42,v*ie,0,Math.PI*2),t.fill(),t.fillStyle=s;}t.restore();}if(t.shadowColor=s,t.shadowBlur=r*.03*a,o.happyEye>.01){let n=.6+.4*o.happyEye;t.globalAlpha=Math.min(1,o.happyEye),t.beginPath(),t.arc(b,y+.045,v*1.15*n,Math.PI*1.12,Math.PI*1.88),t.stroke();}if(o.closedEye>.01){let n=.6+.4*o.closedEye;t.globalAlpha=Math.min(1,o.closedEye),t.beginPath(),t.moveTo(b-v*1.15*n,y+.01),t.quadraticCurveTo(b,y+.07*n,b+v*1.15*n,y+.01),t.stroke();}if(o.xEye>.01){let n=v*.95*(.6+.4*o.xEye);t.globalAlpha=Math.min(1,o.xEye),t.beginPath(),t.moveTo(b-n,y-n),t.lineTo(b+n,y+n),t.moveTo(b+n,y-n),t.lineTo(b-n,y+n),t.stroke();}o.heartEye>.01&&(t.globalAlpha=Math.min(1,o.heartEye),ue(t,b,y,v*1.35*(.5+.5*o.heartEye)),t.fill());}t.shadowBlur=0;let k=h*.6+o.mouthSkew*.07,w=Je+c*.6,T=Math.min(1,Math.max(0,o.mouthRound)),S=o.mouthW,R=t.createLinearGradient(k-S-.02,w,k+S+.02,w);R.addColorStop(0,p),R.addColorStop(.5,l),R.addColorStop(1,p),t.shadowColor=s,t.shadowBlur=r*.02*a;let B=(m,b,y,v)=>{t.fillStyle=u,t.fill(m),t.shadowBlur=0,b>.02&&(t.save(),t.clip(m),t.fillStyle=i,t.globalAlpha*=Math.min(1,(b-.02)*12),t.beginPath(),t.ellipse(k,y,v,b*.55,0,0,Math.PI*2),t.fill(),t.restore()),t.strokeStyle=R;let n=t.lineWidth;t.lineWidth=n*.6,t.stroke(m),t.lineWidth=n,t.shadowBlur=r*.02*a;};if(T<.99){t.globalAlpha=1-T;let m=18,b=f=>o.mouthCurve*.075*(1-f*f),y=(f,F)=>{let x=f/m*2-1,W=k+x*S,ie=w+b(x)+o.mouthSkew*x*.03+o.mouthWave*.018*Math.sin((x+1)*Math.PI*2)+F*(1-x*x);return [W,ie]},v=Math.max(0,o.mouthOpen),n=new Path2D;for(let f=0;f<=m;f++){let[F,x]=y(f,-v*.15);f?n.lineTo(F,x):n.moveTo(F,x);}if(v>.003){for(let f=m;f>=0;f--){let[F,x]=y(f,v);n.lineTo(F,x);}n.closePath(),B(n,v,y(m/2,v)[1],S*.55);}else t.strokeStyle=R,t.stroke(n);}if(T>.01){t.globalAlpha=T;let m=Math.max(.02,o.mouthOpen*.55+.01),b=new Path2D;b.ellipse(k,w+.01,m*.85,m,0,0,Math.PI*2),t.lineWidth=ce*.8,B(b,o.mouthOpen,w+.01+m*.8,m*.6),t.lineWidth=ce;}t.shadowBlur=0,t.globalAlpha=1;}function ue(t,o,e,s){t.beginPath(),t.moveTo(o,e+s*.85),t.bezierCurveTo(o-s*1.25,e+s*.05,o-s*.75,e-s*.95,o,e-s*.35),t.bezierCurveTo(o+s*.75,e-s*.95,o+s*1.25,e+s*.05,o,e+s*.85),t.closePath();}var te=()=>({y:0,rx:0,ry:0,rz:0,st:0,mouth:0,lookX:0,lookY:0}),{sin:g,cos:Ze,abs:Qe,max:Q,exp:et,PI:ee}=Math;function pe(t,o,e,s){switch(s.y=s.rx=s.ry=s.rz=s.st=s.mouth=s.lookX=s.lookY=0,t){case "idle":{let i=g(o*1.6);s.y=.015*i,s.st=.018*i;break}case "thinking":{s.rz=.07*g(o*1.1),s.y=.02*g(o*2.2),s.ry=.15,s.rx=-0.06;break}case "loading":{let i=o*2.4;s.rx=.12*g(i),s.rz=.12*Ze(i),s.ry=.22*g(i),s.y=.02*g(i*2),s.lookX=.55*g(i);break}case "listening":{let i=Q(0,g(o*3.2));s.rx=.09*i*i;let a=Qe(g(o*7.1)*.5+g(o*11.3)*.3+g(o*3.7)*.2);s.st=.03*a;break}case "speaking":{let i=Q(0,g(o*9)*.6+g(o*14.3)*.4)*(.65+.35*g(o*1.7));s.mouth=.11*i,s.st=.04*i,s.rz=.03*g(o*2.1);break}case "happy":{let i=o*1.35%1;s.y=.2*g(ee*i);let a=Q(0,1-i/.14)+Q(0,(i-.9)/.1);s.st=.08*g(ee*i)-.14*a,s.rz=.05*g(o*1.35*ee);break}case "surprised":{if(e<.45){let i=g(ee*e/.45);s.y=.16*i,s.st=.12*i;}s.rz=.01*g(o*38)*et(-e*2),s.rx=-0.06;break}case "sad":{let i=g(o*1.1);s.y=-0.06,s.rx=.2,s.st=-0.04+.015*i,s.rz=-0.04;break}case "error":{let i=e%2.8;if(i<.55){let a=1-i/.55;s.ry=.26*g(i*42)*a,s.rz=.05*g(i*42+1)*a;}s.st=-0.02;break}case "sleeping":{let i=g(o*1);s.st=.04*i,s.y=-0.03,s.rz=.1,s.rx=.1;break}case "love":{s.y=.07+.06*g(o*1.6),s.rz=.05*g(o*.8),s.st=.015*g(o*1.6+1);break}}return s}function Ae(t,o,e,s){for(let i of Object.keys(s))s[i]=t[i]+(o[i]-t[i])*e;return s}var Ee={thinking:"dots",sleeping:"zz",love:"hearts",listening:"waves",surprised:"bang"};var X=new Set,E=null,q=0,Y=0,oe=0,be,Pe=false,N="idle",I={x:0,y:0,lastMove:-1e9,known:false},G={reducedMotion:false};function ot(){if(Pe)return;Pe=true,addEventListener("pointermove",o=>{I.x=o.clientX,I.y=o.clientY,I.lastMove=performance.now(),I.known=true;},{passive:true}),document.addEventListener("visibilitychange",D);let t=matchMedia("(prefers-reduced-motion: reduce)");G.reducedMotion=t.matches,t.addEventListener?.("change",o=>G.reducedMotion=o.matches);}function Fe(t,o){return E||(E=new WebGLRenderer({antialias:true,alpha:true,powerPreference:"low-power",preserveDrawingBuffer:true}),E.setPixelRatio(1),E.setClearColor(0,0),E.setScissorTest(true),E.domElement.addEventListener("webglcontextlost",e=>{e.preventDefault(),E?.dispose(),E=null,q=Y=0,N="idle";})),(t>q||o>Y)&&(q=Math.max(q,t),Y=Math.max(Y,o),E.setSize(q,Y,false)),E}var Re=()=>Y;function Be(t,o){if(N==="ready")return true;if(N==="idle"&&E){N="compiling";let e=E;e.compileAsync(t,o).catch(()=>{}).then(()=>{e===E&&(N="ready"),D();});}return false}function ze(t){ot(),clearTimeout(be),X.add(t),D();}function Oe(t){X.delete(t),X.size===0&&(clearTimeout(be),be=setTimeout(()=>{X.size||!E||(E.dispose(),E.forceContextLoss(),E=null,q=Y=0,N="idle");},1500));}function D(){if(!oe){for(let t of X)if(t.active){oe=requestAnimationFrame(Le);return}}}var st=10,xe=new WeakMap,me=0;function Le(t){oe=0;let o=[...X].filter(a=>a.active);if(!o.length)return;let e=performance.now(),s=o.length,i=0;for(;i<s;i++){let a=o[(me+i)%s],l=xe.get(a)??t-16;if(xe.set(a,t),a.tick(Math.min(.05,Math.max(0,(t-l)/1e3)),t),performance.now()-e>st){i++;break}}me=(me+i)%s,oe=requestAnimationFrame(Le);}var j={pebble:{faceY:.02,faceScale:1.3,animated:false},blob:{faceY:.02,faceScale:1.28,animated:true},drop:{faceY:-0.24,faceScale:1.22,animated:false},bean:{faceY:.14,faceScale:.92,animated:false},cube:{faceY:0,faceScale:1.22,animated:false},ghost:{faceY:.22,faceScale:1.24,animated:true}};function Ie(t){let o=new SphereGeometry(1,t*2,Math.round(t*1.5)),e=o.getAttribute("position"),s=o.getIndex(),i=new Map,a=[],l=new Uint32Array(e.count);for(let r=0;r<e.count;r++){let h=e.getX(r),c=e.getY(r),C=e.getZ(r),A=`${Math.round(h*1e4)},${Math.round(c*1e4)},${Math.round(C*1e4)}`,k=i.get(A);if(k===void 0){k=a.length/3,i.set(A,k);let w=Math.hypot(h,c,C)||1;a.push(h/w,c/w,C/w);}l[r]=k;}let d=[];for(let r=0;r<s.count;r+=3){let h=l[s.getX(r)],c=l[s.getX(r+1)],C=l[s.getX(r+2)];h!==c&&c!==C&&h!==C&&d.push(h,c,C);}o.dispose();let u=new BufferGeometry,p=new Float32Array(a);return u.setAttribute("position",new BufferAttribute(new Float32Array(p),3)),u.setIndex(d),{geometry:u,dirs:p}}var nt=(t,o,e)=>{let s=Math.min(1,Math.max(0,(e-t)/(o-t)));return s*s*(3-2*s)};function fe(t,o,e,s,i,a,l){let d=o,u=e,p=s;switch(t){case "pebble":{d=o*1.1+.04*e,u=e*.92*(.95+.05*Math.tanh(3*e))+.03*o,p=s*.94;break}case "blob":{let r=.97+.045*Math.sin(2.1*o+1.3*i)*Math.sin(2.4*e+1.7*i+1)+.035*Math.sin(2.7*s+2.3*o+1.1*i);d=o*r,u=e*r,p=s*r;break}case "drop":{let r=Math.max(e,0),h=1-.8*Math.pow(r,1.6);d=o*h*.98+.1*r*r*r,u=(e<0?e*.9:e*1.32)-.12,p=s*h*.98;break}case "bean":{let r=e*.78+.42*Math.tanh(e*3);d=(o*.8+.14*(r*r/1.44)-.07)*.9,u=r*.88,p=s*.8*.9;break}case "cube":{let h=Math.pow(Math.pow(Math.abs(o),4.5)+Math.pow(Math.abs(e),4.5)+Math.pow(Math.abs(s),4.5),-0.2222222222222222);d=o*h*.84,u=e*h*.82,p=s*h*.84;break}case "ghost":{if(e>=0)d=o,u=e,p=s;else {let h=(Math.acos(Math.max(-1,e))-Math.PI/2)/(Math.PI/2),c=Math.hypot(o,s)||1,C=o/c,A=s/c,k=Math.atan2(s,o),w=.085*Math.sin(5*k+2.2*i),T=1.04,S=.13,R=-1.05+w,B;if(h<.45){let m=h/.45;B=1+.04*m,u=-m*(1.05-S)+w*nt(.45,1,m);}else if(h<.75){let m=(h-.45)/.3*(Math.PI/2);B=T-S+S*Math.cos(m),u=R+S-S*Math.sin(m);}else {let m=(h-.75)/.25;B=(T-S)*(1-m),u=R+.22*m*m*(3-2*m);}d=C*B,p=A*B;}d*=.9,u=(u+.1)*.9,p*=.9;break}}a[l]=d,a[l+1]=u,a[l+2]=p;}var ve=-0.85,De=new Map;function gt(t){let o=De.get(t);if(!o){let{geometry:e,dirs:s}=Ie(t);o={dirs:s,index:new Uint32Array(e.getIndex().array)},e.dispose(),De.set(t,o);}return o}var V=(t,o)=>1-Math.exp(-t*o),We=t=>1-Math.pow(1-t,3),se=class{constructor(){this.scene=new Scene;this.camera=new PerspectiveCamera(30,1,.1,50);this.group=new Group;this.v=new Vector3;this.geometry=new BufferGeometry;this.faceCanvas=document.createElement("canvas");this.uniforms={uFace:{value:null},uFaceY:{value:0},uFaceScale:{value:1.5}};this.state={...M};this.visible=false;this.connected=false;this.w=0;this.h=0;this.t=Math.random()*100;this.shapeFrom=M.shape;this.shapeTo=M.shape;this.morph=1;this.shapeDirty=true;this.shown=M.mood;this.prevShown=M.mood;this.tau=10;this.prevTau=10;this.blend=1;this.reaction=null;this.pose=te();this.poseA=te();this.poseB=te();this.face=de(Z.idle);this.target=de(Z.idle);this.drawn=new Float32Array($.length+7).fill(NaN);this.blink=1;this.blinkT=-1;this.nextBlink=2+Math.random()*3;this.body=_(M.color,[.95,.63,.48]);this.bodyT=this.body;this.faceC=U(this.body);this.faceCT=this.faceC;this.cheek=J(this.body);this.cheekT=this.cheek;this.look={x:0,y:0};this.wander={x:0,y:0,next:0};this.squash=0;this.squashV=0;this.pop=.6;this.popV=0;this.hover=0;this.hovering=false;this.overlay={dots:0,zz:0,hearts:0,waves:0,bang:0};this.needsFrame=true;this.css=[0,0];this.quality=1;this.cost=0;this.costFrames=0;this.lastFrame=[];this.canvas=document.createElement("canvas"),this.ctx=this.canvas.getContext("2d"),this.faceCtx=this.faceCanvas.getContext("2d"),this.faceCanvas.width=this.faceCanvas.height=256,this.faceTex=new CanvasTexture(this.faceCanvas),this.faceTex.colorSpace=SRGBColorSpace,this.faceTex.minFilter=LinearMipmapLinearFilter,this.faceTex.anisotropy=4,this.uniforms.uFace.value=this.faceTex,this.setSegments(24),this.material=this.createMaterial(),this.mesh=new Mesh(this.geometry,this.material),this.mesh.frustumCulled=false,this.mesh.position.y=-ve,this.group.add(this.mesh),this.scene.add(this.group),this.scene.add(new HemisphereLight(16774120,7234140,1.7));let o=new DirectionalLight(16777215,1.9);o.position.set(2.5,3.5,4);let e=new DirectionalLight(16770770,1.4);e.position.set(-3.5,2,-3);let s=new DirectionalLight(16773350,.45);s.position.set(-2,-1,3),this.scene.add(o,e,s),this.camera.position.set(0,.3,6.6),this.camera.lookAt(0,.12,0);}get active(){return this.visible&&this.connected&&this.w>0}createMaterial(){let o=new MeshStandardMaterial({roughness:.62,metalness:0});return o.color.setRGB(...this.body,SRGBColorSpace),o.customProgramCacheKey=()=>"moodi-1",o.onBeforeCompile=e=>{Object.assign(e.uniforms,this.uniforms),e.vertexShader=e.vertexShader.replace("#include <common>",`#include <common>
2
+ varying vec3 vFaceP;
3
+ varying vec3 vFaceN;`).replace("#include <begin_vertex>",`#include <begin_vertex>
4
+ vFaceP = position;
5
+ vFaceN = objectNormal;`),e.fragmentShader=e.fragmentShader.replace("#include <common>",`#include <common>
6
+ varying vec3 vFaceP;
7
+ varying vec3 vFaceN;
8
+ uniform sampler2D uFace;
9
+ uniform float uFaceY;
10
+ uniform float uFaceScale;`).replace("#include <map_fragment>",`#include <map_fragment>
11
+ vec2 fuv = vec2(vFaceP.x, vFaceP.y - uFaceY) / uFaceScale + 0.5;
12
+ float inb = step(0.0, fuv.x) * step(fuv.x, 1.0) * step(0.0, fuv.y) * step(fuv.y, 1.0);
13
+ vec4 fc = texture2D(uFace, fuv);
14
+ float faceMask = fc.a * inb * smoothstep(0.12, 0.45, normalize(vFaceN).z);
15
+ diffuseColor.rgb = mix(diffuseColor.rgb, fc.rgb, faceMask);`).replace("#include <roughnessmap_fragment>",`#include <roughnessmap_fragment>
16
+ roughnessFactor = mix(roughnessFactor, 0.35, faceMask * 0.7);`).replace("#include <opaque_fragment>",`float rimK = 1.0 - saturate(dot(normal, normalize(vViewPosition)));
17
+ outgoingLight += diffuseColor.rgb * 0.24 * rimK * rimK + vec3(0.05) * pow(rimK, 4.0);
18
+ // soft contact shading on the underside
19
+ outgoingLight *= mix(0.62, 1.0, smoothstep(-0.95, 0.35, normalize(vFaceN).y));
20
+ // eyes and mouth glow a little: mostly unlit, so they read as light, not paint
21
+ float emis = smoothstep(0.75, 1.0, fc.a) * inb * smoothstep(0.12, 0.45, normalize(vFaceN).z);
22
+ outgoingLight = mix(outgoingLight, diffuseColor.rgb * 1.12, emis * 0.65);
23
+ #include <opaque_fragment>`);},o}setSegments(o){let e=gt(o);this.dirs!==e.dirs&&(this.dirs=e.dirs,this.geometry.dispose(),this.geometry=new BufferGeometry,this.geometry.setAttribute("position",new BufferAttribute(new Float32Array(e.dirs.length),3)),this.geometry.setIndex(new BufferAttribute(e.index,1)),this.mesh&&(this.mesh.geometry=this.geometry),this.shapeDirty=true);}connect(){this.connected=true,ze(this);}disconnect(){this.connected=false,Oe(this);}setVisible(o){this.visible=o,o&&D();}resize(o,e){this.css=[o,e];let s=Math.min(2,devicePixelRatio||1)*this.quality,i=Math.max(1,Math.round(o*s)),a=Math.max(1,Math.round(e*s));if(i===this.w&&a===this.h)return;this.w=this.canvas.width=i,this.h=this.canvas.height=a;let l=Math.min(i,a),d=Math.min(o,e);this.camera.position.set(0,.3,d<90?5.1:6.1),this.camera.lookAt(0,d<90?.02:.12,0),this.camera.aspect=i/a,this.camera.updateProjectionMatrix(),this.setSegments(l<160?16:l<400?24:32);let u=l<220?256:512;this.faceCanvas.width!==u&&(this.faceCanvas.width=this.faceCanvas.height=u,this.faceTex.dispose(),this.drawn.fill(NaN)),this.needsFrame=true,D();}update(o){let e=this.state;this.state={...e,...o};let s=this.state;s.shape!==this.shapeTo&&(this.shapeFrom=this.shapeTo,this.shapeTo=s.shape,this.morph=0,G.reducedMotion||(this.squashV-=2.2)),(s.color!==e.color||s.faceColor!==e.faceColor||s.cheekColor!==e.cheekColor)&&(this.bodyT=_(s.color,_(M.color,this.bodyT)),this.faceCT=s.faceColor?_(s.faceColor,U(this.bodyT)):U(this.bodyT),this.cheekT=s.cheekColor?_(s.cheekColor,J(this.bodyT)):J(this.bodyT)),s.mood!==e.mood&&!this.reaction&&this.show(s.mood),this.needsFrame=true,D();}react(o,e=1200){this.reaction={mood:o,left:e/1e3},this.show(o);}get currentMood(){return this.shown}dispose(){this.disconnect(),this.geometry.dispose(),this.material.dispose(),this.faceTex.dispose(),this.onMoodChange=void 0;}show(o){if(o===this.shown)return;let e=this.shown;this.prevShown=e,this.prevTau=this.tau,this.shown=o,this.tau=0,this.blend=0,G.reducedMotion||(this.squashV-=1.1),this.onMoodChange?.({mood:o,previous:e,reaction:!!this.reaction});}tick(o,e){let s=this.state,i=G.reducedMotion,a=o*Math.max(0,s.speed);this.t+=a,this.tau+=a,this.prevTau+=a,this.reaction&&(this.reaction.left-=o,this.reaction.left<=0&&(this.reaction=null,this.show(s.mood))),pe(this.shown,this.t,this.tau,this.poseB),this.blend<1?(this.blend=Math.min(1,this.blend+o/.45),pe(this.prevShown,this.t,this.prevTau,this.poseA),Ae(this.poseA,this.poseB,We(this.blend),this.pose)):Object.assign(this.pose,this.poseB);let l=this.pose;i&&(l.y=l.rx=l.ry=l.rz=l.st=l.lookX=0);let d=s.followCursor&&!i&&s.speed>0?Ce[this.shown]:0,u=0,p=0;if(d>0&&I.known){let n=this.canvas.getBoundingClientRect(),f=Math.max(200,n.width*.85);u=Math.max(-1,Math.min(1,(I.x-(n.left+n.width/2))/f)),p=Math.max(-1,Math.min(1,(I.y-(n.top+n.height*.45))/f)),u*=d,p*=d;}else if(this.shown==="idle"&&!i&&s.speed>0){if(this.t>this.wander.next){let n=Math.random()<.4;this.wander.x=n?0:(Math.random()*2-1)*.6,this.wander.y=n?0:(Math.random()*2-1)*.35,this.wander.next=this.t+1.8+Math.random()*2.6;}u=this.wander.x,p=this.wander.y;}let r=V(7,o);this.look.x+=(u-this.look.x)*r,this.look.y+=(p-this.look.y)*r;let h=Z[this.shown],c=this.target;for(let n of $)c[n]=h[n];c.lookX+=this.look.x+l.lookX,c.lookY+=this.look.y+l.lookY,c.mouthOpen+=l.mouth,c.eyeOpen+=this.hover*.08;let C=V(11,o);for(let n of $)this.face[n]+=(c[n]-this.face[n])*C;let A=V(16,o);if(this.face.lookX+=(c.lookX-this.face.lookX)*A,this.face.lookY+=(c.lookY-this.face.lookY)*A,this.face.mouthOpen+=(c.mouthOpen-this.face.mouthOpen)*A,this.nextBlink-=a,this.nextBlink<=0&&this.blinkT<0&&(this.blinkT=0,this.nextBlink=Math.random()<.2?.25:2.2+Math.random()*3.8),this.blinkT>=0){this.blinkT+=a;let n=this.blinkT/.16;this.blink=n>=1?1:1-Math.sin(Math.PI*n),n>=1&&(this.blinkT=-1);}let k=Math.min(o,1/30);this.squashV+=(-170*this.squash-11*this.squashV)*k,this.squash+=this.squashV*k,i&&(this.squash=this.squashV=0),i?this.pop=1:(this.popV+=(-140*(this.pop-1)-13*this.popV)*k,this.pop+=this.popV*k),this.hover+=((this.hovering&&s.interactive?1:0)-this.hover)*V(12,o);let w=V(9,o);this.body=P(this.body,this.bodyT,w),this.faceC=P(this.faceC,this.faceCT,w),this.cheek=P(this.cheek,this.cheekT,w),this.material.color.setRGB(this.body[0],this.body[1],this.body[2],SRGBColorSpace);let T=Ee[this.shown],S=V(6,o);for(let n of Object.keys(this.overlay))this.overlay[n]+=((T===n?1:0)-this.overlay[n])*S;this.morph<1&&(this.morph=Math.min(1,this.morph+o/(i?.2:.38)),this.shapeDirty=true);let R=j[this.shapeTo].animated||this.morph<1&&j[this.shapeFrom].animated;(this.shapeDirty||R&&a>0)&&this.rebuild();let B=this.group,m=(1+l.st+this.squash)*(1+this.hover*.04),b=1/Math.sqrt(Math.max(.3,1+l.st+this.squash))*(1+this.hover*.04);B.position.y=ve+l.y,B.rotation.set(l.rx+this.look.y*.22*(d>0?1:0),l.ry+this.look.x*.42,l.rz),B.scale.set(b*this.pop,m*this.pop,b*this.pop);let y=this.drawFaceIfNeeded();if(s.speed===0&&!y&&!this.needsFrame&&this.settled())return;let v=performance.now();this.render(l)&&(this.needsFrame=false,this.adapt(performance.now()-v));}adapt(o){this.cost=this.costFrames?this.cost*.9+o*.1:o,!(++this.costFrames<20||this.cost<9||this.quality<=.5)&&(this.quality=Math.max(.5,this.quality-.25),this.costFrames=0,this.resize(this.css[0],this.css[1]));}settled(){let o=this.group,e=this.overlay,s=[e.bang,o.position.y,o.rotation.x,o.rotation.y,o.rotation.z,o.scale.x,o.scale.y,...this.body,this.morph,e.dots,e.zz,e.hearts,e.waves],i=this.lastFrame,a=i.length===s.length;for(let l=0;a&&l<s.length;l++)a=Math.abs(s[l]-i[l])<1e-4;return this.lastFrame=s,a}rebuild(){let o=this.geometry.getAttribute("position"),e=o.array,s=this.dirs,i=We(this.morph),a=this.shapeFrom,l=this.shapeTo,d=new Float32Array(3);for(let r=0;r<s.length;r+=3)fe(l,s[r],s[r+1],s[r+2],this.t,e,r),i<1&&(fe(a,s[r],s[r+1],s[r+2],this.t,d,0),e[r]=d[0]+(e[r]-d[0])*i,e[r+1]=d[1]+(e[r+1]-d[1])*i,e[r+2]=d[2]+(e[r+2]-d[2])*i);o.needsUpdate=true,this.geometry.computeVertexNormals();let u=j[a],p=j[l];this.uniforms.uFaceY.value=u.faceY+(p.faceY-u.faceY)*i,this.uniforms.uFaceScale.value=u.faceScale+(p.faceScale-u.faceScale)*i,this.shapeDirty=false;}drawFaceIfNeeded(){let o=this.face,e=this.drawn,s=false,i=0;for(let d of $)Math.abs(e[i]-o[d])<.002||(s=true,e[i]=o[d]),i++;let a=[this.blink,...this.faceC,...this.cheek];for(let d of a)Math.abs(e[i]-d)<.002||(s=true,e[i]=d),i++;if(!s)return false;let l=he(this.faceC)>.45;return Te(this.faceCtx,o,this.blink,O(this.faceC),O(this.cheek),l?1:.3,O(P(this.faceC,[1,1,1],l?.6:.16)),l?O(P(this.body,[.04,.03,.06],.86)):"#fff",O(l?P(this.body,[.03,.02,.05],.78):P(this.faceC,[0,0,0],.35)),O(this.faceC)),this.faceTex.needsUpdate=true,true}project(o,e,s){let i=this.v.set(o,e,s).applyMatrix4(this.group.matrixWorld).project(this.camera);return [(i.x*.5+.5)*this.w,(-i.y*.5+.5)*this.h]}render(o){let{w:e,h:s,ctx:i}=this,a=Fe(e,s);if(!Be(this.scene,this.camera))return false;if(i.clearRect(0,0,e,s),this.group.updateMatrixWorld(),this.state.shadow){let l=this.v.set(0,ve-.08,0).project(this.camera),d=(l.x*.5+.5)*e,u=(-l.y*.5+.5)*s,p=Math.max(0,o.y)*3,r=Math.min(e,s)*.26*(1-p*.25)*this.pop,h=i.createRadialGradient(d,u,0,d,u,r),c=P(this.body,[.05,.03,.02],.75);h.addColorStop(0,`rgba(${c[0]*255|0},${c[1]*255|0},${c[2]*255|0},${.42*(1-p*.4)})`),h.addColorStop(1,"rgba(0,0,0,0)"),i.fillStyle=h,i.save(),i.translate(d,u),i.scale(1,.22),i.translate(-d,-u),i.beginPath(),i.arc(d,u,r,0,Math.PI*2),i.fill(),i.restore();}return a.setViewport(0,0,e,s),a.setScissor(0,0,e,s),a.render(this.scene,this.camera),i.drawImage(a.domElement,0,Re()-s,e,s,0,0,e,s),this.drawOverlays(),true}drawOverlays(){let o=this.overlay;if(o.dots+o.zz+o.hearts+o.waves+o.bang<.01)return;let{ctx:e}=this,s=this.t,i=G.reducedMotion,a=Math.min(this.w,this.h),l=P(this.body,[1,1,1],.22),d=O(l);if(e.save(),o.dots>.01){let[u,p]=this.project(.6,.92,.2),r=i?2:s*.55%1.6,h=[.014,.022,.052];for(let c=0;c<3;c++){let C=i?1:we(Me((r-c*.18)/.32)),A=i?0:Math.sin(s*2.2+c)*a*.006,k=u+[0,.05,.12][c]*a,w=p-[0,.05,.12][c]*a+A,T=h[c]*a*C;if(!(T<=.2)&&(e.globalAlpha=o.dots*.95,e.fillStyle=d,e.beginPath(),c<2?e.arc(k,w,T,0,Math.PI*2):yt(e,k,w,T),e.fill(),c===2&&C>.6)){e.fillStyle=O(P(this.body,[0,0,0],.35));for(let S=0;S<3;S++){let R=i?0:Math.max(0,Math.sin(s*7-S*.9))*T*.16;e.beginPath(),e.arc(k+(S-1)*T*.34,w-R,T*.1,0,Math.PI*2),e.fill();}}}}if(o.zz>.01){let[u,p]=this.project(.45,.85,.2);e.textAlign="center",e.textBaseline="middle",e.fillStyle=d;for(let r=0;r<3;r++){let h=i?.25+r*.25:(s*.33+r/3)%1,c=u+h*a*.17+Math.sin(h*7+r)*a*.018,C=p-ke(h)*a*.26;e.globalAlpha=o.zz*Math.min(1,h*5)*(1-h)*1.3,e.save(),e.translate(c,C),e.rotate(-0.25+Math.sin(h*5+r)*.22),e.font=`800 ${Math.round(a*(.05+.06*h))}px ui-rounded, system-ui, sans-serif`,e.fillText("z",0,0),e.restore();}}if(o.hearts>.01){let[u,p]=this.project(0,.8,.2),r=P(this.cheek,[1,.3,.45],.55);for(let h=0;h<4;h++){let c=i?.4:(s*.3+h/4)%1,C=h%2?1:-1,A=u+C*a*(.16+.07*(h>>1))+Math.sin(c*6+h)*a*.025,k=p-a*.02-ke(c)*a*.28,w=i?1:we(Me(c/.22)),T=a*(.026+.008*(h%3))*w;e.globalAlpha=o.hearts*(1-Math.max(0,(c-.6)/.4)),e.save(),e.translate(A,k),e.rotate(Math.sin(s*3+h)*.25);let S=e.createLinearGradient(0,-T,0,T);S.addColorStop(0,O(P(r,[1,1,1],.35))),S.addColorStop(1,O(r)),e.fillStyle=S,ue(e,0,0,T),e.fill(),e.restore();}}if(o.waves>.01){e.lineCap="round",e.strokeStyle=d;for(let u of [-1,1]){let[p,r]=this.project(u*1,.12,0);for(let h=0;h<3;h++){let c=i?.3+h*.2:(s*.8+h/3)%1,C=ke(c);e.globalAlpha=o.waves*Math.sin(Math.PI*c)*.9,e.lineWidth=a*(.016-.008*c),e.beginPath();let A=a*(.03+.11*C);u>0?e.arc(p-a*.03,r,A,-0.55,.55):e.arc(p+a*.03,r,A,Math.PI-.55,Math.PI+.55),e.stroke();}}}if(o.bang>.01){let[u,p]=this.project(.55,1,.2),r=i?1:we(Me(this.tau/.35));e.globalAlpha=o.bang,e.fillStyle=d,e.save(),e.translate(u,p),e.rotate(.18),e.scale(r,r),e.beginPath(),e.roundRect(-a*.016,-a*.1,a*.032,a*.07,a*.016),e.arc(0,a*.005,a*.018,0,Math.PI*2),e.fill(),e.restore();}e.restore();}},Me=t=>t<0?0:t>1?1:t,ke=t=>1-(1-t)*(1-t),we=t=>1+(1.9+1)*Math.pow(t-1,3)+1.9*Math.pow(t-1,2);function yt(t,o,e,s){t.ellipse(o,e,s,s*.72,0,0,Math.PI*2),t.moveTo(o-s*.35+s*.45,e-s*.38),t.arc(o-s*.35,e-s*.38,s*.45,0,Math.PI*2),t.moveTo(o+s*.3+s*.5,e-s*.32),t.arc(o+s*.3,e-s*.32,s*.5,0,Math.PI*2);}var vt=["shape","mood","color","face-color","cheek-color","size","speed","follow-cursor","interactive","shadow"],H=(t,o)=>t===null?o:t!=="false"&&t!=="0",_e=t=>t==null||t===""?`${M.size}px`:typeof t=="number"||/^\d+(\.\d+)?$/.test(String(t))?`${t}px`:String(t),Mt=`:host{display:inline-block;position:relative;cursor:pointer;width:var(--moodi-size);height:var(--moodi-size);vertical-align:middle;contain:layout paint style;-webkit-tap-highlight-color:transparent}
24
+ :host([interactive="false"]){pointer-events:none}
25
+ canvas{display:block;width:100%;height:100%}`,kt=typeof HTMLElement>"u"?class{}:HTMLElement,K=class extends kt{constructor(){super();this.buddy=null;this.onEnter=()=>{this.buddy&&(this.buddy.hovering=true);};this.onLeave=()=>{this.buddy&&(this.buddy.hovering=false);};this.onClick=()=>{this.buddy?.state.interactive&&this.react(Math.random()<.5?"happy":"surprised",{duration:1100});};this.root=this.attachShadow({mode:"open"});let e=document.createElement("style");e.textContent=Mt,this.root.append(e),this.style.setProperty("--moodi-size",_e(this.getAttribute("size")));}connectedCallback(){if(this.hasAttribute("role")||this.setAttribute("role","img"),!this.buddy){let e=this.buddy=new se;e.onMoodChange=s=>{this.syncLabel(s.mood),this.dispatchEvent(new CustomEvent("moodchange",{detail:s,bubbles:true}));},this.root.append(e.canvas),e.update(this.readState()),this.addEventListener("pointerenter",this.onEnter),this.addEventListener("pointerleave",this.onLeave),this.addEventListener("click",this.onClick);}this.syncLabel(this.buddy.currentMood),this.buddy.connect(),this.io=new IntersectionObserver(([e])=>this.buddy?.setVisible(e.isIntersecting),{rootMargin:"64px"}),this.io.observe(this),this.ro=new ResizeObserver(([e])=>{let s=e.contentRect;this.buddy?.resize(s.width,s.height);}),this.ro.observe(this);}disconnectedCallback(){this.io?.disconnect(),this.ro?.disconnect(),this.buddy&&(this.buddy.canvas.remove(),this.buddy.dispose(),this.buddy=null),this.removeEventListener("pointerenter",this.onEnter),this.removeEventListener("pointerleave",this.onLeave),this.removeEventListener("click",this.onClick);}attributeChangedCallback(e,s,i){s!==i&&(e==="size"&&this.style.setProperty("--moodi-size",_e(i)),e!=="aria-label"&&(this.buddy?.update(this.readState()),e==="mood"&&this.buddy&&this.syncLabel(this.buddy.currentMood)));}readState(){let e=this.getAttribute("shape"),s=this.getAttribute("mood"),i=parseFloat(this.getAttribute("speed")??"");return {shape:re(e)?e:M.shape,mood:ae(s)?s:M.mood,color:this.getAttribute("color")||M.color,faceColor:this.getAttribute("face-color")||void 0,cheekColor:this.getAttribute("cheek-color")||void 0,speed:Number.isFinite(i)?Math.max(0,i):M.speed,followCursor:H(this.getAttribute("follow-cursor"),M.followCursor),interactive:H(this.getAttribute("interactive"),M.interactive),shadow:H(this.getAttribute("shadow"),M.shadow)}}syncLabel(e){let s=this.getAttribute("aria-label");s&&!s.startsWith("moodi: ")||this.setAttribute("aria-label",`moodi: ${e}`);}react(e,s={}){this.buddy?.react(e,s.duration??1200);}get shape(){let e=this.getAttribute("shape");return re(e)?e:M.shape}set shape(e){this.setAttribute("shape",e);}get mood(){let e=this.getAttribute("mood");return ae(e)?e:M.mood}set mood(e){this.setAttribute("mood",e);}get color(){return this.getAttribute("color")||M.color}set color(e){this.setAttribute("color",e);}get faceColor(){return this.getAttribute("face-color")||void 0}set faceColor(e){e?this.setAttribute("face-color",e):this.removeAttribute("face-color");}get cheekColor(){return this.getAttribute("cheek-color")||void 0}set cheekColor(e){e?this.setAttribute("cheek-color",e):this.removeAttribute("cheek-color");}get size(){let e=this.getAttribute("size");return e?/^\d+(\.\d+)?$/.test(e)?Number(e):e:M.size}set size(e){this.setAttribute("size",String(e));}get speed(){return this.readState().speed}set speed(e){this.setAttribute("speed",String(e));}get followCursor(){return H(this.getAttribute("follow-cursor"),M.followCursor)}set followCursor(e){this.setAttribute("follow-cursor",String(e));}get interactive(){return H(this.getAttribute("interactive"),M.interactive)}set interactive(e){this.setAttribute("interactive",String(e));}get shadow(){return H(this.getAttribute("shadow"),M.shadow)}set shadow(e){this.setAttribute("shadow",String(e));}};K.observedAttributes=vt;function qe(t="moodi-buddy"){typeof window>"u"||!("customElements"in window)||customElements.get(t)||customElements.define(t,class extends K{});}qe();export{M as DEFAULTS,Xe as MOODS,St as MOOD_INFO,K as MoodiBuddyElement,wt as PALETTES,Ne as SHAPES,Ct as SHAPE_LABELS,qe as defineMoodi,ae as isMood,re as isShape};
package/dist/react.cjs ADDED
@@ -0,0 +1,2 @@
1
+ "use client";
2
+ "use strict";var P=Object.create;var l=Object.defineProperty;var B=Object.getOwnPropertyDescriptor;var D=Object.getOwnPropertyNames;var H=Object.getPrototypeOf,R=Object.prototype.hasOwnProperty;var A=(e,o)=>{for(var t in o)l(e,t,{get:o[t],enumerable:!0})},g=(e,o,t,r)=>{if(o&&typeof o=="object"||typeof o=="function")for(let i of D(o))!R.call(e,i)&&i!==t&&l(e,i,{get:()=>o[i],enumerable:!(r=B(o,i))||r.enumerable});return e};var T=(e,o,t)=>(t=e!=null?P(H(e)):{},g(o||!e||!e.__esModule?l(t,"default",{value:e,enumerable:!0}):t,e)),I=e=>g(l({},"__esModule",{value:!0}),e);var G={};A(G,{DEFAULTS:()=>O,MOODS:()=>f,MOOD_INFO:()=>q,Moodi:()=>z,PALETTES:()=>N,SHAPES:()=>h,SHAPE_LABELS:()=>F,isMood:()=>_,isShape:()=>W,loadMoodi:()=>y});module.exports=I(G);var n=require("react");var h=["ghost","pebble","blob","drop","bean","cube"],f=["idle","thinking","loading","listening","speaking","happy","surprised","sad","error","sleeping","love"],O={shape:"ghost",mood:"idle",color:"#6c5ce7",size:160,speed:1,followCursor:!0,interactive:!0,shadow:!0},N={Iris:"#6c5ce7",Peach:"#f2a07b",Sage:"#8fb996",Sky:"#8ec5e8",Lilac:"#b9a3e3",Butter:"#f3d36b",Coral:"#f07167",Ink:"#2b2d42",Cloud:"#ecebe8"},q={idle:{label:"Idle",description:"Breathing softly. Blinks now and then."},thinking:{label:"Thinking",description:"Eyes drift up. Little thoughts pop."},loading:{label:"Loading",description:"Half-lidded, wobbling patiently."},listening:{label:"Listening",description:"All eyes. Nods along to every word."},speaking:{label:"Speaking",description:"Chatty mouth, bouncing to the rhythm."},happy:{label:"Happy",description:"Bounces with joy. Cheeks glow."},surprised:{label:"Surprised",description:"Wide eyes, tiny o. Did not see that coming."},sad:{label:"Sad",description:"Slumps a little. Needs a minute."},error:{label:"Error",description:"Something broke. Shakes it off."},sleeping:{label:"Sleeping",description:"Deep, slow breaths. Do not disturb."},love:{label:"Love",description:"Heart eyes, floating on air."}},F={pebble:"Pebble",blob:"Blob",drop:"Drop",bean:"Bean",cube:"Cube",ghost:"Ghost"},W=e=>h.includes(e),_=e=>f.includes(e);var $=null,j=()=>new Promise(e=>{typeof requestIdleCallback=="function"?requestIdleCallback(()=>e(),{timeout:1200}):setTimeout(e,1)}),y=()=>$??($=j().then(()=>import("moodi")).then(e=>e.defineMoodi())),c=e=>e===void 0?void 0:String(e),m=e=>e===void 0?"160px":typeof e=="number"||/^\d+(\.\d+)?$/.test(e)?`${e}px`:e,z=(0,n.forwardRef)(function({shape:o,mood:t,color:r,faceColor:i,cheekColor:M,size:d,speed:u,followCursor:S,interactive:k,shadow:x,onMoodChange:s,className:E,style:w,...C},v){let a=(0,n.useRef)(null);return(0,n.useImperativeHandle)(v,()=>a.current,[]),(0,n.useEffect)(()=>{y()},[]),(0,n.useEffect)(()=>{let p=a.current;if(!p||!s)return;let b=L=>s(L.detail);return p.addEventListener("moodchange",b),()=>p.removeEventListener("moodchange",b)},[s]),(0,n.createElement)("moodi-buddy",{ref:a,className:E,shape:o,mood:t,color:r,"face-color":i,"cheek-color":M,size:d===void 0?void 0:String(d),speed:u===void 0?void 0:String(u),"follow-cursor":c(S),interactive:c(k),shadow:c(x),style:{display:"inline-block",width:m(d),height:m(d),verticalAlign:"middle",...w},suppressHydrationWarning:!0,...C})});0&&(module.exports={DEFAULTS,MOODS,MOOD_INFO,Moodi,PALETTES,SHAPES,SHAPE_LABELS,isMood,isShape,loadMoodi});
@@ -0,0 +1,111 @@
1
+ import * as react from 'react';
2
+ import { HTMLAttributes } from 'react';
3
+
4
+ type Shape = 'pebble' | 'blob' | 'drop' | 'bean' | 'cube' | 'ghost';
5
+ type Mood = 'idle' | 'thinking' | 'loading' | 'listening' | 'speaking' | 'happy' | 'surprised' | 'sad' | 'error' | 'sleeping' | 'love';
6
+ interface MoodiProps {
7
+ /** Body shape. Default `'ghost'`. */
8
+ shape?: Shape;
9
+ /** Current mood / state. Default `'idle'`. */
10
+ mood?: Mood;
11
+ /** Body color, any CSS color. Default `'#6c5ce7'`. */
12
+ color?: string;
13
+ /** Eyes / mouth color. Auto-contrast by default. */
14
+ faceColor?: string;
15
+ /** Blush color. Derived from the body color by default. */
16
+ cheekColor?: string;
17
+ /** px number or any CSS length. Default `160`. */
18
+ size?: number | string;
19
+ /** Animation speed multiplier, `0` pauses. Default `1`. */
20
+ speed?: number;
21
+ /** Eyes (and slightly the body) follow the pointer. Default `true`. */
22
+ followCursor?: boolean;
23
+ /** Reacts to hover and click. Default `true`. */
24
+ interactive?: boolean;
25
+ /** Soft ground shadow. Default `true`. */
26
+ shadow?: boolean;
27
+ }
28
+ interface MoodChangeDetail {
29
+ mood: Mood;
30
+ previous: Mood;
31
+ /** true while a temporary `react()` reaction is showing */
32
+ reaction: boolean;
33
+ }
34
+ declare const SHAPES: readonly Shape[];
35
+ declare const MOODS: readonly Mood[];
36
+ declare const DEFAULTS: {
37
+ shape: Shape;
38
+ mood: Mood;
39
+ color: string;
40
+ size: number | string;
41
+ speed: number;
42
+ followCursor: boolean;
43
+ interactive: boolean;
44
+ shadow: boolean;
45
+ };
46
+ declare const PALETTES: Readonly<Record<string, string>>;
47
+ declare const MOOD_INFO: Readonly<Record<Mood, {
48
+ label: string;
49
+ description: string;
50
+ }>>;
51
+ declare const SHAPE_LABELS: Readonly<Record<Shape, string>>;
52
+ declare const isShape: (v: unknown) => v is Shape;
53
+ declare const isMood: (v: unknown) => v is Mood;
54
+
55
+ declare const Base: typeof HTMLElement;
56
+ declare class MoodiBuddyElement extends Base {
57
+ static observedAttributes: readonly ["shape", "mood", "color", "face-color", "cheek-color", "size", "speed", "follow-cursor", "interactive", "shadow"];
58
+ private buddy;
59
+ private io?;
60
+ private ro?;
61
+ private root;
62
+ constructor();
63
+ connectedCallback(): void;
64
+ disconnectedCallback(): void;
65
+ attributeChangedCallback(name: string, old: string | null, val: string | null): void;
66
+ private readState;
67
+ private syncLabel;
68
+ private onEnter;
69
+ private onLeave;
70
+ private onClick;
71
+ /** Show a mood for a moment, then return to the current `mood`. */
72
+ react(mood: Mood, opts?: {
73
+ duration?: number;
74
+ }): void;
75
+ get shape(): Shape;
76
+ set shape(v: Shape);
77
+ get mood(): Mood;
78
+ set mood(v: Mood);
79
+ get color(): string;
80
+ set color(v: string);
81
+ get faceColor(): string | undefined;
82
+ set faceColor(v: string | undefined);
83
+ get cheekColor(): string | undefined;
84
+ set cheekColor(v: string | undefined);
85
+ get size(): number | string;
86
+ set size(v: number | string);
87
+ get speed(): number;
88
+ set speed(v: number);
89
+ get followCursor(): boolean;
90
+ set followCursor(v: boolean);
91
+ get interactive(): boolean;
92
+ set interactive(v: boolean);
93
+ get shadow(): boolean;
94
+ set shadow(v: boolean);
95
+ }
96
+
97
+ interface MoodiReactProps extends MoodiProps, Omit<HTMLAttributes<HTMLElement>, keyof MoodiProps | 'children'> {
98
+ onMoodChange?: (detail: MoodChangeDetail) => void;
99
+ }
100
+ /**
101
+ * Loads three.js and registers the element, in the browser only. Waits for an idle
102
+ * moment first so the page paints and hydrates before the 3D work starts.
103
+ */
104
+ declare const loadMoodi: () => Promise<unknown>;
105
+ /**
106
+ * `<Moodi />` renders a `<moodi-buddy>` element. three.js is loaded lazily on the
107
+ * client, so this component is safe to render on the server.
108
+ */
109
+ declare const Moodi: react.ForwardRefExoticComponent<MoodiReactProps & react.RefAttributes<MoodiBuddyElement | null>>;
110
+
111
+ export { DEFAULTS, MOODS, MOOD_INFO, type Mood, type MoodChangeDetail, Moodi, MoodiBuddyElement, type MoodiProps, type MoodiReactProps, PALETTES, SHAPES, SHAPE_LABELS, type Shape, isMood, isShape, loadMoodi };
@@ -0,0 +1,111 @@
1
+ import * as react from 'react';
2
+ import { HTMLAttributes } from 'react';
3
+
4
+ type Shape = 'pebble' | 'blob' | 'drop' | 'bean' | 'cube' | 'ghost';
5
+ type Mood = 'idle' | 'thinking' | 'loading' | 'listening' | 'speaking' | 'happy' | 'surprised' | 'sad' | 'error' | 'sleeping' | 'love';
6
+ interface MoodiProps {
7
+ /** Body shape. Default `'ghost'`. */
8
+ shape?: Shape;
9
+ /** Current mood / state. Default `'idle'`. */
10
+ mood?: Mood;
11
+ /** Body color, any CSS color. Default `'#6c5ce7'`. */
12
+ color?: string;
13
+ /** Eyes / mouth color. Auto-contrast by default. */
14
+ faceColor?: string;
15
+ /** Blush color. Derived from the body color by default. */
16
+ cheekColor?: string;
17
+ /** px number or any CSS length. Default `160`. */
18
+ size?: number | string;
19
+ /** Animation speed multiplier, `0` pauses. Default `1`. */
20
+ speed?: number;
21
+ /** Eyes (and slightly the body) follow the pointer. Default `true`. */
22
+ followCursor?: boolean;
23
+ /** Reacts to hover and click. Default `true`. */
24
+ interactive?: boolean;
25
+ /** Soft ground shadow. Default `true`. */
26
+ shadow?: boolean;
27
+ }
28
+ interface MoodChangeDetail {
29
+ mood: Mood;
30
+ previous: Mood;
31
+ /** true while a temporary `react()` reaction is showing */
32
+ reaction: boolean;
33
+ }
34
+ declare const SHAPES: readonly Shape[];
35
+ declare const MOODS: readonly Mood[];
36
+ declare const DEFAULTS: {
37
+ shape: Shape;
38
+ mood: Mood;
39
+ color: string;
40
+ size: number | string;
41
+ speed: number;
42
+ followCursor: boolean;
43
+ interactive: boolean;
44
+ shadow: boolean;
45
+ };
46
+ declare const PALETTES: Readonly<Record<string, string>>;
47
+ declare const MOOD_INFO: Readonly<Record<Mood, {
48
+ label: string;
49
+ description: string;
50
+ }>>;
51
+ declare const SHAPE_LABELS: Readonly<Record<Shape, string>>;
52
+ declare const isShape: (v: unknown) => v is Shape;
53
+ declare const isMood: (v: unknown) => v is Mood;
54
+
55
+ declare const Base: typeof HTMLElement;
56
+ declare class MoodiBuddyElement extends Base {
57
+ static observedAttributes: readonly ["shape", "mood", "color", "face-color", "cheek-color", "size", "speed", "follow-cursor", "interactive", "shadow"];
58
+ private buddy;
59
+ private io?;
60
+ private ro?;
61
+ private root;
62
+ constructor();
63
+ connectedCallback(): void;
64
+ disconnectedCallback(): void;
65
+ attributeChangedCallback(name: string, old: string | null, val: string | null): void;
66
+ private readState;
67
+ private syncLabel;
68
+ private onEnter;
69
+ private onLeave;
70
+ private onClick;
71
+ /** Show a mood for a moment, then return to the current `mood`. */
72
+ react(mood: Mood, opts?: {
73
+ duration?: number;
74
+ }): void;
75
+ get shape(): Shape;
76
+ set shape(v: Shape);
77
+ get mood(): Mood;
78
+ set mood(v: Mood);
79
+ get color(): string;
80
+ set color(v: string);
81
+ get faceColor(): string | undefined;
82
+ set faceColor(v: string | undefined);
83
+ get cheekColor(): string | undefined;
84
+ set cheekColor(v: string | undefined);
85
+ get size(): number | string;
86
+ set size(v: number | string);
87
+ get speed(): number;
88
+ set speed(v: number);
89
+ get followCursor(): boolean;
90
+ set followCursor(v: boolean);
91
+ get interactive(): boolean;
92
+ set interactive(v: boolean);
93
+ get shadow(): boolean;
94
+ set shadow(v: boolean);
95
+ }
96
+
97
+ interface MoodiReactProps extends MoodiProps, Omit<HTMLAttributes<HTMLElement>, keyof MoodiProps | 'children'> {
98
+ onMoodChange?: (detail: MoodChangeDetail) => void;
99
+ }
100
+ /**
101
+ * Loads three.js and registers the element, in the browser only. Waits for an idle
102
+ * moment first so the page paints and hydrates before the 3D work starts.
103
+ */
104
+ declare const loadMoodi: () => Promise<unknown>;
105
+ /**
106
+ * `<Moodi />` renders a `<moodi-buddy>` element. three.js is loaded lazily on the
107
+ * client, so this component is safe to render on the server.
108
+ */
109
+ declare const Moodi: react.ForwardRefExoticComponent<MoodiReactProps & react.RefAttributes<MoodiBuddyElement | null>>;
110
+
111
+ export { DEFAULTS, MOODS, MOOD_INFO, type Mood, type MoodChangeDetail, Moodi, MoodiBuddyElement, type MoodiProps, type MoodiReactProps, PALETTES, SHAPES, SHAPE_LABELS, type Shape, isMood, isShape, loadMoodi };
package/dist/react.js ADDED
@@ -0,0 +1,2 @@
1
+ "use client";
2
+ import{createElement as C,forwardRef as v,useEffect as s,useImperativeHandle as L,useRef as P}from"react";var E=["ghost","pebble","blob","drop","bean","cube"],w=["idle","thinking","loading","listening","speaking","happy","surprised","sad","error","sleeping","love"],R={shape:"ghost",mood:"idle",color:"#6c5ce7",size:160,speed:1,followCursor:!0,interactive:!0,shadow:!0},A={Iris:"#6c5ce7",Peach:"#f2a07b",Sage:"#8fb996",Sky:"#8ec5e8",Lilac:"#b9a3e3",Butter:"#f3d36b",Coral:"#f07167",Ink:"#2b2d42",Cloud:"#ecebe8"},T={idle:{label:"Idle",description:"Breathing softly. Blinks now and then."},thinking:{label:"Thinking",description:"Eyes drift up. Little thoughts pop."},loading:{label:"Loading",description:"Half-lidded, wobbling patiently."},listening:{label:"Listening",description:"All eyes. Nods along to every word."},speaking:{label:"Speaking",description:"Chatty mouth, bouncing to the rhythm."},happy:{label:"Happy",description:"Bounces with joy. Cheeks glow."},surprised:{label:"Surprised",description:"Wide eyes, tiny o. Did not see that coming."},sad:{label:"Sad",description:"Slumps a little. Needs a minute."},error:{label:"Error",description:"Something broke. Shakes it off."},sleeping:{label:"Sleeping",description:"Deep, slow breaths. Do not disturb."},love:{label:"Love",description:"Heart eyes, floating on air."}},I={pebble:"Pebble",blob:"Blob",drop:"Drop",bean:"Bean",cube:"Cube",ghost:"Ghost"},O=e=>E.includes(e),N=e=>w.includes(e);var B=null,D=()=>new Promise(e=>{typeof requestIdleCallback=="function"?requestIdleCallback(()=>e(),{timeout:1200}):setTimeout(e,1)}),H=()=>B??(B=D().then(()=>import("moodi")).then(e=>e.defineMoodi())),r=e=>e===void 0?void 0:String(e),a=e=>e===void 0?"160px":typeof e=="number"||/^\d+(\.\d+)?$/.test(e)?`${e}px`:e,W=v(function({shape:p,mood:c,color:u,faceColor:b,cheekColor:g,size:o,speed:d,followCursor:h,interactive:f,shadow:m,onMoodChange:n,className:y,style:M,...S},k){let t=P(null);return L(k,()=>t.current,[]),s(()=>{H()},[]),s(()=>{let i=t.current;if(!i||!n)return;let l=x=>n(x.detail);return i.addEventListener("moodchange",l),()=>i.removeEventListener("moodchange",l)},[n]),C("moodi-buddy",{ref:t,className:y,shape:p,mood:c,color:u,"face-color":b,"cheek-color":g,size:o===void 0?void 0:String(o),speed:d===void 0?void 0:String(d),"follow-cursor":r(h),interactive:r(f),shadow:r(m),style:{display:"inline-block",width:a(o),height:a(o),verticalAlign:"middle",...M},suppressHydrationWarning:!0,...S})});export{R as DEFAULTS,w as MOODS,T as MOOD_INFO,W as Moodi,A as PALETTES,E as SHAPES,I as SHAPE_LABELS,N as isMood,O as isShape,H as loadMoodi};
package/package.json CHANGED
@@ -1,6 +1,75 @@
1
1
  {
2
2
  "name": "moodi",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "Tiny 3D mood buddies for your UI.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "require": "./dist/index.cjs"
15
+ },
16
+ "./react": {
17
+ "types": "./dist/react.d.ts",
18
+ "import": "./dist/react.js",
19
+ "require": "./dist/react.cjs"
20
+ }
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "README.md",
25
+ "LICENSE"
26
+ ],
27
+ "sideEffects": [
28
+ "./dist/index.js",
29
+ "./dist/index.cjs"
30
+ ],
31
+ "peerDependencies": {
32
+ "three": ">=0.160.0",
33
+ "react": ">=18"
34
+ },
35
+ "peerDependenciesMeta": {
36
+ "react": {
37
+ "optional": true
38
+ }
39
+ },
40
+ "devDependencies": {
41
+ "@types/react": "^19",
42
+ "@types/three": "^0.186.0",
43
+ "react": "19.3.0",
44
+ "three": "^0.186.1",
45
+ "tsup": "^8.5.1",
46
+ "typescript": "^5"
47
+ },
48
+ "keywords": [
49
+ "3d",
50
+ "avatar",
51
+ "mascot",
52
+ "web-component",
53
+ "react",
54
+ "three",
55
+ "loading",
56
+ "status",
57
+ "ai"
58
+ ],
59
+ "repository": {
60
+ "type": "git",
61
+ "url": "git+https://github.com/korneXD/moodi.git",
62
+ "directory": "packages/moodi"
63
+ },
64
+ "homepage": "https://moodi-two-tau.vercel.app",
65
+ "scripts": {
66
+ "build": "tsup",
67
+ "dev": "tsup --watch",
68
+ "typecheck": "tsc --noEmit",
69
+ "prepublishOnly": "npm run build"
70
+ },
71
+ "author": "itsk0rn3l (https://x.com/itsk0rn3l)",
72
+ "bugs": {
73
+ "url": "https://github.com/korneXD/moodi/issues"
74
+ }
75
+ }