agent-robot-avatar 0.3.2 → 0.5.2

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.
@@ -1,1152 +1,1155 @@
1
- const AgentRobotAvatar = (() => {
2
- const HTMLElementBase = typeof HTMLElement === 'undefined' ? class {} : HTMLElement;
3
- const EASE = {
4
- outQuint: t => 1 - Math.pow(1 - t, 5),
5
- inOutCubic: t => t < 0.5 ? 4*t*t*t : 1 - Math.pow(-2*t + 2, 3)/2,
6
- };
7
-
8
- const POSES = {
9
- idle: {
10
- w: 54, h: 58, rx: 27,
11
- topY: -36, bottomY: 36,
12
- topAL: 0, topAR: 0, bottomAL: 0, bottomAR: 0,
13
- scale: 1,
14
- },
15
- happy: {
16
- w: 58, h: 58, rx: 29,
17
- topY: -36, bottomY: 0,
18
- topAL: 0, topAR: 0, bottomAL: 0, bottomAR: 0,
19
- scale: 1,
20
- },
21
- sad: {
22
- w: 58, h: 58, rx: 29,
23
- topY: -5, bottomY: 36,
24
- topAL: -16, topAR: 16, bottomAL: 0, bottomAR: 0,
25
- scale: 1,
26
- },
27
- angry: {
28
- w: 58, h: 58, rx: 29,
29
- topY: -5, bottomY: 36,
30
- topAL: 16, topAR: -16, bottomAL: 0, bottomAR: 0,
31
- scale: 1,
32
- },
33
- sleepy: {
34
- w: 58, h: 56, rx: 28,
35
- topY: 0, bottomY: 36,
36
- topAL: 0, topAR: 0, bottomAL: 0, bottomAR: 0,
37
- scale: 1,
38
- },
39
- input: {
40
- w: 12, h: 78, rx: 2,
41
- topY: -44, bottomY: 44,
42
- topAL: 0, topAR: 0, bottomAL: 0, bottomAR: 0,
43
- scale: 1,
44
- },
45
- sleep: {
46
- w: 62, h: 10, rx: 2,
47
- topY: -20, bottomY: 20,
48
- topAL: 0, topAR: 0, bottomAL: 0, bottomAR: 0,
49
- scale: 1,
50
- },
51
- surprise: {
52
- w: 68, h: 68, rx: 34,
53
- topY: -40, bottomY: 40,
54
- topAL: 0, topAR: 0, bottomAL: 0, bottomAR: 0,
55
- scale: 1,
56
- },
57
- };
58
-
59
- const clamp = (v, a, b) => Math.max(a, Math.min(b, v));
60
- const lerp = (a, b, t) => a + (b - a) * t;
61
- const clonePose = p => ({...p});
62
- const DEFAULT_SIZE = 112;
63
- const parseSize = value => {
64
- const text = String(value ?? '').trim();
65
- if (!/^(?:\d+(?:\.\d+)?|\.\d+)(?:px)?$/i.test(text)) return DEFAULT_SIZE;
66
- const numeric = Number.parseFloat(text);
67
- return Number.isFinite(numeric) && numeric > 0 ? numeric : DEFAULT_SIZE;
68
- };
69
- const connectedFaces = new Set();
70
-
71
- const forEachConnectedFace = callback => {
72
- for (const face of connectedFaces) {
73
- if (face.isConnected) callback(face);
74
- }
75
- };
76
-
77
- const handleGlobalPointerMove = event => {
78
- forEachConnectedFace(face => {
79
- face._onPointerMove(event);
80
- if (face._dragJelly.active) face._onDragMove(event);
81
- });
82
- };
83
- const handleGlobalPointerDown = event => forEachConnectedFace(face => {
84
- if (typeof face._noteEnvironmentActivity === 'function') face._noteEnvironmentActivity(event);
85
- else face.noteActivity();
86
- });
87
- const handleGlobalKeyDown = event => forEachConnectedFace(face => {
88
- if (typeof face._noteEnvironmentActivity === 'function') face._noteEnvironmentActivity(event);
89
- else face.noteActivity();
90
- });
91
- const handleGlobalPointerEnd = event => {
92
- forEachConnectedFace(face => {
93
- if (face._dragJelly.active) face._onDragEnd(event);
94
- });
95
- };
96
- const handleGlobalVisibility = () => forEachConnectedFace(face => face._onVisibility());
97
-
98
- function registerConnectedFace(face) {
99
- if (connectedFaces.has(face)) return;
100
- const first = connectedFaces.size === 0;
101
- connectedFaces.add(face);
102
- if (!first) return;
103
-
104
- window.addEventListener('pointermove', handleGlobalPointerMove, {passive:true});
105
- window.addEventListener('pointerdown', handleGlobalPointerDown, {passive:true});
106
- window.addEventListener('keydown', handleGlobalKeyDown, {passive:true});
107
- window.addEventListener('pointerup', handleGlobalPointerEnd, {passive:true});
108
- window.addEventListener('pointercancel', handleGlobalPointerEnd, {passive:true});
109
- document.addEventListener('visibilitychange', handleGlobalVisibility);
110
- }
111
-
112
- function unregisterConnectedFace(face) {
113
- connectedFaces.delete(face);
114
- if (connectedFaces.size > 0) return;
115
-
116
- window.removeEventListener('pointermove', handleGlobalPointerMove);
117
- window.removeEventListener('pointerdown', handleGlobalPointerDown);
118
- window.removeEventListener('keydown', handleGlobalKeyDown);
119
- window.removeEventListener('pointerup', handleGlobalPointerEnd);
120
- window.removeEventListener('pointercancel', handleGlobalPointerEnd);
121
- document.removeEventListener('visibilitychange', handleGlobalVisibility);
122
- }
123
-
124
- class AgentFace extends HTMLElementBase {
125
- constructor() {
126
- super();
127
- this.attachShadow({mode: 'open'});
128
- this._state = 'idle';
129
- this._pose = clonePose(POSES.idle);
130
- this._fromPose = clonePose(this._pose);
131
- this._toPose = clonePose(this._pose);
132
- this._morphStart = performance.now();
133
- this._morphDuration = 0;
134
- this._morphEase = EASE.outQuint;
135
-
136
- this._wander = {x:0, y:0};
137
- this._wanderTarget = {x:0, y:0};
138
- this._nextWanderAt = performance.now() + 500;
139
- this._pointer = {x:0, y:0, active:false, lastMove:0};
140
- this._look = {x:0, y:0};
141
- this._headFollowPose = {x:0,y:0,rot:0};
142
- this._headFollowVel = {x:0,y:0,rot:0};
143
- this._headCenteringUntil = 0;
144
- this._eyeMicro = {lX:0,lY:0,rX:0,rY:0};
145
- this._blink = 1;
146
- this._blinkAnim = null;
147
- this._nextBlinkAt = performance.now() + 1800 + Math.random()*1800;
148
- this._inputPhase = 0;
149
- this._eyeNod = null;
150
- this._eyeBob = null;
151
- this._angryEyeDrop = null;
152
- this._surpriseShake = null;
153
- this._expressionLock = false;
154
- this._transitionToken = 0;
155
- this._inputWanted = false;
156
- this._sleeping = false;
157
- this._idleTimer = null;
158
- this._pendingWaits = new Map();
159
- this._boredRoutine = null;
160
- this._boredLookSpeed = null;
161
- this._autoSleepMs = Number(this.getAttribute('auto-sleep')) || 0;
162
- this._lastActivity = performance.now();
163
- this._running = true;
164
- this._framePaused = false;
165
- this._lastFrame = 0;
166
- this._raf = 0;
167
- this._dragJelly = {
168
- active:false, returning:false, snapped:false, moved:false, pointerId:null,
169
- startX:0, startY:0, hotX:120, hotY:120, targetX:0, targetY:0, x:0, y:0, vx:0, vy:0,
170
- targetStretch:0, stretch:0, stretchVel:0, angle:0, threshold:52,
171
- ux:0, uy:0, anchorX:50, anchorY:50, shear:0, targetShear:0, shearVel:0,
172
- targetPullX:0, targetPullY:0, pullX:0, pullY:0, pullVX:0, pullVY:0,
173
- maxDist:0, angryThreshold:32, pendingReaction:null, intent:'angry'
174
- };
175
- this._suppressClick = false;
176
- this._boundDragStart = e => this._onDragStart(e);
177
- this._boundClickCapture = e => this._onClickCapture(e);
178
- this._renderShell();
179
- }
180
-
181
- connectedCallback() {
182
- registerConnectedFace(this);
183
- this.addEventListener('pointerdown', this._boundDragStart);
184
- this.addEventListener('click', this._boundClickCapture, true);
185
- this._running = !document.hidden;
186
- this._lastActivity = performance.now();
187
- this._resumeFrames();
188
- }
189
-
190
- disconnectedCallback() {
191
- // This callback is captured when the custom element is defined, so the
192
- // runtime teardown must be entered from the registered core lifecycle.
193
- this._runtimeDisconnecting = true;
194
- try {
195
- unregisterConnectedFace(this);
196
- this.removeEventListener('pointerdown', this._boundDragStart);
197
- this.removeEventListener('click', this._boundClickCapture, true);
198
- // Removal cancels work; reattachment starts idle with appearance settings intact.
199
- this._resetToIdle(false);
200
- this._blinkAnim = null;
201
- this._blink = 1;
202
- this._pointer.active = false;
203
- this._pointer.influence = 0;
204
- const drag = this._dragJelly;
205
- if (drag.pointerId != null) {
206
- try { this.releasePointerCapture(drag.pointerId); } catch (_) {}
207
- }
208
- drag.active = false;
209
- drag.pointerId = null;
210
- this._finishDragReturn(null);
211
- this._suppressClick = false;
212
- this._running = false;
213
- cancelAnimationFrame(this._raf);
214
- this._raf = 0;
215
- } finally {
216
- this._teardownRuntime?.();
217
- this._runtimeDisconnecting = false;
218
- }
219
- }
220
-
221
- static get observedAttributes() { return ['color','size','auto-sleep']; }
222
- attributeChangedCallback(name, oldV, newV) {
223
- if (oldV === newV) return;
224
- if (name === 'color') this._applyColor(newV);
225
- if (name === 'size') this._syncSizeAttribute();
226
- if (name === 'auto-sleep') {
227
- this._autoSleepMs = Number(newV) || 0;
228
- this._scheduleAutoSleep?.();
229
- }
230
- }
231
-
232
- _syncSizeAttribute() {
233
- if (!this.hasAttribute('size')) {
234
- this.style.removeProperty('--face-size');
235
- return DEFAULT_SIZE;
236
- }
237
- const size = parseSize(this.getAttribute('size'));
238
- this.style.setProperty('--face-size', `${size}px`);
239
- return size;
240
- }
241
-
242
- _applyColor(value) {
243
- const color = value || '#08090b';
244
- for (const part of [this._head, this._leftTop, this._rightTop, this._leftBottom, this._rightBottom, this._antennaDot]) {
245
- if (part) part.setAttribute('fill', color);
246
- }
247
- }
248
-
249
- _renderShell() {
250
- this.shadowRoot.innerHTML = `
251
- <style>
252
- :host{display:inline-block;width:var(--face-size,112px);height:var(--face-size,112px);contain:layout style;overflow:visible;cursor:pointer;user-select:none;-webkit-user-select:none;touch-action:manipulation}
253
- svg{display:block;width:100%;height:100%;overflow:visible}
254
- #dragMotion{transform-box:view-box;transform-origin:120px 120px;will-change:transform}
255
- #headFollow{will-change:transform}
256
- #headMotion{transform-origin:120px 120px;will-change:transform}
257
- #inputL,#inputR{opacity:1}
258
- </style>
259
- <svg viewBox="0 0 240 240" role="img" aria-label="Agent robot avatar">
260
- <defs>
261
- <path id="headShape" d="M 120.00 20.00 L 132.80 20.05 L 139.87 20.20 L 145.68 20.44 L 150.79 20.78 L 155.43 21.22 L 159.71 21.76 L 163.69 22.40 L 167.44 23.14 L 170.99 23.97 L 174.34 24.90 L 177.53 25.93 L 180.57 27.07 L 183.46 28.30 L 186.22 29.63 L 188.85 31.06 L 191.36 32.59 L 193.75 34.22 L 196.03 35.96 L 198.19 37.80 L 200.25 39.75 L 202.20 41.81 L 204.04 43.97 L 205.78 46.25 L 207.41 48.64 L 208.94 51.15 L 210.37 53.78 L 211.70 56.54 L 212.93 59.43 L 214.07 62.47 L 215.10 65.66 L 216.03 69.01 L 216.86 72.56 L 217.60 76.31 L 218.24 80.29 L 218.78 84.57 L 219.22 89.21 L 219.56 94.32 L 219.80 100.13 L 219.95 107.20 L 220.00 120.00 L 219.95 132.80 L 219.80 139.87 L 219.56 145.68 L 219.22 150.79 L 218.78 155.43 L 218.24 159.71 L 217.60 163.69 L 216.86 167.44 L 216.03 170.99 L 215.10 174.34 L 214.07 177.53 L 212.93 180.57 L 211.70 183.46 L 210.37 186.22 L 208.94 188.85 L 207.41 191.36 L 205.78 193.75 L 204.04 196.03 L 202.20 198.19 L 200.25 200.25 L 198.19 202.20 L 196.03 204.04 L 193.75 205.78 L 191.36 207.41 L 188.85 208.94 L 186.22 210.37 L 183.46 211.70 L 180.57 212.93 L 177.53 214.07 L 174.34 215.10 L 170.99 216.03 L 167.44 216.86 L 163.69 217.60 L 159.71 218.24 L 155.43 218.78 L 150.79 219.22 L 145.68 219.56 L 139.87 219.80 L 132.80 219.95 L 120.00 220.00 L 107.20 219.95 L 100.13 219.80 L 94.32 219.56 L 89.21 219.22 L 84.57 218.78 L 80.29 218.24 L 76.31 217.60 L 72.56 216.86 L 69.01 216.03 L 65.66 215.10 L 62.47 214.07 L 59.43 212.93 L 56.54 211.70 L 53.78 210.37 L 51.15 208.94 L 48.64 207.41 L 46.25 205.78 L 43.97 204.04 L 41.81 202.20 L 39.75 200.25 L 37.80 198.19 L 35.96 196.03 L 34.22 193.75 L 32.59 191.36 L 31.06 188.85 L 29.63 186.22 L 28.30 183.46 L 27.07 180.57 L 25.93 177.53 L 24.90 174.34 L 23.97 170.99 L 23.14 167.44 L 22.40 163.69 L 21.76 159.71 L 21.22 155.43 L 20.78 150.79 L 20.44 145.68 L 20.20 139.87 L 20.05 132.80 L 20.00 120.00 L 20.05 107.20 L 20.20 100.13 L 20.44 94.32 L 20.78 89.21 L 21.22 84.57 L 21.76 80.29 L 22.40 76.31 L 23.14 72.56 L 23.97 69.01 L 24.90 65.66 L 25.93 62.47 L 27.07 59.43 L 28.30 56.54 L 29.63 53.78 L 31.06 51.15 L 32.59 48.64 L 34.22 46.25 L 35.96 43.97 L 37.80 41.81 L 39.75 39.75 L 41.81 37.80 L 43.97 35.96 L 46.25 34.22 L 48.64 32.59 L 51.15 31.06 L 53.78 29.63 L 56.54 28.30 L 59.43 27.07 L 62.47 25.93 L 65.66 24.90 L 69.01 23.97 L 72.56 23.14 L 76.31 22.40 L 80.29 21.76 L 84.57 21.22 L 89.21 20.78 L 94.32 20.44 L 100.13 20.20 L 107.20 20.05 Z"/>
262
- <clipPath id="headClip"><use href="#headShape" transform="translate(7.2 7.2) scale(.94)"/></clipPath>
263
- <clipPath id="eyeMaskClip" clipPathUnits="userSpaceOnUse"><rect x="-40" y="-54" width="80" height="108"/></clipPath>
264
- </defs>
265
- <g id="dragMotion">
266
- <g id="headFollow">
267
- <g id="headMotion">
268
- <use id="head" href="#headShape" fill="#08090b" transform="translate(7.2 7.2) scale(.94)"/>
269
- <g clip-path="url(#headClip)">
270
- <g id="leftEye" transform="translate(86 126)">
271
- <ellipse id="leftBase" cx="0" cy="0" rx="27" ry="29" fill="#fff"/>
272
- <rect id="leftInputBase" x="-6" y="-39" width="12" height="78" rx="3" fill="#fff" opacity="0"/>
273
- <g clip-path="url(#eyeMaskClip)">
274
- <rect id="leftTop" x="-70" y="-96" width="140" height="60" fill="#08090b"/>
275
- <rect id="leftBottom" x="-70" y="36" width="140" height="60" fill="#08090b"/>
276
- </g>
277
- </g>
278
- <g id="rightEye" transform="translate(154 126)">
279
- <ellipse id="rightBase" cx="0" cy="0" rx="27" ry="29" fill="#fff"/>
280
- <rect id="rightInputBase" x="-6" y="-39" width="12" height="78" rx="3" fill="#fff" opacity="0"/>
281
- <g clip-path="url(#eyeMaskClip)">
282
- <rect id="rightTop" x="-70" y="-96" width="140" height="60" fill="#08090b"/>
283
- <rect id="rightBottom" x="-70" y="36" width="140" height="60" fill="#08090b"/>
284
- </g>
285
- </g>
286
- </g>
287
- </g>
288
- </g>
289
- </g>
290
- </svg>`;
291
- this._headShape = this.shadowRoot.getElementById('headShape');
292
- this._dragMotion = this.shadowRoot.getElementById('dragMotion');
293
- this._headFollow = this.shadowRoot.getElementById('headFollow');
294
- this._headMotion = this.shadowRoot.getElementById('headMotion');
295
- this._head = this.shadowRoot.getElementById('head');
296
- this._leftEye = this.shadowRoot.getElementById('leftEye');
297
- this._rightEye = this.shadowRoot.getElementById('rightEye');
298
- this._leftBase = this.shadowRoot.getElementById('leftBase');
299
- this._rightBase = this.shadowRoot.getElementById('rightBase');
300
- this._leftInputBase = this.shadowRoot.getElementById('leftInputBase');
301
- this._rightInputBase = this.shadowRoot.getElementById('rightInputBase');
302
- this._leftTop = this.shadowRoot.getElementById('leftTop');
303
- this._rightTop = this.shadowRoot.getElementById('rightTop');
304
- this._leftBottom = this.shadowRoot.getElementById('leftBottom');
305
- this._rightBottom = this.shadowRoot.getElementById('rightBottom');
306
- this._applyColor(this.getAttribute('color'));
307
- this._baseHeadPathD = this._headShape.getAttribute('d');
308
- this._baseHeadPoints = this._parseHeadPoints(this._baseHeadPathD);
309
- }
310
-
311
- _parseHeadPoints(d) {
312
- const nums = (d.match(/-?\d*\.?\d+/g) || []).map(Number);
313
- const pts = [];
314
- for (let i = 0; i < nums.length - 1; i += 2) pts.push({x: nums[i], y: nums[i+1]});
315
- return pts;
316
- }
317
-
318
- _pointsToPath(points) {
319
- if (!points.length) return this._baseHeadPathD || '';
320
- let d = `M ${points[0].x.toFixed(2)} ${points[0].y.toFixed(2)}`;
321
- for (let i = 1; i < points.length; i++) d += ` L ${points[i].x.toFixed(2)} ${points[i].y.toFixed(2)}`;
322
- return `${d} Z`;
323
- }
324
-
325
- _clientToLocal(clientX, clientY) {
326
- const r = this.getBoundingClientRect();
327
- return {
328
- x: clamp(((clientX - r.left) / Math.max(1, r.width)) * 240, 0, 240),
329
- y: clamp(((clientY - r.top) / Math.max(1, r.height)) * 240, 0, 240),
330
- };
331
- }
332
-
333
- _applyHeadDeform() {
334
- const j = this._dragJelly;
335
- if (!this._headShape || !this._baseHeadPoints?.length) return;
336
- const amount = Math.hypot(j.pullX, j.pullY);
337
- if (amount < 0.015) {
338
- this._headShape.setAttribute('d', this._baseHeadPathD);
339
- return;
340
- }
341
- const radius = (50 + amount * 0.8) * 0.92;
342
- const pts = this._baseHeadPoints.map(p => {
343
- const dx = p.x - j.hotX;
344
- const dy = p.y - j.hotY;
345
- const w = Math.exp(-(dx*dx + dy*dy) / (2 * radius * radius));
346
- return {x:p.x + j.pullX * w, y:p.y + j.pullY * w};
347
- });
348
- this._headShape.setAttribute('d', this._pointsToPath(pts));
349
- }
350
-
351
- _onVisibility() {
352
- this._running = !document.hidden;
353
- if (this._running) this._resumeFrames();
354
- else {
355
- cancelAnimationFrame(this._raf);
356
- this._raf = 0;
357
- }
358
- }
359
-
360
- _onClickCapture(e) {
361
- if (!this._suppressClick) return;
362
- e.preventDefault();
363
- e.stopImmediatePropagation();
364
- this._suppressClick = false;
365
- }
366
-
367
- _onPointerMove(e) {
368
- const r = this.getBoundingClientRect();
369
- const cx = r.left + r.width / 2;
370
- const cy = r.top + r.height / 2;
371
- const dx = e.clientX - cx;
372
- const dy = e.clientY - cy;
373
- const near = Math.max(r.width, r.height) * 2.55;
374
- const dist = Math.hypot(dx, dy);
375
- const influence = clamp(1 - dist / near, 0, 1);
376
- this._pointer.x = clamp(dx * .42, -48, 48);
377
- this._pointer.y = clamp(dy * .38, -34, 34);
378
- this._pointer.active = influence > 0.001;
379
- this._pointer.influence = influence;
380
- this._pointer.lastMove = performance.now();
381
- if ((this._pointer.influence||0) > 0.65 && this._boredRoutine) { this._boredRoutine = null; this._boredLookSpeed = null; }
382
- }
383
-
384
- _onDragStart(e) {
385
- if (e.button != null && e.button !== 0) return;
386
- const r = this.getBoundingClientRect();
387
- const j = this._dragJelly;
388
- j.active = true;
389
- j.returning = false;
390
- j.snapped = false;
391
- j.moved = false;
392
- j.pointerId = e.pointerId;
393
- j.startX = e.clientX;
394
- j.startY = e.clientY;
395
- const local = this._clientToLocal(e.clientX, e.clientY);
396
- j.hotX = local.x;
397
- j.hotY = local.y;
398
- j.targetX = j.x;
399
- j.targetY = j.y;
400
- j.targetStretch = j.stretch;
401
- j.targetShear = j.shear;
402
- j.targetPullX = j.pullX;
403
- j.targetPullY = j.pullY;
404
- j.threshold = clamp(r.width * 0.40, 24, 46);
405
- j.maxDist = 0;
406
- j.pendingReaction = null;
407
- j.intent = 'angry';
408
- j.angryThreshold = Math.max(j.threshold * 0.76, 22);
409
- this._suppressClick = false;
410
- this._boredRoutine = null;
411
- this._boredLookSpeed = null;
412
- this._wanderTarget.x = this._wanderTarget.y = 0;
413
- try { this.setPointerCapture(e.pointerId); } catch (_) {}
414
- }
415
-
416
- _onDragMove(e) {
417
- const j = this._dragJelly;
418
- if (!j.active || e.pointerId !== j.pointerId) return;
419
- const dx = e.clientX - j.startX;
420
- const dy = e.clientY - j.startY;
421
- const dist = Math.hypot(dx, dy);
422
- j.maxDist = Math.max(j.maxDist, dist);
423
- if (dist > 2.5) {
424
- j.moved = true;
425
- this._suppressClick = true;
426
- }
427
- const r = this.getBoundingClientRect();
428
- const size = Math.max(1, Math.max(r.width, r.height));
429
- const localScale = 240 / size;
430
- const pdx = dx * localScale;
431
- const pdy = dy * localScale;
432
- const cx = 120, cy = 120;
433
- const rx = j.hotX - cx;
434
- const ry = j.hotY - cy;
435
- const rl = Math.max(1, Math.hypot(rx, ry));
436
- const rux = rx / rl;
437
- const ruy = ry / rl;
438
- const radial = pdx * rux + pdy * ruy;
439
- const tangent = -pdx * ruy + pdy * rux;
440
- const motionLen = Math.max(1, Math.hypot(pdx, pdy));
441
- j.ux = pdx / motionLen;
442
- j.uy = pdy / motionLen;
443
- j.angle = Math.atan2(j.uy, j.ux);
444
- j.intent = radial < -6 ? 'success' : 'angry';
445
- const gain = dist > j.threshold * .60 ? 0.24 : 0.19;
446
- j.targetPullX = clamp(pdx * gain, -25, 25);
447
- j.targetPullY = clamp(pdy * gain, -25, 25);
448
- j.targetX = clamp(dx * 0.065, -5.2, 5.2);
449
- j.targetY = clamp(dy * 0.065, -5.2, 5.2);
450
- j.targetStretch = clamp((radial / 240) * .08, -.028, .052);
451
- j.targetShear = clamp((tangent / 240) * .050, -.022, .022);
452
- j.anchorX = clamp(50 - (rux * 31), 15, 85);
453
- j.anchorY = clamp(50 - (ruy * 31), 15, 85);
454
- }
455
-
456
- _onDragEnd(e) {
457
- const j = this._dragJelly;
458
- if (!j.active || e.pointerId !== j.pointerId) return;
459
- j.active = false;
460
- try { this.releasePointerCapture(e.pointerId); } catch (_) {}
461
- j.pointerId = null;
462
- const reactionThreshold = Math.max(j.angryThreshold, 22);
463
- j.pendingReaction = j.maxDist >= reactionThreshold ? j.intent : null;
464
- j.returning = true;
465
- j.targetX = 0;
466
- j.targetY = 0;
467
- j.targetStretch = 0;
468
- j.targetShear = 0;
469
- j.targetPullX = 0;
470
- j.targetPullY = 0;
471
- }
472
-
473
- _finishDragReturn(reaction) {
474
- const j = this._dragJelly;
475
- j.returning = false;
476
- j.pendingReaction = null;
477
- j.x = j.y = j.vx = j.vy = 0;
478
- j.stretch = j.stretchVel = 0;
479
- j.shear = j.shearVel = 0;
480
- j.pullX = j.pullY = j.pullVX = j.pullVY = 0;
481
- if (this._dragMotion) this._dragMotion.style.transform = '';
482
- if (this._headShape) this._headShape.setAttribute('d', this._baseHeadPathD);
483
- if (reaction === 'angry') this.angry({fromDrag:true});
484
- else if (reaction === 'success') this.success({fromDrag:true});
485
- }
486
-
487
- _updateDragJelly(dt) {
488
- const j = this._dragJelly;
489
- const dtS = Math.min(dt, 34) / 1000;
490
- if (j.active) {
491
- const fast = Math.min(1, dt / 14);
492
- const k = 0.36 + 0.30 * fast;
493
- j.x = lerp(j.x, j.targetX, k);
494
- j.y = lerp(j.y, j.targetY, k);
495
- j.stretch = lerp(j.stretch, j.targetStretch, k*.85);
496
- j.shear = lerp(j.shear, j.targetShear, k*.80);
497
- j.pullX = lerp(j.pullX, j.targetPullX, k*.95);
498
- j.pullY = lerp(j.pullY, j.targetPullY, k*.95);
499
- } else if (j.returning) {
500
- const kx = 135, dx = 8.2;
501
- const ax = -j.x * kx - j.vx * dx;
502
- const ay = -j.y * kx - j.vy * dx;
503
- j.vx += ax * dtS;
504
- j.vy += ay * dtS;
505
- j.x += j.vx * dtS;
506
- j.y += j.vy * dtS;
507
- const ks = 128, ds = 7.5;
508
- const as = -j.stretch * ks - j.stretchVel * ds;
509
- j.stretchVel += as * dtS;
510
- j.stretch += j.stretchVel * dtS;
511
- const kh = 118, dh = 7.2;
512
- const ah = -j.shear * kh - j.shearVel * dh;
513
- j.shearVel += ah * dtS;
514
- j.shear += j.shearVel * dtS;
515
- const kp = 118, dp = 7.1;
516
- const apx = -j.pullX * kp - j.pullVX * dp;
517
- const apy = -j.pullY * kp - j.pullVY * dp;
518
- j.pullVX += apx * dtS;
519
- j.pullVY += apy * dtS;
520
- j.pullX += j.pullVX * dtS;
521
- j.pullY += j.pullVY * dtS;
522
-
523
- const visuallyRecovered = Math.abs(j.x) < 0.34 && Math.abs(j.y) < 0.34 && Math.abs(j.stretch) < 0.008 && Math.abs(j.shear) < 0.0035 && Math.abs(j.pullX) < 0.40 && Math.abs(j.pullY) < 0.40;
524
- if (j.pendingReaction && visuallyRecovered) {
525
- this._finishDragReturn(j.pendingReaction);
526
- } else if (Math.abs(j.x) < 0.02 && Math.abs(j.y) < 0.02 && Math.abs(j.vx) < 0.04 && Math.abs(j.vy) < 0.04 && Math.abs(j.stretch) < 0.001 && Math.abs(j.stretchVel) < 0.003 && Math.abs(j.shear) < 0.0005 && Math.abs(j.shearVel) < 0.002 && Math.abs(j.pullX) < 0.03 && Math.abs(j.pullY) < 0.03 && Math.abs(j.pullVX) < 0.06 && Math.abs(j.pullVY) < 0.06) {
527
- this._finishDragReturn(null);
528
- }
529
- }
530
- j.stretch = clamp(j.stretch, -0.035, 0.055);
531
- j.shear = clamp(j.shear, -0.026, 0.026);
532
- j.pullX = clamp(j.pullX, -26, 26);
533
- j.pullY = clamp(j.pullY, -26, 26);
534
- this._applyHeadDeform();
535
- if (this._dragMotion) {
536
- const sx = 1 + j.stretch;
537
- const sy = 1 - j.stretch * 0.30;
538
- const shearDeg = j.shear * 180 / Math.PI;
539
- this._dragMotion.style.transformOrigin = `${j.anchorX.toFixed(2)}% ${j.anchorY.toFixed(2)}%`;
540
- this._dragMotion.style.transform = `translate(${j.x.toFixed(2)}px, ${j.y.toFixed(2)}px) rotate(${j.angle.toFixed(4)}rad) scale(${sx.toFixed(4)}, ${sy.toFixed(4)}) skewY(${shearDeg.toFixed(3)}deg) rotate(${(-j.angle).toFixed(4)}rad)`;
541
- }
542
- }
543
-
544
- noteActivity(wake=true) {
545
- this._lastActivity = performance.now();
546
- this._resumeFrames();
547
- if (wake && this._sleeping) this.wake();
548
- }
549
-
550
- play(name) {
551
- const action = String(name || '').trim().toLowerCase();
552
- switch (action) {
553
- case 'idle': return this.reset();
554
- case 'bored': return this.bored();
555
- case 'input': return this.input(true);
556
- case 'send': return this.send();
557
- case 'success': return this.success();
558
- case 'error': return this.error();
559
- case 'angry': return this.angry();
560
- case 'surprise': return this.surprise();
561
- case 'sleep': return this.sleep();
562
- case 'wake': return this.wake();
563
- default: throw new Error(`Unknown Agent Robot Avatar action: ${name}`);
564
- }
565
- }
566
-
567
- reset() {
568
- return this._resetToIdle();
569
- }
570
-
571
- _resetToIdle(notify = true) {
572
- this._resetExtensionEffects?.();
573
- this._inputWanted = false;
574
- ++this._transitionToken;
575
- this._cancelPendingWaits();
576
- this._boredRoutine = null;
577
- this._boredLookSpeed = null;
578
- this._sleeping = false;
579
- this._eyeNod = null;
580
- this._eyeBob = null;
581
- this._angryEyeDrop = null;
582
- this._surpriseShake = null;
583
- this.setState('idle', { notify, duration: notify ? 320 : 0 });
584
- return this;
585
- }
586
-
587
- setState(name, opts={}) {
588
- if (!POSES[name]) throw new Error(`Unknown AgentFace state: ${name}`);
589
- this._resumeFrames();
590
- this._state = name;
591
- this._sleeping = name === 'sleep';
592
- if (name === 'idle' && !opts.keepGazeLock) this._releaseExpressionLock();
593
- this._startMorph(POSES[name], opts.duration ?? (name==='surprise'?170:320), opts.ease || EASE.outQuint);
594
- if (opts.notify !== false) this.dispatchEvent(new CustomEvent('face-state', {detail:{state:name}}));
595
- return this;
596
- }
597
-
598
- _startMorph(target, duration, ease) {
599
- this._fromPose = clonePose(this._pose);
600
- this._toPose = clonePose(target);
601
- this._morphStart = performance.now();
602
- this._morphDuration = Math.max(0, duration);
603
- this._morphEase = ease;
604
- }
605
-
606
- async success(opts={}) {
607
- const fromDrag = !!opts.fromDrag;
608
- this.noteActivity();
609
- this._inputWanted = false;
610
- if (fromDrag) {
611
- ++this._transitionToken;
612
- this._expressionLock = true;
613
- this._eyeMicro.lX = this._eyeMicro.lY = 0;
614
- this._eyeMicro.rX = this._eyeMicro.rY = 0;
615
- this._wanderTarget.x = this._wanderTarget.y = 0;
616
- this._headCenteringUntil = performance.now() + 120;
617
- this._look.x = 0;
618
- this._look.y = 0;
619
- } else {
620
- if (!(await this._prepareExpression({normalizePose:true, duration:180, pause:320}))) return;
621
- }
622
- const token = this._transitionToken;
623
- this.setState('happy', {duration: fromDrag ? 180 : 220, keepGazeLock:true});
624
- this._eyeBob = {start: performance.now(), duration: 620, amp: 8};
625
- this._animateHead([
626
- {transform:'translateY(0px)'},
627
- {transform:'translateY(-2.4px)', offset:.42},
628
- {transform:'translateY(0px)'}
629
- ], 760);
630
- await this._wait(980);
631
- if (token !== this._transitionToken) return;
632
- this._eyeBob = null;
633
- if (this._state==='happy') await this._returnToIdle(520);
634
- }
635
-
636
- async error() {
637
- this.noteActivity();
638
- this._inputWanted = false;
639
- if (!(await this._prepareExpression({normalizePose:true, duration:180, pause:320}))) return;
640
- const token = this._transitionToken;
641
- this.setState('sad', {duration:320, keepGazeLock:true});
642
- this._eyeBob = {start: performance.now(), duration: 700, amp: 7};
643
- this._animateHead([
644
- {transform:'translateY(0px)'},
645
- {transform:'translateY(2.6px)', offset:.46},
646
- {transform:'translateY(0px)'}
647
- ], 820);
648
- await this._wait(1180);
649
- if (token !== this._transitionToken) return;
650
- this._eyeBob = null;
651
- if (this._state==='sad') {
652
- await this._returnToIdle(560);
653
- }
654
- }
655
-
656
- async angry(opts={}) {
657
- const fromDrag = !!opts.fromDrag;
658
- this.noteActivity();
659
- this._inputWanted = false;
660
- if (fromDrag) {
661
- ++this._transitionToken;
662
- this._expressionLock = true;
663
- this._eyeMicro.lX = this._eyeMicro.lY = 0;
664
- this._eyeMicro.rX = this._eyeMicro.rY = 0;
665
- this._wanderTarget.x = this._wanderTarget.y = 0;
666
- this._headCenteringUntil = performance.now() + 120;
667
- this._look.x = 0;
668
- this._look.y = 0;
669
- } else {
670
- if (!(await this._prepareExpression({normalizePose:true, duration:180, pause:320}))) return;
671
- }
672
- const token = this._transitionToken;
673
- this.setState('angry', {duration: fromDrag ? 180 : 280, keepGazeLock:true});
674
- this._angryEyeDrop = {start: performance.now(), duration:680, amount:4.8};
675
- this._animateHead([
676
- {transform:'translateY(0px)'},
677
- {transform:'translateY(2.6px)', offset:.46},
678
- {transform:'translateY(0px)'}
679
- ], 820);
680
- await this._wait(980);
681
- if (token !== this._transitionToken) return;
682
- this._angryEyeDrop = null;
683
- if (this._state==='angry') await this._returnToIdle(500);
684
- }
685
-
686
- async surprise() {
687
- this.noteActivity();
688
- this._inputWanted = false;
689
- if (!(await this._prepareExpression({normalizePose:true, duration:180, pause:320}))) return;
690
- const token = this._transitionToken;
691
- this._surpriseShake = null;
692
- const pre = {...POSES.idle, w:34, h:36, rx:18};
693
- this._startMorph(pre, 235, EASE.inOutCubic);
694
- await this._wait(235);
695
- if (token !== this._transitionToken || this._state!=='idle') return;
696
- this.setState('surprise', {duration:175, ease:EASE.outQuint, keepGazeLock:true});
697
- this._animateHead([{transform:'scale(1)'},{transform:'scale(1.035)'}], 175);
698
- await this._wait(175);
699
- if (token !== this._transitionToken || this._state!=='surprise') return;
700
- this._surpriseShake = {start: performance.now(), duration:420};
701
- await this._wait(420);
702
- if (token !== this._transitionToken) return;
703
- this._surpriseShake = null;
704
- if (this._state==='surprise') {
705
- this._animateHead([{transform:'scale(1.035)'},{transform:'scale(1)'}], 430);
706
- await this._returnToIdle(430, EASE.inOutCubic);
707
- }
708
- }
709
-
710
- async send() {
711
- this.noteActivity();
712
- this._inputWanted = false;
713
- if (!(await this._prepareExpression({normalizePose:true, duration:180, pause:320}))) return;
714
- const token = this._transitionToken;
715
- this._eyeNod = {start: performance.now(), duration: 760};
716
- this._animateHead([
717
- {transform:'translateY(0px)'},
718
- {transform:'translateY(5px)', offset:.52},
719
- {transform:'translateY(0px)'}
720
- ], 920);
721
- await this._wait(860);
722
- if (token !== this._transitionToken) return;
723
- this._eyeNod = null;
724
- this._releaseExpressionLock();
725
- }
726
-
727
- async sleep() {
728
- if (this._sleeping) return;
729
- if (!(await this._prepareExpression({normalizePose:true, duration:160}))) return;
730
- const token = this._transitionToken;
731
- this._blinkAnim = null;
732
- this._blink = 1;
733
- this._nextBlinkAt = performance.now() + 10000;
734
- this.setState('sleepy', {duration:940, ease:EASE.inOutCubic, keepGazeLock:true});
735
- await this._wait(980);
736
- if (token !== this._transitionToken || this._state !== 'sleepy') return;
737
- const reopen = {...POSES.idle, topY:-22, bottomY:36};
738
- this._startMorph(reopen, 105, EASE.outQuint);
739
- await this._wait(120);
740
- if (token !== this._transitionToken || this._state !== 'sleepy') return;
741
- this.setState('sleepy', {duration:820, ease:EASE.inOutCubic, keepGazeLock:true});
742
- await this._wait(870);
743
- if (token !== this._transitionToken || this._state !== 'sleepy') return;
744
- await this._wait(120);
745
- if (token !== this._transitionToken || this._state !== 'sleepy') return;
746
- this._animateHead([{transform:'translateY(0)'},{transform:'translateY(4px)'}],980);
747
- this._startMorph(POSES.sleep, 980, EASE.inOutCubic);
748
- this._state = 'sleep';
749
- this.dispatchEvent(new CustomEvent('face-state', {detail:{state:'sleep'}}));
750
- this._sleeping = true;
751
- this._nextBlinkAt = performance.now() + 3200 + Math.random()*2200;
752
- }
753
-
754
- async wake() {
755
- if (!this._sleeping && this._state!=='sleep') return;
756
- const token = ++this._transitionToken;
757
- this._expressionLock = true;
758
- this._sleeping = false;
759
- this._animateHead([
760
- {transform:'translateY(4px) scale(1)'},
761
- {transform:'translateY(-2px) scale(1.015)', offset:.55},
762
- {transform:'translateY(0) scale(1)'}
763
- ], 500);
764
- this.setState('sleepy', {duration:240, keepGazeLock:true});
765
- await this._wait(260);
766
- if (token !== this._transitionToken) return;
767
- this.setState('surprise', {duration:120, keepGazeLock:true});
768
- await this._wait(190);
769
- if (token !== this._transitionToken) return;
770
- await this._returnToIdle(300);
771
- if (token !== this._transitionToken) return;
772
- this._lastActivity = performance.now();
773
- }
774
-
775
- async input(active=true) {
776
- this.noteActivity();
777
- if (active) {
778
- if (this._inputWanted || this._state==='input') return;
779
- this._inputWanted = true;
780
- if (!(await this._prepareExpression({normalizePose:true, duration:140}))) return;
781
- if (!this._inputWanted) return;
782
- this.setState('input', {duration:210, keepGazeLock:true});
783
- } else {
784
- this._inputWanted = false;
785
- ++this._transitionToken;
786
- if (this._state==='input') this.setState('idle', {duration:260});
787
- else this._releaseExpressionLock();
788
- }
789
- }
790
-
791
- _animateHead(frames, duration) {
792
- if (!this._headMotion.animate) return;
793
- const token = this._transitionToken;
794
- const anim = this._headMotion.animate(frames, {duration, easing:'cubic-bezier(.16,1,.3,1)', fill:'forwards'});
795
- anim.onfinish = () => {
796
- if (token === this._transitionToken) {
797
- this._headMotion.style.transform = frames[frames.length-1].transform || '';
798
- }
799
- };
800
- return anim;
801
- }
802
-
803
- _wait(ms) {
804
- return new Promise(resolve => {
805
- const timer = setTimeout(() => {
806
- this._pendingWaits.delete(timer);
807
- resolve();
808
- }, ms);
809
- this._pendingWaits.set(timer, resolve);
810
- });
811
- }
812
-
813
- _cancelPendingWaits() {
814
- for (const [timer, resolve] of this._pendingWaits) {
815
- clearTimeout(timer);
816
- resolve();
817
- }
818
- this._pendingWaits.clear();
819
- }
820
-
821
- async _returnToIdle(duration=320, ease=EASE.outQuint) {
822
- const token = this._transitionToken;
823
- this.setState('idle', {duration, ease, keepGazeLock:true});
824
- this._headCenteringUntil = Math.max(this._headCenteringUntil || 0, performance.now() + duration);
825
- await this._wait(duration);
826
- if (token === this._transitionToken && this._state === 'idle') this._releaseExpressionLock();
827
- }
828
-
829
- async _prepareExpression({normalizePose=true, duration=180, pause=320}={}) {
830
- const token = ++this._transitionToken;
831
- this._expressionLock = true;
832
- this._eyeMicro.lX = this._eyeMicro.lY = 0;
833
- this._eyeMicro.rX = this._eyeMicro.rY = 0;
834
- this._wanderTarget.x = this._wanderTarget.y = 0;
835
- this._headCenteringUntil = performance.now() + duration + pause;
836
- if (normalizePose && this._state !== 'idle') {
837
- this.setState('idle', {duration:Math.min(140, duration), keepGazeLock:true});
838
- }
839
- await this._wait(duration);
840
- if (token !== this._transitionToken) return false;
841
- if (pause > 0) await this._wait(pause);
842
- return token === this._transitionToken;
843
- }
844
-
845
- async bored() {
846
- this.noteActivity();
847
- this._inputWanted = false;
848
- this._sleeping = false;
849
- if (!(await this._prepareExpression({normalizePose:true, duration:180, pause:620}))) return;
850
- this._expressionLock = false;
851
- this._look.x = 0;
852
- this._look.y = 0;
853
- this._wander.x = 0;
854
- this._wander.y = 0;
855
- this._startBoredRoutine(performance.now());
856
- }
857
-
858
- _startBoredRoutine(now) {
859
- this._boredRoutine = {
860
- index: 0,
861
- nextAt: now,
862
- steps: [
863
- {x:-18,y:-11,hold:1400,lookSpeed:2},
864
- {x:18,y:-11,hold:1400,lookSpeed:2},
865
- {x:0,y:0,hold:320,lookSpeed:2},
866
- ]
867
- };
868
- }
869
-
870
- _updateBored(now) {
871
- if (!this._boredRoutine) return false;
872
- if (now < this._boredRoutine.nextAt) return true;
873
- const step = this._boredRoutine.steps[this._boredRoutine.index];
874
- if (!step) {
875
- this._boredRoutine = null;
876
- this._boredLookSpeed = null;
877
- this._wanderTarget.x = this._wanderTarget.y = 0;
878
- return false;
879
- }
880
- this._wanderTarget.x = step.x;
881
- this._wanderTarget.y = step.y;
882
- this._boredLookSpeed = step.lookSpeed ?? null;
883
- this._boredRoutine.index += 1;
884
- this._boredRoutine.nextAt = now + step.hold;
885
- return true;
886
- }
887
-
888
- _releaseExpressionLock() {
889
- this._expressionLock = false;
890
- this._headCenteringUntil = 0;
891
- }
892
-
893
- _updateMorph(now) {
894
- if (!this._morphDuration) {
895
- this._pose = clonePose(this._toPose);
896
- return;
897
- }
898
- const t = clamp((now - this._morphStart) / this._morphDuration, 0, 1);
899
- const k = this._morphEase(t);
900
- const p = {};
901
- for (const key of Object.keys(this._toPose)) p[key] = lerp(this._fromPose[key], this._toPose[key], k);
902
- this._pose = p;
903
- if (t >= 1) this._morphDuration = 0;
904
- }
905
-
906
- _updateBlink(now) {
907
- if (this._sleeping || this._state === 'input') return;
908
- if (this._blinkAnim) {
909
- const t = clamp((now - this._blinkAnim.start) / this._blinkAnim.duration, 0, 1);
910
- this._blink = t < .5 ? lerp(1,.08,t*2) : lerp(.08,1,(t-.5)*2);
911
- if (t >= 1) {
912
- this._blinkAnim = null;
913
- this._blink = 1;
914
- this._nextBlinkAt = now + 1900 + Math.random()*2600;
915
- }
916
- } else if (!this._expressionLock && now >= this._nextBlinkAt) {
917
- this._blinkAnim = {start:now,duration:150+Math.random()*70};
918
- }
919
- }
920
-
921
- _updateLook(now, dt) {
922
- this._updateBored(now);
923
- const pointerFresh = (now - this._pointer.lastMove) < 900;
924
- if (!this._expressionLock && !this._boredRoutine && !this._sleeping && this._state!=='input' && now >= this._nextWanderAt) {
925
- const ampX = 12 + Math.random()*13;
926
- const ampY = 5 + Math.random()*9;
927
- this._wanderTarget.x = (Math.random()*2-1)*ampX;
928
- this._wanderTarget.y = (Math.random()*2-1)*ampY;
929
- this._nextWanderAt = now + 1150 + Math.random()*1900;
930
- }
931
- const draggingJelly = this._dragJelly.active || this._dragJelly.returning;
932
- const pointerMix = (!this._expressionLock && !this._boredRoutine && pointerFresh && this._pointer.active && !this._sleeping && this._state!=='input')
933
- ? Math.pow(this._pointer.influence || 0, 1.6)
934
- : 0;
935
- const targetX = this._expressionLock ? 0 : lerp(this._wanderTarget.x, this._pointer.x, pointerMix);
936
- const targetY = this._expressionLock ? 0 : lerp(this._wanderTarget.y, this._pointer.y, pointerMix);
937
- const lookSpeed = this._expressionLock ? 0.40 : (draggingJelly ? 0.42 : (this._boredRoutine && this._boredLookSpeed != null ? this._boredLookSpeed : (pointerMix > 0.05 ? 0.28 : 0.16)));
938
- const kL = 1 - Math.pow(1 - clamp(lookSpeed,0.02,.9), dt/16.67);
939
- this._look.x = lerp(this._look.x, targetX, kL);
940
- this._look.y = lerp(this._look.y, targetY, kL);
941
- if (this._sleeping) {
942
- this._look.x *= .92;
943
- this._look.y *= .92;
944
- }
945
- }
946
-
947
- _autoSleep(now) {
948
- if (!this._autoSleepMs || this._sleeping || this._expressionLock || ['input','happy','sad','surprise'].includes(this._state)) return;
949
- if (now - this._lastActivity > this._autoSleepMs) this.sleep();
950
- }
951
-
952
- _updateHeadFollow(now, dt) {
953
- const pointerFresh = (now - this._pointer.lastMove) < 900;
954
- const pointerInRange = pointerFresh && this._pointer.active && (this._pointer.influence || 0) > 0.03;
955
- const headCentering = now < (this._headCenteringUntil || 0);
956
- const jellyBusy = this._dragJelly.active || this._dragJelly.returning;
957
- const canFollowPointer = pointerInRange && !jellyBusy && !this._sleeping && !this._boredRoutine && !headCentering && !this._expressionLock;
958
- const srcX = canFollowPointer ? this._pointer.x : this._look.x;
959
- const srcY = canFollowPointer ? this._pointer.y : this._look.y;
960
- const nx = clamp(srcX / 48, -1, 1);
961
- const ny = clamp(srcY / 34, -1, 1);
962
- const diag = clamp(nx * ny, -1, 1);
963
- const targetRot = diag * 8;
964
- const targetX = canFollowPointer ? nx * 4.2 : 0;
965
- const targetY = canFollowPointer ? ny * 4.2 : 0;
966
-
967
- const dtS = Math.min(dt, 34) / 1000;
968
- const rotStiffness = canFollowPointer ? 70 : 56;
969
- const rotDamping = canFollowPointer ? 15 : 13;
970
- const aRot = (targetRot - this._headFollowPose.rot) * rotStiffness - this._headFollowVel.rot * rotDamping;
971
- this._headFollowVel.rot += aRot * dtS;
972
- this._headFollowPose.rot += this._headFollowVel.rot * dtS;
973
- const stiffness = canFollowPointer ? 95 : 80;
974
- const damping = canFollowPointer ? 16 : 14;
975
- const ax = (targetX - this._headFollowPose.x) * stiffness - this._headFollowVel.x * damping;
976
- const ay = (targetY - this._headFollowPose.y) * stiffness - this._headFollowVel.y * damping;
977
- this._headFollowVel.x += ax * dtS;
978
- this._headFollowVel.y += ay * dtS;
979
- this._headFollowPose.x += this._headFollowVel.x * dtS;
980
- this._headFollowPose.y += this._headFollowVel.y * dtS;
981
-
982
- if (!canFollowPointer) {
983
- if (Math.abs(this._headFollowPose.x) < 0.01 && Math.abs(this._headFollowVel.x) < 0.01) { this._headFollowPose.x = 0; this._headFollowVel.x = 0; }
984
- if (Math.abs(this._headFollowPose.y) < 0.01 && Math.abs(this._headFollowVel.y) < 0.01) { this._headFollowPose.y = 0; this._headFollowVel.y = 0; }
985
- if (Math.abs(this._headFollowPose.rot) < 0.01 && Math.abs(this._headFollowVel.rot) < 0.01) { this._headFollowPose.rot = 0; this._headFollowVel.rot = 0; }
986
- }
987
-
988
- if (this._headFollow) {
989
- const a = this._headFollowPose.rot.toFixed(2);
990
- const tx = this._headFollowPose.x.toFixed(2);
991
- const ty = this._headFollowPose.y.toFixed(2);
992
- this._headFollow.setAttribute('transform', `translate(${tx} ${ty}) translate(120 120) rotate(${a}) translate(-120 -120)`);
993
- }
994
- }
995
-
996
- _draw(now) {
997
- const p = this._pose;
998
- let nodY = 0;
999
- let nodScaleY = 1;
1000
- if (this._eyeNod) {
1001
- const elapsed = now - this._eyeNod.start;
1002
- let depth = 0;
1003
- if (elapsed < 150) depth = elapsed / 150;
1004
- else if (elapsed < 300) depth = 1 - (elapsed - 150) / 150;
1005
- else if (elapsed < 450) depth = (elapsed - 300) / 150;
1006
- else if (elapsed < 760) depth = 1 - (elapsed - 450) / 310;
1007
- else { depth = 0; this._eyeNod = null; }
1008
- depth = clamp(depth, 0, 1);
1009
- nodY = 31 * depth;
1010
- nodScaleY = 1 - .30 * depth;
1011
- }
1012
-
1013
- let angryDropY = 0;
1014
- if (this._angryEyeDrop) {
1015
- const t = clamp((now - this._angryEyeDrop.start) / this._angryEyeDrop.duration, 0, 1);
1016
- const settle = 1 - Math.exp(-5.2 * t) * Math.cos(8.6 * t);
1017
- angryDropY = this._angryEyeDrop.amount * settle;
1018
- if (t >= 1) angryDropY = this._angryEyeDrop.amount;
1019
- }
1020
-
1021
- let emotionBobY = 0;
1022
- if (this._eyeBob) {
1023
- const t = clamp((now - this._eyeBob.start) / this._eyeBob.duration, 0, 1);
1024
- const cycles = 2;
1025
- emotionBobY = Math.sin(t * Math.PI * cycles * 2) * this._eyeBob.amp * (1 - 0.38*t);
1026
- if (t >= 1) this._eyeBob = null;
1027
- }
1028
-
1029
- let surpriseScale = 1;
1030
- if (this._surpriseShake) {
1031
- const t = clamp((now - this._surpriseShake.start) / this._surpriseShake.duration, 0, 1);
1032
- surpriseScale = 1 + 0.022 * Math.sin(t * Math.PI * 8) * (1 - 0.18*t);
1033
- if (t >= 1) this._surpriseShake = null;
1034
- }
1035
-
1036
- const blinkH = Math.max(2.6, p.h * this._blink * nodScaleY * surpriseScale);
1037
- const yAdjust = (p.h - blinkH) * 0.02;
1038
- const cursorPeriod = 530;
1039
- const inputBlinkOn = this._state==='input' ? ((Math.floor(now / (cursorPeriod/2)) % 2) === 0) : true;
1040
- const inputPulseL = this._state==='input' ? (inputBlinkOn ? 1 : 0) : 1;
1041
- const inputPulseR = inputPulseL;
1042
-
1043
- const jellyEyeOffset = (cx, cy) => {
1044
- const j = this._dragJelly;
1045
- const amount = Math.hypot(j.pullX, j.pullY);
1046
- if (amount < 0.05) return {x:0, y:0};
1047
- const radius = (50 + amount * 0.8) * 0.92;
1048
- const dx = cx - j.hotX;
1049
- const dy = cy - j.hotY;
1050
- const w = Math.exp(-(dx*dx + dy*dy) / (2 * radius * radius)) * 0.24;
1051
- return {x: j.pullX * w, y: j.pullY * w};
1052
- };
1053
-
1054
- const setEye = (base, inputBase, top, bottom, side, cx, microX, microY, opacity) => {
1055
- const isInput = this._state === 'input';
1056
- const projectedX = cx + this._look.x + microX;
1057
- const edgeDistance = side==='L' ? (projectedX - 32) : (208 - projectedX);
1058
- const edgeFactor = clamp(1 - edgeDistance / 58, 0, 1);
1059
- const baseW = p.w * surpriseScale;
1060
- const baseH = blinkH;
1061
- const w = baseW * (1 - 0.22 * edgeFactor * edgeFactor);
1062
- const h = baseH * (1 - 0.08 * edgeFactor * edgeFactor);
1063
- const inwardShift = ((baseW - w) * 0.58) * (side==='L' ? 1 : -1);
1064
-
1065
- if (isInput) {
1066
- base.setAttribute('opacity', '0');
1067
- inputBase.setAttribute('x', (-p.w/2).toFixed(2));
1068
- inputBase.setAttribute('y', (-p.h/2 + yAdjust).toFixed(2));
1069
- inputBase.setAttribute('width', p.w.toFixed(2));
1070
- inputBase.setAttribute('height', p.h.toFixed(2));
1071
- inputBase.setAttribute('rx', '3');
1072
- inputBase.setAttribute('opacity', opacity.toFixed(3));
1073
- } else {
1074
- inputBase.setAttribute('opacity', '0');
1075
- base.setAttribute('cx', '0');
1076
- base.setAttribute('cy', yAdjust.toFixed(2));
1077
- base.setAttribute('rx', (w/2).toFixed(2));
1078
- base.setAttribute('ry', (h/2).toFixed(2));
1079
- base.setAttribute('opacity', opacity.toFixed(3));
1080
- }
1081
- const topY = p.topY;
1082
- const bottomY = p.bottomY;
1083
- const ta = side==='L' ? p.topAL : p.topAR;
1084
- const ba = side==='L' ? p.bottomAL : p.bottomAR;
1085
- top.setAttribute('y', (topY-90).toFixed(2));
1086
- top.setAttribute('height', '90');
1087
- top.setAttribute('transform', `rotate(${ta.toFixed(2)} 0 ${topY.toFixed(2)})`);
1088
- bottom.setAttribute('y', bottomY.toFixed(2));
1089
- bottom.setAttribute('height', '90');
1090
- bottom.setAttribute('transform', `rotate(${ba.toFixed(2)} 0 ${bottomY.toFixed(2)})`);
1091
- const jelly = jellyEyeOffset(cx, 126);
1092
- return `translate(${(cx + this._look.x + microX + jelly.x + (isInput ? 0 : inwardShift)).toFixed(2)} ${(126 + this._look.y + microY + jelly.y + nodY + angryDropY + emotionBobY).toFixed(2)})`;
1093
- };
1094
-
1095
- const inputCenter = this._state==='input';
1096
- const surpriseSpread = this._state==='surprise' ? 3.4 * EASE.outQuint(clamp((p.w - 34) / (POSES.surprise.w - 34), 0, 1)) : 0;
1097
- const leftCX = inputCenter ? 120 : 86 - surpriseSpread;
1098
- const rightCX = inputCenter ? 120 : 154 + surpriseSpread;
1099
- this._leftEye.setAttribute('transform', setEye(this._leftBase,this._leftInputBase,this._leftTop,this._leftBottom,'L',leftCX,this._eyeMicro.lX,this._eyeMicro.lY,inputPulseL));
1100
- this._rightEye.setAttribute('transform', setEye(this._rightBase,this._rightInputBase,this._rightTop,this._rightBottom,'R',rightCX,this._eyeMicro.rX,this._eyeMicro.rY,inputPulseR));
1101
- }
1102
-
1103
- _resumeFrames() {
1104
- if (!this.isConnected || document.hidden || this._raf) return;
1105
- this._running = true;
1106
- this._framePaused = false;
1107
- this._lastFrame = performance.now();
1108
- this._raf = requestAnimationFrame(this._loop);
1109
- }
1110
-
1111
- _canPauseFrames(now) {
1112
- if (!this._sleeping || this._state !== 'sleep') return false;
1113
- if (now - this._morphStart < this._morphDuration) return false;
1114
- if (this._dragJelly.active || this._dragJelly.returning) return false;
1115
- if (this._waitingFx || this._inspectFx || this._failureFx || this._warningFx || this._systemErrorShake) return false;
1116
- const animations = this._headMotion?.getAnimations?.() || [];
1117
- return !animations.some(animation => animation.playState === 'running' || animation.playState === 'pending');
1118
- }
1119
-
1120
- _loop = (ts=performance.now()) => {
1121
- if (!this._running || !this.isConnected) {
1122
- this._raf = 0;
1123
- return;
1124
- }
1125
- if (ts - this._lastFrame >= 33) {
1126
- const dt = Math.min(66, ts - (this._lastFrame || ts-33));
1127
- this._lastFrame = ts;
1128
- this._updateMorph(ts);
1129
- this._updateBlink(ts);
1130
- this._updateLook(ts, dt);
1131
- this._updateHeadFollow(ts, dt);
1132
- this._updateDragJelly(dt);
1133
- this._draw(ts);
1134
- }
1135
- if (this._canPauseFrames(ts)) {
1136
- this._framePaused = true;
1137
- this._raf = 0;
1138
- return;
1139
- }
1140
- this._raf = requestAnimationFrame(this._loop);
1141
- };
1142
- }
1143
-
1144
- if (typeof customElements !== 'undefined' && !customElements.get('agent-robot-avatar')) {
1145
- customElements.define('agent-robot-avatar', AgentFace);
1146
- }
1147
- if (typeof window !== 'undefined') window.AgentRobotAvatar = AgentFace;
1148
- return AgentFace;
1149
- })();
1150
-
1151
- export { AgentRobotAvatar };
1152
- export default AgentRobotAvatar;
1
+ const AgentRobotAvatar = (() => {
2
+ const HTMLElementBase = typeof HTMLElement === 'undefined' ? class {} : HTMLElement;
3
+ const EASE = {
4
+ outQuint: t => 1 - Math.pow(1 - t, 5),
5
+ inOutCubic: t => t < 0.5 ? 4*t*t*t : 1 - Math.pow(-2*t + 2, 3)/2,
6
+ };
7
+
8
+ const POSES = {
9
+ idle: {
10
+ w: 54, h: 58, rx: 27,
11
+ topY: -36, bottomY: 36,
12
+ topAL: 0, topAR: 0, bottomAL: 0, bottomAR: 0,
13
+ scale: 1,
14
+ },
15
+ happy: {
16
+ w: 58, h: 58, rx: 29,
17
+ topY: -36, bottomY: 0,
18
+ topAL: 0, topAR: 0, bottomAL: 0, bottomAR: 0,
19
+ scale: 1,
20
+ },
21
+ sad: {
22
+ w: 58, h: 58, rx: 29,
23
+ topY: -5, bottomY: 36,
24
+ topAL: -16, topAR: 16, bottomAL: 0, bottomAR: 0,
25
+ scale: 1,
26
+ },
27
+ angry: {
28
+ w: 58, h: 58, rx: 29,
29
+ topY: -5, bottomY: 36,
30
+ topAL: 16, topAR: -16, bottomAL: 0, bottomAR: 0,
31
+ scale: 1,
32
+ },
33
+ sleepy: {
34
+ w: 58, h: 56, rx: 28,
35
+ topY: 0, bottomY: 36,
36
+ topAL: 0, topAR: 0, bottomAL: 0, bottomAR: 0,
37
+ scale: 1,
38
+ },
39
+ input: {
40
+ w: 12, h: 78, rx: 2,
41
+ topY: -44, bottomY: 44,
42
+ topAL: 0, topAR: 0, bottomAL: 0, bottomAR: 0,
43
+ scale: 1,
44
+ },
45
+ sleep: {
46
+ w: 62, h: 10, rx: 2,
47
+ topY: -20, bottomY: 20,
48
+ topAL: 0, topAR: 0, bottomAL: 0, bottomAR: 0,
49
+ scale: 1,
50
+ },
51
+ surprise: {
52
+ w: 68, h: 68, rx: 34,
53
+ topY: -40, bottomY: 40,
54
+ topAL: 0, topAR: 0, bottomAL: 0, bottomAR: 0,
55
+ scale: 1,
56
+ },
57
+ };
58
+
59
+ const clamp = (v, a, b) => Math.max(a, Math.min(b, v));
60
+ const lerp = (a, b, t) => a + (b - a) * t;
61
+ const clonePose = p => ({...p});
62
+ const DEFAULT_SIZE = 112;
63
+ const parseSize = value => {
64
+ const text = String(value ?? '').trim();
65
+ if (!/^(?:\d+(?:\.\d+)?|\.\d+)(?:px)?$/i.test(text)) return DEFAULT_SIZE;
66
+ const numeric = Number.parseFloat(text);
67
+ return Number.isFinite(numeric) && numeric > 0 ? numeric : DEFAULT_SIZE;
68
+ };
69
+ const connectedFaces = new Set();
70
+
71
+ const forEachConnectedFace = callback => {
72
+ for (const face of connectedFaces) {
73
+ if (face.isConnected) callback(face);
74
+ }
75
+ };
76
+
77
+ const handleGlobalPointerMove = event => {
78
+ forEachConnectedFace(face => {
79
+ face._onPointerMove(event);
80
+ if (face._dragJelly.active || face._gestureFx?.held) face._onDragMove(event);
81
+ });
82
+ };
83
+ const handleGlobalPointerDown = event => forEachConnectedFace(face => {
84
+ if (typeof face._noteEnvironmentActivity === 'function') face._noteEnvironmentActivity(event);
85
+ else face.noteActivity();
86
+ });
87
+ const handleGlobalKeyDown = event => forEachConnectedFace(face => {
88
+ if (typeof face._noteEnvironmentActivity === 'function') face._noteEnvironmentActivity(event);
89
+ else face.noteActivity();
90
+ });
91
+ const handleGlobalPointerEnd = event => {
92
+ forEachConnectedFace(face => {
93
+ if (face._dragJelly.active || face._gestureFx?.held) face._onDragEnd(event);
94
+ });
95
+ };
96
+ const handleGlobalVisibility = () => forEachConnectedFace(face => face._onVisibility());
97
+
98
+ function registerConnectedFace(face) {
99
+ if (connectedFaces.has(face)) return;
100
+ const first = connectedFaces.size === 0;
101
+ connectedFaces.add(face);
102
+ if (!first) return;
103
+
104
+ window.addEventListener('pointermove', handleGlobalPointerMove, {passive:true});
105
+ window.addEventListener('pointerdown', handleGlobalPointerDown, {passive:true});
106
+ window.addEventListener('keydown', handleGlobalKeyDown, {passive:true});
107
+ window.addEventListener('pointerup', handleGlobalPointerEnd, {passive:true});
108
+ window.addEventListener('pointercancel', handleGlobalPointerEnd, {passive:true});
109
+ document.addEventListener('visibilitychange', handleGlobalVisibility);
110
+ }
111
+
112
+ function unregisterConnectedFace(face) {
113
+ connectedFaces.delete(face);
114
+ if (connectedFaces.size > 0) return;
115
+
116
+ window.removeEventListener('pointermove', handleGlobalPointerMove);
117
+ window.removeEventListener('pointerdown', handleGlobalPointerDown);
118
+ window.removeEventListener('keydown', handleGlobalKeyDown);
119
+ window.removeEventListener('pointerup', handleGlobalPointerEnd);
120
+ window.removeEventListener('pointercancel', handleGlobalPointerEnd);
121
+ document.removeEventListener('visibilitychange', handleGlobalVisibility);
122
+ }
123
+
124
+ class AgentFace extends HTMLElementBase {
125
+ constructor() {
126
+ super();
127
+ this.attachShadow({mode: 'open'});
128
+ this._state = 'idle';
129
+ this._pose = clonePose(POSES.idle);
130
+ this._fromPose = clonePose(this._pose);
131
+ this._toPose = clonePose(this._pose);
132
+ this._morphStart = performance.now();
133
+ this._morphDuration = 0;
134
+ this._morphEase = EASE.outQuint;
135
+
136
+ this._wander = {x:0, y:0};
137
+ this._wanderTarget = {x:0, y:0};
138
+ this._nextWanderAt = performance.now() + 500;
139
+ this._pointer = {x:0, y:0, active:false, lastMove:0};
140
+ this._look = {x:0, y:0};
141
+ this._headFollowPose = {x:0,y:0,rot:0};
142
+ this._headFollowVel = {x:0,y:0,rot:0};
143
+ this._headCenteringUntil = 0;
144
+ this._eyeMicro = {lX:0,lY:0,rX:0,rY:0};
145
+ this._blink = 1;
146
+ this._blinkAnim = null;
147
+ this._nextBlinkAt = performance.now() + 1800 + Math.random()*1800;
148
+ this._inputPhase = 0;
149
+ this._eyeNod = null;
150
+ this._eyeBob = null;
151
+ this._angryEyeDrop = null;
152
+ this._surpriseShake = null;
153
+ this._expressionLock = false;
154
+ this._transitionToken = 0;
155
+ this._inputWanted = false;
156
+ this._sleeping = false;
157
+ this._idleTimer = null;
158
+ this._pendingWaits = new Map();
159
+ this._boredRoutine = null;
160
+ this._boredLookSpeed = null;
161
+ this._autoSleepMs = Number(this.getAttribute('auto-sleep')) || 0;
162
+ this._lastActivity = performance.now();
163
+ this._running = true;
164
+ this._framePaused = false;
165
+ this._lastFrame = 0;
166
+ this._raf = 0;
167
+ this._dragJelly = {
168
+ active:false, returning:false, snapped:false, moved:false, pointerId:null,
169
+ startX:0, startY:0, hotX:120, hotY:120, targetX:0, targetY:0, x:0, y:0, vx:0, vy:0,
170
+ targetStretch:0, stretch:0, stretchVel:0, angle:0, threshold:52,
171
+ ux:0, uy:0, anchorX:50, anchorY:50, shear:0, targetShear:0, shearVel:0,
172
+ targetPullX:0, targetPullY:0, pullX:0, pullY:0, pullVX:0, pullVY:0,
173
+ maxDist:0, angryThreshold:32, pendingReaction:null, intent:'angry'
174
+ };
175
+ this._suppressClick = false;
176
+ this._boundDragStart = e => this._onDragStart(e);
177
+ this._boundClickCapture = e => this._onClickCapture(e);
178
+ this._renderShell();
179
+ }
180
+
181
+ connectedCallback() {
182
+ registerConnectedFace(this);
183
+ this.addEventListener('pointerdown', this._boundDragStart);
184
+ this.addEventListener('click', this._boundClickCapture, true);
185
+ this._running = !document.hidden;
186
+ this._lastActivity = performance.now();
187
+ this._resumeFrames();
188
+ }
189
+
190
+ disconnectedCallback() {
191
+ // This callback is captured when the custom element is defined, so the
192
+ // runtime teardown must be entered from the registered core lifecycle.
193
+ this._runtimeDisconnecting = true;
194
+ try {
195
+ unregisterConnectedFace(this);
196
+ this.removeEventListener('pointerdown', this._boundDragStart);
197
+ this.removeEventListener('click', this._boundClickCapture, true);
198
+ // Removal cancels work; reattachment starts idle with appearance settings intact.
199
+ this._resetToIdle(false);
200
+ this._blinkAnim = null;
201
+ this._blink = 1;
202
+ this._pointer.active = false;
203
+ this._pointer.influence = 0;
204
+ const drag = this._dragJelly;
205
+ if (drag.pointerId != null) {
206
+ try { this.releasePointerCapture(drag.pointerId); } catch (_) {}
207
+ }
208
+ drag.active = false;
209
+ drag.pointerId = null;
210
+ this._finishDragReturn(null);
211
+ this._suppressClick = false;
212
+ this._running = false;
213
+ cancelAnimationFrame(this._raf);
214
+ this._raf = 0;
215
+ } finally {
216
+ this._teardownRuntime?.();
217
+ this._runtimeDisconnecting = false;
218
+ }
219
+ }
220
+
221
+ static get observedAttributes() { return ['color','size','auto-sleep']; }
222
+ attributeChangedCallback(name, oldV, newV) {
223
+ if (oldV === newV) return;
224
+ if (name === 'color') this._applyColor(newV);
225
+ if (name === 'size') this._syncSizeAttribute();
226
+ if (name === 'auto-sleep') {
227
+ this._autoSleepMs = Number(newV) || 0;
228
+ this._scheduleAutoSleep?.();
229
+ }
230
+ }
231
+
232
+ _syncSizeAttribute() {
233
+ if (!this.hasAttribute('size')) {
234
+ this.style.removeProperty('--face-size');
235
+ return DEFAULT_SIZE;
236
+ }
237
+ const size = parseSize(this.getAttribute('size'));
238
+ this.style.setProperty('--face-size', `${size}px`);
239
+ return size;
240
+ }
241
+
242
+ _applyColor(value) {
243
+ const color = value || '#08090b';
244
+ for (const part of [this._head, this._leftTop, this._rightTop, this._leftBottom, this._rightBottom, this._antennaDot]) {
245
+ if (part) part.setAttribute('fill', color);
246
+ }
247
+ }
248
+
249
+ _renderShell() {
250
+ this.shadowRoot.innerHTML = `
251
+ <style>
252
+ :host{display:inline-block;width:var(--face-size,112px);height:var(--face-size,112px);contain:layout style;overflow:visible;cursor:pointer;user-select:none;-webkit-user-select:none;touch-action:manipulation}
253
+ svg{display:block;width:100%;height:100%;overflow:visible}
254
+ #dragMotion{transform-box:view-box;transform-origin:120px 120px;will-change:transform}
255
+ #headFollow{will-change:transform}
256
+ #headMotion{transform-origin:120px 120px;will-change:transform}
257
+ #inputL,#inputR{opacity:1}
258
+ </style>
259
+ <svg viewBox="0 0 240 240" role="img" aria-label="Agent robot avatar">
260
+ <defs>
261
+ <path id="headShape" d="M 120.00 20.00 L 132.80 20.05 L 139.87 20.20 L 145.68 20.44 L 150.79 20.78 L 155.43 21.22 L 159.71 21.76 L 163.69 22.40 L 167.44 23.14 L 170.99 23.97 L 174.34 24.90 L 177.53 25.93 L 180.57 27.07 L 183.46 28.30 L 186.22 29.63 L 188.85 31.06 L 191.36 32.59 L 193.75 34.22 L 196.03 35.96 L 198.19 37.80 L 200.25 39.75 L 202.20 41.81 L 204.04 43.97 L 205.78 46.25 L 207.41 48.64 L 208.94 51.15 L 210.37 53.78 L 211.70 56.54 L 212.93 59.43 L 214.07 62.47 L 215.10 65.66 L 216.03 69.01 L 216.86 72.56 L 217.60 76.31 L 218.24 80.29 L 218.78 84.57 L 219.22 89.21 L 219.56 94.32 L 219.80 100.13 L 219.95 107.20 L 220.00 120.00 L 219.95 132.80 L 219.80 139.87 L 219.56 145.68 L 219.22 150.79 L 218.78 155.43 L 218.24 159.71 L 217.60 163.69 L 216.86 167.44 L 216.03 170.99 L 215.10 174.34 L 214.07 177.53 L 212.93 180.57 L 211.70 183.46 L 210.37 186.22 L 208.94 188.85 L 207.41 191.36 L 205.78 193.75 L 204.04 196.03 L 202.20 198.19 L 200.25 200.25 L 198.19 202.20 L 196.03 204.04 L 193.75 205.78 L 191.36 207.41 L 188.85 208.94 L 186.22 210.37 L 183.46 211.70 L 180.57 212.93 L 177.53 214.07 L 174.34 215.10 L 170.99 216.03 L 167.44 216.86 L 163.69 217.60 L 159.71 218.24 L 155.43 218.78 L 150.79 219.22 L 145.68 219.56 L 139.87 219.80 L 132.80 219.95 L 120.00 220.00 L 107.20 219.95 L 100.13 219.80 L 94.32 219.56 L 89.21 219.22 L 84.57 218.78 L 80.29 218.24 L 76.31 217.60 L 72.56 216.86 L 69.01 216.03 L 65.66 215.10 L 62.47 214.07 L 59.43 212.93 L 56.54 211.70 L 53.78 210.37 L 51.15 208.94 L 48.64 207.41 L 46.25 205.78 L 43.97 204.04 L 41.81 202.20 L 39.75 200.25 L 37.80 198.19 L 35.96 196.03 L 34.22 193.75 L 32.59 191.36 L 31.06 188.85 L 29.63 186.22 L 28.30 183.46 L 27.07 180.57 L 25.93 177.53 L 24.90 174.34 L 23.97 170.99 L 23.14 167.44 L 22.40 163.69 L 21.76 159.71 L 21.22 155.43 L 20.78 150.79 L 20.44 145.68 L 20.20 139.87 L 20.05 132.80 L 20.00 120.00 L 20.05 107.20 L 20.20 100.13 L 20.44 94.32 L 20.78 89.21 L 21.22 84.57 L 21.76 80.29 L 22.40 76.31 L 23.14 72.56 L 23.97 69.01 L 24.90 65.66 L 25.93 62.47 L 27.07 59.43 L 28.30 56.54 L 29.63 53.78 L 31.06 51.15 L 32.59 48.64 L 34.22 46.25 L 35.96 43.97 L 37.80 41.81 L 39.75 39.75 L 41.81 37.80 L 43.97 35.96 L 46.25 34.22 L 48.64 32.59 L 51.15 31.06 L 53.78 29.63 L 56.54 28.30 L 59.43 27.07 L 62.47 25.93 L 65.66 24.90 L 69.01 23.97 L 72.56 23.14 L 76.31 22.40 L 80.29 21.76 L 84.57 21.22 L 89.21 20.78 L 94.32 20.44 L 100.13 20.20 L 107.20 20.05 Z"/>
262
+ <clipPath id="headClip"><use href="#headShape" transform="translate(7.2 7.2) scale(.94)"/></clipPath>
263
+ <clipPath id="eyeMaskClip" clipPathUnits="userSpaceOnUse"><rect x="-40" y="-54" width="80" height="108"/></clipPath>
264
+ </defs>
265
+ <g id="dragMotion">
266
+ <g id="headFollow">
267
+ <g id="headMotion">
268
+ <use id="head" href="#headShape" fill="#08090b" transform="translate(7.2 7.2) scale(.94)"/>
269
+ <g clip-path="url(#headClip)">
270
+ <g id="leftEye" transform="translate(86 126)">
271
+ <ellipse id="leftBase" cx="0" cy="0" rx="27" ry="29" fill="#fff"/>
272
+ <rect id="leftInputBase" x="-6" y="-39" width="12" height="78" rx="3" fill="#fff" opacity="0"/>
273
+ <g clip-path="url(#eyeMaskClip)">
274
+ <rect id="leftTop" x="-70" y="-96" width="140" height="60" fill="#08090b"/>
275
+ <rect id="leftBottom" x="-70" y="36" width="140" height="60" fill="#08090b"/>
276
+ </g>
277
+ </g>
278
+ <g id="rightEye" transform="translate(154 126)">
279
+ <ellipse id="rightBase" cx="0" cy="0" rx="27" ry="29" fill="#fff"/>
280
+ <rect id="rightInputBase" x="-6" y="-39" width="12" height="78" rx="3" fill="#fff" opacity="0"/>
281
+ <g clip-path="url(#eyeMaskClip)">
282
+ <rect id="rightTop" x="-70" y="-96" width="140" height="60" fill="#08090b"/>
283
+ <rect id="rightBottom" x="-70" y="36" width="140" height="60" fill="#08090b"/>
284
+ </g>
285
+ </g>
286
+ </g>
287
+ </g>
288
+ </g>
289
+ </g>
290
+ </svg>`;
291
+ this._headShape = this.shadowRoot.getElementById('headShape');
292
+ this._dragMotion = this.shadowRoot.getElementById('dragMotion');
293
+ this._headFollow = this.shadowRoot.getElementById('headFollow');
294
+ this._headMotion = this.shadowRoot.getElementById('headMotion');
295
+ this._head = this.shadowRoot.getElementById('head');
296
+ this._leftEye = this.shadowRoot.getElementById('leftEye');
297
+ this._rightEye = this.shadowRoot.getElementById('rightEye');
298
+ this._leftBase = this.shadowRoot.getElementById('leftBase');
299
+ this._rightBase = this.shadowRoot.getElementById('rightBase');
300
+ this._leftInputBase = this.shadowRoot.getElementById('leftInputBase');
301
+ this._rightInputBase = this.shadowRoot.getElementById('rightInputBase');
302
+ this._leftTop = this.shadowRoot.getElementById('leftTop');
303
+ this._rightTop = this.shadowRoot.getElementById('rightTop');
304
+ this._leftBottom = this.shadowRoot.getElementById('leftBottom');
305
+ this._rightBottom = this.shadowRoot.getElementById('rightBottom');
306
+ this._applyColor(this.getAttribute('color'));
307
+ this._baseHeadPathD = this._headShape.getAttribute('d');
308
+ this._baseHeadPoints = this._parseHeadPoints(this._baseHeadPathD);
309
+ }
310
+
311
+ _parseHeadPoints(d) {
312
+ const nums = (d.match(/-?\d*\.?\d+/g) || []).map(Number);
313
+ const pts = [];
314
+ for (let i = 0; i < nums.length - 1; i += 2) pts.push({x: nums[i], y: nums[i+1]});
315
+ return pts;
316
+ }
317
+
318
+ _pointsToPath(points) {
319
+ if (!points.length) return this._baseHeadPathD || '';
320
+ let d = `M ${points[0].x.toFixed(2)} ${points[0].y.toFixed(2)}`;
321
+ for (let i = 1; i < points.length; i++) d += ` L ${points[i].x.toFixed(2)} ${points[i].y.toFixed(2)}`;
322
+ return `${d} Z`;
323
+ }
324
+
325
+ _clientToLocal(clientX, clientY) {
326
+ const r = this.getBoundingClientRect();
327
+ return {
328
+ x: clamp(((clientX - r.left) / Math.max(1, r.width)) * 240, 0, 240),
329
+ y: clamp(((clientY - r.top) / Math.max(1, r.height)) * 240, 0, 240),
330
+ };
331
+ }
332
+
333
+ _applyHeadDeform() {
334
+ const j = this._dragJelly;
335
+ if (!this._headShape || !this._baseHeadPoints?.length) return;
336
+ const amount = Math.hypot(j.pullX, j.pullY);
337
+ if (amount < 0.015) {
338
+ this._headShape.setAttribute('d', this._baseHeadPathD);
339
+ return;
340
+ }
341
+ const radius = (50 + amount * 0.8) * 0.92;
342
+ const pts = this._baseHeadPoints.map(p => {
343
+ const dx = p.x - j.hotX;
344
+ const dy = p.y - j.hotY;
345
+ const w = Math.exp(-(dx*dx + dy*dy) / (2 * radius * radius));
346
+ return {x:p.x + j.pullX * w, y:p.y + j.pullY * w};
347
+ });
348
+ this._headShape.setAttribute('d', this._pointsToPath(pts));
349
+ }
350
+
351
+ _onVisibility() {
352
+ this._running = !document.hidden;
353
+ if (this._running) this._resumeFrames();
354
+ else {
355
+ this._cancelGesture?.();
356
+ cancelAnimationFrame(this._raf);
357
+ this._raf = 0;
358
+ }
359
+ }
360
+
361
+ _onClickCapture(e) {
362
+ if (!this._suppressClick) return;
363
+ e.preventDefault();
364
+ e.stopImmediatePropagation();
365
+ this._suppressClick = false;
366
+ }
367
+
368
+ _onPointerMove(e) {
369
+ const r = this.getBoundingClientRect();
370
+ const cx = r.left + r.width / 2;
371
+ const cy = r.top + r.height / 2;
372
+ const dx = e.clientX - cx;
373
+ const dy = e.clientY - cy;
374
+ const near = Math.max(r.width, r.height) * 2.55;
375
+ const dist = Math.hypot(dx, dy);
376
+ const influence = clamp(1 - dist / near, 0, 1);
377
+ this._pointer.x = clamp(dx * .42, -48, 48);
378
+ this._pointer.y = clamp(dy * .38, -34, 34);
379
+ this._pointer.active = influence > 0.001;
380
+ this._pointer.influence = influence;
381
+ this._pointer.lastMove = performance.now();
382
+ if ((this._pointer.influence||0) > 0.65 && this._boredRoutine) { this._boredRoutine = null; this._boredLookSpeed = null; }
383
+ }
384
+
385
+ _onDragStart(e) {
386
+ if (e.button != null && e.button !== 0) return;
387
+ const r = this.getBoundingClientRect();
388
+ const j = this._dragJelly;
389
+ j.active = true;
390
+ j.returning = false;
391
+ j.snapped = false;
392
+ j.moved = false;
393
+ j.pointerId = e.pointerId;
394
+ j.startX = e.clientX;
395
+ j.startY = e.clientY;
396
+ const local = this._clientToLocal(e.clientX, e.clientY);
397
+ j.hotX = local.x;
398
+ j.hotY = local.y;
399
+ j.targetX = j.x;
400
+ j.targetY = j.y;
401
+ j.targetStretch = j.stretch;
402
+ j.targetShear = j.shear;
403
+ j.targetPullX = j.pullX;
404
+ j.targetPullY = j.pullY;
405
+ j.threshold = clamp(r.width * 0.40, 24, 46);
406
+ j.maxDist = 0;
407
+ j.pendingReaction = null;
408
+ j.intent = 'angry';
409
+ j.angryThreshold = Math.max(j.threshold * 0.76, 22);
410
+ this._suppressClick = false;
411
+ this._boredRoutine = null;
412
+ this._boredLookSpeed = null;
413
+ this._wanderTarget.x = this._wanderTarget.y = 0;
414
+ try { this.setPointerCapture(e.pointerId); } catch (_) {}
415
+ }
416
+
417
+ _onDragMove(e) {
418
+ const j = this._dragJelly;
419
+ if (!j.active || e.pointerId !== j.pointerId) return;
420
+ const dx = e.clientX - j.startX;
421
+ const dy = e.clientY - j.startY;
422
+ const dist = Math.hypot(dx, dy);
423
+ j.maxDist = Math.max(j.maxDist, dist);
424
+ if (dist > 2.5) {
425
+ j.moved = true;
426
+ this._suppressClick = true;
427
+ }
428
+ const r = this.getBoundingClientRect();
429
+ const size = Math.max(1, Math.max(r.width, r.height));
430
+ const localScale = 240 / size;
431
+ const pdx = dx * localScale;
432
+ const pdy = dy * localScale;
433
+ const cx = 120, cy = 120;
434
+ const rx = j.hotX - cx;
435
+ const ry = j.hotY - cy;
436
+ const rl = Math.max(1, Math.hypot(rx, ry));
437
+ const rux = rx / rl;
438
+ const ruy = ry / rl;
439
+ const radial = pdx * rux + pdy * ruy;
440
+ const tangent = -pdx * ruy + pdy * rux;
441
+ const motionLen = Math.max(1, Math.hypot(pdx, pdy));
442
+ j.ux = pdx / motionLen;
443
+ j.uy = pdy / motionLen;
444
+ j.angle = Math.atan2(j.uy, j.ux);
445
+ j.intent = radial < -6 ? 'success' : 'angry';
446
+ const gain = dist > j.threshold * .60 ? 0.24 : 0.19;
447
+ j.targetPullX = clamp(pdx * gain, -25, 25);
448
+ j.targetPullY = clamp(pdy * gain, -25, 25);
449
+ j.targetX = clamp(dx * 0.065, -5.2, 5.2);
450
+ j.targetY = clamp(dy * 0.065, -5.2, 5.2);
451
+ j.targetStretch = clamp((radial / 240) * .08, -.028, .052);
452
+ j.targetShear = clamp((tangent / 240) * .050, -.022, .022);
453
+ j.anchorX = clamp(50 - (rux * 31), 15, 85);
454
+ j.anchorY = clamp(50 - (ruy * 31), 15, 85);
455
+ }
456
+
457
+ _onDragEnd(e) {
458
+ const j = this._dragJelly;
459
+ if (!j.active || e.pointerId !== j.pointerId) return;
460
+ j.active = false;
461
+ try { this.releasePointerCapture(e.pointerId); } catch (_) {}
462
+ j.pointerId = null;
463
+ const reactionThreshold = Math.max(j.angryThreshold, 22);
464
+ j.pendingReaction = j.maxDist >= reactionThreshold ? j.intent : null;
465
+ j.returning = true;
466
+ j.targetX = 0;
467
+ j.targetY = 0;
468
+ j.targetStretch = 0;
469
+ j.targetShear = 0;
470
+ j.targetPullX = 0;
471
+ j.targetPullY = 0;
472
+ }
473
+
474
+ _finishDragReturn(reaction) {
475
+ const j = this._dragJelly;
476
+ j.returning = false;
477
+ j.pendingReaction = null;
478
+ j.x = j.y = j.vx = j.vy = 0;
479
+ j.stretch = j.stretchVel = 0;
480
+ j.shear = j.shearVel = 0;
481
+ j.pullX = j.pullY = j.pullVX = j.pullVY = 0;
482
+ if (this._dragMotion) this._dragMotion.style.transform = '';
483
+ if (this._headShape) this._headShape.setAttribute('d', this._baseHeadPathD);
484
+ if (reaction === 'angry') this.angry({fromDrag:true});
485
+ else if (reaction === 'success') this.success({fromDrag:true});
486
+ }
487
+
488
+ _updateDragJelly(dt) {
489
+ const j = this._dragJelly;
490
+ const dtS = Math.min(dt, 34) / 1000;
491
+ if (j.active) {
492
+ const fast = Math.min(1, dt / 14);
493
+ const k = 0.36 + 0.30 * fast;
494
+ j.x = lerp(j.x, j.targetX, k);
495
+ j.y = lerp(j.y, j.targetY, k);
496
+ j.stretch = lerp(j.stretch, j.targetStretch, k*.85);
497
+ j.shear = lerp(j.shear, j.targetShear, k*.80);
498
+ j.pullX = lerp(j.pullX, j.targetPullX, k*.95);
499
+ j.pullY = lerp(j.pullY, j.targetPullY, k*.95);
500
+ } else if (j.returning) {
501
+ const kx = 135, dx = 8.2;
502
+ const ax = -j.x * kx - j.vx * dx;
503
+ const ay = -j.y * kx - j.vy * dx;
504
+ j.vx += ax * dtS;
505
+ j.vy += ay * dtS;
506
+ j.x += j.vx * dtS;
507
+ j.y += j.vy * dtS;
508
+ const ks = 128, ds = 7.5;
509
+ const as = -j.stretch * ks - j.stretchVel * ds;
510
+ j.stretchVel += as * dtS;
511
+ j.stretch += j.stretchVel * dtS;
512
+ const kh = 118, dh = 7.2;
513
+ const ah = -j.shear * kh - j.shearVel * dh;
514
+ j.shearVel += ah * dtS;
515
+ j.shear += j.shearVel * dtS;
516
+ const kp = 118, dp = 7.1;
517
+ const apx = -j.pullX * kp - j.pullVX * dp;
518
+ const apy = -j.pullY * kp - j.pullVY * dp;
519
+ j.pullVX += apx * dtS;
520
+ j.pullVY += apy * dtS;
521
+ j.pullX += j.pullVX * dtS;
522
+ j.pullY += j.pullVY * dtS;
523
+
524
+ const visuallyRecovered = Math.abs(j.x) < 0.34 && Math.abs(j.y) < 0.34 && Math.abs(j.stretch) < 0.008 && Math.abs(j.shear) < 0.0035 && Math.abs(j.pullX) < 0.40 && Math.abs(j.pullY) < 0.40;
525
+ if (j.pendingReaction && visuallyRecovered) {
526
+ this._finishDragReturn(j.pendingReaction);
527
+ } else if (Math.abs(j.x) < 0.02 && Math.abs(j.y) < 0.02 && Math.abs(j.vx) < 0.04 && Math.abs(j.vy) < 0.04 && Math.abs(j.stretch) < 0.001 && Math.abs(j.stretchVel) < 0.003 && Math.abs(j.shear) < 0.0005 && Math.abs(j.shearVel) < 0.002 && Math.abs(j.pullX) < 0.03 && Math.abs(j.pullY) < 0.03 && Math.abs(j.pullVX) < 0.06 && Math.abs(j.pullVY) < 0.06) {
528
+ this._finishDragReturn(null);
529
+ }
530
+ }
531
+ j.stretch = clamp(j.stretch, -0.035, 0.055);
532
+ j.shear = clamp(j.shear, -0.026, 0.026);
533
+ j.pullX = clamp(j.pullX, -26, 26);
534
+ j.pullY = clamp(j.pullY, -26, 26);
535
+ this._applyHeadDeform();
536
+ if (this._dragMotion) {
537
+ const sx = 1 + j.stretch;
538
+ const sy = 1 - j.stretch * 0.30;
539
+ const shearDeg = j.shear * 180 / Math.PI;
540
+ this._dragMotion.style.transformOrigin = `${j.anchorX.toFixed(2)}% ${j.anchorY.toFixed(2)}%`;
541
+ this._dragMotion.style.transform = `translate(${j.x.toFixed(2)}px, ${j.y.toFixed(2)}px) rotate(${j.angle.toFixed(4)}rad) scale(${sx.toFixed(4)}, ${sy.toFixed(4)}) skewY(${shearDeg.toFixed(3)}deg) rotate(${(-j.angle).toFixed(4)}rad)`;
542
+ }
543
+ }
544
+
545
+ noteActivity(wake=true) {
546
+ this._lastActivity = performance.now();
547
+ this._resumeFrames();
548
+ if (wake && this._sleeping) this.wake();
549
+ }
550
+
551
+ play(name) {
552
+ const action = String(name || '').trim().toLowerCase();
553
+ switch (action) {
554
+ case 'idle': return this.reset();
555
+ case 'bored': return this.bored();
556
+ case 'input': return this.input(true);
557
+ case 'send': return this.send();
558
+ case 'success': return this.success();
559
+ case 'error': return this.error();
560
+ case 'angry': return this.angry();
561
+ case 'surprise': return this.surprise();
562
+ case 'sleep': return this.sleep();
563
+ case 'wake': return this.wake();
564
+ default: throw new Error(`Unknown Agent Robot Avatar action: ${name}`);
565
+ }
566
+ }
567
+
568
+ reset() {
569
+ return this._resetToIdle();
570
+ }
571
+
572
+ _resetToIdle(notify = true) {
573
+ this._resetExtensionEffects?.();
574
+ this._inputWanted = false;
575
+ ++this._transitionToken;
576
+ this._cancelPendingWaits();
577
+ this._boredRoutine = null;
578
+ this._boredLookSpeed = null;
579
+ this._sleeping = false;
580
+ this._eyeNod = null;
581
+ this._eyeBob = null;
582
+ this._angryEyeDrop = null;
583
+ this._surpriseShake = null;
584
+ this.setState('idle', { notify, duration: notify ? 320 : 0 });
585
+ return this;
586
+ }
587
+
588
+ setState(name, opts={}) {
589
+ if (!POSES[name]) throw new Error(`Unknown AgentFace state: ${name}`);
590
+ this._resumeFrames();
591
+ this._state = name;
592
+ this._sleeping = name === 'sleep';
593
+ if (name === 'idle' && !opts.keepGazeLock) this._releaseExpressionLock();
594
+ this._startMorph(POSES[name], opts.duration ?? (name==='surprise'?170:320), opts.ease || EASE.outQuint);
595
+ if (opts.notify !== false) this.dispatchEvent(new CustomEvent('face-state', {detail:{state:name}}));
596
+ return this;
597
+ }
598
+
599
+ _startMorph(target, duration, ease) {
600
+ this._fromPose = clonePose(this._pose);
601
+ this._toPose = clonePose(target);
602
+ this._morphStart = performance.now();
603
+ this._morphDuration = Math.max(0, duration);
604
+ this._morphEase = ease;
605
+ }
606
+
607
+ async success(opts={}) {
608
+ const fromDrag = !!opts.fromDrag;
609
+ this.noteActivity();
610
+ this._inputWanted = false;
611
+ if (fromDrag) {
612
+ ++this._transitionToken;
613
+ this._expressionLock = true;
614
+ this._eyeMicro.lX = this._eyeMicro.lY = 0;
615
+ this._eyeMicro.rX = this._eyeMicro.rY = 0;
616
+ this._wanderTarget.x = this._wanderTarget.y = 0;
617
+ this._headCenteringUntil = performance.now() + 120;
618
+ this._look.x = 0;
619
+ this._look.y = 0;
620
+ } else {
621
+ if (!(await this._prepareExpression({normalizePose:true, duration:180, pause:320}))) return;
622
+ }
623
+ const token = this._transitionToken;
624
+ this.setState('happy', {duration: fromDrag ? 180 : 220, keepGazeLock:true});
625
+ this._eyeBob = {start: performance.now(), duration: 620, amp: 8};
626
+ this._animateHead([
627
+ {transform:'translateY(0px)'},
628
+ {transform:'translateY(-2.4px)', offset:.42},
629
+ {transform:'translateY(0px)'}
630
+ ], 760);
631
+ await this._wait(980);
632
+ if (token !== this._transitionToken) return;
633
+ this._eyeBob = null;
634
+ if (this._state==='happy') await this._returnToIdle(520);
635
+ }
636
+
637
+ async error() {
638
+ this.noteActivity();
639
+ this._inputWanted = false;
640
+ if (!(await this._prepareExpression({normalizePose:true, duration:180, pause:320}))) return;
641
+ const token = this._transitionToken;
642
+ this.setState('sad', {duration:320, keepGazeLock:true});
643
+ this._eyeBob = {start: performance.now(), duration: 700, amp: 7};
644
+ this._animateHead([
645
+ {transform:'translateY(0px)'},
646
+ {transform:'translateY(2.6px)', offset:.46},
647
+ {transform:'translateY(0px)'}
648
+ ], 820);
649
+ await this._wait(1180);
650
+ if (token !== this._transitionToken) return;
651
+ this._eyeBob = null;
652
+ if (this._state==='sad') {
653
+ await this._returnToIdle(560);
654
+ }
655
+ }
656
+
657
+ async angry(opts={}) {
658
+ const fromDrag = !!opts.fromDrag;
659
+ this.noteActivity();
660
+ this._inputWanted = false;
661
+ if (fromDrag) {
662
+ ++this._transitionToken;
663
+ this._expressionLock = true;
664
+ this._eyeMicro.lX = this._eyeMicro.lY = 0;
665
+ this._eyeMicro.rX = this._eyeMicro.rY = 0;
666
+ this._wanderTarget.x = this._wanderTarget.y = 0;
667
+ this._headCenteringUntil = performance.now() + 120;
668
+ this._look.x = 0;
669
+ this._look.y = 0;
670
+ } else {
671
+ if (!(await this._prepareExpression({normalizePose:true, duration:180, pause:320}))) return;
672
+ }
673
+ const token = this._transitionToken;
674
+ this.setState('angry', {duration: fromDrag ? 180 : 280, keepGazeLock:true});
675
+ this._angryEyeDrop = {start: performance.now(), duration:680, amount:4.8};
676
+ this._animateHead([
677
+ {transform:'translateY(0px)'},
678
+ {transform:'translateY(2.6px)', offset:.46},
679
+ {transform:'translateY(0px)'}
680
+ ], 820);
681
+ await this._wait(980);
682
+ if (token !== this._transitionToken) return;
683
+ this._angryEyeDrop = null;
684
+ if (this._state==='angry') await this._returnToIdle(500);
685
+ }
686
+
687
+ async surprise() {
688
+ this.noteActivity();
689
+ this._inputWanted = false;
690
+ if (!(await this._prepareExpression({normalizePose:true, duration:180, pause:320}))) return;
691
+ const token = this._transitionToken;
692
+ this._surpriseShake = null;
693
+ const pre = {...POSES.idle, w:34, h:36, rx:18};
694
+ this._startMorph(pre, 235, EASE.inOutCubic);
695
+ await this._wait(235);
696
+ if (token !== this._transitionToken || this._state!=='idle') return;
697
+ this.setState('surprise', {duration:175, ease:EASE.outQuint, keepGazeLock:true});
698
+ this._animateHead([{transform:'scale(1)'},{transform:'scale(1.035)'}], 175);
699
+ await this._wait(175);
700
+ if (token !== this._transitionToken || this._state!=='surprise') return;
701
+ this._surpriseShake = {start: performance.now(), duration:420};
702
+ await this._wait(420);
703
+ if (token !== this._transitionToken) return;
704
+ this._surpriseShake = null;
705
+ if (this._state==='surprise') {
706
+ this._animateHead([{transform:'scale(1.035)'},{transform:'scale(1)'}], 430);
707
+ await this._returnToIdle(430, EASE.inOutCubic);
708
+ }
709
+ }
710
+
711
+ async send() {
712
+ this.noteActivity();
713
+ this._inputWanted = false;
714
+ if (!(await this._prepareExpression({normalizePose:true, duration:180, pause:320}))) return;
715
+ const token = this._transitionToken;
716
+ this._eyeNod = {start: performance.now(), duration: 760};
717
+ this._animateHead([
718
+ {transform:'translateY(0px)'},
719
+ {transform:'translateY(5px)', offset:.52},
720
+ {transform:'translateY(0px)'}
721
+ ], 920);
722
+ await this._wait(860);
723
+ if (token !== this._transitionToken) return;
724
+ this._eyeNod = null;
725
+ this._releaseExpressionLock();
726
+ }
727
+
728
+ async sleep() {
729
+ if (this._sleeping) return;
730
+ if (!(await this._prepareExpression({normalizePose:true, duration:160}))) return;
731
+ const token = this._transitionToken;
732
+ this._blinkAnim = null;
733
+ this._blink = 1;
734
+ this._nextBlinkAt = performance.now() + 10000;
735
+ this.setState('sleepy', {duration:940, ease:EASE.inOutCubic, keepGazeLock:true});
736
+ await this._wait(980);
737
+ if (token !== this._transitionToken || this._state !== 'sleepy') return;
738
+ const reopen = {...POSES.idle, topY:-22, bottomY:36};
739
+ this._startMorph(reopen, 105, EASE.outQuint);
740
+ await this._wait(120);
741
+ if (token !== this._transitionToken || this._state !== 'sleepy') return;
742
+ this.setState('sleepy', {duration:820, ease:EASE.inOutCubic, keepGazeLock:true});
743
+ await this._wait(870);
744
+ if (token !== this._transitionToken || this._state !== 'sleepy') return;
745
+ await this._wait(120);
746
+ if (token !== this._transitionToken || this._state !== 'sleepy') return;
747
+ this._animateHead([{transform:'translateY(0)'},{transform:'translateY(4px)'}],980);
748
+ this._startMorph(POSES.sleep, 980, EASE.inOutCubic);
749
+ this._state = 'sleep';
750
+ this.dispatchEvent(new CustomEvent('face-state', {detail:{state:'sleep'}}));
751
+ this._sleeping = true;
752
+ this._nextBlinkAt = performance.now() + 3200 + Math.random()*2200;
753
+ }
754
+
755
+ async wake() {
756
+ if (!this._sleeping && this._state!=='sleep') return;
757
+ const token = ++this._transitionToken;
758
+ this._expressionLock = true;
759
+ this._sleeping = false;
760
+ this._animateHead([
761
+ {transform:'translateY(4px) scale(1)'},
762
+ {transform:'translateY(-2px) scale(1.015)', offset:.55},
763
+ {transform:'translateY(0) scale(1)'}
764
+ ], 500);
765
+ this.setState('sleepy', {duration:240, keepGazeLock:true});
766
+ await this._wait(260);
767
+ if (token !== this._transitionToken) return;
768
+ this.setState('surprise', {duration:120, keepGazeLock:true});
769
+ await this._wait(190);
770
+ if (token !== this._transitionToken) return;
771
+ await this._returnToIdle(300);
772
+ if (token !== this._transitionToken) return;
773
+ this._lastActivity = performance.now();
774
+ }
775
+
776
+ async input(active=true) {
777
+ this.noteActivity();
778
+ if (active) {
779
+ if (this._inputWanted || this._state==='input') return;
780
+ this._inputWanted = true;
781
+ if (!(await this._prepareExpression({normalizePose:true, duration:140}))) return;
782
+ if (!this._inputWanted) return;
783
+ this.setState('input', {duration:210, keepGazeLock:true});
784
+ } else {
785
+ this._inputWanted = false;
786
+ ++this._transitionToken;
787
+ if (this._state==='input') this.setState('idle', {duration:260});
788
+ else this._releaseExpressionLock();
789
+ }
790
+ }
791
+
792
+ _animateHead(frames, duration) {
793
+ if (!this._headMotion.animate) return;
794
+ const token = this._transitionToken;
795
+ const anim = this._headMotion.animate(frames, {duration, easing:'cubic-bezier(.16,1,.3,1)', fill:'forwards'});
796
+ anim.onfinish = () => {
797
+ if (token === this._transitionToken) {
798
+ this._headMotion.style.transform = frames[frames.length-1].transform || '';
799
+ }
800
+ };
801
+ return anim;
802
+ }
803
+
804
+ _wait(ms) {
805
+ return new Promise(resolve => {
806
+ const timer = setTimeout(() => {
807
+ this._pendingWaits.delete(timer);
808
+ resolve();
809
+ }, ms);
810
+ this._pendingWaits.set(timer, resolve);
811
+ });
812
+ }
813
+
814
+ _cancelPendingWaits() {
815
+ for (const [timer, resolve] of this._pendingWaits) {
816
+ clearTimeout(timer);
817
+ resolve();
818
+ }
819
+ this._pendingWaits.clear();
820
+ }
821
+
822
+ async _returnToIdle(duration=320, ease=EASE.outQuint) {
823
+ const token = this._transitionToken;
824
+ this.setState('idle', {duration, ease, keepGazeLock:true});
825
+ this._headCenteringUntil = Math.max(this._headCenteringUntil || 0, performance.now() + duration);
826
+ await this._wait(duration);
827
+ if (token === this._transitionToken && this._state === 'idle') this._releaseExpressionLock();
828
+ }
829
+
830
+ async _prepareExpression({normalizePose=true, duration=180, pause=320}={}) {
831
+ const token = ++this._transitionToken;
832
+ this._expressionLock = true;
833
+ this._eyeMicro.lX = this._eyeMicro.lY = 0;
834
+ this._eyeMicro.rX = this._eyeMicro.rY = 0;
835
+ this._wanderTarget.x = this._wanderTarget.y = 0;
836
+ this._headCenteringUntil = performance.now() + duration + pause;
837
+ if (normalizePose && this._state !== 'idle') {
838
+ this.setState('idle', {duration:Math.min(140, duration), keepGazeLock:true});
839
+ }
840
+ await this._wait(duration);
841
+ if (token !== this._transitionToken) return false;
842
+ if (pause > 0) await this._wait(pause);
843
+ return token === this._transitionToken;
844
+ }
845
+
846
+ async bored() {
847
+ this.noteActivity();
848
+ this._inputWanted = false;
849
+ this._sleeping = false;
850
+ if (!(await this._prepareExpression({normalizePose:true, duration:180, pause:620}))) return;
851
+ this._expressionLock = false;
852
+ this._look.x = 0;
853
+ this._look.y = 0;
854
+ this._wander.x = 0;
855
+ this._wander.y = 0;
856
+ this._startBoredRoutine(performance.now());
857
+ }
858
+
859
+ _startBoredRoutine(now) {
860
+ this._boredRoutine = {
861
+ index: 0,
862
+ nextAt: now,
863
+ steps: [
864
+ {x:-18,y:-11,hold:1400,lookSpeed:2},
865
+ {x:18,y:-11,hold:1400,lookSpeed:2},
866
+ {x:0,y:0,hold:320,lookSpeed:2},
867
+ ]
868
+ };
869
+ }
870
+
871
+ _updateBored(now) {
872
+ if (!this._boredRoutine) return false;
873
+ if (now < this._boredRoutine.nextAt) return true;
874
+ const step = this._boredRoutine.steps[this._boredRoutine.index];
875
+ if (!step) {
876
+ this._boredRoutine = null;
877
+ this._boredLookSpeed = null;
878
+ this._wanderTarget.x = this._wanderTarget.y = 0;
879
+ return false;
880
+ }
881
+ this._wanderTarget.x = step.x;
882
+ this._wanderTarget.y = step.y;
883
+ this._boredLookSpeed = step.lookSpeed ?? null;
884
+ this._boredRoutine.index += 1;
885
+ this._boredRoutine.nextAt = now + step.hold;
886
+ return true;
887
+ }
888
+
889
+ _releaseExpressionLock() {
890
+ this._expressionLock = false;
891
+ this._headCenteringUntil = 0;
892
+ }
893
+
894
+ _updateMorph(now) {
895
+ if (!this._morphDuration) {
896
+ this._pose = clonePose(this._toPose);
897
+ return;
898
+ }
899
+ const t = clamp((now - this._morphStart) / this._morphDuration, 0, 1);
900
+ const k = this._morphEase(t);
901
+ const p = {};
902
+ for (const key of Object.keys(this._toPose)) p[key] = lerp(this._fromPose[key], this._toPose[key], k);
903
+ this._pose = p;
904
+ if (t >= 1) this._morphDuration = 0;
905
+ }
906
+
907
+ _updateBlink(now) {
908
+ if (this._sleeping || this._state === 'input') return;
909
+ if (this._blinkAnim) {
910
+ const t = clamp((now - this._blinkAnim.start) / this._blinkAnim.duration, 0, 1);
911
+ this._blink = t < .5 ? lerp(1,.08,t*2) : lerp(.08,1,(t-.5)*2);
912
+ if (t >= 1) {
913
+ this._blinkAnim = null;
914
+ this._blink = 1;
915
+ this._nextBlinkAt = now + 1900 + Math.random()*2600;
916
+ }
917
+ } else if (!this._expressionLock && now >= this._nextBlinkAt) {
918
+ this._blinkAnim = {start:now,duration:150+Math.random()*70};
919
+ }
920
+ }
921
+
922
+ _updateLook(now, dt) {
923
+ this._updateBored(now);
924
+ const pointerFresh = (now - this._pointer.lastMove) < 900;
925
+ const antennaGaze = this._gestureFx?.kind === 'antenna-drag' && this._gestureFx.held;
926
+ const gazeLocked = this._expressionLock && !antennaGaze;
927
+ if (!this._expressionLock && !this._boredRoutine && !this._sleeping && this._state!=='input' && now >= this._nextWanderAt) {
928
+ const ampX = 12 + Math.random()*13;
929
+ const ampY = 5 + Math.random()*9;
930
+ this._wanderTarget.x = (Math.random()*2-1)*ampX;
931
+ this._wanderTarget.y = (Math.random()*2-1)*ampY;
932
+ this._nextWanderAt = now + 1150 + Math.random()*1900;
933
+ }
934
+ const draggingJelly = this._dragJelly.active || this._dragJelly.returning;
935
+ const pointerMix = (!gazeLocked && !this._boredRoutine && pointerFresh && this._pointer.active && !this._sleeping && this._state!=='input')
936
+ ? Math.pow(this._pointer.influence || 0, 1.6)
937
+ : 0;
938
+ const targetX = antennaGaze ? this._gestureFx.gazeX : (gazeLocked ? 0 : lerp(this._wanderTarget.x, this._pointer.x, pointerMix));
939
+ const targetY = antennaGaze ? this._gestureFx.gazeY : (gazeLocked ? 0 : lerp(this._wanderTarget.y, this._pointer.y, pointerMix));
940
+ const lookSpeed = antennaGaze ? 0.42 : (gazeLocked ? 0.40 : (draggingJelly ? 0.42 : (this._boredRoutine && this._boredLookSpeed != null ? this._boredLookSpeed : (pointerMix > 0.05 ? 0.28 : 0.16))));
941
+ const kL = 1 - Math.pow(1 - clamp(lookSpeed,0.02,.9), dt/16.67);
942
+ this._look.x = lerp(this._look.x, targetX, kL);
943
+ this._look.y = lerp(this._look.y, targetY, kL);
944
+ if (this._sleeping) {
945
+ this._look.x *= .92;
946
+ this._look.y *= .92;
947
+ }
948
+ }
949
+
950
+ _autoSleep(now) {
951
+ if (!this._autoSleepMs || this._sleeping || this._expressionLock || ['input','happy','sad','surprise'].includes(this._state)) return;
952
+ if (now - this._lastActivity > this._autoSleepMs) this.sleep();
953
+ }
954
+
955
+ _updateHeadFollow(now, dt) {
956
+ const pointerFresh = (now - this._pointer.lastMove) < 900;
957
+ const pointerInRange = pointerFresh && this._pointer.active && (this._pointer.influence || 0) > 0.03;
958
+ const headCentering = now < (this._headCenteringUntil || 0);
959
+ const jellyBusy = this._dragJelly.active || this._dragJelly.returning;
960
+ const canFollowPointer = pointerInRange && !jellyBusy && !this._sleeping && !this._boredRoutine && !headCentering && !this._expressionLock;
961
+ const srcX = canFollowPointer ? this._pointer.x : this._look.x;
962
+ const srcY = canFollowPointer ? this._pointer.y : this._look.y;
963
+ const nx = clamp(srcX / 48, -1, 1);
964
+ const ny = clamp(srcY / 34, -1, 1);
965
+ const diag = clamp(nx * ny, -1, 1);
966
+ const targetRot = diag * 8;
967
+ const targetX = canFollowPointer ? nx * 4.2 : 0;
968
+ const targetY = canFollowPointer ? ny * 4.2 : 0;
969
+
970
+ const dtS = Math.min(dt, 34) / 1000;
971
+ const rotStiffness = canFollowPointer ? 70 : 56;
972
+ const rotDamping = canFollowPointer ? 15 : 13;
973
+ const aRot = (targetRot - this._headFollowPose.rot) * rotStiffness - this._headFollowVel.rot * rotDamping;
974
+ this._headFollowVel.rot += aRot * dtS;
975
+ this._headFollowPose.rot += this._headFollowVel.rot * dtS;
976
+ const stiffness = canFollowPointer ? 95 : 80;
977
+ const damping = canFollowPointer ? 16 : 14;
978
+ const ax = (targetX - this._headFollowPose.x) * stiffness - this._headFollowVel.x * damping;
979
+ const ay = (targetY - this._headFollowPose.y) * stiffness - this._headFollowVel.y * damping;
980
+ this._headFollowVel.x += ax * dtS;
981
+ this._headFollowVel.y += ay * dtS;
982
+ this._headFollowPose.x += this._headFollowVel.x * dtS;
983
+ this._headFollowPose.y += this._headFollowVel.y * dtS;
984
+
985
+ if (!canFollowPointer) {
986
+ if (Math.abs(this._headFollowPose.x) < 0.01 && Math.abs(this._headFollowVel.x) < 0.01) { this._headFollowPose.x = 0; this._headFollowVel.x = 0; }
987
+ if (Math.abs(this._headFollowPose.y) < 0.01 && Math.abs(this._headFollowVel.y) < 0.01) { this._headFollowPose.y = 0; this._headFollowVel.y = 0; }
988
+ if (Math.abs(this._headFollowPose.rot) < 0.01 && Math.abs(this._headFollowVel.rot) < 0.01) { this._headFollowPose.rot = 0; this._headFollowVel.rot = 0; }
989
+ }
990
+
991
+ if (this._headFollow) {
992
+ const a = this._headFollowPose.rot.toFixed(2);
993
+ const tx = this._headFollowPose.x.toFixed(2);
994
+ const ty = this._headFollowPose.y.toFixed(2);
995
+ this._headFollow.setAttribute('transform', `translate(${tx} ${ty}) translate(120 120) rotate(${a}) translate(-120 -120)`);
996
+ }
997
+ }
998
+
999
+ _draw(now) {
1000
+ const p = this._pose;
1001
+ let nodY = 0;
1002
+ let nodScaleY = 1;
1003
+ if (this._eyeNod) {
1004
+ const elapsed = now - this._eyeNod.start;
1005
+ let depth = 0;
1006
+ if (elapsed < 150) depth = elapsed / 150;
1007
+ else if (elapsed < 300) depth = 1 - (elapsed - 150) / 150;
1008
+ else if (elapsed < 450) depth = (elapsed - 300) / 150;
1009
+ else if (elapsed < 760) depth = 1 - (elapsed - 450) / 310;
1010
+ else { depth = 0; this._eyeNod = null; }
1011
+ depth = clamp(depth, 0, 1);
1012
+ nodY = 31 * depth;
1013
+ nodScaleY = 1 - .30 * depth;
1014
+ }
1015
+
1016
+ let angryDropY = 0;
1017
+ if (this._angryEyeDrop) {
1018
+ const t = clamp((now - this._angryEyeDrop.start) / this._angryEyeDrop.duration, 0, 1);
1019
+ const settle = 1 - Math.exp(-5.2 * t) * Math.cos(8.6 * t);
1020
+ angryDropY = this._angryEyeDrop.amount * settle;
1021
+ if (t >= 1) angryDropY = this._angryEyeDrop.amount;
1022
+ }
1023
+
1024
+ let emotionBobY = 0;
1025
+ if (this._eyeBob) {
1026
+ const t = clamp((now - this._eyeBob.start) / this._eyeBob.duration, 0, 1);
1027
+ const cycles = 2;
1028
+ emotionBobY = Math.sin(t * Math.PI * cycles * 2) * this._eyeBob.amp * (1 - 0.38*t);
1029
+ if (t >= 1) this._eyeBob = null;
1030
+ }
1031
+
1032
+ let surpriseScale = 1;
1033
+ if (this._surpriseShake) {
1034
+ const t = clamp((now - this._surpriseShake.start) / this._surpriseShake.duration, 0, 1);
1035
+ surpriseScale = 1 + 0.022 * Math.sin(t * Math.PI * 8) * (1 - 0.18*t);
1036
+ if (t >= 1) this._surpriseShake = null;
1037
+ }
1038
+
1039
+ const blinkH = Math.max(2.6, p.h * this._blink * nodScaleY * surpriseScale);
1040
+ const yAdjust = (p.h - blinkH) * 0.02;
1041
+ const cursorPeriod = 530;
1042
+ const inputBlinkOn = this._state==='input' ? ((Math.floor(now / (cursorPeriod/2)) % 2) === 0) : true;
1043
+ const inputPulseL = this._state==='input' ? (inputBlinkOn ? 1 : 0) : 1;
1044
+ const inputPulseR = inputPulseL;
1045
+
1046
+ const jellyEyeOffset = (cx, cy) => {
1047
+ const j = this._dragJelly;
1048
+ const amount = Math.hypot(j.pullX, j.pullY);
1049
+ if (amount < 0.05) return {x:0, y:0};
1050
+ const radius = (50 + amount * 0.8) * 0.92;
1051
+ const dx = cx - j.hotX;
1052
+ const dy = cy - j.hotY;
1053
+ const w = Math.exp(-(dx*dx + dy*dy) / (2 * radius * radius)) * 0.24;
1054
+ return {x: j.pullX * w, y: j.pullY * w};
1055
+ };
1056
+
1057
+ const setEye = (base, inputBase, top, bottom, side, cx, microX, microY, opacity) => {
1058
+ const isInput = this._state === 'input';
1059
+ const projectedX = cx + this._look.x + microX;
1060
+ const edgeDistance = side==='L' ? (projectedX - 32) : (208 - projectedX);
1061
+ const edgeFactor = clamp(1 - edgeDistance / 58, 0, 1);
1062
+ const baseW = p.w * surpriseScale;
1063
+ const baseH = blinkH;
1064
+ const w = baseW * (1 - 0.22 * edgeFactor * edgeFactor);
1065
+ const h = baseH * (1 - 0.08 * edgeFactor * edgeFactor);
1066
+ const inwardShift = ((baseW - w) * 0.58) * (side==='L' ? 1 : -1);
1067
+
1068
+ if (isInput) {
1069
+ base.setAttribute('opacity', '0');
1070
+ inputBase.setAttribute('x', (-p.w/2).toFixed(2));
1071
+ inputBase.setAttribute('y', (-p.h/2 + yAdjust).toFixed(2));
1072
+ inputBase.setAttribute('width', p.w.toFixed(2));
1073
+ inputBase.setAttribute('height', p.h.toFixed(2));
1074
+ inputBase.setAttribute('rx', '3');
1075
+ inputBase.setAttribute('opacity', opacity.toFixed(3));
1076
+ } else {
1077
+ inputBase.setAttribute('opacity', '0');
1078
+ base.setAttribute('cx', '0');
1079
+ base.setAttribute('cy', yAdjust.toFixed(2));
1080
+ base.setAttribute('rx', (w/2).toFixed(2));
1081
+ base.setAttribute('ry', (h/2).toFixed(2));
1082
+ base.setAttribute('opacity', opacity.toFixed(3));
1083
+ }
1084
+ const topY = p.topY;
1085
+ const bottomY = p.bottomY;
1086
+ const ta = side==='L' ? p.topAL : p.topAR;
1087
+ const ba = side==='L' ? p.bottomAL : p.bottomAR;
1088
+ top.setAttribute('y', (topY-90).toFixed(2));
1089
+ top.setAttribute('height', '90');
1090
+ top.setAttribute('transform', `rotate(${ta.toFixed(2)} 0 ${topY.toFixed(2)})`);
1091
+ bottom.setAttribute('y', bottomY.toFixed(2));
1092
+ bottom.setAttribute('height', '90');
1093
+ bottom.setAttribute('transform', `rotate(${ba.toFixed(2)} 0 ${bottomY.toFixed(2)})`);
1094
+ const jelly = jellyEyeOffset(cx, 126);
1095
+ return `translate(${(cx + this._look.x + microX + jelly.x + (isInput ? 0 : inwardShift)).toFixed(2)} ${(126 + this._look.y + microY + jelly.y + nodY + angryDropY + emotionBobY).toFixed(2)})`;
1096
+ };
1097
+
1098
+ const inputCenter = this._state==='input';
1099
+ const surpriseSpread = this._state==='surprise' ? 3.4 * EASE.outQuint(clamp((p.w - 34) / (POSES.surprise.w - 34), 0, 1)) : 0;
1100
+ const leftCX = inputCenter ? 120 : 86 - surpriseSpread;
1101
+ const rightCX = inputCenter ? 120 : 154 + surpriseSpread;
1102
+ this._leftEye.setAttribute('transform', setEye(this._leftBase,this._leftInputBase,this._leftTop,this._leftBottom,'L',leftCX,this._eyeMicro.lX,this._eyeMicro.lY,inputPulseL));
1103
+ this._rightEye.setAttribute('transform', setEye(this._rightBase,this._rightInputBase,this._rightTop,this._rightBottom,'R',rightCX,this._eyeMicro.rX,this._eyeMicro.rY,inputPulseR));
1104
+ }
1105
+
1106
+ _resumeFrames() {
1107
+ if (!this.isConnected || document.hidden || this._raf) return;
1108
+ this._running = true;
1109
+ this._framePaused = false;
1110
+ this._lastFrame = performance.now();
1111
+ this._raf = requestAnimationFrame(this._loop);
1112
+ }
1113
+
1114
+ _canPauseFrames(now) {
1115
+ if (!this._sleeping || this._state !== 'sleep') return false;
1116
+ if (now - this._morphStart < this._morphDuration) return false;
1117
+ if (this._dragJelly.active || this._dragJelly.returning) return false;
1118
+ if (this._waitingFx || this._inspectFx || this._failureFx || this._warningFx || this._systemErrorShake) return false;
1119
+ const animations = this._headMotion?.getAnimations?.() || [];
1120
+ return !animations.some(animation => animation.playState === 'running' || animation.playState === 'pending');
1121
+ }
1122
+
1123
+ _loop = (ts=performance.now()) => {
1124
+ if (!this._running || !this.isConnected) {
1125
+ this._raf = 0;
1126
+ return;
1127
+ }
1128
+ if (ts - this._lastFrame >= 33) {
1129
+ const dt = Math.min(66, ts - (this._lastFrame || ts-33));
1130
+ this._lastFrame = ts;
1131
+ this._updateMorph(ts);
1132
+ this._updateBlink(ts);
1133
+ this._updateLook(ts, dt);
1134
+ this._updateHeadFollow(ts, dt);
1135
+ this._updateDragJelly(dt);
1136
+ this._draw(ts);
1137
+ }
1138
+ if (this._canPauseFrames(ts)) {
1139
+ this._framePaused = true;
1140
+ this._raf = 0;
1141
+ return;
1142
+ }
1143
+ this._raf = requestAnimationFrame(this._loop);
1144
+ };
1145
+ }
1146
+
1147
+ if (typeof customElements !== 'undefined' && !customElements.get('agent-robot-avatar')) {
1148
+ customElements.define('agent-robot-avatar', AgentFace);
1149
+ }
1150
+ if (typeof window !== 'undefined') window.AgentRobotAvatar = AgentFace;
1151
+ return AgentFace;
1152
+ })();
1153
+
1154
+ export { AgentRobotAvatar };
1155
+ export default AgentRobotAvatar;